内容简介:Linux daemon是运行于后台常驻内存的一种特殊进程,周期性的执行或者等待trigger执行某个任务,与用户交互断开,独立于控制终端。一个守护进程的父进程是init进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都被丢到了/dev/null中。守护进程一般用作服务器进程,如httpd,syslogd等。
1:什么是 Linux 下的守护进程
Linux daemon是运行于后台常驻内存的一种特殊进程,周期性的执行或者等待trigger执行某个任务,与用户交互断开,独立于控制终端。一个守护进程的父进程是init进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都被丢到了/dev/null中。守护进程一般用作服务器进程,如httpd,syslogd等。
2:进程,进程组,会话,控制终端之间的关系
因为守护进程的创建需要改变这些环境参数,所以了解它们之间的关系很重要:
上图就描述了它们之间的联系:
2.1 进程组 :它是由一个或多个进程组成,进程组号(GID)就是这些进程中的进程组长的PID。
2.2 会话 :其实叫做会话期(session),它包括了期间所有的进程组,一般一个会话期开始于用户login,一般login的是 shell 终端,所以shell终端又是此次会话期的首进程,会话一般结束于logout。对于非进程组长,它可以调用setsid()创建一个新的会话。
2.3 控制终端(tty) :一般就是指shell终端,它在会话期中可有也可以没有。
3:创建一个daemon的几个步骤
3.1 实例(创建一个daemon,每隔10秒向/mydaemon.log文件写入当前时间一共三次)
void mydaemon(void){ pid_t pid; int fd, i, nfiles; struct rlimit rl; pid = fork(); if(pid < 0) ERROR_EXIT("First fork failed!"); if(pid > 0) exit(EXIT_SUCCESS);// father exit if(setsid() == -1) ERROR_EXIT("setsid failed!"); pid = fork(); if(pid < 0) ERROR_EXIT("Second fork failed!"); if(pid > 0)// father exit exit(EXIT_SUCCESS); #ifdef RLIMIT_NOFILE /* 关闭从父进程继承来的文件描述符 */ if (getrlimit(RLIMIT_NOFILE, &rl) == -1) ERROR_EXIT("getrlimit failed!"); nfiles = rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE, &rl); for(i=3; i
3.2 解读创建daemon的过程
A(7~12行):成为后台进程
用fork创建子进程,父进程退出,子进程成为孤儿进程被init接管,子进程变为后台进程。
B(14~15行):脱离父进程的控制终端,登陆会话和进程组
调用setsid()让子进程成为新会话的组长,脱离父进程的会话期。setsid()在调用者是某进程组组长时会失败,但是A已经保证了子进程不会是组长,B之后子进程变成了新会话组的组长。
C(17~22行):禁止进程重新开启控制终端
因为会话组的组长有权限重新打开控制终端,所以这里第二次fork将子进程结束,留着孙进程,孙进程不是会话组的组长所以没有权利再打开控制终端,这样整个程序就与控制终端隔离了。
D(23~31行):关闭文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。
E(32~36行):重定向0,1,2标准文件描述符
将三个标准文件描述符定向到/dev/null中
F(38~40行):改变工作目录和文件掩码
进程活动时,其工作目录所在的文件系统不能卸下(比如工作目录在一个NFS中,运行一个daemon会导致umount无法成功)。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如chdir("/tmp"),进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
注:D,E,F三步是对当前工作环境的修改,可以先做,因为这些修改都会被子进程继承下来
4:实例运行
#define ERROR_EXIT(m)\do\{\ perror(m);\ exit(EXIT_FAILURE);\}\while(0) int main(int argc, char **argv){ time_t t; int fd, i; mydaemon(); fd = open("./mydaemon.log", O_RDWR|O_CREAT, 0644); if(fd < 0) ERROR_EXIT("open /mydaemon.log failed!"); for(i=0; i<3; i++) { t = time(0); char *buf = asctime(localtime(&t)); write(fd, buf, strlen(buf)); sleep(10); } close(fd); return 0;}
上图是main函数,运行结果如下图:
有图可知,在open /mydaemon.log文件没有权限,而切换到root权限后执行成功,文件的内容也是每10秒间隔写入一次时间。因为我创建的mydaemon程序的工作目录已经切换到了根目录,所以普通用户没有在根目录下创建文件的权限。如果这里将文件创建在当前目录的话就不用切换到root权限。
5:函数daemon()
其实在linux下已经有函数daemon函数用于创建一个后台程序了,所以上面的工作已经被加入了函数库,直接使用轮子。
5.1 daemon函数原型及描述
#include
通过man手册的描述可知,函数daemon接收两个参数:
nochdir:如果是0,将当前工作目录切换到根目录"/",否则工作目录不改变。
noclose:如果是0,将0,1,2重定向到/dev/null,否则不变。
5.2 mydaemon和daemon
其实可以将mydeamon函数稍加修改符合daemon函数
void mydaemon(int nochdir, int noclose){ pid_t pid; int fd, i, nfiles; struct rlimit rl; pid = fork(); if(pid < 0) ERROR_EXIT("First fork failed!"); if(pid > 0) exit(EXIT_SUCCESS);// father exit if(setsid() == -1) ERROR_EXIT("setsid failed!"); pid = fork(); if(pid < 0) ERROR_EXIT("Second fork failed!"); if(pid > 0)// father exit exit(EXIT_SUCCESS); #ifdef RLIMIT_NOFILE /* 关闭从父进程继承来的文件描述符 */ if (getrlimit(RLIMIT_NOFILE, &rl) == -1) ERROR_EXIT("getrlimit failed!"); nfiles = rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE, &rl); for(i=3; i
问题:这样普通用户调用mydaemon(0,0)函数时还是会在console提示open mydaemon.log failed!: Permission denied,但是在37~39行已经将它们重定向到了/dev/null了,很疑惑??当你close(0,1,2)之后普通用户才不会提示错误信息。
感谢你耐心的看完了这篇文章,希望能够帮助到你 。 需要C/C++ Linux服务器架构师学习资料加q裙812855908(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
以上所述就是小编给大家介绍的《Linux 守护进程创建原理及简易方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。