内容概要

  • 一、守护进程概述
  • 二、守护进程创建
    • 2.1、创建子进程,父进程退出
    • 2.2、在子进程中创建新会话
      • 2.2.1、进程组和会话期
      • 2.2.2、setsid()函数说明
    • 2.3、改变当前工作目录
    • 2.4、重设文件权限掩码
    • 2.5、关闭不需要的文件描述符
    • 2.6、某些特殊的守护进程打开/dev/null
  • 三、守护进程代码示例

一、守护进程概述

  守护进程是一个生存期较长的进程,他常常在系统引导装入是启动,仅仅在系统关闭的才终止。也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。 Linux 中很多系统服务都是通过守护进程实现的。

  在Linux中,可以根据指令 ps 命令打印进程的状态,在终端输入指令ps -ajx 可以查看当前进程的状态,如下所示(已删除部分内容)。

ubuntu@songshuai:~$ ps -ajx PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND0     1     1     1 ?           -1 Ss       0   0:03 /sbin/init splash0     2     0     0 ?           -1 S        0   0:00 [kthreadd]2     3     0     0 ?           -1 I        0   0:00 [kworker/0:0]2     4     0     0 ?           -1 I<       0   0:00 [kworker/0:0H]2     5     0     0 ?           -1 I        0   0:00 [kworker/u128:0]2     6     0     0 ?           -1 I<       0   0:00 [mm_percpu_wq]2     7     0     0 ?           -1 S        0   0:00 [ksoftirqd/0]2     8     0     0 ?           -1 I        0   0:00 [rcu_sched]2     9     0     0 ?           -1 I        0   0:00 [rcu_bh]1  1215  1215  1215 ?           -1 SLsl     0   0:00 /usr/sbin/lightdm1  1782  1782  1782 tty1      1782 Ss+      0   0:00 /sbin/agetty --noclear tty1 linux1763  1864  1863  1863 ?           -1 S     1000   0:00 upstart-udev-bridge --daemon --user1763  1874  1874  1874 ?           -1 Rs    1000   0:00 dbus-daemon --fork --session --address=unix:abstract=/tmp/dbus-den4cotw41763  1886  1886  1886 ?           -1 Ss    1000   0:00 /usr/lib/x86_64-linux-gnu/hud/window-stack-bridge1763  1911  1910  1910 ?           -1 S     1000   0:00 upstart-dbus-bridge --daemon --system --user --bus-name system1763  1913  1912  1912 ?           -1 S     1000   0:00 upstart-dbus-bridge --daemon --session --user --bus-name session1763  1917  1916  1916 ?           -1 S     1000   0:00 upstart-file-bridge --daemon --user1763  1919  1918  1918 ?           -1 Sl    1000   0:04 /usr/bin/fcitx1763  1943  1943  1943 ?           -1 Ssl   1000   0:00 /usr/lib/x86_64-linux-gnu/bamf/bamfdaemon2589  2594  2594  2594 pts/2     2677 Ss    1000   0:00 bash2594  2677  2677  2594 pts/2     2677 R+    1000   0:00 ps -ajx
ubuntu@songshuai:~$

  在 ps 输出示例中,内核守护进程的名字出现在方括弧中,改版本的 Linux 使用一个名为 kthreadd 的特殊内核进程来创建其他内核进程,所以 kthreadd 表现为其他内核进程的父进程。

  进程 init 的进程通常为 1,他是一个系统守护进程,除了其他工作外,主要负责各个运行层次特定的系统服务。

  在Linux 中,每一个系统与用户进行交流的界面称为 终端。每一个从此终端开始运行的进程都会依附于该终端,这个终端称为这些进程的 控制终端。当控制终端关闭时,相应的进程都会自动结束。但是守护进程却能够突破这种限制,不受终端关闭的影响。反之,如果希望某个进程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个 守护进程

二、守护进程创建

  创建守护进程,需要遵循特定的流程,以防产生不必要的交互过程。下面就分几个步骤来创建一个简单的守护进程。

2.1、创建子进程,父进程退出

  由于守护进程是脱离控制终端的,因此完成第一步后子进程变成后台进程。之后的所有工作都在子进程中完成。而用户通过 shell 可以执行其他的命令,从而在形式上做到了与控制终端的脱离。

  另外一方面虽然子进程继承了父进程的进程组 ID,但获得了一个新的进程 ID,这样保证了子进程不是一个进程组的组长进程。这也是下面要进程的第三步的先决条件。

