守护进程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了守护进程相关的知识,希望对你有一定的参考价值。
-
为什么要引入守护进程:
因为它生存期长,它独立于控制终端、会话周期(下文有解释)执行任务:
由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依赖这个终端,这个终端就称为这些进程的
控制终端。当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它被执行开始运转,直到整个系统关闭时才退出。
-
守护进程的特性:
1> 守护进程最重要的特性是后台运行。
2> 其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录已经文件创建掩码
等。这些环境通常是守护进程从父进程那里继承下来的。
3> 守护进程的启动方式有其特殊之处:它可以在linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户
终端(通常是shell)执行。
-
后台进程 == 守护进程?
在linux下使用&可以使程序进入后台运行模式,使用守护进程方法也可以使程序和终端分离出来,那么后台进程是否就是守护进程?
其实两者不相等!
最直观最重要的区别,守护进程没有控制终端,而后台进程有。
1.基本上任何一个程序都可以后台运行,但守护进程是具有特殊要求的程序,比如要脱离自己的父进程,成为自己的会话组长等,这些要在代码
中显式地写出来。
2.守护进程成为了进程组长(或者会话组长),和控制终端失去了联系(其文件描述符也是继承于父进程的,但是在变成守护进程的同时
stdin,stdout,stderr和控制台失去联系了)。
3.后台的文件描述符也是继承于父进程,例如shell,所以它也可以再当前终端下显式输出数据,但是daemon进程自己变成了进程组长,其文件
描述符号和控制终端没有关联,是脱离控制台的进程。
小结:守护进程肯定是后台进程,但反之不成立。守护进程顾名思义,主要用于一些长期运行,守护着自己的职责(监听端口,监听服务等)。
我们的系统下就有很多守护进程。
-
守护进程编程规则(步骤):
step 1、在后台运行
一些概念:
- 进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID(GID)也是一个进程的必备属性。每个进程都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因为组长进程的退出而受影响。
- 会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退 出,在此期间该用户运行的所有进程都属于这个会话期。
- 控制终端:由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行 的进程都会依赖这个控制终端。
为避免挂起控制终端,将daemon放入后台执行,方法是进程中调用fork,然后使父进程exit,让daemon在后台执行。这样做实现了以下两点:
1.如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕。
2.虽然紫禁城继承了父进程的进程组ID(pgid),但获得了一个新的进程ID(pid),这就保证了子进程不是一个进程组的组长进程,这是
下面要进行的setsid调用的先决条件!
控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。
进程调用 setsid函数建立一个新会话
1 #include<unistd.h> 2 pid_t setsid(void);
如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话,会发生以下3件事
1.该进程会变成新回话的会话首进程(session leader,会话首进程是创建该会话的进程)。此时,该进程是新会话中的唯一进程。(摆脱原会话的控制)
2.该进程成为一个新进程组的组长进程,新进程组ID是该调用进程的进程ID。(摆脱原进程组的控制)
3.该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,那么这种联系也被切断。(摆脱控制终端)
step 2:在子进程中调用setsid创建一个新会话。这会执行上面的3件事!
通过这一步,新的子进程就摆脱了原会话的控制,摆脱了原进程组的控制,摆脱了终端的控制。
step 3:禁止进程重新打开控制终端//可省,很多开源服务没有fork第二次
现在,进程已经成为无终端的会话组长了。
但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端。
因为打开一个控制终端的前提条件是该进程必须是会话组长!所以再fork一次,结束第一子进程,第二子进程继续(第二子进程不再是会话组长),
第二子进程ID != sid(sid是进程第一子进程的会ID:sid)。所以无法打开新的控制终端。
step 4 :将当前目录更改为根目录
从父进程处继承过来的当前工作目录可能在一个挂载的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当
前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载。
或者,某些守护进程还可能会把当前工作目录更改到某个指定的位置,并在此位置进行它们的全部工作。例如,行式打印机假脱机守护进程就可
能将其工作目录更改到它们的spool目录上。
step 5:关闭所有(不再需要的)文件描述符
新进程会从父进程(父进程可能是shell进程,或某个其他进程)那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程
读写,而它们一直消耗系统资源。另外守护进程已经与所属的终端失去联系,那么从终端输入的字符不可能到达守护进程。可以使用open_max函数
或getrlimit函数来判定最高文件描述符值,并关闭0直到该值的所有描述符。
strp 6:重设文件掩码
进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。即调用umask将文件模式创建屏蔽字设置为一
个已知值(通常为0)。
step 7:处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,
子进程将成为 僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux
下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
- 无代码无真相!
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/stat.h> 5 void Daemon() 6 { 7 const int MAXFD=64; 8 int i=0; 9 if(fork()!=0) //父进程退出 10 exit(0); 11 setsid(); //成为新进程组组长和新会话领导,脱离控制终端 12 chdir("/"); //设置工作目录为根目录 13 umask(0); //重设文件访问权限掩码 14 for(;i<MAXFD;i++) //尽可能关闭所有从父进程继承来的文件 15 close(i); 16 } 17 int main() 18 { 19 Daemon(); //成为守护进程 20 while(1){ 21 sleep(1); 22 } 23 return 0; 24 }
(未完,待完善)
以上是关于守护进程的主要内容,如果未能解决你的问题,请参考以下文章