守护进程基本概念
守护进程,又称为精灵进程。它是在后台运行的一种特殊的进程。它独立于控制终端而完成某种独立的任务或等待处理某些发生的事件。
守护进程的特点
普通进程是由用户进行创建,在运行结束或者用户注销后便会终止;
而守护进程不受用户登录注销的影响,是时时刻刻都在跑的进程。
Linux下,用户和系统进行交流的界面称之为终端。每一个终端开始运行的进行都会依附这个终端,这个终端就会被这些进程称之为控制终端。
在控制终端被关闭的时候,相应的进程会被关闭。
但是守护进程可以脱离终端,一直在后台运行。
直到系统关闭或者手动杀死才可以解决守护进程。
如果想让某个进程不受用户终端或者其他变化而终止,那么就需要把这个进程设置成守护进程。
守护进程自成进程组,自成会话。
查看系统中的守护进程
ps axj
其中,a表示不止显示当前用户的进程,也显示其他用户的进程
x表示不仅列出有终端控制的进程,也列出没有终端控制的进程
j表示列出与作业控制有关的信息
TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
在COMMAND一列用[]括起来的名字表示内核线程
这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行, 通常采用以k开头的名字,表示Kernel。
守护进程通 常采用以d结尾的名字,表示Daemon。
创建守护进程的具体步骤
(1)用umask将文件屏蔽字设置为0
文件权限掩码是屏蔽掉文件权限中的对应位。由于使用fork()函数新创建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带了很多的麻烦(比如父进程中的文件没有执行文件的权限,然而在子进程中希望执行相应的文件这个时候就会出问题)。因此在子进程中要把文件的权限掩码设置成为0,即在此时有最大的权限,这样可以大大增强该守护进程的灵活性。
(2)调用fork,父进程进行退出
如果该守护进程是作为一条简单的shell命令启动的,那么⽗父进程终止使得shell认为该命令已经执行完毕。
并且该操作保证子进程不是一个进程组的组长进程。
(3)用setsid创建一个新的会话session
#include<unistd.h>
pid_t setsid(void);
作用:创建一个新的会话
返回值:成功返回会话ID,出错返回-1
调用该函数的结果是:
1. 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
3. 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。
所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
(4)将当前工作目录更改为根目录
防止当前目录有一个目录被删除,导致守护进程无效。
这是因为使用fork()创建的子进程是继承了父进程的当前工作目录
然而在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。
因此通常的做法是让“/”作为守护进程的目录,当然也可以指定其他的别的目录来作为守护进程的工作目录。
(5)关闭不需要的文件描述符
同文件权限码一样,用fork()函数新建的子进程会从父进程那里继承一些已经打开了的文件。
这些文件被打开的文件可能永远不会被守护进程读写,如果不进行关闭的话将会浪费系统的资源,造成进程所在的文件系统无法卸下以及引起预料的错误。
(6)忽略SIG_CHLD信号
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。
这种情 况我们应该避免。这里我们不必对该信号进行处理,所以捕捉并忽略它。
代码
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
void daem()
{
//1、设置umask为0
umask(0);
//2、fork出子进程,终止父进程后,子进程调用setsid创建新的会话
pid_t pid = fork();
if(pid < 0)
{
perror("error");
return ;
}
else if(pid > 0)
{
exit(0);
}
setsid();
//3、设置当前的工作目录为根目录
if(chdir("/") < 0)
{
perror("chdir error");
return;
}
//4、关闭文件描述符表
close(0);
close(1);
close(2);
//5、忽略SIG_CHLD信号
signal(SIGCHLD,SIG_IGN);
}
int main()
{
daem();
while(1);
return 0;
}
运行结果
利用函数来创建守护进程
#include<unistd.h>
int deamon(int nochdir,int noclose);
(1) 该函数主要用于一个想脱离控制台,以守护进程方式运行的程序
(2) 当nochdir参数为0时,deamon函数将当前进程工作的目录改为根目录
(3) 当noclose参数为0时,deamon函数将stdin,stdout,stderr都重定向到/dev/null目录下,写入的数据会直接丢弃
代码
#include<stdio.h>
#include<unistd.h>
int main()
{
daemon(0,0);
while(1);
return 0;
}
运行结果
杀死守护进程方法
利用 ps axj | grep 守护进程名称
来查找到对应的守护进程ID
用kill -9 守护进程ID 指令进行杀死
守护进程fork两次的原因
(1)调用一次fork的作用:
第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上
还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork出子进程,则此时的父进程是进程组组长,就无法调用setsid。
当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。
(2)第二次fork的作用:
虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。
只有会话首进程能打开终端设备,也就是再fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该精灵进程不是对话期的首进程