本文介绍如何使用 C 语言创建 Linux 系统中 SysV 风格的 daemon 程序。注意:这是一种旧式的 daemon 程序写法,进入 systemd 时代后是不需要通过这样的方式创建 daemon 程序的。 本文的演示环境为 ubuntu 18.04。

创建 daemon 程序的流程

通过前文《Linux session(会话)》我们了解到,如果要让程序运行在后台,必须处理好进程的 session。所以在创建 daemon 程序的过程中处理 session 问题是很重要的一步,当然除此之外还需要其它的步骤。下面是在 Linux 系统中创建一个 SysV 风格的 daemon 的基本流程:

  1. 从父进程 fork 出一个子进程
  2. 为子进程创建新的 session ID
  3. 在子进程中再 fork 一次
  4. 修改 umask
  5. 修改进程的当前工作目录
  6. 关闭进程中的文件描述符

接下来我们通过代码来介绍这些操作的含义。

创建 daemon 程序

从父进程 fork 出一个子进程
创建一个子进程,如果成功就让父进程退出,此时的子进程已经成为了 init 进程的子进程:

pid_t pid;

pid = fork();
if (pid < )
exit(EXIT_FAILURE);
if (pid > )
exit(EXIT_SUCCESS);

为子进程创建新的 session ID
运行在后台的进程需要摆脱 session 终端的束缚,通过 setsid() 函数为进程设置新的 session ID 可以做到这一点:

pid_t pid;

pid = fork();
if (pid < )
exit(EXIT_FAILURE);
if (pid > )
exit(EXIT_SUCCESS); if (setsid() < 0)
exit(EXIT_FAILURE);

********************************
执行到这里时,PID==PGID==SID

********************************

在子进程中再 fork 一次
这次 fork 的目的是防止进程再次获得终端。因为只有 session leader 才能获得终端,而这次 fork 使子进程变成了非 session leader:

pid_t pid;

pid = fork();
if (pid < )
exit(EXIT_FAILURE);
if (pid > )
exit(EXIT_SUCCESS); if (setsid() < )
exit(EXIT_FAILURE); /* 第二次 fork */
pid = fork();
if (pid < 0)
exit(EXIT_FAILURE); if (pid > 0)
exit(EXIT_SUCCESS);

********************************
执行到这里时,PGID==SID 但是已经不等于 PID 了,说明进程已经不是 session leader

********************************

修改 umask
为了能够向 daemon 进程创建的任何文件中写入内容(包括日志),必须重置 umask(file mode mask, umask),以确保能够正确地写入或读取这些文件:

umask(0);

修改进程的当前工作目录
必须保证进程的当前工作目录是存在的。因为众多的 Linux 发行版中很多都没有完全遵守标准的文件目录结构,所以最好是把进程的当前工作目录设置为 /,这样可以避免因设置了某个目录而导致它无法被 unmount:

chdir("/");

关闭进程中的文件描述符
关闭进程中所有打开的文件描述符:

int x;
for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
{
close (x);
}

把日志写入 syslog
Daemon 程序的日志非常重要,我们可以通过 openlog、syslog 和 closelog 三个函数把日志内容写入到 syslog  中:

openlog ("daemondemo", LOG_PID, LOG_DAEMON);
syslog (LOG_NOTICE, "Daemon demo is running, number: %d", count);
closelog();

本文 demo 输出的日志如下所示:

