简 述: 前面几篇,刚写过了父子进程的实例。这里写一个守护进程 的例子,从了解到运用 setsid() ;守护进程也就是脱离于终端,不需要和用户交流的,不受注销影响的后台程序(可理解为 win 中的服务 )。

[TOC]


编程环境:

  💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

  💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


守护进程的特点:

  • 后台服务程序
  • 独立(是脱离于)控制终端的,用户不需要和终端交互
  • 周期性的执行某任务
  • 不受用户登录注销的影响
  • 一般采用 d 结尾的名字(服务)

进程组 - 多个进程:

  • 进程组的组长?
    • 组长是是组里的第一个进程
    • 进程组的 ID == 进程组的组长的 ID

会话 - 多个进程组:

进程组是由多个进程组成,而会话是由多个进程组组成成。

  • 创建一个会话注意的事项:
    • 不能是组长进程
    • 创建会话的进程成为新进程组的组长
    • 有些 Linux 版本需要 root 权限执行此操作(Ubuntu 不需要)
    • 创建出的新会话会丢弃原有的控制终端
    • 一般步骤:先 fork(),父亲死,儿子创建绘画操作(setsid())
  • 获取进程所属的会话 ID:
    • pid_t getsid(pid_t pid);
  • 创建一个会话:
    • pid_t setsid(void);

创建守护进程模型:

下面列出创建一个标准的进程守护模型操作流程:

  1. fork() 进程,父进程退出 (必须)
  2. 子进程创建新的会话 (必须)
    • 使用 setsid()
  3. 改变当前工作目录 chdir (可选)
    • 比如,U 盘插在笔记本,运行 U 盘文件夹里面的可执行程序,然后拔掉,会有一些影响
  4. 重设文件掩码 (可选)
    • 子进程会继承父进程的掩码
    • 增加子进程程序操作的灵活性
    • umask(0)
  5. 关闭文件描述符 (可选)
    • 节约资源,关闭此进程的 PCB 的文件描述符表中 0、1、2 三个,因为预警不需要和终端交互,故可关闭
  6. 执行核心工作 (必须)
    • 你想让该守护进程干的事情

写一个例子:

对上面的函数使用,写一个例子:写一个守护进程,每隔 2s 获取一次系统时间,写入到文本文件中。

  • 代码示例

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/stat.h>
    #include <time.h>
    
    void func(int no); //用作回调函数,给 signal() 调用
    
    int main(int argc, char *argv[])
    {
        pid_t pid = fork();
    
        if (pid > 0) {
            _exit(1); //父进程退出
        } else if (pid == 0) {
            setsid();  //子进程创建为会话
            chdir("/Users/muli/Desktop/");  //改变进程的工作目录
            umask(0);    //重设置文件掩码
    
            close(STDIN_FILENO);   //关闭和终端的联系,文件描述符
            close(STDOUT_FILENO);
            close(STDERR_FILENO);
    
            __sigaction_u sigactu;  //设置信号捕捉
            sigactu.__sa_handler = func;
    
            struct sigaction act;
            act.sa_flags = 0;
            act.__sigaction_u = sigactu;
            sigaddset(&act.sa_mask, SIGQUIT);
    
            itimerval time;  //设置周期性的定时器
            time.it_value.tv_sec = 2;
            time.it_value.tv_usec = 0;
            time.it_interval.tv_sec = 1;
            time.it_interval.tv_usec = 0;
    
            sigaction(SIGVTALRM, &act, NULL);          //捕捉(下面一行发射的)信号,在 fun() 里面实现
            setitimer(ITIMER_VIRTUAL, &time, NULL);    //使用定时器,发射信号
    
            while (true); //保持该守护进程不死亡
        }
    
        return 0;
    }
    
    void func(int no) 
    {
        time_t currtime;  //获取系统当前时间,传出参数
        time(&currtime);
        char* ptr = ctime(&currtime);
    
        int fd = open("/Users/muli/Desktop/setsid.txt", O_CREAT | O_WRONLY | O_APPEND, 0777);  //写入磁盘文件
        write(fd, ptr, sizeof(ptr) + 1);
        close(fd);
    }
  • 代码分析:

    这里 sigaction(SIGVTALRM, &act, NULL); 和代码 setitimer(ITIMER_VIRTUAL, &time, NULL); 这一行,成为互相对应的关系。

    ,使用了周期定时器 setitimer,第一个参数填了三个枚举之一,但是对应的 sigaction 信号捕捉中,就要填写对应的参数(见 man 手册);注意,要先有捕捉函数,后实现信号发射函数,避免出现信号已经发射了,但是捕捉函数还没有实现或执行。

  • 运行效果:


文件掩码是什么?

  • linux中的 umask 函数主要用于:在创建新文件或目录时 屏蔽掉新文件或目录不应有的访问允许权限。
  • 文件的访问允许权限共有9种,分别是:rwxrwxrwx
  • 它们分别代表:用户读 用户写 用户执行 组读 组写 组执行 其它读 其它写 其它执行
  • 更多是使用 umask 命令或者 umask() 函数老表示,比如: 777;

下载地址:

16_daemon

欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。