守护进程

来源:互联网 发布:住建部bim数据库 编辑:程序博客网 时间:2024/05/01 09:31

转自:http://www.dutor.net/index.php/2010/09/daemon-process/

守护进程(daemon process,又称精灵进程),是一种运行在后台,不需要与用户进行交互的程序,其特性与Windows中的服务类似。Linux/Unix系统中运行着很多这样的进程,且很多此类进程的进程名都采用named的形式,比如httpd, vsftpd, inetd等。但这只是一种命名惯例,你完全可以采用其它的命名形式。

守护进程的特性

  守护进程有种种特性,使之成为守护进程。
  运行周期较长。守护进程通常在系统启动时由/etc/rc*.d中的启动脚本或者用户在终端手动启动,在系统结束时停止或由用户手动停止。不像普通进程那样在用户退出终端后就被停止,守护进程会一直运行(下面会看到守护进程在启动后会脱离执行它的终端)。
  脱离其运行环境。守护进程没有控制终端。进程在创建时(fork)会继承父进程的PCB(进程控制块),因此会同时拥有父进程的许多资源和关系,比如打开的文件描述符、socket、环境变量、控制终端、进程组、登录会话等。而守护进程需要关闭这些资源、脱离这些关系。

创建守护进程

  鉴于守护进程的这些特性,一个普通进程若想成为守护进程,就需要做一些特殊的操作。

脱离控制终端

  多数用户进程都是从终端运行的,它由与终端关联的shell(用户登录时由login执行的,比如bash程序)经过fork/exec启动。用户程序启动后shell会等待该进程(wait或waitpid等)结束,如果该进程为后台运行,shell会立即返回命令提示符等待用户的其它操作,否则shell会在用户进程结束时才打印命令提示符。
  所以,为了能使守护进程运行后shell立即返回,通常在程序的入口采用下面的代码来完成:

if( fork() > 0)    exit(0);

  程序fork产生新进程,父进程立即返回(shell等待的就是父进程),shell进程的wait也会成功返回,而子进程则会继续执行。注意,此时子进程的父进程已经过继成为init进程(进程号为1)的子进程了。

另立门户

  执行的上面的操作后,用户进程(子进程)并未真正脱离控制终端。因为子进程继承了父进程的关系,属于父进程所在的进程组,和父进程同属于一个作业,还隶属于控制终端所关联的登录会话(login session)。关于进程组、作业、登录会话等概念,请参考《高级Unix环境编程》(Advanced Programming of Unix Environment),及Google。
  若想脱离这些复杂的关系,操作却十分简单,只须在子进程一开始,调用函数setsid()即可。setsid做了以下工作:脱离了原来的进程组和登录会话,创建新的进程组和会话,并担当其进程组的Group Leader和会话的Session Leader.
  这时候,该进程已经真正脱离了其控制终端,即是说,该进程在其原控制终端退出登录后仍可以在系统中运行。

关闭继承打开的文件描述符

  控制终端,准确说是shell默认打开了三个文件,0代表标准输入,1代表标准输出,2代表标准错误输出。由该shell执行的进程也会继承打开这三个文件,所以它的所有子进程默认的输入和输出也都关联到此终端。因为守护进程也可能有其他用户进程通过exec系列函数执行,守护进程同样会继承这些文件描述符。因此在守护进程中需要关闭继承而来的所有描述符,APUE中stevens的代码:

int close_all_fd(void){        struct rlimit lim;        unsigned int i;         if (getrlimit(RLIMIT_NOFILE, &lim) < 0)                return -1;        if (lim.rlim_cur == RLIM_INFINITY)                lim.rlim_cur = 1024;        for (i = 0; i < lim.rlim_cur; i ++) {#ifdef MYPERF                if (i == 1)                        continue;#endif                if (close(i) < 0 && errno != EBADF)                        return -1;        }        return 0;}

改变当前工作目录

  每个进程都有自己的当前工作目录,进程创建时默认继承父进程的工作目录。守护进程需要将当前工作目录改变,否则其所在目录所挂载的文件系统将无法卸载(可能是你不想要的),通常将其改变之根目录,方法是chdir(“/”);

重设文件创建掩码

  为了能够自主控制文件创建权限位,常将文件创建掩码重设为0000,即umask(0);

忽略SIGCHLD信号

  由于守护进程运行周期长,为了避免因为等待子进程而产生僵尸进程,通常需要忽略SIGCHLD信号:signal(SIGCHLD, SIG_IGN);

总结

  方便起见,通常把创建守护进程的一系列操作包装为一个函数,在需要的地方调用即可:

void daemonize();intmain(){    daemonize();    //~ other operations here.    return 0;}voiddaemonize(){    if( fork() > 0)        exit(0);    setsid();    close(0); //~ im so lazy a guy.    close(1);    close(2);    //~ you can use I/O functions, but to/from the black hole...    int fd = open("/dev/null", O_RDWR);    dup2(fd, 1);    dup2(fd, 2);    chdir("/");    umask(0);    signal(SIGCHLD, SIG_IGN);}