"守护进程"(daemon)就是一直在后台运行的进程

 

        //fork执行中已经出现父和子进程,状态同样但不是相同的进程,两条进程执行序都指向了fork函数内建立进程代码后面一句的指令集,
        //此时是父进程占据cpu时间,父进程继续执行根据fork后面的代码实现返回建立的pid,
        //子进程以后继续执行根据fork代码实现返回的是0
        //建立子进程失败返回-1
        $pid = pcntl_fork();
        if (-1 === $pid) {
            throw new Exception('fork fail');
        } elseif ($pid > 0) {
            exit(0);
        }

在Linux/UNIX系统引导的时候会开启不少服务,这些服务称为守护进程(也叫Daemon进程)。守护进程是脱离于控制终端而且在后台周期性地执行某种任务或等待处理某些事件的进程,脱离终端是为了不进程在执行过程当中的信息在任何终端上显示而且进程也不会被任何终端所产生的中断信息所终止。html

 

建立守护进程的通常步骤linux

 

(1) 建立子进程,退出父进程数组

为了脱离控制终端须要退出父进程,以后的工做都由子进程完成。在Linux中父进程先于子进程退出会形成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。服务器

ps –ef | grep ProcName          经过PID/PPID查看进程的父子关系session

 

(2) 在子进程中建立新的会话函数

使用系统函数setsid来完成。测试

man 2 setsid    查看关于setsid函数的说明this

setsid – creates a session and sets theprocess group ID编码

#include <unistd.h>spa

pid_t setsid(void);

setsid() creates a new session if thecalling process is not a process group leader. The calling process is theleader of the new session, the process group leader of the new process group,and has no controlling tty. The process group ID and session ID of the callingprocess are set to the PID of the calling process. The calling process will bethe only process in this new process group and in this new session.

进程组:是一个或多个进程的集合。进程组有进程组ID来惟一标识。除了进程号PID以外,进程组ID也是一个进程的必备属性。每一个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,且该进程组ID不会因组长进程的退出而受到影响。

setsid函数做用:用于建立一个新的会话,并担任该会话组的组长。调用setsid有3个做用

(a) 让进程摆脱原会话的控制;

(b) 让进程摆脱原进程组的控制;

(c) 让进程摆脱原控制终端的控制;

使用setsid函数的目的:因为建立守护进程的第一步调用了fork函数来建立子进程再将父进程退出。因为在调用fork函数时,子进程拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并无改变,所以,这还不是真正意义上的独立开了。使用setsid函数后,可以使进程彻底独立出来,从而摆脱其余进程的控制。

 

(3) 改变当前目录为根目录

使用fork建立的子进程继承了父进程的当前的工做目录。因为在进程运行中,当前目录所在的文件系统是不能卸载的,这对之后的使用会形成诸多的麻烦。所以,一般的作法是让根目录”/”做为守护进程的当前工做目录。这样就能够避免上述的问题。若有特殊的需求,也能够把当前工做目录换成其余的路径。改变工做目录的方法是使用chdir函数。

 

(4) 重设文件权限掩码

文件权限掩码:是指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(对应二进制为,rwx, 101)。因为fork函数建立的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。所以,把文件权限掩码设置为0(即,不屏蔽任何权限),能够加强该守护进程的灵活性。设置文件权限掩码的函数是umask。一般的使用方法为umask(0)。

 

(5) 关闭文件描述符

用fork建立的子进程也会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们同样消耗系统资源,并且可能致使所在的文件系统没法卸载。在使用setsid调用以后,守护进程已经与所属的控制终端失去了联系,所以从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。因此,文件描述符为0、一、2(即,标准输入、标准输出、标准错误输出)的三个文件已经失去了存在的价值,也应该关闭。

 

(6) 守护进程退出处理

当用户须要外部中止守护进程时,一般使用kill命令中止该守护进程。因此,守护进程中须要编码来实现kill发出的signal信号处理,达到进程正常退出。

http://blog.csdn.net/delphiwcdj/article/details/7364343

http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html

 

