目录
一、什么是守护进程?
1、守护进程的概念
2、为什么需要守护进程
二、理解进程组、会话、终端
三、创建守护进程的两种方式
1、nohup命令创建守护进程
2、代码创建守护进程
(1) 创建子进程,父进程退出
(2) 子进程创建新的会话
(3) 更改守护进程的工作目录
(4) 重新设置文件权限掩码
(5) 关闭文件描述符
(6) 完整代码
一、什么是守护进程?
1、守护进程的概念
守护进程又叫精灵进程(Daemon Process),它是一个生存期较长的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。通常服务名称都是以 d 结尾,如http的守护进程 httpd、mysql 的守护进程 mysqld。
- 始终在后台运行
- 独立于控制终端(终端关闭不会影响到守护进程的关闭)
- 生命周期较长,随系统启动和关闭
2、为什么需要守护进程
当我们在终端上运行一个 ./test 程序,./test 对应的进程就会依附于当前终端,一旦当前终端被关闭或者断开连接,那么这个进程也会被关闭。
如果是虚拟机的终端被关闭,意味着这个我们所运行的进程都会被关闭,无论是休眠进程还是僵尸进程,都会被关闭。
如果是连接远端服务器的Xshell被关闭,我们在Xshell上运行的程序也会被关闭,因为打开Xshell还是相当于在远程服务器上打开一个终端。
如果我们希望长期运行某个服务,而且该服务不会受到控制终端的影响,我们可以将我们要运行的程序创建成守护进程。守护进程是直接和系统绑定的,只要虚拟机或者远程服务器不关闭,守护进程就不会退出。
二、理解进程组、会话、终端
1、进程组
进程组是进程的集合,每个进程组都有一个组长,组长的ID就是该进程组的ID(PGID)。最简单的进程组就是父进程创建出了子进程,父子进程都在一个进程组里。
2、会话
会话是进程组的集合,建立会话的就是领导进程,该进程的ID就是会话的SID。会话中的每一个进程组被称为【作业】,会话可以包含一个前台进程组【前台作业】以及多个后台进程组【后台作业】。(会话首进程放到终端解释)
3、终端
一个会话可以有一个控制终端,建立会话和控制终端联系的会话首进程称为“控制进程”。控制终端的输入和输出都会被传递给会话中的前台进程组,以此来达到通过终端来控制会话中的多个作业的目的。
三、创建守护进程的两种方式
守护进程的创建有两种方式,第一种是通过nohup命令创建,第二种是通过代码创建。
1、nohup命令创建守护进程
首先我们先写一个简单的脚本 daemon.c,不让进程退出。
// daemon.c
#include <unistd.h>
#include <stdio.h>
int main(){
while(1)
{
sleep(1);
}
return 0;
}
我们使用gcc 命令编译这个脚本得到执行文件 daemon,然后使用nohup 命令运行这个文件nohup命令的格式是
# 将程序变为守护进程
nohup xxxx &
# 将程序变为守护进程,执行产生的信息输出到log文件
# 2>&1 的作用是标准输出和标准错误同等对待,都输出到log文件
nohup xxxx > log 2>&1 &
我们在命令行输入 nohup ./daemon & ,然后输入 ps ajx | grep ./daemon 查看是否运行成功
2、代码创建守护进程
使用代码创建守护进程可以更加直观地了解到守护进程的创建过程。代码创建守护进程一共分为五个步骤。
(1) 创建子进程,父进程退出
第一步就是让子进程成为孤儿,被init进程收养,此时子进程会转为后台运行,这是撇清关系的必要过程。
// fork返回值大于0代表父进程,等于0代表子进程
if(fork() > 0)
{
exit(0);
}
(2) 子进程创建新的会话
通过创建新的会话,让子进程脱离 init 进程,自己当家做主。
// setsid() 表示创建新的会话
// getsid() 表示根据会话ID获取会话领导进程的PID
if(setsid() < 0)
{
exit(-1);
}
(3) 更改守护进程的工作目录
如果当前目录在日后可能会被移除,所以建议把守护进程的工作目录移动到其他工作目录下,比如根目录,因为守护进程一直在后台运行,其工作目录不能被卸载。
chdir("/");
(4) 重新设置文件权限掩码
新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。如果原本要设置的文件权限是 0666,普通用户的默认权限掩码是 0002,最终创建的文件权限是
更改权限掩码前:
mask & ~umask = 666 & (~002) = 110110110 & 111111101 = 110110100 = 664
更改权限掩码为0后:
mask & ~umask = 666 & (~000) = 110110110 & 111111111 = 110110110 = 666
因此,把文件权限掩码设置为 0,方便子进程更自由的控制文件权限。
if(umask(0) < 0)
{
exit(-1);
}
(5) 关闭文件描述符
子进程被创建时,会继承父进程的三个标准输入输出流,因为守护进程不需要和终端交互,所以为了不受到终端的影响,需要关闭三个标准输入输出流。如Ctrl + C会发送终止进程的信号、Ctrl + Z会将前台进程转为后台进程。
如果需要输出信息,可以将执行过程中的信息输出到指定文件。
close(0); // 关闭标准输入
close(1); // 关闭标准输出
close(2); // 关闭标准错误
(6) 完整代码
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(-1);
}
else if (pid > 0) {
exit(0); // 1. 让父进程退出
}
if (setsid() < 0)
{
perror("setsid"); // 2. 创建新的会话
exit(-1);
}
chdir("/"); // 3. 更改工作目录
if(umask(0) < 0) // 4. 设置权限掩码
{
perror("umask");
exit(-1);
}
close(0); // 5. 关闭标准输入输出
close(1);
close(2);
while(1) // 守护进程开始执行任务
{
// do something
}
return 0;