守护进程,linux守护进程


  • 为什么要引入守护进程:


     

   因为它生存期长,它独立于控制终端、会话周期(下文有解释)执行任务:

   由于在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 }

 

 

(未完,待完善)

 

 

 

 

 

 

 

     

   

  

 

相关内容