一、守护进程和daemonize()函数
这一小节是自己的理解,无干货,下一节有干货。
守护进程查wikipedia的定义:In multitasking computer operating systems, a daemon is a computer program that runs as a background process, rather than being under the direct control of an interactive user.关键词就是后台执行,而非直接用户交互。还有一句“However, more commonly, a daemon may be any background process, whether a child of the init process or not.”,是说啥后台程序都可以叫守护进程。
学究式的讲定义是为了引出困扰我以前的很长一段时间的问题,《unix高级环境编程》里面的建立守护进程的那些步骤到底目的是啥?改工作目录,改建立文件权限mask,fork两次,setsid,关闭或重定向文件描述符,重定义信号处理函数。这个定义说明这些都是不是必须的。
书中定义的实际上是一个deamonize函数,是作者觉得实际守护进程可能会需要的性质和功能。该函数可以从诸多场景调用,比如:
1.父进程fork+exec调用子进程执行新程序,假设子进程完全不需要父进程的任何打开文件,这时这一种守护进程就需要关闭所有的fd(除非每个fd打开时都用closeonexec)可以调用daemonize。这个以前我接触到一个事例,父进程仅仅只是读配置,之后按照配置隔一段时间启动某个程序,并且监控超时任务超时kill;
2.nginx master进程的依赖pid文件,所以pid文件所在的文件系统不能被卸载,所以nginx master调用daemonize修改工作路径到/就显得多余;
3.同上,master创建的pid文件需要nginx程序有读权限(比如nginx -s reload会读取),这时daeminze函数中的umask(0)这一步就没用了。
可见,daemonize的某些步骤对有的场景有用,有的没用。作者应该是想通过daemonize建立一个统一的守护进程初始化状态,之后调用者可以在此基础上统一处理。
总之,下面的问题貌似有答案了:
1.daemonize干嘛要两次fork啊?我的守护进程打死也不会去获得一个终端;
2.daemonize干嘛要关闭文件描述符?我的nginx子进程需要pipe和master进程通信啊。
答案:
1.deamonize函数不是守护进程,是上帝stevens对守护进程可能的需求做的总结,不是调用deamonize用户守护进程会不会获得终端的问题,而是不准;
2.可以个性化自己的守护进程,守护进程的定义可没有让子进程关闭父进程的所有文件描述符。
二、fork两次和终端
守护进程初始化过程其他都还是比较好理解,fork两次这儿很不直观。这一节用实际代码示例说明,终端对进程的影响以及fork两次+setsid的确获取不了终端了。
编译后运行,父进程和子进程都会sleep等待,这时关闭终端(securecrt右上角小红叉)观察日志。
received SIGHUP from 29247,pid:2516 //父子进程收到来自29247的SIGHUP
received SIGHUP from 29247,pid:2517
在此之前从另一个终端ps aux |grep out:
ubuntu 2516 0.0 0.0 12540 820 pts/4 S+ 15:34 0:00 ./a.out
ubuntu 2517 0.0 0.0 12536 160 pts/4 S+ 15:34 0:00 ./a.out
ps axj |grep 29247:
29246 29247 29247 29247 pts/10 29247 Ss+ 500 0:00 -bash //29247是本终端的bash
上述事实说明关闭远程终端,终端对应bash会发送SIGHUP信号给本终端关联的进程,无论是直属子进程还是子进程的子进程。至于底层点的initd\rlogin\tty\ptm\pts\bash的恩怨情仇这儿不管。
2.setsid从本终端脱离后不会收到SIGHUP
1中注释的两行取消注释后:
child session id before:22481,pid:3469,pgid:3468
setting a new session:3469
child session id after:3469,pid:3469,pgid:3469
22481 3468 3468 22481 pts/2 3468 S+ 500 0:00 ./a.out //父进程终端还是pts/2
3468 3469 3469 3469 ? -1 Ss 500 0:00 ./a.out //子进程终端没了,显示?
关闭终端之后子进程不退出。
3.setsid之后子进程作死还是能获得新的终端
把上面代码最后一个sleep替换成下面代码。
ps aux|grep out之后得到:
13235 18358 18358 13235 pts/2 18358 S+ 500 0:00 ./a.out
18358 18359 18359 18359 pts/6 18359 Ss+ 500 0:00 ./a.out
可见子进程已经获得了新终端。
4.setsid之后再次fork获得新终端失败
再次fork可以使得子进程不是当前会话的首进程,从而无法获得新终端:
ubuntu 4478 0.0 0.0 12540 820 pts/2 S+ 14:55 0:00 ./a.out
ubuntu 4479 0.0 0.0 0 0 ? Zs 14:55 0:00 [a.out] <defunct>
ubuntu 4480 0.0 0.0 21100 816 ? S 14:55 0:00 ./a.out
可见,孙进程设置终端失败(显示?)。
这一小节是自己的理解,无干货,下一节有干货。
守护进程查wikipedia的定义:In multitasking computer operating systems, a daemon is a computer program that runs as a background process, rather than being under the direct control of an interactive user.关键词就是后台执行,而非直接用户交互。还有一句“However, more commonly, a daemon may be any background process, whether a child of the init process or not.”,是说啥后台程序都可以叫守护进程。
学究式的讲定义是为了引出困扰我以前的很长一段时间的问题,《unix高级环境编程》里面的建立守护进程的那些步骤到底目的是啥?改工作目录,改建立文件权限mask,fork两次,setsid,关闭或重定向文件描述符,重定义信号处理函数。这个定义说明这些都是不是必须的。
书中定义的实际上是一个deamonize函数,是作者觉得实际守护进程可能会需要的性质和功能。该函数可以从诸多场景调用,比如:
1.父进程fork+exec调用子进程执行新程序,假设子进程完全不需要父进程的任何打开文件,这时这一种守护进程就需要关闭所有的fd(除非每个fd打开时都用closeonexec)可以调用daemonize。这个以前我接触到一个事例,父进程仅仅只是读配置,之后按照配置隔一段时间启动某个程序,并且监控超时任务超时kill;
2.nginx master进程的依赖pid文件,所以pid文件所在的文件系统不能被卸载,所以nginx master调用daemonize修改工作路径到/就显得多余;
3.同上,master创建的pid文件需要nginx程序有读权限(比如nginx -s reload会读取),这时daeminze函数中的umask(0)这一步就没用了。
可见,daemonize的某些步骤对有的场景有用,有的没用。作者应该是想通过daemonize建立一个统一的守护进程初始化状态,之后调用者可以在此基础上统一处理。
总之,下面的问题貌似有答案了:
1.daemonize干嘛要两次fork啊?我的守护进程打死也不会去获得一个终端;
2.daemonize干嘛要关闭文件描述符?我的nginx子进程需要pipe和master进程通信啊。
答案:
1.deamonize函数不是守护进程,是上帝stevens对守护进程可能的需求做的总结,不是调用deamonize用户守护进程会不会获得终端的问题,而是不准;
2.可以个性化自己的守护进程,守护进程的定义可没有让子进程关闭父进程的所有文件描述符。
二、fork两次和终端
守护进程初始化过程其他都还是比较好理解,fork两次这儿很不直观。这一节用实际代码示例说明,终端对进程的影响以及fork两次+setsid的确获取不了终端了。
1.网络终端连接终端导致登录bash发送sighup给该终端对应所有会话的所有进程;
void action(int num,siginfo_t *psi,void *p){
//收到之后信息写入文件
int fd = open("log",O_RDWR | O_APPEND | O_CREAT,S_IRWXU);
string out("received SIGUP from ");
//打印发信号进程id和本进程id
out += to_string(psi->si_pid) + ",pid:" + to_string(getpid()) + "\n";
write(fd,out.c_str(),out.length());
exit(0);
}
int main(){
struct sigaction act;
memset(&act,0,sizeof(struct sigaction));
act.sa_sigaction = action;
act.sa_flags = SA_SIGINFO;
//安装SIGHUP处理函数,fork之前安装,父子进程都生效
sigaction(SIGHUP,&act,NULL);
if(fork() != 0){
printf("parent session id:%u,pid:%u,pgid:%u\n",getsid(0),getpid(),getpgid(0));
sleep(SLEEPTIME);
exit(0);
}
printf("child session id before:%u,pid:%u,pgid:%u\n",getsid(0),getpid(),getpgid(0));
//printf("setting a new session:%d\n",setsid());
//printf("child session id after:%u,pid:%u,pgid:%u\n",getsid(0),getpid(),getpgid(0));
sleep(SLEEPTIME);
}
编译后运行,父进程和子进程都会sleep等待,这时关闭终端(securecrt右上角小红叉)观察日志。
received SIGHUP from 29247,pid:2516 //父子进程收到来自29247的SIGHUP
received SIGHUP from 29247,pid:2517
在此之前从另一个终端ps aux |grep out:
ubuntu 2516 0.0 0.0 12540 820 pts/4 S+ 15:34 0:00 ./a.out
ubuntu 2517 0.0 0.0 12536 160 pts/4 S+ 15:34 0:00 ./a.out
ps axj |grep 29247:
29246 29247 29247 29247 pts/10 29247 Ss+ 500 0:00 -bash //29247是本终端的bash
上述事实说明关闭远程终端,终端对应bash会发送SIGHUP信号给本终端关联的进程,无论是直属子进程还是子进程的子进程。至于底层点的initd\rlogin\tty\ptm\pts\bash的恩怨情仇这儿不管。
2.setsid从本终端脱离后不会收到SIGHUP
1中注释的两行取消注释后:
child session id before:22481,pid:3469,pgid:3468
setting a new session:3469
child session id after:3469,pid:3469,pgid:3469
22481 3468 3468 22481 pts/2 3468 S+ 500 0:00 ./a.out //父进程终端还是pts/2
3468 3469 3469 3469 ? -1 Ss 500 0:00 ./a.out //子进程终端没了,显示?
关闭终端之后子进程不退出。
3.setsid之后子进程作死还是能获得新的终端
把上面代码最后一个sleep替换成下面代码。
int mfd = posix_openpt(O_RDWR);
if(mfd < 0)
perror("open pts error:");
printf("open pts done\n");
if(grantpt(mfd) == -1)
perror("grantpt pts error:");
printf("grantpt pts done\n");
if(unlockpt(mfd) == -1)
perror("unlockpt pts error:");
char *name = ptsname(mfd);
printf("device name is:%s\n",name);
int sfd = open(name,O_RDWR);
dup2(sfd,STDIN_FILENO);
dup2(sfd,STDOUT_FILENO);
dup2(sfd,STDERR_FILENO);
if(ioctl(sfd,TIOCSCTTY,NULL) == -1)
perror("ioctl error:");
sleep(SLEEPTIME);
close(sfd);
close(mfd);
ps aux|grep out之后得到:
13235 18358 18358 13235 pts/2 18358 S+ 500 0:00 ./a.out
18358 18359 18359 18359 pts/6 18359 Ss+ 500 0:00 ./a.out
可见子进程已经获得了新终端。
4.setsid之后再次fork获得新终端失败
再次fork可以使得子进程不是当前会话的首进程,从而无法获得新终端:
ubuntu 4478 0.0 0.0 12540 820 pts/2 S+ 14:55 0:00 ./a.out
ubuntu 4479 0.0 0.0 0 0 ? Zs 14:55 0:00 [a.out] <defunct>
ubuntu 4480 0.0 0.0 21100 816 ? S 14:55 0:00 ./a.out
可见,孙进程设置终端失败(显示?)。