守护进程
普通进程
- 进程有对应的终端,如果终端退出,那么对应的进程也就消失了;它的父进程是一个bash
- 终端被占住了,你输入各种命令这个终端都没有反应;
守护进程
守护进程:一种长期运行的进程,这种进程在后台运行,并且不跟任何的控制终端关联。
基本特点:
- 生存期长,一般是操作系统启动的时候他就启动,操作系统关闭的时候他才关闭。(不是必须但是一般应该是这样)
- 守护进程和终端无关联,也就是说他们没有控制终端,所以控制终端退出不会导致守护进程退出。
- 守护进程是在后台运行,不会占着终端,终端可以执行其他命令。
linux操作系统本身就是很多守护进程在默默运行着,维持在系统的日常活动,30~50个左右。
可以用ps -efj
来观察一下
ppid=0是内核进程,跟随系统启动而启动;生命周期贯穿整个系统,如上图所示仅有2个。
带有方括号[]的被成为内核守护进程
kthtread既是内核进程也是内核守护进程
老祖init也是系统守护进程,负责启动各个运行层次特定的系统服务;因此很多进程的PPID是init,而且这个init负责收养孤儿进程。
cmd列中名字不带[]的普通守护进程(用户级守护进程)
总结
a)大多数守护进程都是以超级 用户特权运行的;
b)守护进程没有控制终端,TTY这列显示’?’
内核守护进程以无控制终端方式启动
普通守护进程可能是守护进程调用了setsid的结果(无控制端)
编写守护进程规则
调用umask(0)
umask是个函数,用来限制(屏蔽)一些文件权限的。umask(0);设置为0后就我们创建文件和设置文件权限不会受到影响。
fork()
fork()一个子进程出来然后父进程退出,这是一个固定套路。
父进程退出让终端释放出来而不是卡住,可以继续解释用户输入的命令。
fork()的目的是想利用子进程成功调用setsid()来建立新会话,父进程是进程组组长无法调用setsid();这样fork()出来的子进程可以拥有一个单独的sid;而且子进程也成为了一个新进程的组长经常;同时,子进程不关联任何终端了。
一些概念
文件描述符
一个正数,用来标识一个文件。
当我们打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符(其实就是代表这个文件的),后续对这个文件的操作的一些函数都会用到这个文件描述符作为参数。
linux有三个特殊的文件描述符,数字分别为0,1,2
0:标准输入【键盘】,对应的符号常量叫做STDIN_FILENO
1:标准输出【屏幕】,对应的符号常量叫做STDOUT_FILENO
2:错误输出【屏幕】,对应的符号常量叫做SIDERR_FIELNO
类Unix操作系统,默认从STDIN_FILENO读数据,向STDOUT_FILENO来写数据,向STDERR_FILENO来写错误;
类Unix操作系统有个说法:一切皆文件,所以它把标准输入,标准输出,标准错误 都看成文件。与其说 把 标准输入,标准输出,标准错误 都看成文件 到不如说 像看待文件一样看待 标准输入,标准输出,标准错误;像操作文件一样操作 标准输入,标准输出,标准错误
同时只要我们的程序一旦运行起来,这三个文件描述符0,1,2会被自动打开,自动指向对应的设备。
文件描述符虽然是数字,但是,如果我们把文件描述符直接理解成指针(指针里边保存的是地址——地址说白了也是个数字);
1 |
|
执行结果如下图所示
输入输出重定向
输出重定向:我标准输出文件描述符1,不指向屏幕了,假如我指向(重定向)一个文件
输出重定向,在命令行中用 >即可;
例如ls -la > myoutfile
,myoutfile文件内容如下图所示:
输入重定向,命令行中用’<’
cat < myinfile
命令表示cat的输入不来自键盘而是来自myinfile文件,从myinfile读入内容,然后通过cat显示到屏幕内容中。
空设备
/dev/null 是一个特殊的设备文件,丢弃一切写入其中的数据,像一个黑洞一样进去就没了。
守护进程重定向描述符
守护进程虽然可以通过终端启动,但是和终端不挂钩。守护进程是在后台运行,它不应该从键盘上接收任何东西,也不应该把输出结果打印到屏幕或者终端上来。
所以,一般按照江湖规矩,我们要把守护进程的 标准输入,标准输出,重定向到 空设备(黑洞);从而确保守护进程不从键盘接收任何东西,也不把输出结果打印到屏幕;
1 | int fd; |
守护进程编写实现范例(nginx源码提炼)
人类高质量代码-ngx_daemon()函数
1、fork()创建子进程,父进程exit()退出;
首先fork生成一个子进程,而父进程直接exit结束,子进程最终会被 init 进程(进程号为 1 )所收养,变为孤儿进程。并由 init 进程对它们完成状态收集工作。
2、在子进程调用setsid()创建新会话;
在调用了 fork() 函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变。这还不是真正意义上的独立开来,而 setsid() 函数能够使进程完全独立出来。
setsid()创建一个新会话,调用进程担任新会话的首进程,其作用有:
- 使当前进程脱离原会话的控制
- 使当前进程脱离原进程组的控制
- 使当前进程脱离原控制终端的控制
这样,当前进程才能实现真正意义上完全独立出来,摆脱其他进程的控制。
3、重设文件权限掩码: umask(0)
文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。
4、修改文件描述符
因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值。
打开黑洞’/dev/null’,输入输出重定向到黑洞里面去。
1 |
|
如果main()在一个递归程序中,exit()仍然会终止程序;但return将控制权移交给递归的前一级,直到最初的那一级,此时return才会终止程序。return和exit()的另一个区别在于,即使在除main()之外的函数中调用exit(),它也将终止程序。
由图中可知该进程是孤儿进程,且不和任何终端关联,并且是个会话领导者。
守护进程不会收到的信号
SIGHUP信号
如果我 xshell终端要断开的话,系统就会发送SIGHUP信号(终端断开信号),给session leader,也就是这个bash进程
bash进程 收到 SIGHUP信号后,bash会把这个信号发送给session里边的所有进程,收到这个SIGHUP信号的进程的缺省动作就是退出;
因此守护进程不会收到来自内核的 SIGHUP 信号; 潜台词就是 如果守护进程收到了 SIGHUP信号,那么肯定是另外的进程发给你的;
很多守护进程把这个信号作为通知信号,表示配置文件已经发生改动,守护进程应该重新读入其配置文件;
比如nginx运行的时候修改了一些配置文件conf/,我们需要它立即启用,我们只需执行命令sudo ./ngnix -s reload
,这条命令等价于给master进程发了一个SIGHUP信号,等价于kill -1 masterPID
。执行命令后四个worker进程会被杀死,重新开启四个新的worker进程。
SIGINT,SIGWINCH信号
守护进程不会收到来自内核的SIGINT(ctrl+c),SIGWINCH(终端大小改变)信号
守护进程与后台进程区别
- 守护进程和终端不挂钩;后台进程能往终端上输出东西(和终端挂钩);
- 守护进程关闭终端时不受影响,后台进程会随着终端的退出而退出;
- 守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变。