目录
1. 守护进程是什么
2. 怎么用守护进程
2.1 有趣小例子
2.2 man daemon
3. 源码解析
3.1 GUN C daemon.c
3.2 daemon.c 解析
3.3 BUGS
4. 后记
1. 守护进程是什么
Linux Daemon (守护进程) 是运行在后台的一种特殊进程. 它独立于控制终端并且周期性地执行某种任务或等待处理
某些发生的事件. 不依赖用户输入就能提供某种服务.
Linux 系统中大多数服务都是通过守护进程实现的. 常见的守护进程包括系统日志进程 syslogd, Web 服务器 httpd ,
MySQL 数据库服务器 mysqld 等. 守护进程的命名我们通常约定以 d 结尾.
2. 怎么用守护进程
2.1 有趣小例子
- #include <time.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- //
- // gcc -g -O2 -Wall -Wextra -o demo demo.c
- //
- int main(void) {
- // 创建守护进程
- if (daemon(0, 0)) {
- perror("daemon error");
- exit(EXIT_FAILURE);
- }
- // 守护进程 构建文本任务
- FILE * txt = fopen("demo.log", "w");
- if (txt) {
- fprintf(txt, "%ld, hello, 世界", time(NULL));
- fclose(txt);
- }
- exit(EXIT_SUCCESS);
- }
结果出人意料呢? daemon(0, 0) ~ 通过 GNU C 库 提供的 api, 轻巧的创建了守护进程.
有心同行也可以将上面素材当做守护进程面试题, 不需要死记硬背, 简单交流下就可以考察出候选人是否严谨和用心.
2.2 man daemon
全貌了解 daemon() 函数最简单方法还是看 man daemon 手册, 摘录些一块学习学习, 温故温故.
- DAEMON(3) Linux Programmer's Manual DAEMON(3)
- NAME
- daemon - run in the background
- SYNOPSIS
- #include <unistd.h>
- int daemon(int nochdir, int noclose);
- Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
- daemon():
- Since glibc 2.21:
- _DEFAULT_SOURCE
- In glibc 2.19 and 2.20:
- _DEFAULT_SOURCE || (_XOPEN_SOURCE && _XOPEN_SOURCE < 500)
- Up to and including glibc 2.19:
- _BSD_SOURCE || (_XOPEN_SOURCE && _XOPEN_SOURCE < 500)
- DESCRIPTION
- The daemon() function is for programs wishing to detach themselves from
- the controlling terminal and run in the background as system daemons.
- If nochdir is zero, daemon() changes the process's current working
- directory to the root directory ("/"); otherwise, the current working
- directory is left unchanged.
- If noclose is zero, daemon() redirects standard input, standard output
- and standard error to /dev/null; otherwise, no changes are made to
- these file descriptors.
- RETURN VALUE
- (This function forks, and if the fork(2) succeeds, the parent calls
- _exit(2), so that further errors are seen by the child only.) On suc‐
- cess daemon() returns zero. If an error occurs, daemon() returns -1
- and sets errno to any of the errors specified for the fork(2) and set‐
- sid(2).
- ATTRIBUTES
- For an explanation of the terms used in this section, see
- attributes(7).
- ┌──────────┬───────────────┬─────────┐
- │Interface │ Attribute │ Value │
- ├──────────┼───────────────┼─────────┤
- │daemon() │ Thread safety │ MT-Safe │
- └──────────┴───────────────┴─────────┘
- CONFORMING TO
- Not in POSIX.1. A similar function appears on the BSDs. The daemon()
- function first appeared in 4.4BSD.
- NOTES
- The glibc implementation can also return -1 when /dev/null exists but
- is not a character device with the expected major and minor numbers.
- In this case, errno need not be set.
- BUGS
- The GNU C library implementation of this function was taken from BSD,
- and does not employ the double-fork technique (i.e., fork(2), set‐
- sid(2), fork(2)) that is necessary to ensure that the resulting daemon
- process is not a session leader. Instead, the resulting daemon is a
- session leader. On systems that follow System V semantics (e.g.,
- Linux), this means that if the daemon opens a terminal that is not
- already a controlling terminal for another session, then that terminal
- will inadvertently become the controlling terminal for the daemon.
- SEE ALSO
- fork(2), setsid(2), daemon(7), logrotate(8)
- COLOPHON
- This page is part of release 4.15 of the Linux man-pages project. A
- description of the project, information about reporting bugs, and the
- latest version of this page, can be found at
- https://www.kernel.org/doc/man-pages/.
- GNU 2017-11-26 DAEMON(3)
翻译其中核心的几小段, 有更好翻译可以提供或者告知, 文章会迅速修正.
- NAME
- daemon - 运行在后台
- SYNOPSIS
- #include <unistd.h>
- int daemon(int nochdir, int noclose);
- DESCRIPTION
- daemon() 函数希望运行程序脱离控制终端, 作为系统守护进程在后台运行.
- 如果 nochdir 是 0, daemon() 将更改当前进程工作目录到 "/" 根目录. 否则保持
- 不变.
- 如果 noclose 是 0, deamon() 将重定向 STDIN_FILENO 标准输入, STDOUT_FILENO
- 标准输出, STDERR_FILENO 标准错误 到 /dev/null, 否则保持不变.
- RETURN VALUE
- 函数内部会执行 fork, 如果 fork 成功, 父进程会调用 _exit 退出. 执行成功返回 0.
- 发生错误时候将返回 -1, errno 的设置依赖 fork(), setsid(), daemon() 源码.
3. 源码解析
3.1 GUN C daemon.c
glibc-2.33/misc/daemon.c
- 1 /*-
- 2 * Copyright (c) 1990, 1993
- 3 * The Regents of the University of California. All rights reserved.
- 4 *
- 5 * Redistribution and use in source and binary forms, with or without
- 6 * modification, are permitted provided that the following conditions
- 7 * are met:
- 8 * 1. Redistributions of source code must retain the above copyright
- 9 * notice, this list of conditions and the following disclaimer.
- 10 * 2. Redistributions in binary form must reproduce the above copyright
- 11 * notice, this list of conditions and the following disclaimer in the
- 12 * documentation and/or other materials provided with the distribution.
- 13 * 4. Neither the name of the University nor the names of its contributors
- 14 * may be used to endorse or promote products derived from this software
- 15 * without specific prior written permission.
- 16 *
- 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- 27 * SUCH DAMAGE.
- 28 */
- 29
- 30 #if defined(LIBC_SCCS) && !defined(lint)
- 31 static char sccsid[] = "@(#)daemon.c 8.1 (Berkeley) 6/4/93";
- 32 #endif /* LIBC_SCCS and not lint */
- 33
- 34 #include <errno.h>
- 35 #include <fcntl.h>
- 36 #include <paths.h>
- 37 #include <unistd.h>
- 38 #include <sys/stat.h>
- 39
- 40 #include <device-nrs.h>
- 41 #include <not-cancel.h>
- 42
- 43 int
- 44 daemon (int nochdir, int noclose)
- 45 {
- 46 int fd;
- 47
- 48 switch (__fork()) {
- 49 case -1:
- 50 return (-1);
- 51 case 0:
- 52 break;
- 53 default:
- 54 _exit(0);
- 55 }
- 56
- 57 if (__setsid() == -1)
- 58 return (-1);
- 59
- 60 if (!nochdir)
- 61 (void)__chdir("/");
- 62
- 63 if (!noclose) {
- 64 struct stat64 st;
- 65
- 66 if ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, 0)) != -1
- 67 && (__builtin_expect (__fstat64 (fd, &st), 0)
- 68 == 0)) {
- 69 if (__builtin_expect (S_ISCHR (st.st_mode), 1) != 0
- 70 #if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR
- 71 && (st.st_rdev
- 72 == makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR))
- 73 #endif
- 74 ) {
- 75 (void)__dup2(fd, STDIN_FILENO);
- 76 (void)__dup2(fd, STDOUT_FILENO);
- 77 (void)__dup2(fd, STDERR_FILENO);
- 78 if (fd > 2)
- 79 (void)__close (fd);
- 80 } else {
- 81 /* We must set an errno value since no
- 82 function call actually failed. */
- 83 __close_nocancel_nostatus (fd);
- 84 __set_errno (ENODEV);
- 85 return -1;
- 86 }
- 87 } else {
- 88 __close_nocancel_nostatus (fd);
- 89 return -1;
- 90 }
- 91 }
- 92 return (0);
- 93 }
3.2 daemon.c 解析
30-32 行 SCCS ID (SCCS 代表源代码控制系统)
66 行 和 88 行 类似 open 和 close
- // sysdeps/generic/not-cancel.h
-
- /* By default we have none. Map the name to the normal functions. */
- #define __open_nocancel(...) \
- __open (__VA_ARGS__)
- #define __close_nocancel(fd) \
- __close (fd)
不过 88 行不够严禁, 因为当 fd == -1 时候, 会 __close_nocancel_nostatus (-1) 会引发一个 @errno{EBADF, 9, Bad file descriptor}.
67 - 73 行 (__builtin_expect (EXP, N) 表达意思是告诉编译器预测 EXP 表试式 == 常量 N 概率很大, 返回值是 EXP ) 大致
意思获取文件属性, 并且不是字节设备. makedev 用于构建设备 id.
75-77 三行, 将 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO 句柄指向 fd 句柄所指向的 dev/null 文件.
78-79 行, 很漂亮很严谨功力很厚.
3.3 BUGS
在 2.2 中有这段话,
- BUGS
- The GNU C library implementation of this function was taken from BSD,
- and does not employ the double-fork technique (i.e., fork(2), set‐
- sid(2), fork(2)) that is necessary to ensure that the resulting daemon
- process is not a session leader. Instead, the resulting daemon is a
- session leader. On systems that follow System V semantics (e.g.,
- Linux), this means that if the daemon opens a terminal that is not
- already a controlling terminal for another session, then that terminal
- will inadvertently become the controlling terminal for the daemon.
- BUGS
- GNUC 库 这个 daemon() 函数的实现取自 BSD 源码. 没有采用两次 double fork
- 设置 sid 机制, 来确保生成的守护进程不是会话负责人. 相反, 这里生成的守护
- 进程是会话负责人 (session leader). 在遵循 System V 语义系统上, 创建的守
- 护进程在重新打开终端时候, 新开终端会自动成为守护进程的控制终端.
参照这些内容我们补充一个大致符合 System V 版本 daemon
- /*
- * 创建守护进程
- */
- void daemon_service(void) {
- // fork 后父进程 exit 退出, 保证子进程可以成功 setsid() 拥有一个新会话
- switch (fork()) {
- case -1:
- exit(EXIT_FAILURE);
- case 0:
- break;
- default:
- exit(EXIT_SUCCESS);
- }
- // 子进程创建新会话
- // 执行成功后 Process ID(PID) == Process Group ID(PGID) == Session ID(SID)
- if (setsid() == -1)
- exit(EXIT_FAILURE);
- // 二次 fork 后孙进程不再是会话组首进程, 因而孙进程无法重新打开一个新的控制终端
- // 执行成功后 Process ID(PID) != Process Group ID(PGID) == Session ID(SID)
- switch (fork()) {
- case -1:
- exit(EXIT_FAILURE);
- case 0:
- break;
- default:
- exit(EXIT_SUCCESS);
- }
- // 子进程设置新的工作目录是 根目录 "/", 避免存在挂载磁盘一直被占用的情况
- if (chdir("/")) {}
- // 子进程重置 创建文件 权限
- umask(0);
- // 相关句柄善后, 节省资源
- fflush(stderr);
- fflush(stdout);
- for (int fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--)
- close(fd);
- }
4. 后记
欢迎交流指正 ~
感恩? 不忘初心, 与善者同行 ?