73-守护进程(概念)

1. 守护进程的概念

在我们之前写的大多数程序中,如果你关闭控制终端,就很可能会导致在该控制终端中运行的进程也退出。

控制终端关闭时,该控制终端符合下面条件的进程会收到 SIGHUP 信号:

  • 前台进程组的所有进程
  • 某个后台进程组中存在停止的进程(ps ajx 命令中,进程状态显示为 T)。控制终端关闭,导致该后台进程组成为孤儿进程组,则该孤儿进程组中的所有进程都收到 SIGHUP 信号。

所以,并不是所有情况下控制终端中的进程都会收到 SIGHUP 信号。

有时候,我们希望进程不和任何控制终端挂钩,即使你关闭终端对它也没有任何影响。另外我们不希望这种进程向终端读写数据(需要关闭标准输入、标准输出和标准错误),它会一直在后台运行着,像这种进程,称之为守护进程,或者叫精灵进程(daemon process).

图 1 中展示的都是守护进程。COMMAND 一栏中,带方括号的是内核级守护进程。我们看到 1 号 init 进程也是守护进程。守护进程都没有控制终端,比如 TTY 一栏全部是问号,既然没有控制终端,那也就没有前台进程组和后台进程组的概念了,所以 TPGID 一栏都是 -1.


这里写图片描述
图1 守护进程

2. 创建守护进程

本篇先不打算讲解守护进程的编写步骤,我们先使用系统提供的函数 daemon 编写一个守护进程,它在后台工作,并不断向test.log文件写入数据。

daemon 的原型如下:

int daemon(int nochdir, int noclose);

参数 nochdir 如果为 0,表示将当前工作目录切换到根目录 “/”;否则当前目录不变。

参数 noclose 如果为 0,表示重定向标准输入、标准输出和标准错误到文件 /dev/null;否则这些文件描述符不变。

2.1 程序清单

  • 代码
// daemon.c
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int  main() {
  // 先打印进程的 pid 等信息
  printf("pid: %d, ppid: %d, sid: %d\n", getpid(), getppid(), getsid(getpid()));

  // 将进程设置为守护进程
  daemon(0, 0); 

  int fd; 
  char buf[256];

  // 守护进程向标准输出打印信息,并向 test.log 写信息
  while(1) {
    // 实际上,这一行永远不会打印到屏幕,因为守护进程没有控制终端,标准输出也被重定向到了 /dev/null
    printf("pid: %d, ppid: %d, sid: %d\n", getpid(), getppid(), getsid(getpid()));
    // 因为 daemon 函数改变了当前工作目录,所以这里使用全路径定位到当前目录。
    fd = open("/home/allen/apue/relationship/daemon/test.log", O_WRONLY | O_APPEND | O_CREAT, 0664);
    if (fd > 0) {
      sprintf(buf, "pid: %d, ppid: %d, sid: %d\n", getpid(), getppid(), getsid(getpid()));
      write(fd, buf, strlen(buf));
    }   

    sleep(3);
  }
  return 0;
}
  • 编译和运行
$ gcc daemon.c -o daemon
$ ./daemon

2.2 结果分析

当我们运行 ./daemon 后,发现屏幕只输出一行:


这里写图片描述
图2

使用 ls 命令查看当前文件夹下,发现生成了 test.log 文件。

查看 test.log 文件:


这里写图片描述
图3 test.log 文件

使用 ps ajx 查看进程信息:


这里写图片描述
图4 ps ajx 查看进程信息

值得注意的是,我们发现图 2 和图 3 中的信息是一致的,只是图 1 中的结果,貌似不怎么对?

图 1 中打印的进程 pid 明明是 6169,父进程是 3993,而且它也在会话 3993 中。可到了图 2 和图 3 中,情况貌似有变化,它的进程 id 号“变成”了 6170,父进程是 1,另外,它在会话 6170 中!

其实,这没有什么不对,如果你能结合我们前面学过的有关进程间关系所有的知识,你能想明白。

daemon 函数里头肯定是动了手脚,我们应该能想到,图 2 中的结果,实际上已经不是你启动的那个进程了,而是你启动的那个进程的子进程!而你启动的那个进程,已经运行结束,子进程被 1 号进程收养,成为了孤儿进程。另外,该子进程还创建了一个新会话!

到这里,我想即便你不使用系统提供的 daemon 函数,你也能够自己手工写一个类似这样的函数了。

只不过,除了 fork 子进程,创建新会话,你还应该关注一些其它的细节,这将在下一篇博文介绍。

3. 总结

  • 理解什么是守护进程
  • 学会使用 daemon 函数
  • 理解 daemon 函数做了什么工作

练习 1 :完成本文中的例子。
练习 2:不查阅资料,尝试自己编写一个 daemon 函数。
思考:守护进程一定不能向标准输入写数据吗?

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值