说明:
  由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程(孤儿进程可以查看博文:Linux – 多进程编程之 - 基础实现、孤儿进程)。在 Linux 中,每当系统发现一个孤儿进程,就会自动由 1号进程(也就是 init 进程)收养它,这样,原先的子进程就会变成 init 进程的子进程了。

2.2、在子进程中创建新会话

  这个步骤是创建守护进程中最重要的一步,在这里使用的函数是 setsid()

2.2.1、进程组和会话期

  这里先要明确两个概念:进程组和会话期。

进程组
  进程组是一个或多个进程的集合。进程组由进程组 ID 来唯一标识。除了进程号( PID )之外,进程组 ID 也是一个进程的必备属性。
每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID ,且进程组 ID 不会因组长进程的退出而受到影响。

会话期
  会话组是一个或多个进程组的集合。通常一个会话开始于用户登录,终止于用户退出;或者说开始于终端打开,结束于终端关闭。会话期的第一个进程称为会话组长。在此期间该用户运行的所有进程都属于这个会话期。

  进程组和会话期之间的关系如图2.1所示。

图2.1 进程组和会话期之间的关系图

2.2.2、setsid()函数说明

1、setsid()函数原型

  setsid()函数原型如下所示(使用指令 man 2 setsid 即可显示如下代码)。

#include <sys/types.h>
#include <unistd.h>pid_t setsid(void);

功能:
  如果调用进程不是进程组长,则 setsid() 将创建一个新会话。调用进程将成为新会话的会话组组长(即,其会话 ID 与其进程 ID 相同)。同时调用进程也将成为会话中新进程组的进程组组长(即,其进程组 ID 与其进程 ID 相同)。调用进程将是新进程组和新会话中的唯一进程。
参数:无
返回:
  成功:返回调用进程的(新)会话ID
  失败:返回(pid_t)-1,并设置 errno

2、setsid()函数作用

  上面已经提到,setsid() 函数用于创建一个新的会话,并担任该会话的组长,所以调用 setsid() 有下面 3 个作用。

1、让进程摆脱原会话的控制
2、让进程摆脱原进程组的控制
3、让进程摆脱原控制终端的控制

  由于在调用 fork() 函数时,子进程 全盘复制 了父进程的会话期进程组和控制终端等。所以虽然父进程退出了,但原先的 会话期进程组控制终端等并没有改变,因此,子进程并不是真正意义上的独立,而 setsid() 函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

2.3、改变当前工作目录

  使用 fork() 函数创建的子进程是完全继承了父进程的当前工作目录,所以从父进程继承过来的当前工作目录可能是一个挂载的文件系统中。因为守护进程有一般情况是在系统在引导之前是一直从在的,所以在进程工作的过程中当前目录所在的文件系统(比如“/mnt/usb” 等)是不能卸载的。

  因此,一般的做法是将根目录作为守护进程的当前工作目录,这样就可以避免上述问题。当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如“/tmp”。

  改变工作目录的函数是 chdir() 函数,其函数原型如下所示。

#include <unistd.h>int chdir(const char *path);

功能:
  改变调用者的工作目录
参数:
  path:新的工作目录的路径
返回:
  成功:返回0
  失败:返回-1,同时设置errno

2.4、重设文件权限掩码

  文件权限掩码(通常用八进制表示)的作用是屏蔽文件权限中的对应位。例如,如果文件权限拖码是050,它表示屏蔽了文件组拥有者的可读与可执行权限。由于使用 fork() 函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了一定的影响。如果守护进程需要创建文件,那么他可能需要设置特定的权限。因此,把文件权限掩码设置为一个已知的值(通常设置为0),可以增强该守护进程的灵活性。

  设置文件权限掩码的函数是 umask()。在这里,通常的使用方法为 umask(0)。其函数原型如下所示。

#include <sys/types.h>
#include <sys/stat.h>mode_t umask(mode_t mask);

功能:
  umask() 将调用进程的文件模式创建掩码( umask)设置为 mask & 0777(即仅使用掩码的文件权限位)。
参数:
  mask:要设置的权限值,用八进制表示
返回:
  此系统调用始终成功,并返回掩码的上一个值。

