守护进程与孤儿进程

基本定义
       守护进程:是生存期长的一种进程。它们常常在系统引导装入时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以它们是在后台运行的。UNIX系统有很多守护进程,它们执行日常事务活动。

       孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

       僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

       在说明fork函数时,子进程是在父进程调用fork后生成的。子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,又将如何呢?对于父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程收养。操作过程:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID)。这种处理方法保证了每个进程有一个父进程。
       如果子进程是在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?如果子进程完全消失了,父进程在最终准备好检查子进程是否终止时是无法获取它的终止状态的。内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵尸进程。
      最后一个要考虑的问题是:一个由init进程收养的进程终止时会发生什么?它会不会变成一个僵尸进程?对此问题的回答是“否”,因为init被编写成无论何时只要一个子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中塞满僵尸进程。当提及“一个init的子进程”时,这指的可能是init直接产生的进程,也可能是父进程已终止,由init收养的进程。

守护进程的特征
       系统进程依赖于操作系统的实现。父进程ID为0的各进程通常是内核进程,它们作为系统引导装入过程的一部分而启动。(init是个例外,它是一个由内核在引导装入时启动的用户层次的命令)。内核进程是特殊的,通常存在于系统的整个生命期中。它们以超级用户特权运行,无控制终端,无命令行。
       在PS的输出实例中,内核守护进程的名字出现在方括号中。该版本的Linux使用一个名为kthreadd的特殊内核进程来创建其他内核进程,所以kthreadd表现为其他内核进程的父进程。对于需要在进程上下文执行工作但却不被用户层进程上下文调用的每一个内核组件,通常有它自己的内核守护进程。例如,在Linux中:
kswapd守护进程也被称为内存换页守护进程。它支持虚拟内存子系统在经过一段时间后将脏页慢慢写回磁盘来回收这些页面。
       flush守护进程在可用内存达到设置的最小阈值时将脏页面冲洗至磁盘。它也定期地将脏页面冲洗回磁盘来减少在系统出现故障时发生的数据丢失。
       sync_supers守护进程定期将文件系统元数据冲洗至磁盘。
       jbd守护进程帮助实现了ext4文件系统中的日志功能。
       进程1通常是init,它是一个系统守护进程,除了其他工作外,主要负责启动各运行层次特定的系统服务。
       rpcbind守护进程提供将远程过程调用程序号映射为网络端口号的服务。rsyslogd守护进程可以被由管理员启用的将系统消息记入日志的任何程序使用。可以在一台实际的控制台上打印这些消息,也可以将它们写到一个文件中。
       inetd守护进程侦听系统网络接口,以便取得来自网络的对各种网络服务进程的请求。
       cron守护进程在定期安排的日期和时间执行命令。许多系统管理任务是通过cron每隔一段固定的时间就运行相关程序而得以实现的。atd守护进程与cron类似,它允许用户在指定的时间执行任务,但是每个任务它只执行一次,而非在定期安排的时间反复执行。sshd守护进程提供了安全的远程登录和执行设施。
       大多数守护进程都以超级用户(root)特权运行。所有的守护进程都没有控制终端。其终端名设置为问号。

守护进程编程规则
       在编写守护进程程序时需遵循一些基本规则,以防止产生不必要的交互作用。下面先说明这些规则,    然后给出一个按照这些规则编写的函数。
     (1)调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)。由继承得来的文件模式创建屏蔽套接字可能会被设置为拒绝某些权限。如果守护进程要创建文件,那么它可能要设置特定的权限。例如,若守护进程要创建组可读、组可写的文件,继承的文件模式创建屏蔽字可能会屏蔽上述两种权限中的一种,而使其无法发挥作用。另一方面,如果守护进程调用的库函数创建了文件,那么将文件模式创建屏蔽字设置为一个限制性更强的值(如007)可能会更明智,因为库函数可能不允许调用者通过一个显式地函数参数来设置权限。
     (2)调用fork,然后使其父进程exit。这样做实现了以下几点。第一,如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕。第二,虽然子进程继承了父进程的进程组ID,但获得了一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。
     (3)调用setsid创建一个新会话。使调用进程:(a)称为新会话的首进程(b)称为新进程组的组长进程(c)没有控制终端
     (4)将当前目录更改为根目录。从父进程处继承过来的当前工作目录可能在一个挂载的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载。
     (5)关闭不再需要的文件描述符。这使守护进程不再持有从父进程继承来的任何文件描述符(父进程可能是shell进程,或某个其他进程)。可以使用open_max函数或getrlimit函数来判定最高文件描述符值,并关闭直到该值的所有描述符。
     (6)某些守护进程打开/dev/null使其具有文件描述符0,1,2,这样,任何一个试图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果。因为守护进程并不与终端设备相关联,所以其输出无处显示,也无处从交互式用户那里接收输入。即使守护进程是从交互式会话启动的,但是守护进程是在后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们不希望在该终端上见到守护进程的输出,用户也不期望他们在终端上的输入被守护进程读取。

下面是代码:

#include"apue.h"
#include<syslog.h>
#include<fcntl.h>
#include<sys/resource.h>

void daemonize(const char *cmd)
{
    int i,fd0,fd1,fd2;
    pid_t pid;
    struct rlimit r1;
    struct sigaction sa;
    umask(0);
    if(getrlimit(RLIMIT_NOFILE,&r1)<0)
      err_quit("%s:can't get file limit",cmd);

    if((pid=fork())<0)
      err_quit("%s:can't fork",cmd);
    else if(pid!=0)
      exit(0);
    setsid();

    sa.sa_handler=SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags=0;
    if(sigaction(SIGHUP,&sa,NULL)<0)
      err_quit("%s:can't ignore SIGHUP",cmd);
    if((pid=fork())<0)
      err_quit("%s:can't fork",cmd);
    else if(pid!=0)
      exit(0);
    if(chdir("/")<0)
      err_quit("%s:can't change directory to /",cmd);

    if(r1.rlim_max==RLIM_INFINITY)
      r1.rlim_max=1024;
    for(i=0;i<r1.rlim_max;i++)
      close(i);
    fd0=open("/dev/null",O_RDWR);
    fd1=dup(0);
    fd2=dup(0);

    openlog(cmd,LOG_CONS,LOG_DAEMON);
    if(fd0!=0||fd1!=1||fd2!=2){
        syslog(LOG_ERR,"unexpected file descriptors %d %d %d",fd0,fd1,fd2);
        exit(1);
    }
}
int main()
{
    const char *cmd="main";
    daemonize(cmd);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/GUI1259802368/article/details/81407068