【问题描述】在 友善之臂视频监控方案源码学习(1) - 架构分析一文中,执行过程步骤2,根据解析的参数,判断是否创建守护进程。本文对这一过程详细分析。
【解析】
守护进程的创建代码如下所示:
/* fork to the background */
if ( daemon ) {
LOG("enabling daemon mode");
daemon_mode();
}
当start_uvc_yuv.sh之类的shell脚本中,指定-b选项时,将启用守护进程模式。
守护进程实现代码如下所示:
void daemon_mode(void) {
int fr=0;
fr = fork();
if( fr < 0 ) {
fprintf(stderr, "fork() failed\n");
exit(1);
}
if ( fr > 0 ) {
exit(0);
}
if( setsid() < 0 ) {
fprintf(stderr, "setsid() failed\n");
exit(1);
}
fr = fork();
if( fr < 0 ) {
fprintf(stderr, "fork() failed\n");
exit(1);
}
if ( fr > 0 ) {
fprintf(stderr, "forked to background (%d)\n", fr);
exit(0);
}
umask(0);
fr = chdir("/");
if ( fr != 0 ) {
fprintf(stderr, "chdir(/) failed\n");
exit(0);
}
close(0);
close(1);
close(2);
open("/dev/null", O_RDWR);
fr = dup(0);
fr = dup(0);
}
其实现可概括如下:
(1) 利用fork()创建进程
fr = fork();
if( fr < 0 ) {
fprintf(stderr, "fork() failed\n");
exit(1);
}
(2) 父进程退出,使子进程成为孤儿进程,1号进程(init进程)收养该孤儿进程
if ( fr > 0 ) {
exit(0);
}
(3) 开启新会话
if( setsid() < 0 ) {
fprintf(stderr, "setsid() failed\n");
exit(1);
}
在调用了fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等。虽然父进程退出了,但会话期、进程组、控制终端等并没有改变。因此,这还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。
(4) 再次调用fork
fr = fork();
if( fr < 0 ) {
fprintf(stderr, "fork() failed\n");
exit(1);
}
if ( fr > 0 ) {
fprintf(stderr, "forked to background (%d)\n", fr);
exit(0);
}
POSIX标准中,setsid()系统调用,将进程与当前的会话过程和进程组分开。但是,这一作用,需要执行进程本身不是会话过程的领头进程。因此,可以通过在第一次fork()系统调用(父进程退出)后,在子进程中执行setsid()系统调用来脱离进程组。这样,新的子进程成了新的会话过程的领头进程,也没有控制终端;但是,当它这种领头进程去打开未成为某个会话过程的控制终端的终端设备时,这类终端设备会自动成为这个没有控制终端的会话过程的控制终端。从而,背离了守护进程没有控制终端的要求。因此,需要第二次fork()系统调用(父进程退出)后,在子进程中去完成余下工作。再次fork创建一个子进程,防止第一次创建的子进程获取控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
(5) 设置掩码
umask(0);
就是设置文件权限。把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。
(6) 改变目录为根目录
fr = chdir("/");
if ( fr != 0 ) {
fprintf(stderr, "chdir(/) failed\n");
exit(0);
}
使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。
(7) 关闭文件描述符
close(0);
close(1);
close(2);
在上面的第三步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。
(8) 创建空设备
open("/dev/null", O_RDWR);
空设备用于接收错误信息。 find指令中常见这种做法:
find / -name sh 2>/dev/null
(9) 描述符置0
fr = dup(0);
fr = dup(0);
【源码下载】
http://download.youkuaiyun.com/detail/tandesir/4915905
【学习参考】
1 谈谈守护进程与僵尸进程(fork两次的另一种解释)
转载请标明出处,仅供学习交流,勿用于商业目的
Copyright @ http://blog.youkuaiyun.com/tandesir