Вы находитесь на странице: 1из 12

ЛАБОРАТОРНАЯ РАБОТА №4 ФОНОВЫЕ ПРОЦЕСЫ И СИГНАЛЫ

1.1. Сессии и работа с фоновыми процессами (демонами) в Linux.

Демоны (daemons) - это неинтерактивные (фоновые) программы, которые


обеспечивают различные сервисы в системе. К стандартным демонам Linux относятся:
init, который является прародителем всех процессов системы, cron, который обеспечивает
запуск программ в определенные моменты времени, sendmail - обеспечивает
отправление и получение электронной почты и т.д.
Каждый демон отличается тем, что он составляет собственный сеанс (сессию) и
группу процессов, не принадлежит ни к одному из пользовательских сеансов (сессий) и
групп и не имеет управляющего терминала.
Сессия - это набор из одной или более групп процессов. Обычно сессия состоит из
всех процессов, которые запущены между входом пользователя в систему и его выходом
из нее.
Каждая сессия характеризуется идентификатором сессии sid. Обычно лидером
сессии является shell-процесс, с которым взаимодействует пользователь:

PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
166 167 167 167 p0 897 S 501 0:00 -bash
167 897 897 167 p0 897 R 501 0:00 ps -jx

Здесь лидером сессии (SID==PID) является bash (shell-процесс), а ps принадлежит этой же


сессии.

Для создания новой сессии используется вызов setsid();


Процесс при этом
а) становится лидером новой сессии
б) становится лидером группы процессов
в) не имеет управляющего терминала.

Для определения идентификатора текущей сессии используется вызов getsid(0);

От демона требуется, чтобы


1) он не имел управляющего терминала, т.к. он не должен реагировать на прерывания
при попытках ввода-вывода с использованием управляющего терминала
2) он закрыл все открытые файлы, особенно стандартные потоки ввода-вывода, т.к. они
должны быть закрыты, когда пользователь вышел из системы, а демон должен
продолжать работу и после этого.
3) он был лидером группы процессов и лидером сессии, чтобы он не мог получать
сигналы для данной группы (например, при нажатии ^C или выходе из системы)

Для того, чтобы всего этого достичь, в демоне нужно осуществить следующие действия:

1) произвести потомка:
if ((pid = fork()) < 0) return -1;

Это нужно по двум причинам:


а) чтобы сразу вернуться в командный процессор, выйдя из предка на шаге 2)
б) чтобы новый процесс гарантированно не мог стать лидером группы процессов, т.к. он
наследует эту группу от предка - это нужно для вызова setsid() на шаге 3а)

2) выйти из предка

if (pid != 0) { // предок
printf ("daemon started with pid=%d\n", pid);
exit (0);
}

3) в потомке

а) создать новую сессию:

setsid();

Процесс при этом, как мы уже видели


а) становится лидером новой сессии
б) становится лидером группы процессов в) не имеет управляющего терминала.

Главный смысл этого вызова - отключиться от управляющего терминала и потерять


связь с текущей сессией, чтобы не получать никаких сигналов.

б) сменить текущий каталог на корневой (каталог может представлять раздел


диска, при этом если это текущий каталог демона, то он не может быть отключен
(размонтирован), пока демон не закончит работу).

chdir("/");

в) закрыть все открытые файлы (файловые дескрипторы).

#include <sys/resource.h>
void main()
{
struct rlimit flim;
getrlimit(RLIMIT_NOFILE, &flim);
for (fd = 0; fd < flim.rlim_max; fd++)
close(fd);
}
г) задать бесконечный цикл (для нашего простейшего демона)

for (; ;) pause();

Функция pause() приостанавливает программу в ожидании сигнала.

После запуска демона ps -jx | grep 'PID\|parent' даст нам

PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 839 839 839 ? -1 S 501 34:32 ./parent -d

Как мы видим, наш процесс является лидером сессии (PID==SID), лидером группы
процессов (PID==PGID) и не имеет управляющего терминала (TTY==?), а его предком
является init (PPID==1), т.к. непосредственный предок прекратил выполнение.

1.2. Сигналы

Процессы Linux могут взаимодействовать, отправлять друг другу сообщения и