当咱们只fork()一次后,存在父进程和子进程。这时有两种方法来避免产生僵尸进程:

  • 父进程调用waitpid()等函数来接收子进程退出状态。
  • 父进程先结束,子进程则自动托管到Init进程(pid = 1)。

      目前先考虑子进程先于父进程结束的状况:     

  • 若父进程未处理子进程退出状态,在父进程退出前,子进程一直处于僵尸进程状态。
  • 若父进程调用waitpid()(这里使用阻塞调用确保子进程先于父进程结束)来等待子进程结束,将会使父进程在调用waitpid()后进入睡眠状态,只有子进程结束父进程的waitpid()才会返回。 若是存在子进程结束,但父进程还未执行到waitpid()的状况,那么这段时期子进程也将处于僵尸进程状态。

      由此,能够看出父进程与子进程有父子关系,除非保证父进程先于子进程结束或者保证父进程在子进程结束前执行waitpid(),子进程均有机会成为僵尸进程。那么如何使父进程更方便地建立不会成为僵尸进程的子进程呢?这就要用两次fork()了。

      父进程一次fork()后产生一个子进程随后当即执行waitpid(子进程pid, NULL, 0)来等待子进程结束,而后子进程fork()后产生孙子进程随后当即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不须要子进程的返回值),而后父进程继续执行。这时的孙子进程因为失去了它的父进程(便是父进程的子进程),将被转交给Init进程托管。因而父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其子进程结束时会自动收尸,这样也就不会产生僵尸进程了。

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <sys/wait.h>  
  
int main(void)  
{  
    pid_t   pid;  
  
    if( ( pid = fork() ) < 0 ){  
        fprintf( stdout, "fork error!\n" );  
    } else if( pid == 0 ) {                     /*first child*/  
        if (( pid = fork()) < 0 )  
            printf( "fork error!\n" );  
        else if( pid > 0 )  
            exit( 0 );  
      
    sleep( 2 );  
    printf( "Second child , parent pid = %d\n", getppid() );  
    exit( 0 );            
    }  
  
    if( waitpid( pid, NULL, 0 ) != pid )  
        printf( "waitpid error!\n" );  
  
    printf( "father of original!\n" );  
  
    exit( 0 );  
  
  
}  

 进程调用fork与文件描述符的共享(fork,dump)

 Linux的进程描述task_struct{}中有一个数组专门用于记录一打开的文件,其中文件描述符做为该数组的下标,数组元素为指向所打开的文件所建立的文件表项。以下图所示,文件表项是用于描述文件当前被某个进程打开后的状态信息,包括文件状态标志,记录当前文件读取的位移量(能够经过接口lseek设置),以及文件的i节点指针(i节点描述文件的具体信息,如:建立,修改时间,文件大小,文件存储的块信息)。

       不一样进程打开同一个文件后,进程表和文件表的关系以下图所示:

进程的fork与文件描述符的拷贝

进程的所打开文件和在fork后的结构图以下所示,子进程是共享父进程的文件表项;

能够经过一个测试实例来证明以上的描述:

测试源码

 

[cpp]  view plain  copy
 
 在CODE上查看代码片派生到个人代码片
  1. #include "slp.h"  
  2.   
  3. int main()  
  4. {  
  5.     int fd1,fd2,fd3,nr;  
  6.     char buff[20];  
  7.     pid_t pid;  
  8.     fd1 = open("data.in",O_RDWR);  
  9.     pid = fork();  
  10.     if(pid == 0)  
  11.     {     
  12.         nr = read(fd1,buff,10);  
  13.         buff[nr]='\0';  
  14.         printf("pid#%d content#%s#\n",getpid(),buff);  
  15.         exit(0);  
  16.     }     
  17.     nr = read(fd1,buff,10);  
  18.     buff[nr]='\0';  
  19.     printf("pid#%d content#%s#\n",getpid(),buff);  
  20.     return 0;  
  21. }  

测试用例

[html]  view plain  copy
 
 在CODE上查看代码片派生到个人代码片
  1. data.in  
  2. abcdefghijklmnopqrstuvwxyz1234567890  
  3. EOF  


测试结果:
pid#20029 content#abcdefghij#
pid#20030 content#klmnopqrst#

 

结果分析:

进程20029对文件的读取后的当前位置应该为data.in的k字符所在的位置,进程20030是由20029进程以后开始读取的,他读取文件内容不是从a开始,而是从k开始,说明20030共享了20029的文件表。

进程dump一个文件描述符

 

 

总结

进程调用fork后,子进程和父进程的文件描述符所对应的文件表项是共享的,这意味着子进程对文件的读写直接影响父进程的文件位移量(反之同理)。

进程中调用fd2 = dump(fd1) 产生的新的fd2所指向的文件表项和fd1指向的文件表项是相同的;

进程中分别调用:fd1 = open("data.in",O_RDWR); fd2 = open("data.in",O_RDWR); 那么fd1和fd2指向的文件表项是不一样的。

原文连接:http://blog.csdn.net/ordeder/article/details/21716639 

服务器端预先建立子进程(work)同时监听服务端口和惊群现象

http://blog.csdn.net/ordeder/article/details/21716639

相关文章
相关标签/搜索