完整的代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h> static void demo_daemon()
{
pid_t pid; /* Fork off the parent process */
pid = fork(); /* An error occurred */
if (pid < )
exit(EXIT_FAILURE); /* Success: Let the parent terminate */
if (pid > )
exit(EXIT_SUCCESS); /* On success: The child process becomes session leader */
if (setsid() < )
exit(EXIT_FAILURE); /* Catch, ignore and handle signals */
//TODO: Implement a working signal handler */
//signal(SIGCHLD, SIG_IGN);
//signal(SIGHUP, SIG_IGN); /* Fork off for the second time*/
pid = fork(); /* An error occurred */
if (pid < )
exit(EXIT_FAILURE); /* Success: Let the parent terminate */
if (pid > )
exit(EXIT_SUCCESS); /* Set new file permissions */
umask(); /* Change the working directory to the root directory */
/* or another appropriated directory */
chdir("/"); /* Close all open file descriptors */
int x;
for (x = sysconf(_SC_OPEN_MAX); x>=; x--)
{
close (x);
} /* Open the log file */
openlog ("daemondemo", LOG_PID, LOG_DAEMON);
} int main()
{
int count = ;
demo_daemon(); while ()
{
//TODO: Insert daemon code here.
count ++;
syslog (LOG_NOTICE, "Daemon demo is running, number: %d", count);
sleep ();
if(count > )
{
break;
}
} syslog (LOG_NOTICE, "Daemon demo terminated.");
closelog(); return EXIT_SUCCESS;
}

把上面的代码保存到文件 daemondemo.c 中(也可以从这里下代码),然后执行下面的命令进行编译就可以得到可执行文件 daemondemo:

$ gcc -Wall daemondemo.c -o daemondemo

关于 fork 两次

这是一个很有意思的话题,有人说需要 fork 两次,有人说第二次是可选的,究竟该如何做呢?当我们理解了第二次 fork 的用途后就可以自行决定是否需要第二次 fork 了。
这还需要从 session 的控制终端说起。控制终端是进程的一个属性,通过 fork 系统调用创建的子进程会从父进程那里继承控制终端。这样,session 中的所有进程都从 session 领头进程那里继承控制终端。前面已经说过了,要把程序变成 daemon,就得让进程摆脱 session 的终端。而这些在第一次 fork 后调用 setsid() 函数就搞定了。那么如果接下来不小心再给进程添加了终端该怎么办?答案是不让你添加!这就是第二次 fork 的作用。只有 session leader 才能获得终端,而第二次 fork 使子进程变成了非 session leader,你想犯错也不给你机会了。

像 nginx 和 gblic 的 daemon 函数的实现都是 fork 一次,所以说第二次 fork 是可选的,你可以根据自己的实际情况来决定。

参考:
Linux Daemon Writing HOWTO
Creating a daemon in Linux
daemon man page
daemon 函数
Unix Daemon Server Programming
glibc daemon.c

