Створення демона в Linux


110

У Linux я хочу додати демон, який неможливо зупинити і який відстежує зміни файлової системи. Якщо будь-які зміни будуть виявлені, він повинен записати шлях до консолі, де він був запущений, а також новий рядок.

У мене вже готовий код зміни файлової системи, але я не можу зрозуміти, як створити демон.

Мій код звідси: http://www.yolinux.com/TUTORIALS/ForkExecProcess.html

Що робити після вилки?

int main (int argc, char **argv) {

  pid_t pID = fork();
  if (pID == 0)  {              // child
          // Code only executed by child process    
      sIdentifier = "Child Process: ";
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        exit(1);
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

      sIdentifier = "Parent Process:";
    }       

    return 0;
}

1
Можливий дублікат: stackoverflow.com/q/5384168/1076451
Химера

1
можливий дублікат: stackoverflow.com/questions/5384168/… для демонізованої частини, stackoverflow.com/questions/931093/… для файлової системи
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Якщо вам не потрібна відповідність POSIX, можливо, вас зацікавить inotifyAPI. Див: inotify_init, inotify_add_watch, inotify_rm_watch.
patryk.beza

Відповіді:


216

У Linux я хочу додати демон, який неможливо зупинити і який відстежує зміни файлової системи. Якщо будь-які зміни будуть виявлені, слід записати шлях до консолі, де вона була запущена + новий рядок.

Демони працюють у фоновому режимі і (як правило ...) не належать до TTY, тому ви не можете використовувати stdout / stderr так, як ви, мабуть, хочете. Зазвичай демон-syslog ( syslogd ) використовується для реєстрації повідомлень у файлах (налагодження, помилка, ...).

Крім того, є кілька необхідних кроків для демонстрації процесу.


Якщо я правильно пам'ятаю, ці кроки:

  • відвалити від батьківського процесу і нехай це припинити , якщо розгалуження було успішним. -> Оскільки батьківський процес закінчився, дочірній процес зараз працює у фоновому режимі.
  • setid - Створіть новий сеанс. Процес виклику стає лідером нового сеансу, а керівником групи процесів нової групи процесів. Зараз процес від'єднаний від керуючого терміналу (CTTY).
  • Ловлять сигнали - ігноруйте та / або обробляйте сигнали.
  • fork знову і нехай батьківський процес закінчується, щоб уникнути позбавлення від ведучого сеансу. (Тільки ведучі сесії можуть отримати TTY знову.)
  • chdir - Змінення робочого каталогу демона.
  • umask - Зміна маски режиму файлу відповідно до потреб демон.
  • Закрити - Закрийте всі дескриптори відкритих файлів, які можуть бути успадковані від батьківського процесу.

Щоб дати точку відліку: подивіться на цей код скелета, який показує основні кроки. Цей код тепер також може бути роздвоєний на GitHub: Основний скелет демона Linux

/*
 * daemonize.c
 * This example daemonizes a process, writes a few log messages,
 * sleeps 20 seconds and terminates afterwards.
 */

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

static void skeleton_daemon()
{
    pid_t pid;

    /* Fork off the parent process */
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* On success: The child process becomes session leader */
    if (setsid() < 0)
        exit(EXIT_FAILURE);

    /* Catch, ignore and handle signals */
    //TODO: Implement a working signal handler */
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    /* Fork off for the second time*/
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* Set new file permissions */
    umask(0);

    /* Change the working directory to the root directory */
    /* or another appropriated directory */
    chdir("/");

    /* Close all open file descriptors */
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
    {
        close (x);
    }

    /* Open the log file */
    openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
    skeleton_daemon();

    while (1)
    {
        //TODO: Insert daemon code here.
        syslog (LOG_NOTICE, "First daemon started.");
        sleep (20);
        break;
    }

    syslog (LOG_NOTICE, "First daemon terminated.");
    closelog();

    return EXIT_SUCCESS;
}


  • Складіть код: gcc -o firstdaemon daemonize.c
  • Запустіть демон: ./firstdaemon
  • Перевірте, чи все працює належним чином: ps -xj | grep firstdaemon

  • Вихід повинен бути подібним до цього:

+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| PPID | ПІД | ПГІД | SID | TTY | TPGID | СТАТ | UID | ЧАС | ШМД |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

