守护进程(Daemon)被翻译为精灵进程、后台进程(不推荐这么叫),是一种旨在运行于相对干净环境、不受终端影响的、常驻内存的进程,像西方神话中的精灵拥有不死的特性,长期稳定提供某种功能或者服务。
在Unix/Linux系统中,使用ps命令可以看到许多以-d结尾的进程,它们大多都是守护进程。
行于相对干净环境、不受终端影响的、常驻内存的进程,长期稳定提供某种功能或者服务。
1、提供系统网络服务等重要服务的核心程序
2、日志服务程序
由于终端的关闭会触发SIGHUP并发送给终端所关联的会话的所有进程,而一开始进程尚未脱离原会话,因此应尽早忽略该信号,避免被挂断信号误杀。
从终端(不管是远程登录窗口还是本地伪终端)启动的进程所在的会话都关联了控制终端,而控制终端会有各种数据或信号的输入干扰,为了避开这些干扰,需要脱离控制终端,而脱离控制终端的简单做法就是新建一个新的、没有控制终端的会话。但创建一个新会话的进程必须是非进程组组长,而在Linux系统中,从终端启动的进程默认就是其所在进程组的组长,因此摆脱这一困境的简单做法就是让其产生一个子进程,退出原进程(父进程)并让子进程继续下面的步骤。
创建新会话,脱离原会话,脱离控制终端
此时的进程是其所在的会话的创始进程,而创始进程拥有可以再次关联的控制终端的权限,为了避免此种情况的发生,最简单的做法就是退出当前创始进程,改由其子进程(非创始进程)继续完成成为守护进程的使命。
虽然此时进程的父进程、祖父进程已经退出,但进程组是一直都在的,且处于新会话中的孙子进程一直都在其祖父进程的进程组之中,而进程组是可以传递信号的,因此为了与任何方面脱离关系,应该“自立门户”创建新进程组,并将自身置入其中。
文件资源是可以在父进程之间代际相传的,这其中也包括了标准输入输出文件,而作为守护进程,是一种在后台运行的程序,运行过程一般无需交互,若有消息需要输出一般会以系统日志的方式输入到指定日志文件中。因此,为了节约系统资源,也为了避免不必要的逻辑谬误,守护进程一般都需要将所有从父辈进程继承下来的文件全部关闭。
在Linux系统中创建一个新文件时,可以通过相关的函数参数指定文件的权限,但是其实被创建出来的文件的权限并非创建时指定的权限,该权限与系统当前的文件权限掩码做位与操作之后的值才是文件真正的权限,因此为了让守护进程在后续工作过程创建文件时指定的权限不受系统文件权限掩码干扰,可以将umask设置为0。
任何一个进程都有一个当前工作路径,从终端启动的进程的工作路径就是启动时终端所在的系统路径,而当一个进程的工作路径被卸载时,进程也会随之消亡。守护进程为了避免此种情况发生,最简单的做法就是将自身的工作路径切换到一个无法被卸载的路径下,比如根目录。
先看一幅图
进程组是由若干个进程组织的在一起的,这样就可以方便管理多个进程,可以给组内的所有进程统一发送信号。
设置/获取进程所属的进程组的函数接口是:
// 获取进程pid所在进程组ID pid_t getpgid(pid_t pid); // 将进程pid所在的进程组设置为pgid int setpgid(pid_t pid, pid_t pgid); // 如果pid == 0, 则设置本进程 // 如果pgid == 0, 等价于pgid == pid // 如果进程pid与进程组pgid不在同一会话内,设置失败
一般而言,进程从终端启动时,会自动创建一个新进程组,并且该进程组内只包含这个创始进程,而其后代进程默认都将会被装载在该进程组内部,这个进程组被称为前台进程组。
前台进程组最大的特点是:可以接收控制终端发来的信号(Ctrl+C,或者键盘输入的内容等)所谓控制终端,一般就是指标准输入设备键盘。
3、后台进程组
不接受控制终端的输入和信号的进程组被称为后台进程组,但是控制终端依然可以向其发送挂断信号。可以在运行某个程序时加上“&”使得该进程进入后台机进程
./a.out &
会话(session)原本指的是一个登录过程所产生的所有进程组的总和,可以理解为一个登录窗口就是一个会话,但在伪终端中,每打开一个窗口实际上也是创建了一个会话,可以把会话理解为进程组的进程组。它的作用可以总结为:
(1)可关联一个控制终端设备,并从控制终端获得输入信息
(2)可对会话内的所有进程组统一发送信号
(3)可将控制终端的关闭动作,转换为触发挂断信号并发送给所有进程
当我们打开一个伪终端,或者打开一个远程登录工具输入账户密码的过程中,默认都调用了如下函数去创建一个新的会话
pid_t setsid(void);
注意:
(1)进程组组长不能调用该函数
(2)新创建的会话没有关联控制终端,因此其内进程不受控制终端影响
(3)创建会话的进程,称为该会话的创始进程,创始进程有权捕获一个控制终端(在编写守护进程时通常需要避免),会话的其余成员进程无权获得控制终端
控制终端通常会关联一个输入设备,可以给前台进程组发送数据或信号,平常使用的许多信号快捷键,就是通过控制终端发送给前台进程组内的进程
控制终端不仅可以向前台进程组发送数据,也能向整个会话发送挂断信号,默认情况相爱收到SIGHUP的进程会被终止,因此,为了避免被控制终端“误杀”,常驻内存的守护进程必备步骤包括忽略掉SIGHUP、脱离控制终端、避免再次获取控制终端等操作。
编写一个守护进程
// 守护进程的案例 #include
#include #include #include #include #include int main(int argc, char *argv[]) { // 1、忽略挂断SIGHUP信号,防止被终端误杀 signal(SIGHUP, SIG_IGN); // 2、退出父进程,产生子进程,为成功创建新会话做准备 if(fork() > 0) { exit(0); } // 3、创建新会话,脱离原会话,脱离控制终端 setsid(); // 4、产生孙子进程,断绝重新关联控制终端的可能性 if(fork() > 0) { exit(0); } // 5、脱离原进程组,创建并进入只包含自身的进程组 setpgid(0, 0); // 6、关闭父辈继承下来的所有文件 for(int i = 0; i < sysconf(_SC_OPEN_MAX); i++) { close(i); } // 7、避开系统文件权限掩码的干扰 umask(0); // 8、避免所在路径被卸载,切换为根目录 chdir("/"); // 以下编写服务程序 pause(); return 0; }
守护进程的常用于一些特定的场合,如需要长期驻扎在内存中的,提供稳定可靠服务的程序。编写守护进程需要遵循一些步骤,案例给了一个参考,可以在下面编写自己的程序