การสร้าง daemon ใน Linux


110

ใน Linux ฉันต้องการเพิ่ม daemon ที่ไม่สามารถหยุดทำงานได้และตรวจสอบการเปลี่ยนแปลงของระบบไฟล์ หากตรวจพบการเปลี่ยนแปลงใด ๆ ควรเขียนเส้นทางไปยังคอนโซลที่เริ่มต้นพร้อมกับขึ้นบรรทัดใหม่

ฉันมีรหัสการเปลี่ยนระบบไฟล์เกือบพร้อมแล้ว แต่ฉันไม่สามารถหาวิธีสร้าง daemon ได้

รหัสของฉันมาจากที่นี่: http://www.yolinux.com/TUTORIALS/ForkExecProcesses.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
Chimera

1
เป็นไปได้ที่ซ้ำกันของ: stackoverflow.com/questions/5384168/…สำหรับส่วน daemonize, stackoverflow.com/questions/931093/…สำหรับนาฬิการะบบไฟล์
Ciro Santilli 郝海东冠状病六四事件法轮功

หากคุณไม่ต้องการการปฏิบัติตามข้อกำหนด POSIX คุณอาจสนใจinotifyAPI inotify_initดู: inotify_add_watch, inotify_rm_watch,
patryk.beza

คำตอบ:


216

ใน Linux ฉันต้องการเพิ่ม daemon ที่ไม่สามารถหยุดทำงานได้และตรวจสอบการเปลี่ยนแปลงของระบบไฟล์ หากตรวจพบการเปลี่ยนแปลงใด ๆ ควรเขียนเส้นทางไปยังคอนโซลที่เริ่มต้น + ขึ้นบรรทัดใหม่

Daemons ทำงานอยู่เบื้องหลังและ (โดยปกติ ... ) ไม่ได้อยู่ใน TTY นั่นเป็นเหตุผลที่คุณไม่สามารถใช้ stdout / stderr ในแบบที่คุณต้องการได้ โดยปกติแล้ว syslog daemon ( syslogd ) จะใช้สำหรับการล็อกข้อความไปยังไฟล์ (debug, error, ... )

นอกจากนั้นยังมีขั้นตอนที่จำเป็นอีกสองสามขั้นตอนในการแสดงกระบวนการ


ถ้าจำไม่ผิดขั้นตอนเหล่านี้คือ:

  • แยกกระบวนการหลักออกและปล่อยให้กระบวนการสิ้นสุดลงหากการตีประสบความสำเร็จ -> เนื่องจากกระบวนการหลักสิ้นสุดลงกระบวนการย่อยจึงทำงานในพื้นหลัง
  • setsid - สร้างเซสชันใหม่ กระบวนการเรียกจะกลายเป็นผู้นำของเซสชันใหม่และหัวหน้ากลุ่มกระบวนการของกลุ่มกระบวนการใหม่ ตอนนี้กระบวนการถูกแยกออกจากเทอร์มินัลควบคุม (CTTY)
  • จับสัญญาณ - ละเว้นและ / หรือจัดการสัญญาณ
  • ส้อมอีกครั้งและปล่อยให้กระบวนการหลักยุติลงเพื่อให้แน่ใจว่าคุณได้กำจัดกระบวนการเซสชันที่เป็นผู้นำ (เฉพาะผู้นำเซสชั่นเท่านั้นที่จะได้รับ TTY อีกครั้ง)
  • chdir - เปลี่ยนไดเร็กทอรีการทำงานของ daemon
  • อูมาสก์ - เปลี่ยนรูปแบบโหมดไฟล์ตามความต้องการของ daemon
  • ปิด - ปิดตัวอธิบายไฟล์ที่เปิดอยู่ทั้งหมดที่อาจสืบทอดมาจากกระบวนการหลัก

เพื่อให้คุณเป็นจุดเริ่มต้น: ดูรหัสโครงกระดูกนี้ที่แสดงขั้นตอนพื้นฐาน ตอนนี้โค้ดนี้สามารถแยกบน GitHub: โครงกระดูกพื้นฐานของ linux daemon