Що ви повинні тут побачити:

  • У демона немає керуючого терміналу ( TTY =? )
  • Ідентифікатор батьківського процесу ( PPID ) дорівнює 1 (процес init)
  • PID! = SID , який означає , що наш процес не є лідером сеансу
    (з другої вилки ())
  • Оскільки PID! = SID, наш процес не може знову взяти під контроль TTY

Читання системного журналу:

  • Знайдіть свій файл syslog. Моя тут:/var/log/syslog
  • Зробіть: grep firstdaemon /var/log/syslog

  • Вихід повинен бути подібним до цього:

  firstdaemon [3387]: Перший демон почався.
  firstdaemon [3387]: Перший демон припинено.


Примітка. Насправді ви також хотіли б реалізувати обробник сигналів та налаштувати журнал належним чином (Файли, рівні журналу ...).

Подальше читання:


Вау Дякую! Це чудово. Отже, я повинен ввести свій код в цикл while і це?
chrisMe

В основному, так. Але цей код - лише приклад. Це повністю залежить від того, чого ви хочете досягти, використовуючи демон-процес. Не забудьте прочитати і цю відповідь: @Edwin
Pascal Werkl

1
Замість другого fork(), чому б не просто використовувати setsid()?
Химера

1
Зверніть увагу , що функція забезпечує більш повний і надійний механізм для керуючих сигналів; нові програми слід використовувати, а не використовувати . sigaction()sigaction()signal()
patryk.beza

4
Слід зауважити глядачам, що цей метод - «старий» спосіб. Новий рекомендований спосіб створення демона - це "демон нового стилю", знайдений тут: 0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons або
Starlord

30

man 7 daemonописується, як створити демон досить докладно. Моя відповідь - лише уривок із цього посібника.

Існує щонайменше два типи демонів:

  1. традиційні демони SysV ( старого стилю ),
  2. системні демони ( новий стиль ).

Демони SysV

Якщо вас цікавить традиційний демон SysV , вам слід виконати наступні кроки :

  1. Закрийте всі відкриті дескриптори файлів, крім стандартного вводу , виводу та помилки (тобто перші три дескриптори файлів 0, 1, 2). Це гарантує, що жоден випадково переданий дескриптор файлу не залишається навколо в процесі демон. У Linux це найкраще реалізувати шляхом ітерації через /proc/self/fd, із запасом ітерації з дескриптора файлів 3 до значення, поверненого getrlimit()для RLIMIT_NOFILE.
  2. Скиньте всі обробники сигналів за замовчуванням. Найкраще це зробити шляхом ітерації через наявні сигнали до межі _NSIGта скидання їх до SIG_DFL.
  3. Скиньте сигнальну маску за допомогою sigprocmask().
  4. Санітизуйте блок навколишнього середовища, видаляючи чи скидаючи змінні середовища, що може негативно впливати на час виконання демона.
  5. Зателефонуйте fork(), щоб створити фоновий процес.
  6. У дитини зателефонуйте, setsid()щоб відключитися від будь-якого терміналу та створити незалежний сеанс .
  7. У дитини fork()знову зателефонуйте , щоб переконатися, що демон більше не зможе знову придбати термінал.
  8. Подзвоніть exit()у першу дитину, щоб лише друга дитина (власне процес демона) залишалася навколо. Це гарантує, що процес демона буде перетворений на init / PID 1, як і всі демони.
  9. У демонстраційному процесі підключіться /dev/nullдо стандартного вводу , виводу та помилки .
  10. В процесі демона, скинути umaskв 0, так що режими файл передається open(), mkdir()і таковою безпосередньо контролювати режим доступу до файлів і каталогів.
  11. У процесі демона змініть поточний каталог на кореневий каталог ( /), щоб уникнути того, що демон мимоволі блокує точки монтажу від відключення.
  12. У процесі демон, записуйте демона PID (як повернуто getpid()) у файл PID, наприклад /run/foobar.pid(для гіпотетичного демона "foobar"), щоб переконатися, що демон не може бути запущений більше одного разу. Це потрібно реалізовувати без гонок, щоб файл PID оновлювався лише тоді, коли він одночасно перевіряється, що PID, який раніше зберігався у файлі PID, більше не існує або належить до іноземного процесу.
  13. У демонстраційному процесі відмовтеся від привілеїв, якщо це можливо та застосовно.
  14. Після демонстраційного процесу повідомляйте про початковий процес, що розпочався, ініціалізація завершена. Це може бути реалізовано за допомогою безіменної труби або подібного каналу зв'язку, який створюється до першого fork()і, отже, доступного як в оригінальному, так і в демон-процесі.
  15. Телефонуйте exit()в оригінальному процесі. Процес, який викликав демона, повинен мати можливість покладатися на те, що це exit()відбувається після завершення ініціалізації та встановлення та доступу всіх зовнішніх каналів зв'язку.