прерываться друг другом. Они могут даже организоваться и совместно использовать
сегменты памяти, но они остаются обособленными объектами операционной системы.
Процессы не настроены на совместное использование переменных.
Существует класс процесса, именуемый потоком (thread), который доступен во
многих системах UNIX и Linux. Несмотря на то, что потоки трудно, программировать, они
могут быть очень важны для некоторых приложений, таких как многопоточные серверы
баз данных. Программирование потоков в Linux (и вообще в UNIX) не так распространено,
как применение множественных процессов, поскольку процессы Linux очень легко
применять и программирование множественных взаимодействующих процессов гораздо
легче программирования потоков.
Сигнал — это событие, генерируемое системами UNIX и Linux в ответ на некоторую
ситуацию, получив сообщение о котором процесс, в свою очередь, может предпринять
какое-то действие. В Linux применяется термин "возбуждать" (raise) для обозначения
генерации сигнала и термин "захватывать" (catch) для обозначения получения или
приема сигнала. Сигналы возбуждаются некоторыми ошибочными ситуациями, например
нарушениями сегментации памяти, ошибками процессора при выполнении операций с
плавающей точкой или некорректными командами. Они генерируются командной
оболочкой и обработчиками терминалов для вызова прерываний и могут явно
пересылаться от одного процесса к другому как способ передачи информации или
коррекции поведения. Во всех этих случаях программный интерфейс один и тот же.
Сигналы могут возбуждаться, улавливаться и соответственно обрабатываться или (по
крайней мере, некоторые) игнорироваться.
Имена сигналов задаются с помощью включенного заголовочного файла signal.h.
Они начинаются с префикса
SIG

Сигналы бывают такие:


номер название комментарий
1 SIGHUP разрыв связи - выход
пользователя из системы
2 SIGINT прерывание от клавиатуры
(например, ^C)
11 SIGSEGV запись в неверную область
памяти (segmentation fault)
10 SIGUSR1 пользовательские
12 SIGUSR2 сигналы
15 SIGTERM окончание программы
9 SIGKILL уничтожение программы

Для всех вышеприведенных сигналов, кроме SIGSEGV, действием по умолчанию


является прерывание программы (для SIGSEGV это генерация т.н. дампа памяти - файла
core). Сигнал SIGKILL не может быть перехвачен или отменен.

Программа может устанавливать свой обработчик сигнала, который будет


вызываться всегда, когда процессу будет отправлен этот сигнал. Для этого используется
функция signal(), описанная в signal.h.

Синтаксис

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

Параметры
sig - сигнал, который нужно перехватить или игнорировать, задается аргументом
func - функция, которую следует вызвать при получении заданного сигнала, содержится в
аргументе. Эта функция должна принимать единственный аргумент типа – int (принятый
сигнал) и иметь тип void.

Результат
Функция сигнала возвращает функцию того же типа, которая является предыдущим
значением функции, заданной для обработки сигнала, или одно из двух специальных
значений:
SIG_IGN — игнорировать сигнал;
SIG_DFL — восстановить поведение по умолчанию.

func - имя объявленной ранее функции, имеющей следующий вид:

void func (int signum)


{ // обработка
}
Эта функция и становится обработчиком сигналов. Параметр signum определяет,
какой сигнал пришел в обработчик (один и тот же обработчик может регистрироваться
для нескольких сигналов).

После регистрации обработчик user_handler() будет вызываться всегда, когда придет


соответствующий сигнал, например

#include<signal.h>
void main()
{
signal (SIGINT, func);
signal (SIGUSR1, func);
}

он будет вызываться при SIGUSR1 и SIGINT и внутри него можно писать

void user_handler (int signum)


{
switch (signum) {
case SIGUSR1: // обработка SIGUSR1
case SIGINT: // обработка SIGINT
}
}

Пример программы обработка сигнала

Функция sigCatch реагирует на сигнал, передаваемый в параметре signum. Эта функция


будет вызываться, когда возникнет сигнал. Она выводит сообщение и затем
восстанавливает обработку сигнала по умолчанию для сигнала SIGINT (генерируется при
нажатии комбинации клавиш <Ctrl>+<C>).
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void sigCatch (int signum) {


printf("Catch! - I got signal %d\n", signum);
(void)signal(SIGINT, SIG_DFL);
}

Функция main должна взаимодействовать с сигналом SIGINT, генерируемым при нажатии


комбинации клавиш <Ctrl>+<C>. В остальное время она находится в бесконечном цикле,
выводя один раз в секунду сообщение.
int main() {
(void)signal(SIGINT, sigCatch);
while(1) {
printf("Catch signal!\n");
sleep(1);}}
Окончательный вариант программы представлен на рисунке ниже.

Ввод комбинации клавиш <Ctrl>+<C> (отображается как ^C в следующем далее


выводе) в первый раз заставляет программу отреагировать и продолжиться. Когда вы
нажимаете <Ctrl>+<C> снова, программа завершается, т.к. сигнал SIGINT вернул
программе стандартное поведение, заставляющее ее завершиться.

По умолчанию функция signal() определяет ненадежную обработку сигналов. Дело


