关于C#:在Linux中创建守护进程

Creating a daemon in Linux

在Linux中我想添加一个无法停止的守护进程,它监视文件系统的变化。
如果检测到任何更改,它应该将路径写入启动它的控制台加上换行符。

我已经有文件系统更改代码几乎准备好但我无法弄清楚如何创建一个守护进程。

我的代码来自:http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

叉子后要做什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}


In Linux i want to add a daemon that cannot be stopped and which monitors filesystem changes. If any changes would be detected it should write the path to the console where it was started + a newline.

守护进程在后台工作,(通常......)不属于TTY,这就是为什么你不能以你想要的方式使用stdout / stderr。
通常,syslog守护程序(syslogd)用于将消息记录到文件(debug,error,...)。

除此之外,还有一些必要的步骤来守护进程。

如果我没记错,这些步骤是:

  • 分离父进程&amp;如果分叉成功,让它终止。 - >由于父进程已终止,子进程现在在后台运行。
  • setsid - 创建一个新会话。调用进程成为新会话的领导者和新进程组的进程组负责人。该过程现在与其控制终端(CTTY)分离。
  • 捕获信号 - 忽略和/或处理信号。
  • 再次叉和&amp;让父进程终止以确保您摆脱会话引导进程。 (只有会议领导者可能会再次获得TTY。)
  • chdir - 更改守护程序的工作目录。
  • umask - 根据守护程序的需要更改文件模式掩码。
  • close - 关闭可能从父进程继承的所有打开的文件描述符。

为您提供一个起点:查看显示基本步骤的骨架代码。此代码现在也可以在GitHub上分叉:Linux守护程序的基本框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*
 * 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

  • 输出应该类似于这个:

1
2
3
4
5
+------+------+------+------+-----+-------+------+------+------+-----+
| PPID | PID  | PGID | SID  | TTY | TPGID | STAT | UID  | TIME | CMD |
+------+------+------+------+-----+-------+------+------+------+-----+
|    1 | 3387 | 3386 | 3386 | ?   |    -1 | S    | 1000 | 0:00 | ./  |
+------+------+------+------+-----+-------+------+------+------+-----+

你应该看到的是:

  • 守护进程没有控制终端(TTY =?)
  • 父进程ID(PPID)为1(init进程)
  • PID!= SID表示我们的流程不是会话领导者
    (因为第二个fork())
  • 因为PID!= SID,我们的进程无法再次控制TTY

阅读系统日志:

  • 找到您的syslog文件。我在这里:/var/log/syslog
  • 做一个:grep firstdaemon /var/log/syslog

  • 输出应该类似于这个:

1
2
  firstdaemon[3387]: First daemon started.
  firstdaemon[3387]: First daemon terminated.

一张纸条:
实际上,您还需要实现信号处理程序并正确设置日志记录(文件,日志级别......)。

进一步阅读:

  • Linux-UNIX-Programmierung - 德语
  • Unix守护程序服务器编程


man 7 daemon描述了如何非常详细地创建守护进程。我的回答只是摘自本手册。

