守护进程是什么
守护进程是一类运行在系统后台,不受任何终端、上下文影响的特殊后台进程。从运行开始,直到系统关闭时才会结束。
通常写一个服务程序,或者程序从设备开机起到设备关机一直都在运行,就适合做成守护进程形式。
为什么要守护进程
简单来说,就是为了使程序运行起来后,不受一些外部因素的干扰而异常退出。
linux系统运行起来后,pid为1的进程是init进程,负责启动linux系统,其他的进程都是在他创建出来的。我们在某个终端上通过命令./a.out的形式来运行一个程序,这种终端程序就是我们所运行程序的父进程。那么程序中相关的文件描述符、跟终端相关的信号等资源还是会和运行的进程产生联系。为了后台进程拜托终端的影响,必须让进程脱离父进程,成为孤儿进程,从而能够被init接管。同时关闭不必要的文件描述符,释放不必要的资源。
如何实现一个守护进程
- 调用umask将文件模式创建屏蔽字设置为0,这样可以不受集成而来的屏蔽字的影响。
- 调用fork,并且在子进程中调用setsid。
要创建一个新的session,摆脱原来的session,切断与原来终端的联系,做法是调用setsid。但是调用者必须不是进程组组长。我们通常执行程序,例如./a.out,此时a.out就是session leader(进程组ID(PGID)与pid相同)。所以要创建守护进程,第一步要先fork一次,这样fork出来的子进程,就不是session leader。这里需要说明一下,一个session,是一个或多个进程组的集合。session从登录进入,到登出为一个session的生命周期结束。在这个过程中,会创建多个进程组。 - 再次调用fork。这一步不是必须的,之所以调用是为了防止进程再次打开控制终端。因为只有session leader才能打开进程终端,再fork一次,子进程就不是session leader了。也就无法取得控制终端。当然为防止进程取得会话终端,也可以换一种方式:就是无论何时打开一个终端设备都要指定O_NOCTTY参数。
- 将当前工作目录改为根目录。这一步也是为了不受从父进程继承而来的工作目录的 影响。
- 关闭不必要的文件描述符。可以使用getrlimit函数来获取当前打开的文件描述符的最大值,然后关闭全部。
- 为了使标准输入、标准输出、标准错误输出的相关调用不报异常。可以将0,1, 2 三个文件描述符绑定到/dev/null下。
代码:
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
int main(int argc, char* argv[])
{
int i = 0;
int fd = 0;
int fd0, fd1, fd2;
int fork_num = 5;
pid_t pid;
struct rlimit fd_limit;
getrlimit(RLIMIT_NOFILE, &fd_limit);
//设置文件模式创建屏蔽字
umask(0);
//第一次fork,使得子进程可以调用setsid来创建新的会话
pid = fork();
if (pid > 0) {
//父进程退出
exit(0);
}
//创建新的会话,切段与之前终端的联系
setsid();
//再次fork,防止进程再次打开控制终端
pid = fork();
if (pid > 0) {
//退出
exit(0);
}
//设定工作目录为根目录
chdir("/");
//关闭所有打开的文件描述符
for (i = 0; i < fd_limit.rlim_max; ++i)
close(i);
//将标准输入输出、标准错误重定向到/dev/null
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
//运行程序
//TODO...
while (1) {
sleep(1);
}
}
运行程序后通过ps命令来查看:
可以看出。a.out的进程组ID是9026,但是没有一个活动进程的id是9026。a.out现在是一个孤儿进程在运行着。同时它也不是session leader,不会获取到控制终端。