在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的守护进程:
- 传统的SysV守护进程( 旧式 ),
- systemd守护进程( 新风格 )。
SysV守护进程
如果您对传统的SysV守护程序感兴趣,则应该执行以下步骤 :
- closures除标准input , 输出和错误之外的所有打开的文件描述符(即前三个文件描述符0,1,2)。 这可以确保在守护进程中不会意外传递文件描述符。 在Linux上,最好通过迭代
/proc/self/fd
,使用从文件描述符3迭代到由getrlimit()
返回的值的回退。- 将所有信号处理程序重置为默认值。 最好通过迭代可用信号达到
_NSIG
的限制并将其重置为SIG_DFL
。- 使用
sigprocmask()
重置信号掩码。- 清理环境块,删除或重置可能对守护进程运行时造成负面影响的环境variables。
- 调用
fork()
来创build一个后台进程。- 在孩子中,调用
setsid()
从任何terminal分离并创build一个独立的会话 。- 在孩子中,再次调用
fork()
,以确保守护进程再也不能重新获取terminal。- 在第一个孩子中调用
exit()
,这样只有第二个孩子(实际的守护进程)停留在周围。 这可以确保守护进程被重新初始化为init / PID 1,就像所有的守护进程一样。- 在守护进程中,将
/dev/null
连接到标准input , 输出和错误 。- 在守护进程中,将
umask
重置为0,以便传递给open()
,mkdir()
等的文件模式直接控制所创build的文件和目录的访问模式。- 在守护进程中,将当前目录更改为根目录(
/
),以避免守护进程无意中阻止挂载点被卸载。- 在守护进程中,将守护进程的PID (由
getpid()
返回)写入一个PID文件,例如/run/foobar.pid
(对于假设的守护进程“foobar”),以确保守护进程不能启动多次。 这必须以无竞争的方式实现,以便只有在先前存储在PID文件中的PID不再存在或属于外部进程的情况下,才validationPID文件。- 在守护进程中,放弃特权,如果可能并适用的话。
- 从守护进程中,通知初始化完成的初始进程。 这可以通过在第一个
fork()
之前创build的未命名的pipe道或类似的通信通道来实现,因此在原始进程和守护进程中都可用。- 在原始进程中调用
exit()
。 调用守护进程的进程必须能够依靠这个exit()
在初始化完成之后发生,并且所有的外部通信通道都被build立并可访问。
注意这个警告:
BSD
daemon()
函数不应该被使用,因为它只实现了这些步骤的一个子集 。需要提供与SysV系统兼容的守护进程应该实现上面指出的scheme。 但是,build议通过命令行参数使此行为可选和可configuration,以便于debugging以及简化与使用systemd的系统的集成。
请注意, daemon()
不符合POSIX标准。
新式的守护进程
对于新式守护进程,build议使用以下步骤 :
- 如果收到
SIGTERM
,closures守护进程并干净地退出。- 如果收到
SIGHUP
,重新加载configuration文件(如果适用)。- 从主守护进程提供正确的退出代码,因为init系统使用它来检测服务错误和问题。 build议遵循SysV init脚本的LSBbuild议中定义的退出代码scheme。
- 如果可能且适用,通过D-Bus IPC系统公开守护进程的控制接口,并获取总线名称作为初始化的最后一步。
- 为了在systemd中集成,提供一个.service 单元文件,其中包含关于启动,停止和维护守护进程的信息。 有关详细信息,请参阅
systemd.service(5)
。- 尽可能地依靠init系统的function来限制守护进程对文件,服务和其他资源的访问,即在systemd的情况下,依靠systemd的资源限制控制而不是实现你自己的,依靠systemd的特权代码而不是在守护进程中实现它,和类似的。 请参阅
systemd.exec(5)
了解可用的控件。- 如果使用D-Bus,则通过提供D-Bus服务激活configuration文件使守护程序总线可激活。 这有很多好处:你的守护进程可能会按需启动; 它可以与需要它的其他守护进程并行启动 – 这可以最大化并行和启动速度 ; 您的守护进程可以在失败时重新启动,而不会丢失任何总线请求,因为总线队列请求可激活的服务。 详情请参阅下文 。
- 如果守护进程通过套接字向其他本地进程或远程客户端提供服务,那么应该按照下面指出的scheme进行套接字激活 。 与D-Bus激活一样,这可以按需启动服务,并且可以改善服务启动的并行性。 此外,对于无状态协议(如syslog,DNS),实现基于套接字的激活的守护进程可以重新启动,而不会丢失单个请求。 详情请参阅下文 。
- 如果适用,守护程序应通过
sd_notify(3)
接口通知init系统启动完成或状态更新。- 而不是使用
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信号接收,并设置标志(如标志,以指示正常关机)。
尝试使用daemon
function:
#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