首先,我们在Linux的shell下面运行一条指令ps -ajx,其中 ps是查看当前系统进程状态的指令,-a显示由其它用户所拥有的进程的状态,-x显示没有控制终端的进程的状态,-j显示与作业相关的信息。
我们在图中看到,图中所列举的这些都是与控制终端无关的进程,在Linux中我们称这些进程为守护进程,也叫做精灵进程,今天我们就来谈一谈Linux下面的守护进程把。
什么是守护进程?
守护进程也被称为精灵进程,是运行在后台的一种特殊的进程,它独立于终端并且周期性的执行某种任务。他们的生期很长,在系统引导装入时启动,仅在系统关闭时才停止(注意这里是停止,而不是终止)。
守护进程的特征
如上图,我们发现所有的守护进程首先他们是没有控制终端的,即他们独立于终端(他们的终端名被设置为?)。其中内核守护进程以控制终端的方式启动,用户层守护进程缺少控制终端是因为他们调用了setsid函数的结果;后面我们将会提到。许多用户层的守护进程都是进程组的组长进程,以及会话的首进程,即他们自成进程组,自成会话。,最后所有用户层守护进程的父进程都是init进程。
怎样创建一个用户层的守护进程
创建一个用户层守护进程的步骤如下:
1. 首先,调用umask函数设置文件权限,这是因为守护进程可能会创建某些文件,所以我们要设定特定的权限。
2. 调用fork函数,然后让父进程被终止;这样做的原因是因为下面我们要调用setsid函数创建会话,因为setsid函数不能被组长进程调用,而如果我们只创建一个进程,然后再让这个进程创建子进程,那么父进程一定是组长进程,让父进程终止的原因是因为有一条规则叫做孤儿进程的领养机制,这样我们就能保证子进程的父进程为1号进程了。
3. 调用setsid函数创建一个新的会话;下面我们先来看看setsid函数把:
#include <unistd.h>
pid_t setsid(void);
这个函数的功能主要有下面几点:
- 调用该函数的当前进程成为所创建会话的首进程,此外,当前进程时会话中的唯一进程。
- 该进程创建了一个新的进程组,并成为组长进程。
- 调用完该函数后,该进程就没有控制终端了,因为setsid函数会切断当前进程与其控制终端的联系,使其独立于终端。
还有一点是,如果调用进程已经是一个进程组的组长,那么该函数会出错。
在了解了这个函数后,我们发现,这个函数是创建守护进程的关键的步骤,因为守护进程没有控制终端,守护进程自成会话和进程组。
- 因为守护进程的工作目录大部分都在跟目录,所以我们通过chdir函数,将守护进程的工作目录设置为根目录。
- 下面我们关闭文件描述符,因为在守护进程中这些文件描述符是多余的,如果不关闭让守护进程占着的话是一种对资源的浪费。
- 忽略SIGCHLD信号,守护进程默认也是这样做的。
下面,基于这些步骤,我们来编写自己的守护进程把:
/*************************************************************************
> File Name: mydemon.c
> Author: LZH
> Mail: 597995302@qq.com
> Created Time: Fri 24 Feb 2017 11:33:38 PM PST
************************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>
#include<stdlib.h>
#define PATH "/" //创建的守护进程的工作目录
void mydemond()
{
umask(0);
pid_t pid=fork();
if(pid > 0){
exit(0); //这里让父进程终止,然后子进程调用setsid函数创建会话
}
pid_t sid=setsid();
if(sid==-1){
perror("setsid...");
exit(1);
}
if(chdir(PATH)!=0)
{
perror("chdir...");
exit(1);
}
close(0);
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);
}
int main()
{
mydemond();
while(1){
sleep(1);
}
return 0;
}
----------
下面我们来运行一下守护进程,查看结果:
下面我们在注销掉当前终端,看看守护进程的状态。
创建守护进程fork两次
下面我们来换一种思路,我们在创建守护进程时fork两次来看看会有什么结果!
/*************************************************************************
> File Name: mydemon.c
> Author: LZH
> Mail: 597995302@qq.com
> Created Time: Fri 24 Feb 2017 11:33:38 PM PST
************************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>
#include<stdlib.h>
#define PATH "/"
void mydemond()
{
umask(0);
pid_t pid=fork();
if(pid > 0){
exit(0);
}
else{
//fork once child
pid_t sid=setsid();
if(sid==-1){
perror("setsid...");
exit(1);
}
pid_t newpid=fork();
if(newpid > 0){
//fork twice father
exit(0);
}
if(chdir(PATH)!=0)
{
perror("chdir...");
exit(1);
}
close(0);
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);
}
}
int main()
{
mydemond();
while(1){
sleep(1);
}
return 0;
}
下面我们运行后来看看fork两次的守护进程的状态。
此时,守护进程已经不是自成会话和进程组了,那我们为什么要这样做呢?
fork一次与fork两次的区别
前面我们fork一次,是为了保证调用setsid函数的进程不是组长进程(因为setsid函数不允许组长进程调用),这时,我们创建出来的守护进程虽然独立于控制终端,但是由于它自成进程组,自成会话。所以作为一个会话首进程,它拥有打开一个控制终端的权利,如果他打开了一个控制终端,那么它就和守护进程独立于控制终端相互矛盾了,所以就有了fork两次。
fork两次保证了第二次fork出来的孙子进程不是会话的首进程,所以这时我们在终止掉儿子进程,此时会话首进程被终止,所以这个会话中的其他进程就没有打开控制终端的权利了。