博客
关于我
Linux 守护进程
阅读量:436 次
发布时间:2019-03-06

本文共 3430 字,大约阅读时间需要 11 分钟。

守护进程的深度解析

1. 守护进程的定义

守护进程(Daemon)是Linux系统中运行于后台的一种特殊进程。它独立于控制终端,能够周期性地执行特定任务或等待处理,通常不依赖用户输入即可提供服务。常见的守护进程包括syslogd、httpd、mysqld等,其命名习惯约定以"d"结尾。

2. 使用守护进程的方法

2.1 有趣的小例子

通过代码示例可以快速体验守护进程的特性。以下是一个简单的守护进程示例:

#include 
#include
#include
int main() { 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);}

运行该程序后,程序会成为守护进程,日志文件demo.log会记录当前时间和消息。

2.2 man手册

为了全面了解daemon()函数的使用方法,建议查阅其man手册:

man daemon

手册内容摘录如下:

  • 名称:daemon - 运行在后台
  • 摘要daemon()函数用于让程序脱离控制终端,作为系统守护进程运行。
  • 语法int daemon(int nochdir, int noclose);
  • 描述
    • 如果nochdir为0,进程的工作目录会被更改为根目录/
    • 如果noclose为0,标准输入、输出和错误会被重定向到/dev/null
  • 返回值:成功返回0,错误返回-1。

2.3 BUGS与改进

在glibc 2.2版本中,daemon()函数存在一个潜在问题:它不使用double-fork技术,导致生成的守护进程可能成为会话负责人。为了避免这种情况,可以采用以下改进方法:

void daemon_service(void) {    // 第一次fork,确保父进程退出    switch (fork()) {        case -1:            exit(EXIT_FAILURE);        case 0:            break;        default:            exit(EXIT_SUCCESS);    }    // 设置新会话    if (setsid() == -1) {        exit(EXIT_FAILURE);    }    // 第二次fork,避免成为会话负责人    switch (fork()) {        case -1:            exit(EXIT_FAILURE);        case 0:            break;        default:            exit(EXIT_SUCCESS);    }    // 设置工作目录为根目录    if (chdir("/")) {}    // 重置文件权限    umask(0);    // 处理标准输入输出    int fd = sysconf(_SC_OPEN_MAX);    if (fd == -1) {        fd = INT_MAX_CLOSE;    }    while (fd >= 0) {        close(fd--);    }    // 重定向到/dev/null    fd = open(_PATH_DEVNULL, O_RDWR);    if (fd == STDIN_FILENO) {        dup2(fd, STDOUT_FILENO);        dup2(fd, STDERR_FILENO);    }}

3. 源码解析

3.1 GUN C 库的daemon.c实现

glibc的daemon.c实现如下:

#include 
#include
#include
#include
#include
int daemon(int nochdir, int noclose) { int fd; switch (__fork()) { case -1: return (-1); case 0: break; default: _exit(0); } if (__setsid() == -1) { return (-1); } if (!nochdir) { (void) chdir("/"); } if (!noclose) { struct stat64 st; if ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, 0)) != -1 && (__builtin_expect(__fstat64(fd, &st), 0) == 0)) { if (__builtin_expect(S_ISCHR(st.st_mode), 1) != 070 || (!defined(DEV_NULL_MAJOR) && !defined(DEV_NULL_MINOR) || st.st_rdev != makedev(DEV_NULL_MAJOR, DEV_NULL_MINOR))) { (void) dup2(fd, STDIN_FILENO); (void) dup2(fd, STDOUT_FILENO); (void) dup2(fd, STDERR_FILENO); if (fd > 2) { (void) close(fd); } } else { (void) __close_nocancel_nostatus(fd); __set_errno(ENODEV); return (-1); } } else { (void) __close_nocancel_nostatus(fd); __set_errno(ENODEV); return (-1); } } else { (void) __close_nocancel_nostatus(fd); return (-1); } return (0);}

3.2 核心代码解析

  • 66-73行:获取/dev/null的属性,确保它是字符设备。
  • 75-77行:将标准输入、输出和错误重定向到/dev/null
  • 78-79行:严谨处理文件描述符,防止资源泄漏。

3.3 BUGS解析

原glibc实现缺乏double-fork技术,导致守护进程可能成为会话负责人。修改后的实现通过double-fork确保守护进程不再是会话负责人。

4. 后记

欢迎交流和指正。如果您对守护进程有更深入的需求,或者发现其他问题,请随时联系。感谢您的阅读与支持!

转载地址:http://ljiyz.baihongyu.com/

你可能感兴趣的文章
python基础系列四:字符串,列表,字典,元组之间的转换
查看>>
python基础篇(9):模块
查看>>
python基础篇(8):异常处理
查看>>
python基础篇(8):字符串常用处理汇总
查看>>
python基础篇(7):函数进阶知识点
查看>>
python基础篇(6):global关键字
查看>>
python基础篇(5):None类型
查看>>
python基础篇(4):range语句
查看>>
python基础篇(3):print()补偿知识点
查看>>
python基础篇(2):字符串扩展知识点
查看>>
python基础篇(1):type()
查看>>
python基础篇(15):闭包
查看>>
python基础篇(14):多态
查看>>
python基础篇(13):类型注解
查看>>
python基础篇(12):继承
查看>>
python基础篇(11):封装
查看>>
python基础篇(10):自定义包
查看>>
python基础篇(10):类、对象与构造方法
查看>>
python系列【仅供参考】:python测试javaSDK总结
查看>>
python系列【仅供参考】:python斗地主
查看>>