目录
- 什么是守护进程?
- 守护进程、前台进程、后台进程的区别?
- 前台进程和后台进程
- 后台进程和守护进程
- C语言实现守护进程
什么是守护进程?
守护进程是一个在后台运行并且不受任何终端控制的进程。
很多进程名字后面加了个d,基本就是个守护进程(这算个约定俗称的规则)。比如:
mysql(数据库),ssh(shell登录),cron(定时器)都是以守护进程的方式在运行。
守护进程、前台进程、后台进程的区别?
前台进程和后台进程
先聊下前台进程和后台进程的差别。这里写个简单的测试例子。
// gcc -g -O0 test.c -o test
#include <sys/wait.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int i = 0;
while(1)
{
printf("into while %d\n", i++);
sleep(10);
}
}
如上图,如果直接启动./test,那么它是个 前台进程,或者称为前台任务(foreground job),它会独占命令行窗口,只有运行完了或者手动中止,才能执行其他命令。
如果是./test &方式启动(后面加一个&符号),那么它就变成后台进程,也可以称为后台任务(background job)。
后台任务会:
1. 继承当前 session(对话)的标准输出(stdout)和标准错误(stderr)。因此,后台任务的所有输出依然会同步地在命令行下显示。
2. 不再继承当前 session 的标准输入(stdin)。你无法向这个任务输入指令了。如果它试图读取标准输入,就会暂停执行(halt)。
也可以用bg命令使任务变成后台任务,fg命令使任务再变回前台任务。如下:
后台进程和守护进程
Q:后台进程是否就是守护进程了呢?或者说,用户退出session(也即:用户退出shell)后,后台进程是否还会继续执行?
A:后台进程和守护进程不一样。
这里涉及到SIGHUP信号。我们先了解下,退出shell的过程。
step1:用户准备退出session(shell)。
step2:系统向该shell窗口进程发出SIGHUP信号。
step3:shell窗口进程将SIGHUP信号发送给所有子进程。
step4:子进程受到SIGHUP信号,自动退出。
这里其实间接解释了为何前台进程会随着shell退出而退出。
- 对于前台进程,当shell退出,子进程会收到SIGHUP信号而退出;
- 对于后台进程,当shell退出,是否发送SIGHUP信号给后台进程,取决于shell的 huponexit 参数,当该参数是off,则shell退出,不会发送SIGHUP信号给后台进程;当该参数是on,则shell退出时,会发送SIGHUP信号给后台进程,则后台进程也会跟随退出。
想要看huponexit参数的值,可以使用 shopt 命令。
shopt | grep huponexit
我测试机的结果是:
因此,后台进程和守护进程并不相同,比如:有的系统huponexit是on的话,那么shell退出时候,后台进程也会相应地退出。
那么如何使得后台进程变成守护进程呢?
可以使用nohup命令。也可以用disown命令,不过稍微繁琐一点,这里不展开讲,有兴趣参考链接。下面讲解 nohup 命令。
举例,写一个循环例子:
// gcc -g -O0 test.c -o test
#include <stdio.h>
int main(int argc, char *argv[])
{
int i = 0;
while(1)
{
printf("i=%d\n", i++);
// 这个语句很重要,没有这句fflush,nohup.out可能没有输出
fflush(NULL);
sleep(1);
}
}
运行命令:
nohup ./test &
如果不想用默认的输出nohup.out,可以自己指定输出:
nohup ./test > a.txt 2>&1 &
总结
nohup都干了些什么事?
nohup命令实际将shell的子进程和shell进程本身分离,主要做了下面三件事:
- 阻止SIGHUP信号发到这个进程。
- 关闭标准输入。该进程不再能够接收任何输入,即使运行在前台。
- 重定向标准输出和标准错误到文件nohup.out。(这个可以自定义修改)
注意
- nohup命令不会自动把进程变为"后台任务",所以必须加上&符号。
C语言实现守护进程
简单 一点,直接用daemon函数即可,当然,复杂点,还可以自己实现守护进程的逻辑,比如:让程序在后台执行、调用setsid()创建一个新对话期、禁止进程重新打开控制终端、关闭不再需要的文件描述符、将当前目录更改为根目录、子进程从父进程继承的文件创建屏蔽字可能会拒绝某些许可权、处理SIGCHLD信号 等等。这个就不展开讲,有兴趣自己百度下,我们这边还是讲最简单的系统API daemon函数。
头文件
#include <unistd.h>
函数原型
int daemon(int nochdir, int noclose);
参数
nochdir=0,daemon函数会把当前工作目录改为根目录;否则,当前工作目录保持不变。
noclose=0,daemon函数重定向标准输入、标准输出、标准错误到/dev/null;否则,不会重定向这三个fd(即:0,1,2)。
返回值
成功返回0;失败返回-1,并会设置errno。
举例:
// gcc -g -O0 test.c -o test
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
daemon(1, 0);
int i = 0;
while(1)
{
printf("i=%d\n", i++);
// 这个语句很重要,没有这句fflush,nohup.out可能没有输出
fflush(NULL);
sleep(1);
}
}