创建 SysV 风格的 linux daemon 程序的更多相关文章

  1. 嵌入式Linux应用程序开发详解------(创建守护进程)

    嵌入式Linux应用程序开发详解 华清远见 本文只是阅读文摘. 创建一个守护进程的步骤: 1.创建一个子进程,然后退出父进程: 2.在子进程中使用创建新会话---setsid(): 3.改变当前工作目 ...

  2. Linux Daemon 类程序

    1.后台daemon程序(精灵程序) 在Linux中专门提供了一个函数来完成这个daemon化的过程,这个函数的原型如下 int daemon (int __nochdir, int __noclos ...

  3. Linux C 程序 进程控制(17)

    进程控制 1.进程概述现代操作系统的特点在于程序的并行执行.Linux是一个多用户多任务的操作系统.ps .pstree 查看进程进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得.// ...

  4. 使用PHP脚本来写Daemon程序

    什么是Daemon进程   这又是一个有趣的概念,daemon在英语中是"精灵"的意思,就像我们经常在迪斯尼动画里见到的那些,有些会飞,有些不会,经常围着动画片的主人公转来转去,啰 ...

  5. Linux 高性能服务器编程——Linux服务器程序规范

    问题聚焦:     除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范.     工欲善其事,必先利其器,这篇主要来探 ...

  6. 构建 iOS 风格移动 Web 应用程序的8款开发框架

    使用 HTML5,CSS3 和 JavaScript 开发移动应用经过实践证明是一种可行的方式.这里收录了几款 iOS 风格的手机应用程序开发框架,帮助您使用擅长的 Web 技术来开发移动应用程序.这 ...

  7. 使用 iosOverlay.js 创建 iOS 风格的提示和通知

    iosOverlay.js 用于在 Web 项目中实现 iOS 风格的通知和提示效果.为了防止图标加载的时候闪烁,你需要预加载的图像资源.不兼容 CSS 动画的浏览器需要 jQuery 支持.浏览器兼 ...

  8. linux应用程序开发-文件编程-系统调用方式

    在看韦东山视频linux驱动方面有一些吃力,究其原因,虽然接触过linux应用程序编程,但是没有深入去理解,相关函数用法不清楚,正好看到国嵌视频对这一方面讲的比较透彻, 所以把学习过程记录下来,也作为 ...

  9. linux c程序中获取shell脚本输出的实现方法

    linux c程序中获取shell脚本输出的实现方法 1. 前言Unix界有一句名言:“一行shell脚本胜过万行C程序”,虽然这句话有些夸张,但不可否认的是,借助脚本确实能够极大的简化一些编程工作. ...

随机推荐

  1. [C++]那些年被虐的STL

    首先很感谢**P1135奇怪的电梯 **[2.14补充:此题已被AC!然后将被我花式虐[From语]哈哈哈哈哈哈哈哈哈哈好嗨哟感觉人生已经到达了巅峰感觉人生已经到达了高潮]这道题了!在做这道题的我大致 ...

  2. Selenium系列(六) - 强制等待、隐式等待、显式等待

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  3. JSP学习笔记(四)

    Java Servlet servlet的部署.创建与运行 servlet的工作原理 重定向与转发 使用session 1.servlet的部署.创建与运行 [1].创建Servlet 创建一个Ser ...

  4. 一篇漫画故事带你理解透HTTPS(下)

    上下集知识点总结: 前情提要: 蝙蝠纪元,疫情之下.二丫欲访问京东购物,不料弹出安全提示,遂找二毛一探究竟.二毛一顿排查后,开始用通俗易懂的语言深入浅出的向二丫解释 HTTP作用及优缺点.HTTPS的 ...

  5. js内置对象常用方法

    JS内置对象: ● String对象:处理所有的字符串操作 ● Math对象:处理所有的数学运算 ● Date对象:处理日期和时间的存储.转化和表达 ● Array对象:提供一个数组的模型.存储大量有 ...

  6. 《java编程思想》操作符

    1. 自动递增和递减 递增和递减运算是两种相当不错的快捷运算,递减操作符是 "--",意为减少一个单位,递增操作符是 "++",意为增加一个单位.这两个操作符各 ...

  7. 实现ls

    一.任务详情 参考伪代码实现ls的功能,提交代码的编译,运行结果截图,码云代码链接 打开目录文件 针对目录文件 读取目录条目 显示文件名 关闭文件目录文件 二.伪代码 opendir while(re ...

  8. Sublime Text 2 Install Package Debug

    本文转载自CSDN空间freshlover的博客<Sublime Text 无法使用Package Control或插件安装失败的解决方法>,转载请注明出处,谢谢! Sublime Tex ...

  9. 分享一款一直在维护的【网络开发运维|通用调试工具】: http请求, websocket,cmd, RSA,DES, 参数签名工具,脚本批量生成工具,google动态口令,端口检测,组件注册,js混淆...

    首先发下下载地址:https://files.cnblogs.com/files/taohuadaozhu/ConfigLab.Test.ex.rar 日常开发,运维,跨部门跨公司对接中.  想快速调 ...

  10. Java读源码之ReentrantLock

    前言 ReentrantLock 可重入锁,应该是除了 synchronized 关键字外用的最多的线程同步手段了,虽然JVM维护者疯狂优化 synchronized 使其已经拥有了很好的性能.但 R ...