守护进程构造原理及实现

守护进程详解

    • 守护进程的作用
    • 守护进程实例
    • 守护进程特性及构造原理
      • 守护进程特性
      • 进程、进程组、控制终端登陆会话之间的关系
      • 守护进程的构造原理(APUE中的创建步骤,实际操作中有些不同)
        • 调用umask将文件模式创建屏蔽字设置为一个已知值(通常为0):
        • 调用fork,然后是父进程exit:
        • 调用setsid创建一个新会话:
        • 改变当前工作目录:
        • 关闭不再需要的文件描述符:
        • 某些守护继承打开/dev/null使其具有文件描述符0、1和2:
    • 守护进程的代码实现

守护进程的作用

守护进程(daemon):在后台运行的一种特殊进程,没有控制终端,周期性的运行某项任务。在Linux上很多服务都是以守护进程的形式运行在系统上,如inetd进程侦听系统网络接口,定时任务进程cron。

守护进程实例

在CentOS系统上输入ps命令,查看所有的进程情况(图中列出部分):
守护进程构造原理及实现_第1张图片
注意事项:

  1. 系统创建守护进程由方括号阔起来:由系统创建的守护进程名由方括号扩起来,[Kthreadd]进程是所有系统守护进程的父进程,Kthreadd进程的进程号为2,可见其他守护进程的父进程为2;
  2. 守护进程都是与终端分离的,因此其终端(TTY)那一栏显示为“?”,大多数守护进程都是以超级用户root特权运行;
  3. 进程号为1的进程为init进程,父进程ID为0的进程通常是内核进程;

守护进程特性及构造原理

守护进程特性

  1. 守护进程要想专注的在系统上周期性执行任务,必须与运行前环境隔离开来。包括:控制终端、父进程的文件描述符、工作目录、文件掩码、会话和进程组等,这些环境通常是由守护进程从其父进程继承而来的。
  2. 守护进程可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是 shell)执行。

进程、进程组、控制终端登陆会话之间的关系

守护进程构造原理及实现_第2张图片

  1. 进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID);
  2. 登陆会话可以包含多个进程组,这些进程组共享一个控制终端(这个控制终端通常是创建进程的登陆终端);

守护进程的构造原理(APUE中的创建步骤,实际操作中有些不同)

调用umask将文件模式创建屏蔽字设置为一个已知值(通常为0):

  • 由继承而来的文件模式创建屏蔽字可能会被设置为拒绝某种权限;
  • 如果守护进程要创建文件,那么它可能要设置特定的权限;

调用fork,然后是父进程exit:

  • 如果守护进程是作为一条简单的shell命令启动的,那么父进程种植会让shell认为这条命令已经执行完毕;
  • 虽然子进程继承了父进程的进程组ID,但获得了一个新的进程ID,这就保证了紫禁城不是一个进程组的组长ID;

调用setsid创建一个新会话:

  • 与原来登陆的会话和进程组脱离,成为新会话的首进程;
  • 成为新进程组的组长进程;
  • 没有控制终端;

改变当前工作目录:

  • 因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录一直是存在的,所以如果守护进程的当前工作目录一直挂在文件系统中,那么该文件系统就不能被卸载;

关闭不再需要的文件描述符:

  • 守护进程不再持有从父进程继承来的任何文件描述符(父进程可能是shell进程或某个其他进程)
  • 可以使用getrlimit来判断最高文件描述符值,并关闭直到该值的所有描述符;

某些守护继承打开/dev/null使其具有文件描述符0、1和2:

  • 任何一个试图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果,因为守护进程并不与终端设备相关联;
  • 守护进程是后台运行的,所以登陆会话的种植并不影响守护进程;

守护进程的代码实现

daemon.c守护进程创建函数

#include 
#include 
#include 
#include 
#include 
#include 

void init_daemon(void)

{
    int pid;
    int i;
    //结束父进程,摆脱会话组长身份。
    if(pid=fork())
        exit(0);
    else if(pid< 0)
        exit(1);
    //第一子进程,后台继续执行
    setsid();
    //第一子进程成为新的会话组长和进程组长,并与控制终端分离
    //第二子进程,结束第一子进程
    if(pid=fork())
        exit(0);
    else if(pid< 0)
        exit(1);//fork失败,退出
    //第二子进程不再是会话组长,也就不会请求打开控制终端。
    for(i=0;i< NOFILE;++i)//关闭所有打开的文件描述符
        close(i);
    //改变工作目录到/tmp
    chdir("/tmp");
    //重设 文件创建掩模
    umask(0);
    return;
}

test.c测试函数

#include 
#include 

//守护进程初始化函数
void init_daemon(void);
main()
{
    FILE *fp;
    time_t t;
    init_daemon();
    //每隔10s向test.log报告运行状态
    while(1)
    {
        //延时10s
        sleep(10);
        if((fp=fopen("/usr/wefang/test.log","a")) >=0)
        {
            t=time(0);
            fprintf(fp,"Test daemon at %s",asctime(localtime(&t)) );
            fclose(fp);
        }
    }
}

将两个函数编译为可执行文件daemon,执行编译语句gcc test.c daemon.c -o daemon,编译成功,没问题直接运行;
在这里插入图片描述
查看daemon进程状态,守护进程daemon正在运行:
守护进程构造原理及实现_第3张图片
查看日志文件内容,创建的守护进程每格十秒更新一次test.log日志。
守护进程构造原理及实现_第4张图片
说明:在系统调用库中有一个库函数可以直接使一个进程变成守护进程,
#include
int daemon(int nochdir, int noclose);

你可能感兴趣的:(C++)