2.5、关闭不需要的文件描述符

  同样地,用 fork() 函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程访问,但它们一样占用系统资源,而且还可能导致所在的文件系统无法被卸载。

  特别是守护进程和终端无关,所以指向终端设备的标准输入、标准输出和标准错误流等已经不再使用,应当被关闭。

  可以使用函数 getdtablesize() 来获取当前金成文件描述符表的大小,并通过使用 close() 来依次关闭。

函数原型如下。

#include <unistd.h>
int getdtablesize(void);

getdtablesize()函数返回进程可以打开的最大文件数,比文件描述符的最大可能值多一个。

#include <unistd.h>
int close(int fd);

close() 用于关闭文件描述符,关闭成功则返回 0,失败则返回 -1 并设置 errno

  所以关闭文件描述符的代码可以如下写法。

int num = getdtablesize(); // 获取当前进程文件描述符表大小for (int i = 0; i < num; i++)
{close (i);
}

2.6、某些特殊的守护进程打开/dev/null

  某些特殊的守护进程打开/dev/null,使其具有文件描述符0、1、2,这样任何一个试图读标准输入、标准输出、标准出错时都不会有任何效果,这样符合了守护进程不与终端设备相关联的属性。

  综上所述,一个守护进程就可以创建成功了,所以创建守护进程的流程可以总结为如图2.2所示。

图2.2 创建守护进程流程图

三、守护进程代码示例

  用下面的一个简单的程序,用来示例守护进程的完整的创建过程。守护进程的主要工作则是每隔一定时间向日志文件“/daemon.log”文件中写入内容。

#include <fcntl.h>     // for O_APPEND ..
#include <stdio.h>     // for perror ..
#include <stdlib.h>    // for exit ..
#include <string.h>    // for strlen
#include <sys/stat.h>  // for umask
#include <sys/types.h> // for setsid
#include <unistd.h>    // for setsidint main(int argc, const char *argv[])
{pid_t pid = 0;int i = 0;char *filePath = "daemon.log";/* 第一步:创建子进程,父进程退出 */pid = fork();if (pid == -1) /* fork出错 */{perror("fork error");exit(EXIT_FAILURE);}else if (pid == 0) /* 子进程 */{pid_t temp_pid = 0;/* 周期计数的变量 */int cycleCnt = 0;/* 第二步:创建新的会话 */temp_pid = setsid();/* 第三部:改变当前的工作路径*/chdir("/");/* 第四步:改变进程本身的umask */umask(0);/* 第五步:关闭所有可能已打开的文件描述符 */int num = getdtablesize(); /* 获取当前进程文件描述符表大小 */for (i = 0; i < num; i++){close(i);}/* 至此,守护进程创建完成,以下正式开始守护进程的工作 *//* 1、打开要操作的文件 */int fd = open(filePath, O_RDWR | O_CREAT | O_APPEND, 0600);if (fd == -1) /* open操作失败 */{perror("open error");exit(EXIT_FAILURE);}/* 2、在文件中循环写入测试数据 */while (1){/* 写入内容的缓冲区定义 */char writeBuff[128] = {0};/* 周期运行计数自加 */cycleCnt++;/* 写入的数据拼接 */sprintf(writeBuff, "I'm Daemon Process, Running %d\n", cycleCnt);/* 写入到文件中 */write(fd, writeBuff, strlen(writeBuff));/* 休眠片刻 */sleep(1);}}else /* 父进程 */{/* 父进程退出 */exit(EXIT_SUCCESS);}return 0;
}

  编译并运行上述程序,运行效果及进程状态如图3.1所示。

图3.1 运行效果及进程状态效果图

  需要注意的是,因为守护进程的工作目录已经修改为“\”,并且程序中需要在根目录中创建文件并写入,所以需要root权限,运行程序使用 sudo 命令(下同)。

  此时查看程序记录的文件,使用指令sudo cat daemon.log 即可显示,文件内容如图3.2所示。

图3.2 文件内容显示效果图

  至此为止,守护进程相关的主要知识基本上总结的差不多了。基于守护进程的特征,那么在记录守护进程的异常处理方面也需要特殊的处理,那么将会下一篇进程守护进程的出错的处理。

  好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

上一篇:Linux – 多进程编程之 - 僵尸进程
下一篇:Linux – 多进程编程之 - 守护进程的出错处理