Зверніть увагу на це попередження:

BSD daemon()функція не повинна бути використана, оскільки вона реалізує тільки підмножина цих кроків.

Демон, який повинен забезпечити сумісність із системами SysV, повинен реалізувати схему, вказану вище. Однак рекомендується зробити цю поведінку необов'язковою і налаштовуватися через аргумент командного рядка, щоб полегшити налагодження, а також спростити інтеграцію в системи, що використовують systemd.

Зауважте, що daemon()це не сумісно з POSIX .


Демони нового стилю

Для демонів нового стилю рекомендуються наступні кроки :

  1. Якщо SIGTERMотримано, вимкніть демон і чисто вийдіть.
  2. Якщо SIGHUPотримано, перезавантажте конфігураційні файли, якщо це стосується.
  3. Надайте правильний код виходу з основного процесу демон, оскільки це використовується системою init для виявлення сервісних помилок та проблем. Рекомендується дотримуватися схеми вихідного коду, визначеної в рекомендаціях LSB для сценаріїв init SysV .
  4. Якщо це можливо та застосовно, відкрийте інтерфейс управління демона через систему IP- протоколу D-Bus та схопіть назву шини як останній крок ініціалізації.
  5. Для інтеграції в systemd надайте файл .service unit, який містить інформацію про запуск, зупинку та інше підтримку демона. Детальніше systemd.service(5)див.
  6. Наскільки це можливо, покладайтеся на функціональність системи init, щоб обмежити доступ демон до файлів, служб та інших ресурсів, тобто у випадку systemd, покладайтеся на контроль обмеження ресурсів systemd замість того, щоб реалізувати свій власний, покладайтеся на відмову від привілеїв systemd код замість того, щоб реалізовувати його в демоні тощо. Див systemd.exec(5). Доступні елементи керування.
  7. Якщо використовується D-Bus , зробіть активацію шини демона, надавши файл конфігурації активації послуги D-Bus . Це має ряд переваг: ваш демон може запускатися ліниво на вимогу; він може бути запущений паралельно іншим демонам, які цього вимагають - що максимально збільшує паралелізацію та швидкість завантаження ; ваш демон може бути перезапущений при відмові, не втрачаючи жодних запитів на шину, оскільки шини запитують запити на активовані послуги. Детальніше дивіться нижче .
  8. Якщо ваш демон надає послуги іншим локальним процесам або віддаленим клієнтам через сокет, він повинен бути активований сокетом, дотримуючись схеми, вказаної нижче . Як і активація D-Bus, це дозволяє запускати послуги за запитом, а також дозволяє покращити паралелізацію запуску послуги. Крім того, для протоколів без стану (таких як syslog, DNS) демон, що реалізує активацію на основі сокета, може бути перезапущений, не втрачаючи жодного запиту. Детальніше дивіться нижче .
  9. Якщо можливо, демон повинен повідомити систему init про завершення запуску або оновлення статусу через sd_notify(3)інтерфейс.
  10. Замість використання syslog()виклику для входу безпосередньо в систему системного журналу, демон нового стилю може вибрати просто увійти до стандартної помилки через fprintf(), яку потім пересилає в syslog системою init. Якщо рівні журналу необхідні, їх можна кодувати шляхом префіксації окремих ліній журналу рядками типу "<4>" (для рівня журналу 4 "ПОПЕРЕДЖЕННЯ" у схемі пріоритетів syslog), дотримуючись аналогічного стилю, як printk()система рівня ядра Linux . Для отримання додаткової інформації див sd-daemon(3)і systemd.exec(5).

Щоб дізнатися більше читати цілком man 7 daemon.


11

