编写守护进程时为什么要setsid
答案:1 悬赏:0 手机版
解决时间 2021-03-25 21:21
- 提问者网友:佞臣
- 2021-03-25 11:20
编写守护进程时为什么要setsid
最佳答案
- 五星知识达人网友:荒野風
- 2021-03-25 12:11
1、第一次fork,确保当前的process一定不是session leader,关于session,继续往下看
2、setsid,创建新的session,因为session leader调用setsid不会创建新session,所以我们才需要之前的那个fork。
那session到底是什么?当你通过控制台登录时,你就创建了一个session,一个session就是一个controlling terminal、一个controlling process group,再加上一堆后台process group,其中controlling process一般就是login shell,controlling terminal就是你在敲键盘看显示器的那个“终端”。在shell中,你可以用"&"将一个命令在后台运行,或者你可以按Ctrl-Z,然后用bg命令将其放入后台,这时你可以用jobs命令看到后台运行的进程,这些后台进程就是background process group
看看当你logout的时候会发生什么,此时controlling terminal会被关闭,这个session中所有进程都会收到SIGHUP和SIGTERM/SIGQUIT,对于这些信号的缺省操作就是结束进程。
作为一个daemon,你当然不希望用户logout的时候就退出,解决方案有几种,一种就是忽略上述所有信号,例如nohup程序干的就是这个,但这样做有个小问题,很多程序使用信号作为一种简单的IPC机制,忽略这些信号会导致这种IPC失效;另外一种方案就是让进程从这个controlling terminal上脱离,setsid将会创建一个新的session,使当前进程成为新session的leader,并且不再关联之前session的controlling terminal。
3、第二次fork,这件事情有点晦涩,其实很多文档上并没说的太清楚,具体原因是这样的,即使一个进程创建了新的session,它依然有可能获得一个controlling terminal,比如你可以用ioctl(TIOCSCTTY),这样做的后果就是,新的session有了controlling terminal,然后前面我们提到的所有问题依然可能发生。为了彻底解决这个问题,我们需要第二次fork,因为ioctl(TIOCSCTTY)手册中在非常不起眼的地方提到了,只有session leader才能为session打开controlling terminal,第二次fork之后,子进程就是session中第二个进程,它这辈子再没机会成为session leader了(除非它调用setsid),也就再没能力打开controlling terminal了。这样我们就一劳永逸的解决了所有问题。
所以,如果你的程序行为很固定,你知道它不会无聊到去打开controlling terminal的话,第二次fork其实是不必要的,但是如果你是在写一个库,而且不知道使用它的程序到底会有什么样的行为,你最好还是再fork一次。
其实干了所有上述的事,依然还有一些遗留问题,你还需要:
1、chdir("/"),这样可以防止你的程序工作目录所在的文件系统无法umount的问题
2、close/reopen所有你不需要的fd,尤其是stdin/stdout,因为这些fd是从父进程继承下来的,如果它们是管道,而对端有关闭了,读写它们会产生SIGPIPE
3、设置正确的umask,这也是从父进程继承下来的,不一定是你想要的值
4、设置适当的env,同上
5、设置适当的sigmask
6、如果你的daemon需要创建子进程,你可能还需要signal(SIGCHLD, SIG_IGN),防止退出的子进程成为zombie
2、setsid,创建新的session,因为session leader调用setsid不会创建新session,所以我们才需要之前的那个fork。
那session到底是什么?当你通过控制台登录时,你就创建了一个session,一个session就是一个controlling terminal、一个controlling process group,再加上一堆后台process group,其中controlling process一般就是login shell,controlling terminal就是你在敲键盘看显示器的那个“终端”。在shell中,你可以用"&"将一个命令在后台运行,或者你可以按Ctrl-Z,然后用bg命令将其放入后台,这时你可以用jobs命令看到后台运行的进程,这些后台进程就是background process group
看看当你logout的时候会发生什么,此时controlling terminal会被关闭,这个session中所有进程都会收到SIGHUP和SIGTERM/SIGQUIT,对于这些信号的缺省操作就是结束进程。
作为一个daemon,你当然不希望用户logout的时候就退出,解决方案有几种,一种就是忽略上述所有信号,例如nohup程序干的就是这个,但这样做有个小问题,很多程序使用信号作为一种简单的IPC机制,忽略这些信号会导致这种IPC失效;另外一种方案就是让进程从这个controlling terminal上脱离,setsid将会创建一个新的session,使当前进程成为新session的leader,并且不再关联之前session的controlling terminal。
3、第二次fork,这件事情有点晦涩,其实很多文档上并没说的太清楚,具体原因是这样的,即使一个进程创建了新的session,它依然有可能获得一个controlling terminal,比如你可以用ioctl(TIOCSCTTY),这样做的后果就是,新的session有了controlling terminal,然后前面我们提到的所有问题依然可能发生。为了彻底解决这个问题,我们需要第二次fork,因为ioctl(TIOCSCTTY)手册中在非常不起眼的地方提到了,只有session leader才能为session打开controlling terminal,第二次fork之后,子进程就是session中第二个进程,它这辈子再没机会成为session leader了(除非它调用setsid),也就再没能力打开controlling terminal了。这样我们就一劳永逸的解决了所有问题。
所以,如果你的程序行为很固定,你知道它不会无聊到去打开controlling terminal的话,第二次fork其实是不必要的,但是如果你是在写一个库,而且不知道使用它的程序到底会有什么样的行为,你最好还是再fork一次。
其实干了所有上述的事,依然还有一些遗留问题,你还需要:
1、chdir("/"),这样可以防止你的程序工作目录所在的文件系统无法umount的问题
2、close/reopen所有你不需要的fd,尤其是stdin/stdout,因为这些fd是从父进程继承下来的,如果它们是管道,而对端有关闭了,读写它们会产生SIGPIPE
3、设置正确的umask,这也是从父进程继承下来的,不一定是你想要的值
4、设置适当的env,同上
5、设置适当的sigmask
6、如果你的daemon需要创建子进程,你可能还需要signal(SIGCHLD, SIG_IGN),防止退出的子进程成为zombie
我要举报
如以上回答内容为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
点此我要举报以上问答信息
大家都在看
推荐资讯