в том, что после обработки сигнала его обработчик сбрасывается в действие по
умолчанию, например, обработчик SIGUSR1 в нашем примере после обработки будет
сброшен в действие по умолчанию (а это завершение программы), и если послать
процессу этот сигнал еще раз, он прекратит свое выполнение.
Есть ряд способов решения этой проблемы:
1) восстанавливать обработчик вручную:
void user_handler (int signum)
{
switch (signum) {
case SIGUSR1:
signal (SIGUSR1, user_handler);
// обработка
(void)signal(SIGINT, SIG_DFL);
}
}

При этом, если сигнал поступит между предыдущим и переустановкой обработчика, то


он потеряется (именно поэтому такие сигналы называются ненадежными). Для случая
нашей работы (ручная отправка сигналов) это маловероятно.
2) использовать надежную обработку сигналов в стиле BSD UNIX. Для этого нужно
подключить не signal.h, а bsd/signal.h. Функция signal() остается без изменений, но
обработчик не сбрасывается и восстанавливать его не нужно:

#include<bsd/signal.h>
void user_handler (int signum)
{
switch (signum) {
case SIGUSR1:
// обработка }}

При компиляции в этом случае в makefile нужно задать дополнительно путь к


подключаемым файлам так (все остальное без изменений):

CXXFLAGS = -I/usr/include

3) использовать надежную обработку сигналов с помощью функции sigaction(). Все


варианты signal() внутри вызывают sigaction(), она обладает большей гибкостью, но
сложнее в использовании.

Если нам нужно организовать ожидание сигнала, то, как мы уже видели, можно
использовать функцию pause(). Если бы мы просто задали в нашем демоне бесконечный
цикл, это было бы слишком накладно с точки зрения процессорного времени.

Для отправления сигналов процессу используются команды kill и killall.

kill [сигнал] pid

Команда kill посылает заданный сигнал (по умолчанию SIGTERM) процессу с


идентификатором pid. Сигнал задается как -код, где код соответствует имени сигнала без
префикса SIG, т.е. -INT для SIGINT, -USR1 для SIGUSR1 и т.д. Можно задавать и численный
код сигнала, например -9 задаст неперехватываемый сигнал SIGKILL.
kill -USR1 1250
пошлет сигнал SIGUSR1 процессу с pid=1250

kill 1250
пошлет ему же SIGTERM

kill -9 1250
пошлет ему же SIGKILL (если он не был убит предыдущим действием, SIGKILL используется
в крайних случаях, когда SIGTERM не помогает).

killall [сигнал] имя-процесса

посылает заданный сигнал всем процессам, в чьем имени содержится имя-процесса.


Удобен, когда не хочется вспоминать pid.

Например
killall -USR1 parent

отправит SIGUSR1 всем запущенным процессам parent.

Из программы сигнал отправляется с помощью функции kill().

Синтаксис
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

Параметры
sig - заданный сигнал
pid - код процесса (если -1, то всем)

Результат
В случае успеха она возвращает 0. Функция kill завершится аварийно, вернет -1 и
установит значение переменной errno, если задан неверный сигнал, (errno равна EINVAL),
у процесса нет полномочий (EPERM) или заданный процесс не существует (ESRCH).

На сигнал реагируют только те процессы, для которых у отправителя сигнала


достаточно прав (т.е. нельзя уничтожить процесс, запущенный от суперпользователя root,
из пользовательской программы).

Сигналы предоставляют полезное средство, именуемое будильником или


сигналом тревоги. Вызов функции alarm может применяться для формирования сигнала
SIGALRM в определенное время в будущем.
Синтаксис

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

Параметры
Вызов alarm намечает доставку сигнала SIGALRM через seconds секунд. В
действительности сигнал будильника будет доставлен чуть позже из-за обработки
задержек и учета неопределенностей. Значение 0 отменяет любой невыполненный
запрос на сигнал будильника. Вызов функции alarm до получения сигнала может вызвать
сброс графика доставки. У каждого процесса может быть только один невыполненный
сигнал будильника.

Результат
Функция alarm возвращает количество секунд, оставшихся до отправки любого
невыполненного вызова, alarm, или -1 в случае аварийного завершения.

Пример программы Будильник.

В программе alarm.cpp первая функция, ding, имитирует будильник.

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

static int alarm_fired = 0;


void ding(int sig) {
alarm_fired = 1;
}

В функции main вы заставляете дочерний процесс ждать пять секунд перед


отправкой сигнала SIGALRM в свой родительский процесс:

int main() {
pid_t pid;
printf("alarm application starting\n");
pid = fork();
switch(pid) {
case -1:
/* Аварийное завершение */
perror("fork failed");
exit(1);
case 0:
/* Дочерний процесс */
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}

Родительский процесс устроен так, что перехватывает сигнал SIGALRM с помощью


вызова signal и затем ждет неизбежности:

/* Если мы оказались здесь, то мы — родительский процесс */


printf("waiting for alarm to go off\n");
(void)signal(SIGALRM, ding);
pause();
if (alarm_fired) printf("Ding!\n");
printf("done\n");
exit(0);
}

Когда вы выполните программу, то увидите, что она делает паузу на пять секунд, в
течение которых ждет имитации будильника:

$ ./alarm
alarm application starting
waiting for alarm to go off
<5 second pause>
Ding!
Done $

В этой программе вводится новая функция pause, которая просто


приостанавливает выполнение программы до появления сигнала. Когда она получит
сигнал, выполняется любой установленный обработчик, и выполнение продолжается как
обычно.

Синтаксис

#include <unistd.h>
int pause(void);

Результат
Функция возвращает -1 (если следующий полученный сигнал не вызвал завершения
программы) с переменной errno, равной EINTR, в случае прерывания сигналом. Лучше
для ожидания сигналов применять функцию sigsuspend.

1.3. Работа с системным журналом

В UNIX-подобных системах существует возможность централизованного сбора


информации от различных приложений - системный журнал (system log, syslog).
Существует фоновый процесс (демон) syslogd, работающий постоянно, который и
управляет сбором информации от приложений. Обычно информация записывается в
файлы, находящиеся в каталоге /var/log (это настраивается в конфигурационном файле
/etc/syslog.conf).
Прежде всего запись в системный журнал необходима демонам (фоновым
процессам), потому что они не имеют управляющего терминала, а открывать для каждого
процесса запись в файл операциями файлового ввода-вывода не совсем удобно.
Для вызова функций работы с журналом необходимо подключать заголовочный
файл syslog.h.
Для того, чтобы начать запись в журнал, необходимо прежде всего этот журнал
открыть с помощью функции openlog():

void openlog( char *ident, int option, int facility);

- ident - строка символов, которая будет выводиться каждый раз вместе с информацией
для того, чтобы легче было разобраться, кто что записал в журнал. В лабораторной
работе рекомендуется указывать в ident группу и вариант (т.к. в системный журнал будут
писать демоны разных групп и разобраться, где чей, будет не просто);
- option задает дополнительные параметры журнала, например LOG_PID означает, что
для каждой строки нужно указывать идентификатор процесса, который ее записал;
- facility означает т.н. источник информации (может быть LOG_KERN – от ядра, LOG_USER -
пользовательский процесс и т.д.). Мы будем использовать источник LOG_LOCAL0 (может
быть до LOG_LOCAL7 – это источник, определяемый пользователем) с тем, чтобы не
смешивать сообщения данной работы с другими сообщениями системы.

openlog ("i38a1-daemon", LOG_PID, LOG_LOCAL0);

Следует отметить, что закрытие открытых дескрипторов файлов на шаге 3в) в