Ви не можете створити процес в Linux, який неможливо вбити. Користовий користувач (uid = 0) може надсилати сигнал до процесу, і є два сигнали, які неможливо спіймати, SIGKILL = 9, SIGSTOP = 19. І інші сигнали (якщо вони не знайдені) також можуть призвести до припинення процесу.

Можливо, ви хочете отримати більш загальну функцію демонізації, де ви можете вказати назву програми / демона та шлях для запуску програми (можливо, "/" або "/ tmp"). Ви також можете надати файл (и) для stderr та stdout (і, можливо, шлях управління за допомогою stdin).

Ось необхідні включає:

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

І тут є більш загальна функція,

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
    if(!path) { path="/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %s\n",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsid\n");
        exit(EXIT_FAILURE);
    }

    //catch/ignore signals
    signal(SIGCHLD,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    //fork second time
    if ( (child=fork())<0) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if( child>0 ) { //parent
        exit(EXIT_SUCCESS);
    }

    //new file permissions
    umask(0);
    //change to path directory
    chdir(path);

    //Close all open file descriptors
    int fd;
    for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
    {
        close(fd);
    }

    //reopen stdin, stdout, stderr
    stdin=fopen(infile,"r");   //fd=0
    stdout=fopen(outfile,"w+");  //fd=1
    stderr=fopen(errfile,"w+");  //fd=2

    //open syslog
    openlog(name,LOG_PID,LOG_DAEMON);
    return(0);
}

Ось зразкова програма, яка стає демоном, зависає, а потім відходить.

int
main()
{
    int res;
    int ttl=120;
    int delay=5;
    if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
        fprintf(stderr,"error: daemonize failed\n");
        exit(EXIT_FAILURE);
    }
    while( ttl>0 ) {
        //daemon code here
        syslog(LOG_NOTICE,"daemon ttl %d",ttl);
        sleep(delay);
        ttl-=delay;
    }
    syslog(LOG_NOTICE,"daemon ttl expired");
    closelog();
    return(EXIT_SUCCESS);
}

Зверніть увагу, що SIG_IGN вказує на отримання та ігнорування сигналу. Ви можете створити обробник сигналу, який може реєструвати отримання сигналу, та встановлювати прапори (наприклад, прапор для вказівки витонченого відключення).


8

Спробуйте скористатися daemonфункцією:

#include <unistd.h>

int daemon(int nochdir, int noclose);

На сторінці чоловіка :

Функція daemon () призначена для програм, які бажають відірватися від керуючого терміналу і працювати у фоновому режимі як демони системи.

Якщо nochdir дорівнює нулю, daemon () змінює поточний робочий каталог виклику процесу на кореневий каталог ("/"); в іншому випадку поточний робочий каталог залишається незмінним.

Якщо noclose дорівнює нулю, daemon () перенаправляє стандартний вхід, стандартний вихід і стандартну помилку на / dev / null; інакше в цих дескрипторах файлів не вносяться зміни.


2
Зауважте, що daemon(7)вручну згадуються етапи створення демона і попереджається: Функція BSD daemon()не повинна використовуватися, оскільки вона реалізує лише підмножину цих кроків. daemonФункція вперше з'явилася в 4.4BSD і не відповідає POSIX .
patryk.beza

2
Зауважимо також, що попередження про використання daemon () міститься у старому стилі SysV у розділі man daemon (7) . Використання daemon () не перешкоджає системним.
Грег Макферран

7

Я можу зупинитися на першій вимозі "Демон, якого неможливо зупинити ..."

Неможливо мій друг; однак ви можете досягти цього за допомогою набагато кращого інструменту, модуля ядра.

http://www.infoq.com/articles/inotify-linux-file-system-event-monitoring

Усі демони можна зупинити. Деякі легше зупиняються, ніж інші. Навіть демон-пара з партнером утримується, відновлюючи партнера, якщо він загублений, можна зупинити. Ви просто повинні трохи більше працювати над цим.


7
Я думаю, кажучи "Демон, якого неможливо зупинити", автор насправді означає, що демон завжди працює на тлі, коли сеанс припиняється.
FaceBro

6

Якщо ваш додаток один із:

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}

і ви не заперечуєте залежність від NodeJS, тоді встановіть NodeJS, а потім:

npm install -g pm2

pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above

pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores

pm2 list

Щоб усі програми працювали під час перезавантаження (та демон-вечір pm2):

pm2 startup

pm2 save

Тепер ти можеш:

