在Linux中创build一个守护进程

在Linux中,我想添加一个不能停止的守护进程,并监视文件系统的变化。 如果检测到任何更改,则应将path写入启动的控制台以及换行符。

我已经有文件系统改变的代码几乎准备好了,但我不知道如何创build一个守护进程。

我的代码是从这里: 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; } 

在Linux中,我想添加一个守护进程,不能停止,哪个监视文件系统的变化。 如果检测到任何更改,则应将path写入到启动的控制台+换行符。

守护进程在后台工作,并且(通常…)不属于TTY,这就是为什么您不能使用stdout / stderr的原因。 通常使用syslog守护进程( syslogd )来logging消息到文件(debug,error,…)。

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


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

  • 分离父进程,并让它在分叉成功时终止。 – >因为父进程已经终止,subprocess现在在后台运行。
  • setsid – 创build一个新的会话。 调用过程成为新进程组的领导者和新进程组的进程组组长。 该过程现在从其控制terminal(CTTY)分离。
  • 捕捉信号 – 忽略和/或处理信号。
  • 再次分叉,并让父进程终止,以确保您摆脱了会话引导过程。 (只有会话领导者可能再次获得TTY。)
  • chdir – 更改守护进程的工作目录。
  • umask – 根据守护进程的需要更改文件模式掩码。
  • closures – closures可能从父进程inheritance的所有打开的文件描述符。

为了给你一个出发点:看看这个骨架代码,显示了基本的步骤:

 /* * 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 ps -xj | grep firstdaemon

  • 输出应该类似于这个:

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

你应该看到的是:

  • 守护进程没有控制terminal( TTY =?
  • 父进程ID( PPID )是1 (init进程)
  • PID!= SID这意味着我们的过程不是会议的领导者
    (因为第二个fork())
  • 由于PID!= SID,我们的进程不能再次控制TTY

读取系统日志:

  • find您的系统日志文件。 我在这里: /var/log/syslog
  • 做一个: grep firstdaemon /var/log/syslog

  • 输出应该类似于这个:

   firstdaemon [3387]:第一个守护进程启动。
   firstdaemon [3387]:第一个守护进程终止。

注意:实际上你也想要实现一个信号处理程序并正确设置日志(文件,日志级别…)。

进一步阅读:

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

man 7 daemon描述了如何创build守护进程的细节。 我的答案只是本手册的摘录。

至less有两种types的守护进程:

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

SysV守护进程

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

  1. closures除标准input输出错误之外的所有打开的文件描述符(即前三个文件描述符0,1,2)。 这可以确保在守护进程中不会意外传递文件描述符。 在Linux上,最好通过迭代/proc/self/fd ,使用从文件描述符3迭代到由getrlimit()返回的值的回退。
  2. 将所有信号处理程序重置为默认值。 最好通过迭代可用信号达到_NSIG的限制并将其重置为SIG_DFL
  3. 使用sigprocmask()重置信号掩码。
  4. 清理环境块,删除或重置可能对守护进程运行时造成负面影响的环境variables。
  5. 调用fork()来创build一个后台进程。
  6. 在孩子中,调用setsid()从任何terminal分离并创build一个独立的会话 。
  7. 在孩子中,再次调用fork() ,以确保守护进程再也不能重新获取terminal。
  8. 在第一个孩子中调用exit() ,这样只有第二个孩子(实际的守护进程)停留在周围。 这可以确保守护进程被重新初始化为init / PID 1,就像所有的守护进程一样。
  9. 在守护进程中,将/dev/null连接到标准input输出错误
  10. 在守护进程中,将umask重置为0,以便传递给open()mkdir()等的文件模式直接控制所创build的文件和目录的访问模式。
  11. 在守护进程中,将当前目录更改为根目录( / ),以避免守护进程无意中阻止挂载点被卸载。
  12. 在守护进程中,将守护进程的PID (由getpid()返回)写入一个PID文件,例如/run/foobar.pid (对于假设的守护进程“foobar”),以确保守护进程不能启动多次。 这必须以无竞争的方式实现,以便只有在先前存储在PID文件中的PID不再存在或属于外部进程的情况下,才validationPID文件。
  13. 在守护进程中,放弃特权,如果可能并适用的话。
  14. 从守护进程中,通知初始化完成的初始进程。 这可以通过在第一个fork()之前创build的未命名的pipe道或类似的通信通道来实现,因此在原始进程和守护进程中都可用。
  15. 在原始进程中调用exit() 。 调用守护进程的进程必须能够依靠这个exit() 初始化完成之后发生并且所有的外部通信通道都被build立并可访问。

注意这个警告:

BSD daemon()函数不应该被使用,因为它只实现了这些步骤的一个子集

需要提供与SysV系统兼容的守护进程应该实现上面指出的scheme。 但是,build议通过命令行参数使此行为可选和可configuration,以便于debugging以及简化与使用systemd的系统的集成。

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


新式的守护进程

对于新式守护进程,build议使用以下步骤 :

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

要了解更多阅读整个man 7 daemon

我可以停止在第一个要求“守护进程不能停止 …”

我的朋友不可能; 不过,你可以用一个更好的工具来实现一个内核模块。

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

所有守护进程都可以停止。 有些人比其他人更容易停下来。 即使是与合作伙伴守护的守护进程,如果丢失,重新生成伙伴也可以停止。 你只需要努力一点就可以了。

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

您可能需要更一般的守护进程function,您可以在其中指定程序/守护进程的名称,以及运行程序的path(可能是“/”或“/ tmp”)。 您可能还想提供stderr和stdout(可能还有一个使用stdin的控制path)的文件。

这里是必要的包括:

 #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) 

这是一个更普遍的function,

 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指示捕捉并忽略信号。 您可以build立一个信号处理程序,可以logging信号接收,并设置标志(如标志,以指示正常关机)。

尝试使用daemonfunction:

 #include <unistd.h> int daemon(int nochdir, int noclose); 

从手册页 :

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

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

如果noclose为零,daemon()将标准input,标准输出和标准错误redirect到/ dev / null; 否则,不会对这些文件描述符进行更改。

如果你的应用程序是以下之一:

 { ".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 

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

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

在Windows上,您提供服务,注册服务,然后将其设置为在pipe理 – >服务面板中自动启动。

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

也许这将有助于: http : //www.netzmafia.de/skripten/unix/linux-daemon-howto.html