至少有两种类型的守护进程:

  • 传统的SysV守护进程(旧式),
  • systemd守护进程(新式)。
  • SysV守护进程

    如果您对传统的SysV守护程序感兴趣,则应执行以下步骤:

  • Close all open file descriptors except standard input, output, and error (i.e. the first three file descriptors 0, 1, 2). This ensures that no accidentally passed file descriptor stays around in the daemon process. On Linux, this is best implemented by iterating through /proc/self/fd, with a fallback of iterating from file descriptor 3 to the value returned by getrlimit() for RLIMIT_NOFILE.
  • Reset all signal handlers to their default. This is best done by iterating through the available signals up to the limit of _NSIG and resetting them to SIG_DFL.
  • Reset the signal mask using sigprocmask().
  • Sanitize the environment block, removing or resetting environment variables that might negatively impact daemon runtime.
  • Call fork(), to create a background process.
  • In the child, call setsid() to detach from any terminal and create an independent session.
  • In the child, call fork() again, to ensure that the daemon can never re-acquire a terminal again.
  • Call exit() in the first child, so that only the second child (the actual daemon process) stays around. This ensures that the daemon process is re-parented to init/PID 1, as all daemons should be.
  • In the daemon process, connect /dev/null to standard input, output, and error.
  • In the daemon process, reset the umask to 0, so that the file modes passed to open(), mkdir() and suchlike directly control the access mode of the created files and directories.
  • In the daemon process, change the current directory to the root directory (/), in order to avoid that the daemon involuntarily blocks mount points from being unmounted.
  • In the daemon process, write the daemon PID (as returned by getpid()) to a PID file, for example /run/foobar.pid (for a hypothetical daemon"foobar") to ensure that the daemon cannot be started more than once. This must be implemented in race-free fashion so that the PID file is only updated when it is verified at the same time that the PID previously stored in the PID file no longer exists or belongs to a foreign process.
  • In the daemon process, drop privileges, if possible and applicable.
  • From the daemon process, notify the original process started that initialization is complete. This can be implemented via an unnamed pipe or similar communication channel that is created before the first fork() and hence available in both the original and the daemon process.
  • Call exit() in the original process. The process that invoked the daemon must be able to rely on that this exit() happens after initialization is complete and all external communication channels are established and accessible.
  • 注意这个警告:

    The BSD daemon() function should not be used, as it implements only a subset of these steps.

    A daemon that needs to provide compatibility with SysV systems should implement the scheme pointed out above. However, it is recommended to make this behavior optional and configurable via a command line argument to ease debugging as well as to simplify integration into systems using systemd.

    请注意,daemon()不符合POSIX标准。

    新式守护进程

    对于新式守护程序,建议执行以下步骤:

  • If SIGTERM is received, shut down the daemon and exit cleanly.
  • If SIGHUP is received, reload the configuration files, if this applies.
  • Provide a correct exit code from the main daemon process, as this is used by the init system to detect service errors and problems. It is recommended to follow the exit code scheme as defined in the LSB recommendations for SysV init scripts.
  • If possible and applicable, expose the daemon's control interface via the D-Bus IPC system and grab a bus name as last step of initialization.
  • For integration in systemd, provide a .service unit file that carries information about starting, stopping and otherwise maintaining the daemon. See systemd.service(5) for details.
  • As much as possible, rely on the init system's functionality to limit the access of the daemon to files, services and other resources, i.e. in the case of systemd, rely on systemd's resource limit control instead of implementing your own, rely on systemd's privilege dropping code instead of implementing it in the daemon, and similar. See systemd.exec(5) for the available controls.
  • If D-Bus is used, make your daemon bus-activatable by supplying a D-Bus service activation configuration file. This has multiple advantages: your daemon may be started lazily on-demand; it may be started in parallel to other daemons requiring it — which maximizes parallelization and boot-up speed; your daemon can be restarted on failure without losing any bus requests, as the bus queues requests for activatable services. See below for details.
  • If your daemon provides services to other local processes or remote clients via a socket, it should be made socket-activatable following the scheme pointed out below. Like D-Bus activation, this enables on-demand starting of services as well as it allows improved parallelization of service start-up. Also, for state-less protocols (such as syslog, DNS), a daemon implementing socket-based activation can be restarted without losing a single request. See below for details.
  • If applicable, a daemon should notify the init system about startup completion or status updates via the sd_notify(3) interface.
  • Instead of using the syslog() call to log directly to the system syslog service, a new-style daemon may choose to simply log to standard error via fprintf(), which is then forwarded to syslog by the init system. If log levels are necessary, these can be encoded by prefixing individual log lines with strings like"<4>" (for log level 4"WARNING" in the syslog priority scheme), following a similar style as the Linux kernel's printk() level system. For details, see sd-daemon(3) and systemd.exec(5).
  • 要了解更多信息,请阅读整个man 7 daemon


    你不能在linux中创建一个无法杀死的进程。 root用户(uid = 0)可以向进程发送信号,并且有两个信号无法捕获,SIGKILL = 9,SIGSTOP = 19。其他信号(未被捕获时)也可能导致进程终止。

    您可能需要一个更通用的守护进程函数,您可以在其中指定程序/守护程序的名称,以及运行程序的路径(可能是"/"或"/ tmp")。您可能还想为stderr和stdout(以及可能使用stdin的控制路径)提供文件。

    以下是必要的包括:

    1
    2
    3
    4
    5
    6
    #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)

    这是一个更通用的功能,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    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
    ",name,path,outfile,infile);
        pid_t child;
        //fork, detach from process group leader
        if( (child=fork())<0 ) { //failed fork
            fprintf(stderr,"
    error: failed fork
    ");
            exit(EXIT_FAILURE);
        }
        if (child>0) { //parent
            exit(EXIT_SUCCESS);
        }
        if( setsid()<0 ) { //failed to become session leader
            fprintf(stderr,"
    error: failed setsid
    ");
            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
    ");
            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);
    }

    这是一个示例程序,它成为守护进程,挂起,然后离开。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int
    main()
    {
        int res;
        int ttl=120;
        int delay=5;
        if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
            fprintf(stderr,"error: daemonize failed
    "
    );
            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表示捕获并忽略该信号。您可以构建一个可以记录信号接收的信号处理程序,并设置标志(例如标志以指示正常关闭)。


    如果您的应用是以下之一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
     ".sh":"bash",
     ".py":"python",
     ".rb":"ruby",
     ".coffee" :"coffee",
     ".php":"php",
     ".pl" :"perl",
     ".js" :"node"
    }

    你不介意NodeJS依赖,然后安装NodeJS然后:

    1
    2
    3
    4
    5
    6
    7
    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):

    1
    2
    3
    pm2 startup

    pm2 save

    现在你可以:

    1
    service pm2 stop|restart|start|status

    (还可以轻松地监视应用目录中的代码更改,并在代码更改发生时自动重启应用进程)


    尝试使用daemon功能:

    1
    2
    3
    #include <unistd.h>

    int daemon(int nochdir, int noclose);

    从手册页:

    The daemon() function is for programs wishing to detach themselves
    from the controlling terminal and run in the background as system
    daemons.

    If nochdir is zero, daemon() changes the calling process's current
    working directory to the root directory ("/"); otherwise, the current
    working directory is left unchanged.

    If noclose is zero, daemon() redirects standard input, standard
    output and standard error to /dev/null; otherwise, no changes are
    made to these file descriptors.


    我可以停止第一个要求"一个无法停止的守护进程......"

    不可能是我的朋友;但是,您可以使用更好的工具,内核模块来实现相同的功能。

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

    所有守护进程都可以停止。有些人比其他人更容易被阻止。即使与伴侣保持一对守护进程,如果丢失也会重新生成伴侣,可以停止。你只需要更加努力地工作。


    通过调用fork(),您已经创建了一个子进程。如果fork成功(fork返回非零PID),则执行将从子进程内的此点继续执行。在这种情况下,我们希望优雅地退出父进程,然后继续我们在子进程中的工作。

    也许这会有所帮助:
    http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html


    守护进程只是后台进程。如果你想在操作系统启动时启动你的程序,在linux上,你将启动命令添加到/etc/rc.d/rc.local(在所有其他脚本之后运行)或/etc/startup.sh

    在Windows上,您创建服务,注册服务,然后将其设置为在管理 - >服务面板中启动时自动启动。