демоне необходимо осуществить до начала работы с системным журналом, т.к. журнал
тоже является открытым дескриптором файла и он будет закрыт, что неправильно.

Запись в журнал производится с помощью функции syslog()

Синтаксис

syslog (level, format, ...);

где level определяет уровень серьезности сообщения, а дальнейшие параметры


аналогичны printf()

Пример:
syslog (LOG_INFO, "daemon started with pid=%d", pid);

Вот какой вид имеет одна строка записи в журнале (при LOG_PID):
Oct 4 12:44:17 asu i38a1-daemon[2074]: daemon started with
pid=2074
После окончания работы с журналом его следует закрыть: closelog();

В лабораторной работе демон должен заносить в журнал информацию


1) о том, что он стартовал,
2) в случае получения сигнала
3) при окончании работы по сигналу SIGINT.

Для работы нужно использовать источник сообщений LOG_LOCAL0, при этом


система настроена так, что запись пойдет в файл /var/log/userlog.

Просмотр этого файла по мере накопления данных:


tail -f /var/log/userlog

Окончание просмотра - ^C.

1.4 Последовательность выполнения работы

1. Ознакомиться с теоретическим материалом.


2. Разработать приложение для Linux, которое
- запускается как демон (фоновый процесс)
- отключается от терминала, сообщает о себе в системном журнале и ждет сигналов
- по сигналу SIGUSR1 записывает информацию о получении сигнала в системный журнал;
- по сигналу SIGINT завершает выполнение (тоже с записью в журнале)
Уметь смотреть состояние запущенного демона командой ps и объяснять содержимое
различных полей вывода ps.

1.5 Требования к отчету


Отчет должен содержать следующие разделы:
1. Титульный лист, оформленный согласно утвержденному образцу.
2. Цели выполняемой лабораторной работы.
3. Задание на лабораторную работу.
4. Исходные тексты созданных программ.
5. Результаты работы программ с использованием средств передачи сигналов.
6. Выводы (с пояснением различий результатов работы программ).

Вам также может понравиться