допмат-блок9-блок9

Демоните в света на Unix традиционно се наричат ​​процеси, които не взаимодействат директно с потребителя. Няма контролен терминал и съответно потребителски интерфейс. Трябва да използвате други програми, за да управлявате демони.

Самото име "демони" идва от факта, че много процеси от този тип прекарват по-голямата част от времето си в очакване на събитие. Когато това събитие се случи, демонът се събужда (изскача като джак в кутията), върши работата си и отново заспива в очакване на събитието.

Трябва да се отбележи, че много демони, като например сървър на база данни, могат да отнемат почти цялото процесорно време и други системни ресурси. Такива демони работят много повече, отколкото спят.

Малко вероятно е да се наложи да създавате свой собствен демон, тъй като наборът от задачи, за които може да е необходим демон, не е толкова голям. Обхватът на демоните е създаването на такива приложения, които могат и трябва да работят без намесата на потребителя. Обикновено това са различни видове сървъри.

Въпреки това, демоните използват много важни елементи на системата и разбирането на това как работят демоните допринася за разбирането на това как работи Unix/Linux като цяло (наред с други неща, един добър демон ще ни помогне да научим някои особености на използването на сигнали, които не сме срещали преди).

Като пример за демон ще разгледаме прост мрежов сървър, aahzd, който може да приема клиентски заявки и да връща отговори.

Изходният код за нашия сървър aahzd.c е модифициран изходен код за демонстрационния демон, написан от Дейвид Джилис, по-долу е изходният код (на функцията main() на нашия демон):

const char *constgLockFilePath = "/var/run/aahzd.pid"; int main(intargc,char *argv[])

