我们上篇博客在会话中,提到了话首进程bash。我们再来看看这个话首进程。
我们在查看bash进程的时候,发现在PPID这一栏,话首进程也是有父进程的。它的父进程的ID是27357,这就奇怪了,按道理来说打开终端,bash最先运行。难道打开终端前还有比bash更先的进程已经运行了?我们再来看看这个27357到底是什么?
我们发现在bash前还是有别的进程的。而这些进程是什么时候运行的呢?如果这些进程时在打开终端时运行,那么为什么它们不是话首进程呢?
这时候就要引入一个新的概念:守护进程!!!
守护进程(Daemon)
守护进程也叫作精灵进程,是在后台运行的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或者处理某些发生的事件。Linux的大多是服务器就是用守护进程实现的。Linux系统启动的时候会启动很多系统服务进程,这些系统服务进程没有控制终端,不能与用户之间交互。其它进程都是在用户登录或运行程序的时候创建的,在运行结束或者用户注销的时候终止。但是系统服务进程(守护进程)不受用户登录注销的影响,它们一直在运行。
我们可以用指令ps axj
来查看进系统中的进程。其中参数a
表示不仅列当前用户的进程,也列出所有其他用户的进程。参数x
表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j
表示列出与作业控制相关的进程。
- 凡是TGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
- 在COMMAND一列用[ ]括起来的名字表示内核线程,这些线程在内核里创建,没有空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel内核。
- 可以看出,守护进程通常采用以d结尾的名字,表示Daemon。
守护进程的创建
#include <unistd.h>
pid_t setsid(void);
//创建守护进程,该函数调用成功返回新创建的会话的ID
//也就是调用该函数的进程ID,调用失败返回-1
创建守护进程最关键的一步就是调用setsid函数后创建一个新的会话,并成为会话的话首进程。但是调用这个函数之前,这个进程不允许是该进程组的组长,否则调用失败。因为不允许两个进程组的组ID是同一个ID,所以不允许进程组的组长调用这个函数。
要解决这个问题,其实也很简单。我们可以在进程中利用fork创建子进程,然后让子进程调用setsid,这时候子进程一定不是该进程组的组长,所以一定调用成功。
成功调用该函数的结果是:
- 创建一个新的会话,并且当前进程称为话首进程,当前进程ID就是会话ID
- 创建一个新的进程组,当前进程称为该进程组的组长,当前进程ID就是进程组ID
- 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通文件,而不是控制终端。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void mydeamon()
{
int fd0;
pid_t id;
struct sigaction sa;
//设置屏蔽字为0
umask(0);
id = fork();
if(id < 0)
{
printf("fork error!\n");
exit(1);
}
//让父进程退出
if(id > 0)
{
exit(0);
}
if(setsid() < 0)
{
printf("setsid error!\n");
exit(1);
}
//初始信号集
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
//忽略SIGCHLD信号
if(sigaction(SIGCHLD, &sa, NULL) < 0)
{
perror("sigaction");
exit(1);
}
fd0 = open("/dev/null", O_RDWR);
close(0);
dup2(fd0, 1);
dup2(fd0, 2);
}
int main()
{
mydeamon();
while(1) {
sleep(1);
}
return 0;
}
创建之后,我们注销掉这个终端,再次重启之后,发现此时守护进程还在。
欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!