/*
 * 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
  • เริ่ม daemon: ./firstdaemon
  • ตรวจสอบว่าทุกอย่างทำงานถูกต้องหรือไม่: ps -xj | grep firstdaemon

  • ผลลัพธ์ควรคล้ายกับผลลัพธ์นี้:

+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | เวลา | CMD |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | เหรอ? | -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

สิ่งที่คุณควรเห็นคือ:

  • ภูตไม่มีเทอร์มินัลควบคุม ( TTY =? )
  • รหัสกระบวนการหลัก ( PPID ) คือ1 (กระบวนการเริ่มต้น)
  • PID! = SIDซึ่งหมายความว่ากระบวนการของเราไม่ได้เป็นผู้นำในเซสชั่น
    (เพราะส้อมที่สอง ())
  • เนื่องจาก PID! = SID กระบวนการของเราไม่สามารถควบคุม TTY ได้อีก

อ่าน syslog:

  • ค้นหาไฟล์ syslog ของคุณ ฉันอยู่ที่นี่:/var/log/syslog
  • ทำ: grep firstdaemon /var/log/syslog

  • ผลลัพธ์ควรคล้ายกับผลลัพธ์นี้:

  firstdaemon [3387]: ภูตตัวแรกเริ่มต้น
  firstdaemon [3387]: ภูตตัวแรกสิ้นสุดลง


หมายเหตุ: ในความเป็นจริงคุณยังต้องการใช้ตัวจัดการสัญญาณและตั้งค่าการบันทึกอย่างถูกต้อง (ไฟล์ระดับการบันทึก ... )

อ่านเพิ่มเติม:


ว้าวขอบคุณ! เยี่ยมมาก ดังนั้นฉันจึงต้องใส่รหัสของฉันลงใน while Loop แล้วใช่ไหม
chrisMe

โดยพื้นฐานแล้วใช่ แต่โค้ดนี้เป็นเพียงตัวอย่างเท่านั้น ทั้งหมดขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุโดยใช้กระบวนการ daemon อย่าลืมอ่านคำตอบนี้ด้วย: @Edwin
Pascal Werkl

1
แทนที่จะใช้ครั้งที่สองfork()ทำไมไม่ใช้setsid()?
Chimera

1
หมายเหตุว่าฟังก์ชั่นให้เป็นกลไกที่ครอบคลุมมากขึ้นและมีความน่าเชื่อถือในการควบคุมสัญญาณ; การใช้งานใหม่ควรใช้มากกว่า sigaction()sigaction()signal()
patryk.beza

4
ควรแจ้งให้ผู้ชมทราบว่าวิธีนี้เป็นวิธี "เก่า" วิธีใหม่ที่แนะนำในการสร้าง daemon คือการใช้ "new style daemon" ที่นี่: 0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemonsหรือ
Starlord

30

man 7 daemonอธิบายวิธีการสร้าง daemon โดยละเอียด คำตอบของฉันเป็นเพียงการตัดตอนมาจากคู่มือนี้

ภูตมีอย่างน้อยสองประเภท:

  1. ภูตSysVแบบดั้งเดิม(แบบเก่า )
  2. systemd daemons ( รูปแบบใหม่ )

SysV Daemons

หากคุณสนใจSysV daemon แบบดั้งเดิมคุณควรใช้ขั้นตอนต่อไปนี้ :

  1. ที่เปิดอยู่ทั้งหมดอธิบายไฟล์ยกเว้นมาตรฐานการป้อนข้อมูล , การส่งออกและข้อผิดพลาด (เช่นไฟล์สามคนแรกอธิบาย 0, 1, 2) สิ่งนี้ช่วยให้มั่นใจได้ว่าไม่มีตัวอธิบายไฟล์ที่ส่งผ่านโดยบังเอิญอยู่ในกระบวนการ daemon บน Linux นี้มีการใช้งานที่ดีที่สุดโดยการทำซ้ำผ่าน/proc/self/fdกับทางเลือกของ iterating จากไฟล์อธิบาย 3 ถึงค่าส่งกลับโดยสำหรับgetrlimit()RLIMIT_NOFILE
  2. รีเซ็ตตัวจัดการสัญญาณทั้งหมดเป็นค่าเริ่มต้น นี้จะกระทำที่ดีที่สุดโดยการทำซ้ำผ่านสัญญาณที่มีอยู่ถึงขีด จำกัด ของและการตั้งค่าให้พวกเขา_NSIGSIG_DFL
  3. รีเซ็ตหน้ากากสัญญาณโดยใช้sigprocmask().
  4. ล้างบล็อกสภาพแวดล้อมลบหรือรีเซ็ตตัวแปรสภาพแวดล้อมที่อาจส่งผลเสียต่อ daemon runtime
  5. โทรfork()เพื่อสร้างกระบวนการพื้นหลัง
  6. ในเด็กที่โทรsetsid()ที่จะแยกออกจากสถานีใด ๆ และสร้างความเป็นอิสระของเซสชั่น
  7. ในลูกให้โทรfork()อีกครั้งเพื่อให้แน่ใจว่า daemon ไม่สามารถรับเทอร์มินัลใหม่ได้อีก
  8. เรียกexit()ลูกคนแรกเพื่อให้เฉพาะลูกที่สอง (กระบวนการ daemon ที่แท้จริง) อยู่รอบ ๆ เพื่อให้แน่ใจว่ากระบวนการ daemon ถูกพาเรนต์ไปที่ init / PID 1 อีกครั้งตามที่ควรจะเป็น daemons ทั้งหมด
  9. ในกระบวนการภูตเชื่อมต่อ/dev/nullมาตรฐานการป้อนข้อมูล , การส่งออกและข้อผิดพลาด
  10. ในกระบวนการภูต, ตั้งค่าumaskให้เป็น 0 เพื่อให้รูปแบบไฟล์ที่ส่งผ่านไปopen(), mkdir()และเช่นกันโดยตรงควบคุมโหมดการเข้าถึงของไฟล์ที่สร้างขึ้นและไดเรกทอรี
  11. ในกระบวนการ daemon ให้เปลี่ยนไดเร็กทอรีปัจจุบันเป็นไดเร็กทอรี root ( /) เพื่อหลีกเลี่ยงไม่ให้ daemon บล็อกจุดเมาต์โดยไม่ได้ตั้งใจไม่ให้ถูกยกเลิกการต่อเชื่อม
  12. ในกระบวนการ daemon เขียน daemon PID (ตามที่ส่งคืนโดยgetpid()) ไปยังไฟล์ PID ตัวอย่างเช่น/run/foobar.pid(สำหรับ daemon สมมุติ "foobar") เพื่อให้แน่ใจว่า daemon ไม่สามารถเริ่มทำงานได้มากกว่าหนึ่งครั้ง สิ่งนี้จะต้องถูกนำไปใช้ในรูปแบบที่ปราศจากการแข่งขันเพื่อให้ไฟล์ PID ได้รับการอัปเดตก็ต่อเมื่อได้รับการยืนยันในเวลาเดียวกันกับที่ PID ที่เก็บไว้ก่อนหน้านี้ในไฟล์ PID ไม่มีอยู่อีกต่อไปหรือเป็นของกระบวนการต่างประเทศ
  13. ในกระบวนการ daemon ให้ปล่อยสิทธิ์หากเป็นไปได้และใช้ได้
  14. จากกระบวนการ daemon แจ้งให้กระบวนการดั้งเดิมเริ่มต้นว่าการเตรียมใช้งานเสร็จสมบูรณ์ สิ่งนี้สามารถนำไปใช้ผ่านท่อที่ไม่มีชื่อหรือช่องทางการสื่อสารที่คล้ายกันซึ่งสร้างขึ้นก่อนช่องแรกfork()และด้วยเหตุนี้จึงมีให้ใช้งานทั้งในกระบวนการดั้งเดิมและกระบวนการดีมอน
  15. โทรexit()ในกระบวนการเดิม กระบวนการที่เรียกใช้ daemon ต้องสามารถพึ่งพาได้ว่าสิ่งนี้exit()เกิดขึ้นหลังจากการเตรียมใช้งานเสร็จสมบูรณ์และช่องทางการสื่อสารภายนอกทั้งหมดได้รับการจัดตั้งและเข้าถึงได้

สังเกตคำเตือนนี้:

ไม่ควรdaemon()ใช้ฟังก์ชันBSD เนื่องจากใช้เพียงส่วนย่อยของขั้นตอนเหล่านี้

daemon ที่ต้องการให้เข้ากันได้กับระบบ SysV ควรใช้โครงร่างที่ระบุไว้ข้างต้น อย่างไรก็ตามขอแนะนำให้ทำให้ลักษณะการทำงานนี้เป็นทางเลือกและกำหนดค่าได้ผ่านอาร์กิวเมนต์บรรทัดคำสั่งเพื่อลดความยุ่งยากในการดีบักรวมทั้งทำให้การรวมเข้ากับระบบง่ายขึ้นโดยใช้ systemd

โปรดทราบว่าdaemon()ไม่สอดคล้องกับPOSIX


Daemons รูปแบบใหม่

สำหรับ daemons สไตล์ใหม่แนะนำให้ทำตามขั้นตอนต่อไปนี้ :

  1. หากSIGTERMได้รับให้ปิด daemon และออกอย่างหมดจด
  2. หากSIGHUPได้รับให้โหลดไฟล์คอนฟิกูเรชันใหม่อีกครั้งหากมีผลบังคับใช้
  3. ระบุรหัสออกที่ถูกต้องจากกระบวนการ daemon หลักเนื่องจากระบบเริ่มใช้เพื่อตรวจหาข้อผิดพลาดและปัญหาของบริการ ก็จะแนะนำให้ทำตามโครงการรหัสทางออกที่กำหนดไว้ในLSB คำแนะนำสำหรับ SysV init สคริปต์
  4. ถ้าเป็นไปได้และใช้ได้ให้เปิดเผยอินเทอร์เฟซการควบคุมของ daemon ผ่านระบบD-Bus IPCและจับชื่อบัสเป็นขั้นตอนสุดท้ายของการเริ่มต้น
  5. สำหรับการรวมใน systemd ให้จัดเตรียมไฟล์. service unitที่มีข้อมูลเกี่ยวกับการเริ่มต้นการหยุดและการบำรุงรักษา daemon ดูsystemd.service(5)รายละเอียด
  6. ให้มากที่สุดเท่าที่จะเป็นไปได้ให้พึ่งพาฟังก์ชันการทำงานของระบบ init เพื่อ จำกัด การเข้าถึง daemon ไปยังไฟล์บริการและทรัพยากรอื่น ๆ เช่นในกรณีของ systemd ให้พึ่งพาการควบคุมขีด จำกัด ทรัพยากรของ systemd แทนที่จะใช้งานของคุณเองพึ่งพาการลดสิทธิ์ของ systemd รหัสแทนที่จะนำไปใช้ใน daemon และคล้ายกัน ดูsystemd.exec(5)การควบคุมที่มี
  7. หากD-Busจะใช้รถบัสให้เลือกเปิดภูตของคุณโดยการจัดหาบริการที่เปิดใช้งาน D-Bus แฟ้มการกำหนดค่า สิ่งนี้มีข้อดีหลายประการ: ภูตของคุณอาจเริ่มทำงานตามความต้องการอย่างเฉื่อยชา มันอาจจะเริ่มต้นในขนานกับภูตอื่น ๆ ที่ต้องการมัน - ซึ่งจะเพิ่มแบบขนานและความเร็วในการบูตขึ้น ; daemon ของคุณสามารถเริ่มต้นใหม่ได้เมื่อเกิดความล้มเหลวโดยไม่สูญเสียคำขอบัสใด ๆ เนื่องจากคิวบัสร้องขอบริการที่เปิดใช้งานได้ ดูรายละเอียดด้านล่าง
  8. หาก daemon ของคุณให้บริการแก่กระบวนการโลคัลอื่น ๆ หรือไคลเอนต์ระยะไกลผ่านทางซ็อกเก็ตควรทำให้ซ็อกเก็ตเปิดใช้งานได้ตามรูปแบบที่ระบุด้านล่าง เช่นเดียวกับการเปิดใช้งาน D-Bus สิ่งนี้จะช่วยให้สามารถเริ่มบริการได้ตามต้องการและยังช่วยให้การเริ่มต้นบริการแบบขนานดีขึ้น นอกจากนี้สำหรับโปรโตคอลที่ไม่ระบุสถานะ (เช่น syslog, DNS) daemon ที่ใช้การเปิดใช้งานซ็อกเก็ตสามารถเริ่มต้นใหม่ได้โดยไม่สูญเสียการร้องขอแม้แต่ครั้งเดียว ดูรายละเอียดด้านล่าง
  9. ถ้าเป็นไปได้ daemon ควรแจ้งระบบ init เกี่ยวกับความสมบูรณ์ของการเริ่มต้นหรือการอัพเดตสถานะผ่านทางไฟล์ sd_notify(3)อินเทอร์เฟซ
  10. แทนที่จะใช้การsyslog()โทรเพื่อเข้าสู่ระบบโดยตรงไปยังเซอร์วิส syslog ของระบบ daemon รูปแบบใหม่อาจเลือกเพียงแค่ล็อกไปยังข้อผิดพลาดมาตรฐานผ่าน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 และสัญญาณอื่น ๆ (เมื่อไม่มีการตรวจจับ) อาจส่งผลให้กระบวนการยุติลงได้เช่นกัน

คุณอาจต้องการฟังก์ชัน daemonize ทั่วไปมากขึ้นซึ่งคุณสามารถระบุชื่อสำหรับโปรแกรม / daemon ของคุณและเส้นทางในการรันโปรแกรมของคุณ (อาจเป็น "/" หรือ "/ 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)คู่มือกล่าวถึงขั้นตอนในการสร้าง daemon และเตือนว่า: ไม่ควรใช้ฟังก์ชันBSD daemon()เนื่องจากใช้เพียงส่วนย่อยของขั้นตอนเหล่านี้ daemonฟังก์ชั่นปรากฏตัวครั้งแรกใน4.4BSDและมีไม่ POSIX สอดคล้อง
patryk.beza

2
ยังทราบว่าคำเตือนเกี่ยวกับการใช้ภูต () คือในส่วนสไตล์เก่า SysV ของภูต (7) หน้าคน ไม่สนับสนุนการใช้ daemon () สำหรับ systemd
Greg McPherran

7

ฉันสามารถหยุดได้ที่ข้อกำหนดแรก "ภูตที่ไม่สามารถหยุดได้ ... "

เป็นไปไม่ได้เพื่อนของฉัน; อย่างไรก็ตามคุณสามารถทำสิ่งเดียวกันได้ด้วยเครื่องมือที่ดีกว่ามากนั่นคือโมดูลเคอร์เนล

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

ภูตทั้งหมดสามารถหยุดได้ บางคนหยุดได้ง่ายกว่าคนอื่น ๆ แม้แต่ภูตที่จับคู่กับคู่หูในการระงับการเกิดใหม่หากแพ้คู่หูก็สามารถหยุดได้ คุณต้องทำงานหนักขึ้นเล็กน้อย


7
ฉันคิดว่าการพูดว่า "ภูตที่ไม่สามารถหยุดได้" ผู้เขียนหมายความว่า daemon มักจะทำงานอยู่เบื้องหลังเสมอเมื่อเซสชันสิ้นสุดลง
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

หากต้องการให้แอปทั้งหมดทำงานเมื่อรีบูต (และ daemonise pm2):

pm2 startup

pm2 save

ตอนนี้คุณสามารถ:

service pm2 stop|restart|start|status

(ยังช่วยให้คุณดูการเปลี่ยนแปลงรหัสในไดเรกทอรีแอปของคุณได้อย่างง่ายดายและรีสตาร์ทกระบวนการแอปโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงรหัสเกิดขึ้น)


2
สิ่งนี้ไม่เกี่ยวข้องกับ C.
melpomene

4
ฉันขอขอบคุณที่มีแท็ก C อย่างไรก็ตาม OP ไม่ได้กล่าวถึงข้อกำหนดเกี่ยวกับ C ในคำถาม ชื่อเรื่องกำลังสร้างปีศาจใน linux คำตอบนี้เป็นที่พอใจ
danday74

1
โอ้คุณพูดถูก มันติดแท็ก C แต่ข้อกำหนดที่แท้จริงคือ C ++ (ตามหลักฐานจากรหัสของ OP และบทความที่เชื่อมโยง)
melpomene

3

คุณได้สร้างกระบวนการย่อยด้วยการเรียก 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
ขอบคุณ. ดังนั้นจึงไม่มีความแตกต่างระหว่าง "daemon" กับ Programm ธรรมดา? ฉันไม่อยากให้ปิดง่ายๆ
chrisMe

1
ไม่ภูตเป็นเพียงกระบวนการเบื้องหลัง โดยเฉพาะอย่างยิ่งคุณแยกจากผู้ปกครองเรียกใช้กระบวนการย่อยและยุติพาเรนต์ (เพื่อให้ไม่มีการเข้าถึงโปรแกรมเทอร์มินัล) นั่นคือสิ่งที่จำเป็นแม้ว่าจะต้องเป็น "ภูต": en.wikipedia.org/wiki/Daemon_(computing)
Magn3s1um

1

เทมเพลต Daemon

ฉันเขียนเทมเพลต daemon ตามลิงค์ daemon: รูปแบบใหม่

คุณสามารถค้นหารหัสเทมเพลตทั้งหมดได้ใน 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.