在Linux中创建守护进程

chr*_*sMe 97 c linux daemon

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

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

我的代码来自: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;
}
Run Code Online (Sandbox Code Playgroud)

Pas*_*rkl 198

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

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

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


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

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

为您提供一个起点:查看此框架代码,其中显示了基本步骤:

/*
 * 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);
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)


  • 编译代码: gcc -o firstdaemon daemonize.c
  • 启动守护进程: ./firstdaemon
  • 检查一切是否正常工作: ps -xj | grep firstdaemon

  • 输出应该类似于这个:

+------+------+------+------+-----+-------+------+------+------+-----+
| 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,这意味着我们的进程不是会话组长
    (因为第二叉())
  • 因为PID!= SID,我们的进程无法再次控制TTY

阅读系统日志:

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

  • 输出应该类似于这个:

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


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

进一步阅读:

  • 应该注意观众认为这种方法是"老"的方式.创建守护程序的新推荐方法是使用此处的"新样式守护程序":http://0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons或 (4认同)

pat*_*eza 29

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

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

  1. 传统的SysV守护进程(旧式),
  2. systemd守护进程(新式).

SysV守护进程

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

  1. 关闭除标准输入,输出错误之外的所有打开文件描述符(即前三个文件描述符0,1,2).这可确保在守护进程中不会意外传递文件描述符.在Linux上,最好通过迭代来实现/proc/self/fd,其中从文件描述符3迭代到getrlimit()for 返回的值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文件时更新PID文件.
  13. 在守护进程中,如果可能且适用,请删除权限.
  14. 从守护进程,通知原始进程启动初始化完成.这可以通过未命名的管道或类似的通信通道来实现,该通道在第一个之前创建fork(),因此在原始和守护进程中都可用.
  15. 请致电exit()原始流程.调用守护程序的进程必须能够依赖于初始化完成exit()发生这种情况并且所有外部通信通道都已建立并可访问.

注意这个警告:

不应使用BSD daemon()函数,因为它仅实现这些步骤的子集.

需要提供与SysV系统兼容的守护进程应该实现上面指出的方案.但是,建议通过命令行参数使此行为成为可选和可配置的,以便于调试以及使用systemd简化与系统的集成.

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


新式守护进程

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

  1. 如果SIGTERM收到,请关闭守护程序并干净地退出.
  2. 如果SIGHUP收到,请重新加载配置文件(如果适用).
  3. 从主守护进程提供正确的退出代码,因为init系统使用它来检测服务错误和问题.建议遵循LSB对SysV init脚本的建议中定义的退出代码方案.
  4. 如果可能且适用,请通过D-Bus IPC系统公开守护程序的控制接口,并在初始化的最后一步获取总线名称.
  5. 要在systemd中集成,请提供.service 单元文件,该文件包含有关启动,停止和以其他方式维护守护程序的信息.详情systemd.service(5)请见.
  6. 尽可能依靠init系统的功能来限制守护进程对文件,服务和其他资源的访问,即在systemd的情况下,依赖systemd的资源限制控制而不是实现自己的,依赖systemd的权限下降代码而不是在守护进程中实现它,类似.请参阅systemd.exec(5)可用控件.
  7. 如果使用D-Bus,请通过提供D-Bus服务激活配置文件使您的守护程序总线可激活.这有多个优点:您的守护进程可以按需启动; 它可以与其他需要它的守护进程并行启动 - 最大化并行化和启动速度 ; 您的守护程序可以在失败时重新启动而不会丢失任何总线请求,因为总线会对可激活服务的请求进行排队.请参阅下文了解详情.
  8. 如果你的守护进程通过一个套接字提供给当地其他进程或远程客户提供服务,应当取得插座激活后的方案指出了下面.与D-Bus激活一样,这可以按需启动服务,并且可以改善服务启动的并行化.此外,对于无状态协议(例如syslog,DNS),可以重新启动实现基于套接字的激活的守护程序,而不会丢失单个请求.请参阅下文了解详情.
  9. 如果适用,守护程序应通过sd_notify(3)接口通知init系统有关启动完成或状态更新的信息.
  10. syslog()新样式守护程序可以选择简单地记录到标准错误fprintf(),然后由init系统转发到syslog,而不是使用调用直接记录到系统syslog服务.如果需要日志级别,可以通过在单个日志行前加上字符串(如"<4>")(对于syslog优先级方案中的日志级别4"WARNING")进行编码,遵循与Linux内核printk()级别系统类似的样式.有关详细信息,请参阅sd-daemon(3)systemd.exec(5).

了解更多阅读整体man 7 daemon.


Chu*_*ill 8

你不能在linux中创建一个无法杀死的进程.root用户(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)
Run Code Online (Sandbox Code Playgroud)

这是一个更通用的功能,

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,SIG_IGN表示捕获并忽略该信号.您可以构建一个可以记录信号接收的信号处理程序,并设置标志(例如标志以指示正常关闭).


wei*_*yin 7

尝试使用该daemon功能:

#include <unistd.h>

int daemon(int nochdir, int noclose);
Run Code Online (Sandbox Code Playgroud)

手册页:

守护进程()函数用于希望从控制终端分离并在后台作为系统守护进程运行的程序.

如果nochdir为零,则daemon()将调用进程的当前工作目录更改为根目录("/"); 否则,当前工作目录保持不变.

如果noclose为零,则守护进程()将标准输入,标准输出和标准错误重定向到/ dev/null; 否则,不会对这些文件描述符进行任何更改.

  • 请注意,[`daemon(7)`](http://man7.org/linux/man-pages/man7/daemon.7.html) 手册提到了创建守护进程的步骤并警告说: *BSD [`daemon( )`](http://man7.org/linux/man-pages/man3/daemon.3.html) 函数不应使用,因为它只实现了这些步骤的一个子集。* [`daemon`](http ://man7.org/linux/man-pages/man3/daemon.3.html) 函数首次出现在 [4.4BSD](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution#4.4BSD_and_descendants) 并且是 * *不** [POSIX 兼容](http://stackoverflow.com/questions/1780599/i-never-really-understood-what-is-posix)。 (2认同)
  • 另请注意,有关使用 daemon() 的警告位于 [daemon(7) 手册页](http://man7.org/linux/man-pages/man7/daemon.7.html) 的旧式 SysV 部分. systemd 不鼓励使用 daemon()。 (2认同)

Edw*_*uck 6

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

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

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

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

  • 我想通过说"一个无法停止的守护进程",作者实际上意味着当会话终止时守护进程总是在运行后台. (5认同)

dan*_*y74 6

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

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}
Run Code Online (Sandbox Code Playgroud)

并且您不介意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
Run Code Online (Sandbox Code Playgroud)

要使所有应用程序在重新启动时运行(并守护pm2):

pm2 startup

pm2 save
Run Code Online (Sandbox Code Playgroud)

现在你可以:

service pm2 stop|restart|start|status
Run Code Online (Sandbox Code Playgroud)

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

  • 这与C无关。 (2认同)
  • 我感谢有一个C标签。但是,OP在问题中未提及有关C的要求。标题是在Linux中创建一个恶魔。这个答案满足了。 (2认同)