专栏首页嵌入式Linux系统开发教你写一个守护进程

教你写一个守护进程

守护进程(Daemon)也称为精灵进程,是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些事情的发生,主要表现为以下两个特点:

⚫ 长期运行。守护进程是一种生存期很长的一种进程,它们一般在系统启动时开始运行,除非强行终止,否则直到系统关机都会保持运行。与守护进程相比,普通进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但守护进程不受用户登录注销的影响,它们将会一直运行着、直到系统关机。

⚫ 与控制终端脱离。在 Linux 中,系统与用户交互的界面称为终端,每一个从终端开始运行的进程都会依附于这个终端,这是上一小节给大家介绍的控制终端,也就是会话的控制终端。当控制终端被关闭的时候,该会话就会退出,由控制终端运行的所有进程都会被终止,这使得普通进程都是和运行该进程的终端相绑定的;但守护进程能突破这种限制,它脱离终端并且在后台运行,脱离终端的目的是为了避免进程在运行的过程中的信息在终端显示并且进程也不会被任何终端所产生的信息所打断。

守护进程是一种很有用的进程。Linux 中大多数服务器就是用守护进程实现的,譬如,Internet 服务器 inetd、Web 服务器 httpd 等。同时,守护进程完成许多系统任务,譬如作业规划进程 crond 等。

守护进程 Daemon,通常简称为 d,一般进程名后面带有 d 就表示它是一个守护进程。守护进程与终端无任何关联,用户的登录与注销与守护进程无关、不受其影响,守护进程自成进程组、自成会话,即pid=gid=sid。通过命令"ps -ajx"查看系统所有的进程,如下所示:

TTY 一栏是问号?表示该进程没有控制终端,也就是守护进程,其中 COMMAND 一栏使用中括号[]括起来的表示内核线程,这些线程是在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用 k 开头的名字,表示 Kernel。

编写守护进程程序

  1. 创建子进程、终止父进程。父进程调用 fork()创建子进程,然后父进程使用 exit()退出,这样做实现了下面几点。第一,如果该守护进程是作为一条简单地 shell 命令启动,那么父进程终止会让 shell 认为这条命令已经执行完毕。第二,虽然子进程继承了父进程的进程组ID,但它有自己独立的进程ID,这保证了子进程不是一个进程组的组长进程,这是下面将要调用 setsid 函数的先决条件!
  2. 子进程调用 setsid 创建会话。setsid()函数创建新的会话,由于之前子进程并不是进程组的组长进程,所以调用 setsid()会使得子进程创建一个新的会话,子进程成为新会话的首领进程,同样也创建了新的进程组、子进程成为组长进程,此时创建的会话将没有控制终端。所以这里调用 setsid 有三个作用:让子进程摆脱原会话的控制、让子进程摆脱原进程组的控制和让子进程摆脱原控制终端的控制。在调用 fork 函数时,子进程继承了父进程的会话、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。setsid 函数能够使子进程完全独立出来,从而脱离所有其他进程的控制。
  3. 将工作目录更改为根目录。子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其 它目录来作为守护进程的工作目录。
  4. 重设文件权限掩码 umask。文件权限掩码 umask 用于对新建文件的权限位进行屏蔽,在 5.5.5 小节中有介绍。由于使用 fork 函数新建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩 码设置为 0,确保子进程有最大操作权限、这样可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask,通常的使用方法为 umask(0)。
  5. 关闭不再需要的文件描述符。子进程继承了父进程的所有文件描述符,这些被打开的文件可能永远不会被守护进程(此时守护进程指的就是子进程,父进程退出、子进程成为守护进程)读或写,但它们一样消耗系统资源,可能导致所在的文件系统无法卸载,所以必须关闭这些文件,这使得守护进程不再持有从其父进程继承过来的任何文件描述符。
  6. 将文件描述符号为 0、1、2 定位到/dev/null。将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null,这使得守护进程的输出无处显示、也无处从交互式用户那里接收输入。
  7. 忽略 SIGCHLD 信号。处理 SIGCHLD 信号不是必须的,但对于某些进程,特别是并发服务器进程往往是特别重要的,服务器进程在接收到客户端请求时会创建子进程去处理该请求,如果子进程结束之后,父进程没有去 wait 回收子进程,则子进程将成为僵尸进程;如果父进程 wait 等待子进程退出,将又会增加父进程的负担、也就是增加服务器的负担,影响服务器进程的并发性能,在 Linux 下,可以将 SIGCHLD 信号的处理方式设置为SIG_IGN,也就是忽略该信号,可让内核将僵尸进程转交给 init 进程去处理,这样既不会产生僵尸进程、又省去了服务器进程回收子进程所占用的时间。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
