普通进程:
守护进程:
使用ps命令查看进程:

根据内容,我们可以把这些守护进程分为以下几种:
具体的实现流程为:
调用fork或vfork生成一个进程,然后父进程退出。 这一步的作用是让子进程调用setsid能够脱离原来的进程组,让其不受终端控制。
子进程调用setsid,使子进程成为一个新的会话组长和进程组组长。相当于与原来的会话脱离,这样当终端退出时,就不会给子进程发SIGHUP信号。这一步很重要。
禁止进程重新打开控制终端(这一步不是必须的)。通过使进程不再是会话组长来实现,再一次通过fork创建新的子进程,使调用fork的进程退出 。
将当前进程工作目录更改为根目录(因为根目录下的文件不易被删除)。
关闭不再需要的文件描述符。
#include
#include
#include
#include
#include
#include
//创建守护进程
int create_daemon()
{
int fd = 0;
switch (fork())
{
case -1:
//创建子进程失败,这里可以写入错误日志或者打印错误信息
return -1;
case 0:
//子进程,break,执行下面的动作
break;
default:
//父进程,直接退出
exit(0);
}
//只有子进程才会走到这里
//调用setsid脱离当前终端
if (setsid() == -1)
{
// setsid调用错误写入错误日志
return -1;
}
//chdir("/");
//关闭所有文件描述符(除了标准输出), getdtablesize函数获取进程可打开最大文件数
//也可以将标准输出进行重定向到文件中,这样就不会显示到屏幕上了
close(0);
for (int i = 2; i < getdtablesize(); i++)
{
close(i);
}
return 1;
}
int main()
{
if (create_daemon() != 1)
{
//创建守护进程失败
return 1;
}
else
{
//到这里,就是守护进程需要执行的动作,可以使用execl进行程序替换
while (1)
{
sleep(1);
//由于重定向了标准输入和输出,所以这一句不会打印到显示器
printf("休息1秒,进程id=%d\n", getpid());
}
}
return 0;
}
可以看到守护进程和它的bash不在同一个进程组中,因此bash退出并不会影响守护进程。

当bash退出的时候,它会向会话中所有的进程发送SIGHUP信号,让它们都退出。
setsid()调用成功后,返回新的会话ID,调用setsid函数的进程成为新的会话的领头进程,并与其父进程的会话组和进程组脱离。

以下面的代码为例进行测试:
#include
#include
#include
#include
#include
#include
void sig_usr(int signo)
{
printf("收到了SIGUSR1信号,进程id=%d\n", getpid());
}
int main()
{
pid_t pid;
printf("进程开始执行\n");
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
{
printf("无法捕捉SIGUSR1信号\n");
exit(1);
}
//创建子进程
pid = fork();
if (pid < 0)
{
printf("子进程创建失败,程序退出\n");
exit(1);
}
while (1)
{
sleep(1);
printf("休眠一秒,进程id=%d\n", getpid());
}
printf("程序结束\n");
return 0;
}
我们用strcae来跟踪bash、父进程、子进程:

接着,我们手动关闭bash:

接下来,我们对子进程进行一些修改,让它调用setsid:

启动程序,再次查看,发现子进程的SID字段和PGRP字段都和父进程不相同:

我们再用同样的方式跟踪一下,可以看到子进程没有退出,并且被1号进程收养:

那你肯定会有这样的疑问,为什么需要子进程来调用setsid呢?父进程直接调用不行吗?
我们再将代码改一下,这一次父进程不创建子进程了,直接让父进程执行setsid:


可以看到执行失败,这主要的原因是:
另外再补充一点,既然bash退出的时候会向所有会话组的进程发送SIGHUP信号,那只需要捕捉这个信号,让其不执行原来的动作,就不会退出了。
nohup这个命令就能够让程序在运行时忽略SIGHUP信号,比如想让test程序在后台执行,将输出信息输出到log文件里,并且bash退出时test程序不退出,就可以这样写:
nohup ./test > log.txt 2>&1 &