守护进程:
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。
守护进程的特点:
(1)、Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。
(2)、其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着。
(3)、一般是孤儿进程
(4)、其父进程是一号进程,通常以d结尾
(5)、在后台运行,独立于终端,周期性的以某种任务或等待处理某些发生的事(生命周期为7*24小时)
守护进程存在的原因
daemon函数存在的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。而接收进程处理这些信号缺省动作会让进程退出。这些信号会由于终端上敲一些特殊按键而产生。
守护进程的创建过程
(1)、首先要设置文件创建屏蔽字umask(0)
原因:因为守护进程可能创建文件,而不修改则在创建文件的时候和目标文件的权限不同。
(2)、在父进程中fork出子进程(也就是我们目的的守护进程),然后让父进程退出
原因:如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。保证子进程不是一个进程组的组长进程。
(3)、chdir修改当前工作目录
原因:守护进程有可能访问根目录下的文件,如果没有修改必须先到达根目录,而且如果没有修改则守护进程创建的文件在当前目录下,如果当前目录被删除则找不到守护进程所创建的根目录,而创建在根目录下则永远不会被删除。
(4)、关闭不需要的文件描述符
原因:我们的守护进程是用用父进程fork出来的,而守护进程在不必需要某些父进程的文件描述符。
(5)、忽略SIGCHLD信号
原因:子进程退出不给父进程发送信号。
创建守护进程的函数:
#include <unistd.h>
pid_t setsid(void);
该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就⾏行了。fork创建的⼦子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子 进程中调用setsid就不会有问题了。
成功调用该函数的结果是:
1. 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
3. 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开⽂文件而不是控制终端了。
在上图中我们可以看出守护进程的父进程id为1,自成进程组,自成回话,且与中断控制没有关系。
查看守护进程的详细信息:
从上图中可以看出我创建出来的守护进程的id为2547,我们可以cd /proc/2547。
在上图中我们看到cwp-> / ,cwd就是显示当前工作目录,而cwp-> /就是当前工作目录为根目录,下面还有一个exe
它后面显示的是守护进程所在的目录,
我们可以看到当我们cd fd 时下面什么都没出现,因为我们已经关闭了守护进程的3个文件描述符所以什么都不显示
下面我们测试一下,关闭一个文件描述符和不更改工作目录
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void mydaemon()
{
umask(0);
pid_t id = fork();
if(id > 0)
exit(0);
setsid();
//chdir("/");
close(0);
//close(1);
//close(2);
signal(SIGCHLD, SIG_IGN);
}
int main()
{
mydaemon();
while(1);
return 0;
}
从上图中我们可以看出这次创建的守护进程的id为2881他的工作目录和运行的目录是一样的,而且我们也看到了当我cd fd 中然后 ls 就显示了 1 2,因为这次close(0),也就是标准输入文件描述符,而1和2标准输出和标准错误没有关闭所以就显示了它们两个。
还有一个函数可以创建守护进程:
#include<unistd.h>
int daemon(int nochdir,int noclose);
damon参数可以从表面意思上看出为工作目录和关闭的文件描述符。
参数1和参数2他们都为0时,设置工作目录为根目录,将标准输入,输出和错误重定向到/dev/null。
/dev/null为liunx下的黑洞,这个黑洞是写不满的而且写入东西则会直接被丢弃。
#include <stdio.h>
#include <unistd.h>
int main()
{
daemon(0, 0);
while(1);
return 0;
}
守护进程创建过程中两次fork:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void mydaemon()
{
umask(0);
if (fork() > 0)
exit(0);
setsid();
signal(SIGCHLD, SIG_IGN);
pid_t id = fork();
if(id > 0)
exit(0);
chdir("/");
close(0);
close(1);
close(2);
}
int main()
{
mydaemon();
while(1);
return 0;
}
从上图可以看出这一次创建的守护进程的pid != sid
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)。所以也无法打开新的控制终端。