int main(void) {
   pid_t pid;
   int i;
   /* 创建子进程 */
   pid = fork();
   if (0 > pid) {
     perror("fork error");
     exit(-1);
   }
   else if (0 < pid)//父进程
     exit(0); //直接退出
   /*
   *子进程
   */
   /* 1.创建新的会话、脱离控制终端 */
   if (0 > setsid()) {
   perror("setsid error");
   exit(-1);
   }
   /* 2.设置当前工作目录为根目录 */
   if (0 > chdir("/")) {
     perror("chdir error");
     exit(-1);
   }
   /* 3.重设文件权限掩码 umask */
   umask(0);
   /* 4.关闭所有文件描述符 */
   for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
     close(i);
   /* 5.将文件描述符号为 0、1、2 定位到/dev/null */
   open("/dev/null", O_RDWR);
   dup(0);
   dup(0);
   /* 6.忽略 SIGCHLD 信号 */
   signal(SIGCHLD, SIG_IGN);
   /* 正式进入到守护进程 */
   for ( ; ; ) {
     sleep(1);
     puts("守护进程运行中......");
   }
   exit(0);
}

整个代码的编写都是根据上面的介绍来完成的。运行之后,没有任何打印信息输出,原因在于守护进程已经脱离了控制终端,它的打印信息并不会输出显示到终端,在代码中已经将标准输入、输出以及错误重定位到了/dev/null,/dev/null 是一个黑洞文件,自 然是看不到输出信息。

守护进程可以通过终端命令行启动,但通常它们是由系统初始化脚本进行启动,譬如/etc/rc*或 /etc/init.d/*等。

·················· END ··················

本文分享自微信公众号 - 嵌入式Linux系统开发(Jason_Linux_),作者:Jasonangel

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • PHP 编写守护进程

    ITer.996
  • php如何编写守护进程

    守护进程是指在后台运行的程序,用于执行一些特定的任务,比如我们常见的apache、nginx、mysql等等,就是启动了守护进程,接收客户端的连接并处理相应的请...

    北溟有鱼QAQ
  • 教你写一个云上Hello world小程序

    小程序上线一月有余,有不少创业团队如朝夕日历、小睡眠、小密圈等工具性的应用享受了这一波产品红利,收获了数十万新用户。笔者近期接触了不少从事后端开发的Java、C...

    贺嘉
  • 教你写一个迷你版的Tomcat

    Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打交道。这是一只神奇的猫,今天让我来抽象你,实现你!

    Bug开发工程师
  • 教你写一个迷你版的Tomcat~

    Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打交道。这是一只神奇的猫,今天让我来抽象你,实现你!

    田维常
  • 手把手教你写一个AST

    AST 解析器工作中经常用到,Vue.js 中的 VNode 就是如此! 其实如果有需要将 非结构化数据转 换成 结构化对象用 来分析、处理、渲染的场景,我们都...

    WecTeam
  • 编码之道(一):程序员的"圣经"

    程序员是个群体,当我们说一个群体,一定意味着它有一些共通点,不然不能称之为群体。而每一个群体必然有一个大家都认同的价值观,否则不能形成群体。

    御剑
  • 一文教你如何进行SCI写作

    在学习了一系列的生信分析系列后,最近慢慢有个感悟。生信分析固然重要,但是或多或少生信分析更多是一个工具一个媒介去探索生物学中的问题。往往很多时候,虽然你把大部分...

    生信菜鸟团
  • 一步步教你编写不可维护的 PHP 代码

    随着失业率越来越高,很多人意识到保全自己的工作是多么的重要。那么,什么是保住自己工作,并让自己无可替代的好方法呢?一个很简单的事实是只要你的代码没有人能够维护,...

    猿哥
  • 手把手教你写一个composer包

    Composer 是一个命令行工具,它的作用就是帮我们的项目管理所依赖的开发包,属于依赖包管理工具。

    宣言言言
  • 手把手教你写一个composer包

    Composer 是一个命令行工具,它的作用就是帮我们的项目管理所依赖的开发包,属于依赖包管理工具。

    北溟有鱼QAQ
  • 手把手教你写一个composer包

    Composer 是一个命令行工具,它的作用就是帮我们的项目管理所依赖的开发包,属于依赖包管理工具。

    仙士可
  • 将Spring Boot作为守护进程启动的一种简单的方法

    我有一个SpringBoot应用,通过shell脚本start.sh启动。现在我期望通过守护进程的方式启动这个应用,这样我即使关掉终端,该应用也能继续运行。

    Jerry Wang
  • 在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)

    在上一篇文章《C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载》中,我们了解发C#/.NET创建基于Topshelf Window...

    Rector
  • Nginx(4):守护进程,一份nginx实现,一份我的实现,看着拿呗

    nginx的源码是比muduo要复杂些哈,muduo跟我以前写过的服务端项目有很多共通之处,就相当于是剥离了业务代码的网络层框架,所以看起来也比较亲切。这个ng...

    看、未来
  • python多进程写同一个list/dict

    JNingWei
  • 写个更牛逼的Transform | Plugin 进阶教程

    .markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:...

    逮虾户
  • 手把手教你写一个sketch插件

    sketch是一款轻量、易用的矢量设计工具。尽管如此,在使用过程中有些功能还是未能满足,亦或者在设计或开发流程中有些工作还略显繁琐,所幸sketch有提供API...

    陆陆
  • 教你如何编写第一个爬虫

    2019年不管是编程语言排行榜还是在互联网行业,Python一直备受争议,到底是Java热门还是Python热门也是一直让人争吵的话题。

    AI科技大本营

扫码关注云+社区

领取腾讯云代金券