守护进程

理论

「守护进程」是 Linux 的一种长期运行的后台服务进程,也有人称它为「精灵进程」。我们常见的 httpd、named、sshd 等服务都是以守护进程 Daemon 方式运行的,通常服务名称以字母d结尾,也就是 Daemon 第一个字母。与普通进程相比它大概有如下特点:

  • 无需控制终端(不需要与用户交互)
  • 在后台运行
  • 生命周期比较长,一般是随系统启动和关闭

守护进程是个特殊的孤儿进程,这种进程脱离终端。为什么要脱离终端呢?

之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。

如何查看守护进程

  • a 表示不仅列当前用户的进程,也列出所有其他用户的进程
  • x 表示不仅列有控制终端的进程,也列出所有无控制终端的进程
  • j 表示列出与作业控制相关的信息

    从上图可以看出守护进行的一些特点:

  • 守护进程基本上都是以超级用户启动( UID 为 0 )
  • 没有控制终端( TTY 为 ?)
  • 终端进程组 ID 为 -1 ( TPGID 表示终端进程组 ID)

一般情况下,守护进程可以通过以下方式启动:

  • 在系统启动时由启动脚本启动,这些启动脚本通常放在 /etc/rc.d 目录下;
  • 利用 inetd 超级服务器启动,如 telnet 等;
  • 由 cron 定时启动以及在终端用 nohup 启动的进程也是守护进程。

如何编写守护进程?

(1) 屏蔽一些控制终端操作的信号

这是为了防止守护进行在没有运行起来前,控制终端受到干扰退出或挂起。

 signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGHUP ,SIG_IGN);

(2) 生成一个进程,使之脱离控制终端、登录会话和进程组

  • 创建子进程,父进程退出。

    • 程序中的父进程一般是一个进程组的组长,fork()产生子进程(将来会生成守护进程)
    • 由于在调用fork()函数时,子进程全盘复制了父进程的会话期、进程组和控制终端等,虽然父进程退出了,但原先的会话期、进程组和控制终端等并没有改变,因此,还不是真正意义上的独立;
    • 由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在Linux中,每当系统发现一个孤儿进程,就会自动由1号进程收养。原先的子进程就会变成init进程的子进程。
    • 为什么要父进程退出:进程组组长无法调用setsid
  • 调用setsid(),用于生成一个新的会话 注意如果当前进程是会话组长时,调用失败。第一点已经可以保证进程不是会话组长了,所以setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离;
    // 2.1)创建子进程,父进程退出if( pid=fork() ){ // 父进程exit(0); //结束父进程,子进程继续}else if(pid< 0){ // 出错perror("fork");exit(EXIT_FAILURE);}//以下是子进程// 2.2)脱离控制终端、登录会话和进程组setsid();

关于 setsid

• 首先内核会创建一个新的会话,并让该进程成为该会话的leader进程,
• 同时伴随该session的建立,一个新的进程组也会被创建,同时该进程成为该进程组的组长。
• 该进程此时还没有和任何控制终端关联。若需要则要另外调用tcsetpgrp

(3) 禁止进程重新打开控制终端

  • 进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端。(只有会话组长才能打开控制终端
// 3)禁止进程重新打开控制终端if( pid=fork() ){ // 父进程(第一个子进程执行fork产生第一个进程的孙进程)exit(0);      // 结束第一子进程}else if(pid< 0){ // 出错perror("fork");exit(EXIT_FAILURE);}
//第二子进程继续(第二子进程不再是会话组长) 

(4) 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误;

 // NOFILE 为 <sys/param.h> 的宏定义// NOFILE 为文件描述符最大个数,不同系统有不同限制for(i=0; i< NOFILE; ++i){// 关闭打开的文件描述符close(i);}

(5) 改变当前工作目录

使用fork()创建的子进程继承了父进程的当前工作目录。

由于在进程运行过程中,当前目录所在的文件系统(如“/mnt/usb”等)是不能卸载的,这对以后的使用会造成诸多的麻烦(如系统由于某种原因要进入单用户模式)。

因此,通常的做法是让“/”作为守护进程的当前工作目录,这样就可以避免上述问题。当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir()。

chdir("/tmp");

(6) 重设文件创建掩模

进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);

