关于守护进程

  • 为什么要设置守护进程?
  • 守护进程———>就是脱离用户终端的后台进程
  • 当我们关闭终端时,该进程也不会退出。举个例子:比如我们远程登录一个linux系统,通过终端运行服务器程序,此时我们关闭终端退出来的时候,我们希望该服务器程序依然运行。此时就需要使该服务器进程是守护进程。
  • 他是Linux的一种长期运行的后台服务进程。我们常见的httpd,named,sshd等服务都是以守护进程Daemon方式运行的,通常服务名称以字母d结尾。
  • 与普通进程相比的特点有:
  • 无需终端控制(不需要与用户交互)
  • 在后台运行
  • 生命周期比较长,一般是随系统启动和关闭
  • 守护进程的实现步骤:
  • 1.让进程在后台执行。
  • 方法是:调用fork产生一个子进程,让父进程退出,子进程就成了孤儿进程 ,使子进程成为后天进程。
  • 2.在子进程中创建新的会话,脱离控制终端
  • 刚开始,一个会话组id对应一个终端,会话组里包含前台进程组和后台进程组。当关闭终端时,会话组里的所有进程都关闭。所以子进程要想脱离终端时,使用setsid()就可创建新的会话组,并且担任该会话组的组长,独立出终端。
  • 3.禁止进程重新打开控制终端
  • 此时,进程已经成为一个无终端的会话组长,但他还可以重新申请打开一个终端,为了避免这样,可以使进程不再是会话组长来实现,在一次通过fork创建新的子进程,是调用fork的进程退出
  • 4.关闭不再需要的文件描述符
  • 创建的子进程从父进程继承打开的文件描述。如果不关闭,会有问题。先得到最高文件描述符值,然后用一个循环程序,关闭0到最高文件描述符值的所有文件描述符。
  • 5.改变当前工作目录为根目录
  • 子进程会继承父进程的工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。所以需要使用chdir改变子进程的工作目录为根目录。
  • 4.重设文件权限掩码
    文件权限掩码是屏蔽掉文件权限中的对应位。由于使用fork()函数新创建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带了很多的麻烦(比如父进程中的文件没有执行文件的权限,然而在子进程中希望执行相应的文件这个时候就会出问题)。因此在子进程中要把文件的权限掩码设置成为0,即在此时有最大的权限,这样可以大大增强该守护进程的灵活性。设置的方法是:umask(0)
  • 6.守护进程的退出,信号注册
    通过kill命令,使守护进程退出,通常需要signal注册信号处理函数,进行退出处理。
  • 代码示例:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/param.h>
#include<sys/stat.h>
#include<time.h>
#include<syslog.h>
int init_daemon(void)
{
    int pid;
    int i;
    /*忽略终端I/O信号,STOP信号*/
    signal(SIGTTOU,SIG_IGN);
    signal(SIGTTIN,SIG_IGN);
    signal(SIGTSTP,SIG_IGN);
    signal(SIGHUP,SIG_IGN);
    pid=fork();
    if(pid>0)
        exit(0);//结束父进程,使进程成为后台进程
    else if(pid<0)
        return -1;
    /*建立一个新的进程组,使子进程成为这个进程的首进程,使该进程脱离所有终端*/
    setsid();
    /*再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端*/
    pid=fork();
    if(pid>0)
        exit(0);
    else if(pid<0)
        return -1;
    /*关闭所有从父进程继承的不再需要的文件描述符*/
    for(i=0;i<NOFILE;close(i++))
        ;
    /*改变工作目录,使进程不与任何文件联系*/
    chdir("/");
    /*将文件屏蔽字设置为0*/
    umask(0);
    /*忽略SIGCHLD信号*/
    signal(SIGCHLD,SIG_IGN);
    return 0;
}
int main(void)
{
    time_t now;
    init_daemon();
    syslog(LOG_USER|LOG_INFO,"测试守护进程!\n");
    while(1)
    {
        sleep(8);
        time(&now);
        syslog(LOG_USER|LOG_INFO,"系统时间:\t%s\t\t\n",ctime(&now));
    }
}