if ((fd = open(gLockFilePath, O_RDONLY))

убиване(pid, SIGUSR1); изход (EXIT_SUCCESS);

printf("използване %s [stopprestart]\n", argv[0]); изход (EXIT_FAILURE);

if((result = BecomeDaemonProcess(gLockFilePath, "aahzd", LOG_DEBUG, &gLockFileDesc, &daemonPID)) 1) (ще се върнем към него по-късно) и разгледайте основните стъпки на демона. Функцията BecomeDaemonProcess() превръща нормален конзолен процес на Linux в

Функцията ConfigureSignalHandlers() конфигурира манипулатори на сигнали, а функцията BindPassiveSocket() отваря конкретен TCP/IP порт, за да слуша за входящи заявки.

Това е последвано от цикъл, в който сървърът обработва заявките. Много мрежови сървъри, при получаване на заявка, създават дъщерен процес, за да я обработят. Така се постига възможността за паралелна обработка на заявките. Някои сървъри използват нишки за паралелна обработка на заявки.

Що се отнася до нашия сървър, от съображения за простота, той обработва заявките в последователен (блокиращ) режим. Нормалното излизане от цикъла на заявката се случва, когато процесът получи сигнала SIGUSER1. След излизане от цикъла, процесът извиква функцията TidyUp() и излиза.

Със сигурност можем да прекратим, като му изпратим сигнал SIGKILL (SIGTERM и някои други), но потребителският сигнал SIGUSER1 гарантира грациозно прекратяване на нашия демон. „Учтиво прекратяване“ означава, че сървърът ще отговори на текущата заявка, преди да я прекрати и изтрие

Нека сега разгледаме по-подробно функцията BecomeDaemonProcess(), която прави обикновен Linux процес демон. Правим основната директория текущата директория

Веднъж стартиран, нашият демон може да работи, докато системата не се рестартира, така че

текущата директория трябва да принадлежи към файлова система, която не може да бъде демонтирана.

Всеки създава така наречения (или заключващ файл). Този файл обикновено се намира в директорията /var/run и се нарича daemon.pid, където "daemon" е името на демона:

lockFD = отворен (lockFileName, O_RDWRO_CREATO_EXCL, 0644);

Файлът за заключване съдържа PID стойността на процеса на демон. Този файл е важен по две причини. неговото присъствие ви позволява да установите, че системата вече изпълнява едно копие на демона. Повечето демони, включително нашия, трябва да работят в не повече от един екземпляр (което има смисъл, като се има предвид, че демоните често имат достъп до несподелени ресурси, като мрежови портове).

При излизане изтрива, което показва, че може да се стартира друго копие на процеса. Въпреки това, демонът не винаги излиза нормално, оставяйки несъществуващ процес на диска.

Това, изглежда, може да се превърне в непреодолима пречка за повторното стартиране на демона, но всъщност демоните успешно се справят с такива ситуации. По време на стартиране демонът проверява за съществуването на устройство с подходящото име. Ако такъв файл съществува, демонът чете PID стойността от него и използва функцията kill(2), за да провери дали в системата съществува процес с посочения PID.

Ако процесът съществува, тогава потребителят се опитва да стартира демона отново. В този случай програмата показва подходящо съобщение и приключва. Ако в системата няма процес с посочения PID, тогава той е принадлежал на необичайно прекратен демон.

В тази ситуация програмата обикновено съветва потребителя да деинсталира (винаги е по-добре да прехвърлите отговорността в такива случаи на потребителя) и да опитате да я стартирате отново.

Може, разбира се, да се случи след спешен случайкогато демонът приключи, той ще остане на диска и след това друг процес ще получи същия PID, който е имал демонът. В тази ситуация за току-що стартирания демон всичко ще изглежда като негово копие, което вече работи в системата, и няма да можете да стартирате демона отново. За щастие описаната ситуация е изключително малко вероятна.

Втората причина файлът за заключване да се счита за полезен е, че с този файл можем бързо да открием PID на демон, без да прибягваме до командата ps. След това нашият демон извиква функцията fork(3), която създава копие на своя процес. След това родителският процес излиза:

case 0: /* ние сме в дъщерен процес */ break;

case fork() се провали - случи се нещо ужасно */ fprintf(stderr,"Грешка: неуспешно първоначално разклонение: %s\n",

по подразбиране: /* ние сме в родителския процес, излезте от него */ exit(0);

Това се прави с цел прекъсване на връзката с контролния терминал.

Всеки Unix терминал има набор от групи процеси, свързани с него, наречени сесия. Във всеки даден момент само една от групите процеси, включени в сесията, има достъп до терминала (т.е. може да извършва I/O с помощта на терминала). Тази група се нарича преден план (приоритет).

Всяка сесия има един, наречен лидер на сесията. Ако

стартиран от конзолата, той естествено става част от приоритетната група процеси, включени в сесията на съответния терминал.

За да прекъсне връзката с терминала, демонът трябва да започне нова сесия, която не е свързана с терминала. За да може даден демон да започне нова сесия, той самият не трябва да бъде лидер на друга сесия. Извикването fork() създава дъщерен процес, за който не е известно, че е лидер на сесията. След това дъщерният процес, получен от fork(), започва нова сесия чрез извикване на функциятаsetsid(2). В този случай процесът става водещ (и единствен участник) на новата сесия.

if(i != lockFD) затвори(i);

Функцията sysconf() с параметъра _SC_OPEN_MAX връща максималния възможен брой манипулатори, които нашата програма може да отвори. Извикваме функцията close() за всеки манипулатор (независимо дали е отворен или не), с изключение на манипулатора, който трябва да остане отворен.

Докато демонът работи, стандартните манипулатори за въвеждане, изход и грешки също трябва да бъдат отворени, тъй като те се изискват от много стандартни библиотечни функции. В същото време тези дескриптори не трябва да сочат към действителни I/O потоци.

За да разрешим този проблем, затваряме първите три дескриптора и след това ги отваряме отново с /dev/null като име на файл:

stdioFD = open("/dev/null", O_RDWR); дублиране (stdioFD);

Сега можем да сме сигурни, че демонът няма да получи достъп до терминала. Все пак демонът трябва да може да отпечатва съобщения за своята работа. Лог файловете традиционно се използват за това Лог файловете за демон са като черните кутии на самолетите. Ако демонът се провали, потребителят може да анализира лог файла, за да определи причината за повредата. Нищо не пречи на нашия демон да отвори собствен лог файл, но това не е много удобно. Повечето демони използват помощната програма syslog, която регистрира много системни събития. Разкриваме журнала на syslog с помощта на функцията openlog(3):

openlog(logPrefix, LOG_PIDLOG_CONSLOG_NDELAYLOG_NOWAIT, LOG_LOCAL0); (недействително) setlogmask(LOG_UPTO(logLevel));

Първият параметър на функцията openlog() е префикс, който ще бъде добавен към всеки запис в системния журнал. Следван отразлични опции на syslog. функция setlogmask(3).

ви позволява да зададете нивото на приоритет на съобщенията, които се записват в регистъра на събитията.

Когато извикваме функцията BecomeDaemonProcess(), предаваме стойността LOG_DEBUG в параметъра logLevel. В комбинация с макроса LOG_UPTO това означава, че всички съобщения с приоритет ще бъдат регистрирани, като се започне от най-високия и се стигне до LOG_DEBUG.

Последното нещо, което трябва да направим, за да "демонизираме" процес, е да извикаме функцията setpgrp();

Това извикване създава нова група процеси, чийто идентификатор е идентификаторът на текущия процес. Това завършва работата на функцията BecomeDaemonProcess(), тъй като сега процесът се превърна в истински демон.

Функцията ConfigureSignalHandlers() конфигурира манипулатори на сигнали. Сигналите, които нашият демон ще получи, могат да бъдат разделени на три групи: игнорирани, фатални и обработени. Чрез извикване на signal(SIGUSR2, SIG_IGN) казваме на нашия демон да игнорира сигнала SIGUSR2.

FatalSigHandler() записва информация за получения сигнал в регистъра на събитията и след това прекратява процеса, като преди това извиква функциите closelog() и TidyUp(), които освобождават всички ресурси, заети от процеса:

void FatalSigHandler(int sig) < #ifdef _GNU_SOURCE

syslog(LOG_LOCAL0LOG_INFO,"уловен сигнал: %s - излизане",strsignal(sig));

syslog(LOG_LOCAL0LOG_INFO,"уловен сигнал: %d - излизане",sig);

sigtermSA.sa_handler = TermHandler; sigemptyset(&sigtermSA.sa_mask); sigtermSA.sa_flags = 0; sigaction(SIGTERM, &sigtermSA, NULL); sigusr1SA.sa_handler = Usr1Handler; sigemptyset(&sigusr1SA.sa_mask); sigusr1SA.sa_flags = 0; sigaction(SIGUSR1, &sigusr1SA, NULL); sighupSA.sa_handler = HupHandler; sigemptyset(&sighupSA.sa_mask);sighupSA.sa_flags = 0;

Манипулаторът TermHandler() извиква функцията TidyUp() и прекратява процеса. Манипулаторът Usr1Handler() записва изящен изход в системния журнал и задава променливата gGracefulShutdown на 1 (което, запомнете, кара цикъла на заявката да излезе, когато цикълът е готов да го направи). Манипулаторът на сигнала HupHandler() също записва в системния журнал и след това задава gGracefulShutdown и gCaughtHupSignal на 1.

В реалния живот получаването на сигнал SIGHUP кара демона да се рестартира, което е придружено от повторно четене на конфигурационния файл (който обикновено се чете от демона по време на стартиране) и нулиране на стойностите на параметрите, записани в него. Необходимостта от повторно четене на конфигурационния файл е най-честата причина за рестартиране на демони. Нашият демон няма конфигурационен файл, така че няма много работа по време на процеса на рестартиране.

Обърнете внимание на типа на променливите gGracefulShutdown и gCaughtHupSignal. Не сме срещали типа sig_atomic_t преди. Използването на този тип гарантира, че четенето и записването на данни в променливите gGracefulShutdown и gCaughtHupSignal ще се извършват атомарно, с една инструкция на процесора, която не може да бъде прекъсната.

Атомарността на променливите gGracefulShutdown и gCaughtHupSignal е важна, защото както манипулаторите на сигнали, така и основната програмна функция имат достъп до тях едновременно. По същата причина маркираме посочените променливи с ключовата дума volatile.

Функцията BindPassiveSocket() отваря сървърния порт за слушане (порт 30333 в нашия случай) на всички налични мрежови интерфейси и връща подходящия сокет:

int BindPassiveSocket(const intportNum, int *const boundSocket)

struct sockaddr_in грях; int newsock, optval; size_t избран;

memset(&sin.sin_zero, 0, 8); sin.sin_port = htons(portNum); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY);

if((newsock= socket(PF_INET, SOCK_STREAM, 0)) Съседни файлове в папка block9

    #