umask(0);

(7) 处理 SIGCHLD 信号

但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源(关于僵尸进程的更多详情, 请看《特殊进程之僵尸进程》)。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN 。关于信号的更详细用法, 请看《信号中断处理》。

 signal(SIGCHLD, SIG_IGN);

总结:

#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/syslog.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>int init_daemon(void)
{ int pid; int i;// 1)屏蔽一些控制终端操作的信号signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGHUP ,SIG_IGN);// 2)在后台运行if( pid=fork() ){ // 父进程exit(0); //结束父进程,子进程继续}else if(pid< 0){ // 出错perror("fork");exit(EXIT_FAILURE);}//以下为第一个子进程的操作;// 3)脱离控制终端、登录会话和进程组setsid();  // 4)禁止进程重新打开控制终端if( pid=fork() ){ // 第一子进程调用fork生成孙进程exit(0);      // 结束第一子进程 }else if(pid< 0){ // 出错perror("fork");exit(EXIT_FAILURE);}  //第二子进程(孙进程)继续(第二子进程不再是会话组长),以下为第二子进程的操作;// 5)关闭打开的文件描述符// NOFILE 为 <sys/param.h> 的宏定义// NOFILE 为文件描述符最大个数,不同系统有不同限制for(i=0; i< NOFILE; ++i){close(i);}// 6)改变当前工作目录chdir("/tmp"); // 7)重设文件创建掩模umask(0);  // 8)处理 SIGCHLD 信号signal(SIGCHLD,SIG_IGN);return 0;
} int main(int argc, char *argv[])
{init_daemon();while(1);return 0;
}

SIGTERM

一个daemon通常是在系统关闭时才终止。可以在系统关闭时执行特定的应用程序的脚本来停止的。