Linux -- 多进程编程之 - 守护进程相关推荐

  1. 【Linux系统编程】守护进程、线程

    ------------->[Linux系统编程/网络编程](学习目录汇总) <-------------- 目录 1.守护进程 1.1 进程组 1.2 会话 1.3 setsid()函数 ...

  2. Linux系统编程之--守护进程的创建和详解【转】

    本文转载自:http://www.cnblogs.com/mickole/p/3188321.html 一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终 ...

  3. Linux多进程编程之 孤儿进程僵尸进程+wait函数

    我们可否想过一个问题:使用fork()函数创建子进程,因为父进程和子进程的执行顺序是随机的 当父进程已经结束了,子进程还会继续存在并正常执行吗? 我们先看这个例子: guer1.c #include& ...

  4. 【Linux系统编程】特殊进程之守护进程

    00. 目录 文章目录 00. 目录 01. 守护进程概述 02. 守护进程查看方法 03. 编写守护进程的步骤 04. 守护进程代码 05. 附录 01. 守护进程概述 守护进程(Daemon Pr ...

  5. python并发编程之semaphore(信号量)_python 之 并发编程(守护进程、互斥锁、IPC通信机制)...

    9.5 守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就立即终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic process ...

  6. LINUX 多进程编程 C语言实例

    LINUX多进程编程 简单实例 1.ps与top命令 查看进程状态 2.系统调用ping,并执行 #include <stdio.h> #include <string.h> ...

  7. 【Linux系统编程】特殊进程之孤儿进程

    00. 目录 文章目录 00. 目录 01. 孤儿进程概述 02. 孤儿进程代码 03. 附录 01. 孤儿进程概述 父进程运行结束,但子进程还在运行的子进程就称为孤儿进程(Orphan Proces ...

  8. 【Linux系统编程】特殊进程之僵尸进程

    00. 目录 文章目录 00. 目录 01. 僵尸进程概述 02. 僵尸进程案例 03. 避免僵尸进程 04. 附录 01. 僵尸进程概述 进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵 ...

  9. 嵌入式linux系统下简单守护进程(daemon)的编写

    最近公司项目需要,需要在我们的嵌入式linux设备中创建一个守护进程,用于保护系统中的主进程,防止某些不可预期的意外导致主进程异常结束后,系统完全宕机没有任何反应,破坏用户体验感.但是,查阅诸多资料之 ...

最新文章

  1. Javascript的原型链、instanceof与typeof
  2. vue一级分类和耳机分类_?1K411023 岩土分类与不良土质处理方法·2020年一级市政建造师...
  3. Python黑科技:在家远程遥控公司电脑,python+微信一键连接!
  4. 浏览器的同源策略与跨域问题的解决方案
  5. python安装cvxopt_python如何安装cvxopt
  6. 非常好用的离线地图APP
  7. 盘点丨12款数据库建模工具特点,总有一款适合你!
  8. VITS 语音合成完全端到端TTS的里程碑
  9. VMWare、Ubuntu、ROS安装参考汇总
  10. Mac Pro 开不了机
  11. eclips开发工具的使用
  12. 基于Node.js的3DTiles三维倾斜摄影模型爬虫
  13. php期末大作业可以做什么,期末要交一个基于php连入数据的大作业
  14. 图像分割:LR-ASPP模型介绍
  15. 数据相关的在职研究生_西南政法大学拟清理20名博士研究生的学籍!
  16. 程序员圈“内卷”这么严重,如何才能更进一步,实现个人价值?
  17. 使用mysql_upgrade升级mysql5.1至5.6的数据库升级实施方案
  18. sqlDbx连接oracle64位
  19. WdatePicker使用方法
  20. python中的方法

热门文章

  1. android10.9 华为,福利升级:华为这4款手机将率先支持安卓 9.0系统更新
  2. python 获取时间
  3. Linux源码 哪个版本,有没有办法将不同版本的linux源码合并
  4. ORACLE的数据库审计 audit
  5. Android 仿微信朋友圈9宫格图片展示多选图片
  6. 青之文学--获得再逝去,岂不是残酷?
  7. 注意!又有一种闲鱼新骗局近期开始传播
  8. apk文件上传到服务器,把apk文件上传到云服务器
  9. Android M AudioPolicy 分析
  10. 鲁大师光线追踪测试上线:你的显卡可以给“光追”跑分了!