博客
关于我
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/

你可能感兴趣的文章
opencv15-边缘处理
查看>>
opencv16-Sobel算子
查看>>
opencv17-laplance算子
查看>>
opencv2-矩阵掩膜操作
查看>>
opencv20-霍夫圆检测
查看>>
opencv21-像素重映射
查看>>
opencv22-直方图均衡化
查看>>
opencv23-直方图计算
查看>>
opencv24-直方图比较
查看>>
opencv25-直方图反向投影
查看>>
opencv26-模板匹配
查看>>
opencv27-轮廓发现
查看>>
opencv28-凸包
查看>>
opencv29-轮廓周围绘制矩形框和圆形框
查看>>
OpenCV3 install tutorial for Mac
查看>>
opencv3-Mat对象
查看>>
opencv30-图像矩
查看>>
opencv32-基于距离变换和分水岭的图像分割
查看>>
opencv4-图像操作
查看>>
opencv5-图像混合
查看>>