因为在系统关闭的时候 init 进程会向所有其子进程发送SIGTERM信号。默认情况下,SIGTERM信号会终止一个进程。如果daemon在终止之前需要做些清理工作,那么就需要为这个信号建立一个处理器。这个处理器必须能快速地完成清理工作,因为 init 在发完 SIGTERM 信号的 5 秒之后会发送一个 SIGKILL 信号。(这并不意味着这个daemon 能够执行 5 秒的 CPU 时间,因为 init 会同时向系统中的所有进程发送信号,而它们可能都试图在 5 秒内完成清理工作。

由于 daemon 是长时间运行的,因此要特别小心潜在的内存泄露问题和文件描述符泄露

很多 daemon 需要确保同一时刻只有一个实例处于活跃状态。如让两个 cron daemon 都试图实行计划任务毫无意义。

使用 SIGHUP 重新初始化一个 daemon

由于很多 daemon 需要持续运行,因此在设计 daemon 程序时需要克服一些障碍。

logrotate 程序可以用来自动旋转 daemon 的日志文件,具体可参考 logrotate(8)手册。

解决这两个问题的方案是让 daemon 为 SIGHUP 建立一个处理器,并在收到这个信号时采取所需的措施。由于 daemon 没有控制终端,因此内核永远不会向 daemon 发送这个信号。这样 daemon就可以使用 SIGHUP 信号来达到目的。

总结

daemon是一个长时间运行并且没有控制终端的进程(即它运行在后台)。daemon执行特定的任务,如提供一个网络登录工具或服务 Web 页面。一个程序要成为 daemon 需要按序执行一组步骤,包括调用 fork()和 setsid()。

daemon应该在合适的地方处理SIGTERM和SIGHUP信号。SIGTERM信号的处理方式应该是按序关闭这个 daemon,而SIGHUP信号则是提供了一种机制让daemon通过读取器配置文件并重新打开所使用的所有日志文件来重新初始化自身

linux守护进程以及如何编写守护进程程序相关推荐

  1. Linux GCC简明教程(编写c语言程序)

    市面上常见的 Linux 都是发行版本,典型的 Linux 发行版包含了 Linux 内核.桌面环境(例如 GNOME.KDE.Unity 等)和各种常用的必备工具(例如 Shell.gcc.VIM. ...

  2. Linux系统(Ubuntu)编写C语言程序

    1.在当前目录创建hello.c文件,命令vi hello.c(用到编辑器vim,Linux安装vim教程,参考博客Linux安装vim) 2.进入编辑模式(按下键盘"i") 3. ...

  3. linux编写守护进程

    1.实验目的 通过编写一个完整的守护进程,使读者掌握守护进程编写和调试的方法,并且进一步熟悉如何编写多进程 程序. 2.实验内容 在该实验中,读者首先建立起一个守护进程,然后在该守护进程中新建一个子进 ...

  4. python3 编写守护进程程序思路

    1. fork子进程,父进程退出 通常,我们执行服务端程序的时候都会通过终端连接到服务器,成功连接后会加载shell环境,终端和shell都是进程,shell进程是终端进程的子进程,通过ps命令可以很 ...

  5. Linux进程全解10——守护进程

    以下内容源于朱有鹏<物联网大讲堂>的课程学习整理,如有侵权,请告知删除. 一.守护进程介绍 1.进程查看命令ps ps -ajx偏向显示各种有关的ID号: ps -aux偏向显示进程各种占 ...

  6. Linux进程间关系之守护进程

    概念 守护进程也称精灵进程,是运行在后台的一种特殊进程.守护进程独立于控制终端并且周期性的执行某种任务或者等待处理某些打算的事件.可认为守护进程目的就是防止终端产生的一些信号让进程退出 特点 所有的守 ...

  7. 黑马程序员Linux系统开发视频之创建守护进程模型

    黑马程序员Linux系统开发视频之创建守护进程模型 1.创建子进程,父进程退出   所有工作在子进程中进行形式上脱离了控制终端 2.在子进程中创建新会话   setsid()函数   使子进程完全独立 ...

  8. Linux系统编程(六)守护进程

    Linux系统编程(六)守护进程 一.进程组 概念 二.会话 创建会话的条件 守护进程 概念 守护进程模型 创建守护进程 一.进程组 概念 进程组,也称之为作业.代表一个或多个进程的集合.每个进程都属 ...

  9. linux守护进程写法_搞懂进程组、会话、控制终端关系,才能明白守护进程如何创建...

    守护进程 概念: 守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程.周期性的执行某种任务或等待处理某些发生的事件. Linux系统有很多守护进程,大多数服务都是用守护进程实现的 ...

最新文章

  1. Await, and UI, and deadlocks! Oh my!
  2. 为什么QQ传文件比MSN快 揭密背后原因
  3. 函数的多态性以及虚函数
  4. 【Python】获取星期字符串
  5. Echarts数据可视化全解注释
  6. module 'bit' not found:No LuaRocks module found for bit
  7. 正则全攻略使用手册,你确定不进来看看吗
  8. Ubuntu14.04 WPS 安装
  9. 2022-03-24 windows pc和Android 手机同屏软件vysor,download网址: https://www.vysor.io/#
  10. java软件工程师工作业绩_java软件工程师的工作描述怎么写
  11. JPA中@Enumerated注解
  12. 为什么毕业后五年,你们的贫富差距越拉越大
  13. Android流畅度总结
  14. Java学习(入门知识)
  15. 基于RedHat6.5的Greenplum环境配置
  16. ARM汇编中的:比较指令--CMN / CMP / TEQ / TST
  17. 12. tie_breaker的使用原因和使用方法
  18. 杠上植物大战僵尸210331
  19. 使用MindStudio利用TSM模型实现视频分类任务
  20. 面试必备之建造者模式

热门文章

  1. AHP层次分析法与python代码讲解(处理论文、建模)
  2. NPDP认证|如何实现产品的组合管理?
  3. 【go】配置goproxy
  4. 【学习】从零开始的Android音视频开发(7)——AwesomePlayer的构造、解码过程
  5. 单精度浮点数 float、双精度浮点数 double
  6. 2.3 组合框(Combo Box)和列表框(List Box)使用实例
  7. 基于51单片机设计的呼吸灯
  8. 学习项目-plsql实现简易学生管理系统
  9. 共建数据库可信开源社区 | openGauss Meetup(长沙站)圆满结束
  10. MATLAB截面数据空间计量模型代码②