一、简介
守护进程也叫做精灵进程,是一种特殊的进程。它的生命周期很长,常常在系统引导装入时启动,在系统关闭的时候才结束。由于他们没有控制终端,所以他们是在后台运行的。在Linux系统上存在很多守护进程,这些守护进程执行了日常事务活动。同时大多数服务器也是用守护进程实现的。
我们可以用命令ps axj 查看系统中的进程。
其中a表示不但列出当前用户进程,还列出所有其他用户进程,x表示列出所有有或者没有控制终端的进程,j表示与作业相关的进程。
我们可以看到其中TPGID中为-1的全部都是守护进程,也就是没有控制终端的进程。
同时我们可以发现其他内核进程的父进程都是2号进程,2号进程名字叫做kthreadd,它是一种特殊的内核进程,用来创建其他内核进程。
init进程的父进程和kthreadd进程的父进程都是0号进程,但是init是一个系统守护进程,除了其他工作外,主要负责各运行层次特定的系统服务。
二、创建守护进程
(1)setsid函数
创建守护进程的最关键的一步是调用setsid函数创建一个新的会话,并成为会话组长。
#include <unistd.h>
pid_t setsid(void);
返回值
成功返回创建会话的id(也就是当前进程的id),出错返回-1成功调用这个函数的结果是创建一个新的会话,当前进程成为会话首进程,当前进程的id就是会话的id
- 创建一个新的进程组,当前进程成为进程组的首进程,当前进程id就是进程组的id。
- 如果当前进程原有一个控制终端,则它会失去这个控制终端,成为一个没有控制终端的进程。所谓的失去控制终端指的是原来的控制终端仍然是打开的,可以读写,但是之时一个普通的打开文件而不是控制终端了。
总结来说就是一句话:
将当前调用进程设置为独立进程组,并且设置当前调用进程称为一个独立的会话,并于该控制终端去掉关联
(2)创建守护进程的规则
创建守护进程的方法一般就是有下面六个规则:
调用umask将文件模式创建屏蔽字设置为0.
因为如果守护进程需要进行创建文件,那么它可能需要设置特定的权限,如果是继承的来的文件创建屏蔽字可能会被设置为拒绝某些权限。调用fork()函数,然后父进程exit()
首先保证当前进程不能是组长。所以要先fork让子进程执行,父进程直接退出(保证接下来执行的进程绝对不是该进程组的组长)调用setsid创建一个新的会话。
将当前工作目录更改为根目录
如果当前目录中的其中一个被删除掉,则程序跑不起来关闭不再需要的文件描述符
使守护进程不再具有从其父进程继承来的任何文件描述符。忽略SIGCHLD信号
(3)为什么有人fork2次
1、第一次fork的作用是让shell 认为本条命令 已经终止,不用挂在终端输入上。还有一个作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader). 此时父进程是进程组组长。
2、setsid() 是本函数最重要的一个调用。它完成了daemon函数想要做的大部分事情。调用完整个函数。子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。到了这一步,基本上不管控制终端如何怎么样。新的进程都不会收到那些信号。
3、经过前面2个步骤,基本想要做的都做了。第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前提条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。
(4)deamon函数创建守护进程
其实,linux提供了daemon函数用于创建守护进程
#include
int daemon(int nochdir, int noclose);
1. daemon()函数主要用于希望脱离控制台,以守护进程形式在后台运行的程序。
2. 当nochdir为0时,daemon将更改进程的根目录为root(“/”)。
3. 当noclose为0是,daemon将进城的STDIN, STDOUT, STDERR都重定向到/dev/null。
daemon的实现大致如下:
int daemon( int nochdir, int noclose )
{
pid_t pid;
if ( !nochdir && chdir("/") != 0 ) //如果nochdir=0,那么改变到"/"根目录
return -1;
if ( !noclose ) //如果没有noclose标志
{
int fd = open("/dev/null", O_RDWR);
if ( fd < 0 )
return -1;
/* 重定向标准输入、输出、错误 到/dev/null,
键盘的输入将对进程无任何影响,进程的输出也不会输出到终端
*/
dup(fd, 0);
dup(fd, 1);
dup(fd, 2);
close(fd);
}
pid = fork(); //创建子进程.
if (pid < 0) //失败
return -1;
if (pid > 0)
_exit(0); //返回执行的是父进程,那么父进程退出,让子进程变成真正的孤儿进程.
//创建的 daemon子进程执行到这里了
if ( setsid() < 0 ) //创建新的会话,并使得子进程成为新会话的领头进程
return -1;
return 0; //成功创建daemon子进程
}
使用实例:
int main()
{
daemon(1, 1); //参数根据需求确定
/* 在这里添加你需要在后台做的工作代码 */
}
如何杀死这样的进程:
通过ps+grep找到对应的后台进程,使用kill命令将进程杀死;也可创建shell脚本对进程的启动、关闭、重启进行自动管理
deamon()可以直接将程序精灵化,不一样的是他把0、1、2设置为null,这个设备是字符设备,null相当于一个无底洞,写入的任何数据都会被丢弃
受否需要更改目录,是否需要关闭文件描述符的重定向