service pm2 stop|restart|start|status

(також легко дозволяє спостерігати за змінами коду в каталозі додатків та автоматично перезапускати процес програми, коли відбувається зміна коду)


2
Це не має нічого спільного з С.
мельпомена

4
Я вдячний, що є тег С. Однак ОП у питанні не згадує вимоги щодо С. Назва - це створення демона в Linux. Ця відповідь задовольняє це.
понеділок74

1
О, ти маєш рацію. Він позначений тегом C, але фактична вимога - C ++ (про що свідчить код ОП та пов'язана стаття).
Мельпомена

3

Зателефонувавши до fork (), ви створили дочірній процес. Якщо вилка буде успішною (fork повернув ненульовий PID), виконання буде продовжено з цього моменту в межах дочірнього процесу. У цьому випадку ми хочемо витончено вийти з батьківського процесу, а потім продовжувати свою роботу в дочірньому процесі.

Можливо, це допоможе: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html


2

Демон - це лише процес на задньому плані. Якщо ви хочете запустити свою програму, коли ОС завантажиться в Linux, ви додасте команду start у /etc/rc.d/rc.local (запустити після всіх інших сценаріїв) або /etc/startup.sh

У Windows ви робите послугу, реєструєте її, а потім встановлюєте її для автоматичного запуску при завантаженні в адміністрації -> панелі послуг.


1
Дякую. Тож чи немає різниці між "демоном" і просто звичайною Програмою? Я не хочу, щоб це було легко закрити.
chrisMe

1
Ні, демон - це лише фоновий процес. Більш конкретно, ви відхиляєтесь від батьків, запускаєте дочірній процес та припиняєте батьківське (щоб не було термінального доступу до програми). це не потрібне, хоча, щоб бути "демон": en.wikipedia.org/wiki/Daemon_(computing)
Magn3s1um

1

Шаблон демона

Я написав шаблон демона, що слідує за новим стилем демон: посилання

Ви можете знайти весь код шаблону на GitHub: тут

Main.cpp

// This function will be called when the daemon receive a SIGHUP signal.
void reload() {
    LOG_INFO("Reload function called.");
}

int main(int argc, char **argv) {
    // The Daemon class is a singleton to avoid be instantiate more than once
    Daemon& daemon = Daemon::instance();
    // Set the reload function to be called in case of receiving a SIGHUP signal
    daemon.setReloadFunction(reload);
    // Daemon main loop
    int count = 0;
    while(daemon.IsRunning()) {
        LOG_DEBUG("Count: ", count++);
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    LOG_INFO("The daemon process ended gracefully.");
}

Daemon.hpp

class Daemon {
    public:

    static Daemon& instance() {
        static Daemon instance;
        return instance;
    }

    void setReloadFunction(std::function<void()> func);

    bool IsRunning();

    private:

    std::function<void()> m_reloadFunc;
    bool m_isRunning;
    bool m_reload;

    Daemon();
    Daemon(Daemon const&) = delete;
    void operator=(Daemon const&) = delete;

    void Reload();

    static void signalHandler(int signal);
};

Daemon.cpp

Daemon::Daemon() {
    m_isRunning = true;
    m_reload = false;
    signal(SIGINT, Daemon::signalHandler);
    signal(SIGTERM, Daemon::signalHandler);
    signal(SIGHUP, Daemon::signalHandler);
}

void Daemon::setReloadFunction(std::function<void()> func) {
    m_reloadFunc = func;
}

bool Daemon::IsRunning() {
    if (m_reload) {
        m_reload = false;
        m_reloadFunc();
    }
    return m_isRunning;
}

void Daemon::signalHandler(int signal) {
    LOG_INFO("Interrup signal number [", signal,"] recived.");
    switch(signal) {
        case SIGINT:
        case SIGTERM: {
            Daemon::instance().m_isRunning = false;
            break;
        }
        case SIGHUP: {
            Daemon::instance().m_reload = true;
            break;
        }
    }
}

daemon-template.service

[Unit]
Description=Simple daemon template
After=network.taget

[Service]
Type=simple
ExecStart=/usr/bin/daemon-template --conf_file /etc/daemon-template/daemon-tenplate.conf
ExecReload=/bin/kill -HUP $MAINPID
User=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=daemon-template

[Install]
WantedBy=multi-user.target
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.