作者: andylin02
学习章节: 第十三章 守护进程和inetd超级服务器
关键词: 守护进程, syslogd, daemon_init, inetd, xinetd, 超级服务器, TCP Wrapper, 进程组, 会话


一、章节概述

1.1 本章焦点

第十三章聚焦于Unix/Linux系统中不可或缺的守护进程概念。在Unix系统中,用户通常与shell进行交互式会话。然而,许多网络服务需要在后台默默运行,不受用户登录/注销的影响,甚至需要在系统引导时启动。这些服务程序就是守护进程,它们没有控制终端,在后台运行,执行各种管理任务。

本章的核心任务是:理解守护进程的本质,掌握将普通进程转化为守护进程的完整步骤,了解syslog日志系统的运作机制,以及学习如何使用inetd超级服务器简化网络服务的管理。本章中提出的daemon_init函数是一个实际可用的接口,在后续章节中会经常用到。

💡 本章核心价值:读完第十三章,你将能够:

  • 深刻理解守护进程的运行环境与普通进程的区别
  • 独立编写可靠的守护进程程序
  • 熟练使用syslog函数进行程序日志记录
  • 理解inetd的设计思想和工作流程
  • 了解xinetd相比inetd的增强特性

二、守护进程基础

2.1 什么是守护进程

守护进程是在后台运行且不与任何控制终端关联的进程。Unix系统通常有很多守护进程在后台运行(约20到50个的量级),执行不同的管理任务。

守护进程的特点如下:

特点 说明
生存期长 通常在系统引导时启动,仅在系统关闭时才终止
无控制终端 不与键盘、显示器等终端设备关联
后台运行 不占用用户的交互式会话
超级用户特权 大多数守护进程以超级用户特权运行

2.2 守护进程与普通进程的区别

┌─────────────────────────────────────────────────────────────────────────────┐
│                   守护进程与普通进程的运行环境对比                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  普通进程(前台运行)                                                        │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 用户登录 ──→ Shell ──→ 执行程序 ──→ 进程依附于终端                   │   │
│  │                              │                                      │   │
│  │                              ▼                                      │   │
│  │                    终端关闭 → 进程收到SIGHUP信号 → 进程终止          │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  守护进程(后台运行)                                                        │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 系统启动 ──→ 初始化脚本 ──→ 守护进程启动                              │   │
│  │                              │                                      │   │
│  │                              ▼                                      │   │
│  │                    1. 与终端脱离                                    │   │
│  │                    2. 创建新会话                                    │   │
│  │                    3. 独立运行,不受终端关闭影响                     │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  💡 关键区别:守护进程通过setsid()创建新会话,完全脱离控制终端              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2.3 守护进程的启动方式

启动方式 说明 示例
系统初始化脚本 在系统引导阶段启动,通常位于/etc目录或以/etc/rc开头的目录中,一开始就拥有超级用户特权 syslogd、inetd、Web服务器、sendmail
inetd超级服务器 inetd监听网络请求,在请求到达时才启动相应的实际服务器 Telnet、FTP等服务
cron守护进程 按照规则定期执行程序,由它启动的程序也作为守护进程运行 定时任务
at命令 指定将来某个时刻执行程序,实际由cron启动执行 一次性定时任务
用户终端 从终端启动(前台或后台),用于测试或重启守护进程 测试用途

💡 关键理解:守护进程由系统初始化脚本启动时没有控制终端,这是系统初始化脚本启动进程的副作用。如果从用户终端启动,守护进程必须主动脱离与控制终端的关联,以避免与作业控制、终端会话管理、终端产生的信号等发生不期望的交互。

三、进程组与会话

理解守护进程的工作原理,需要先理解Unix系统中的两个核心概念——进程组(Process Group)和会话(Session)。

3.1 进程组

每个进程除了有进程ID外,还属于一个进程组。进程组是一个或多个进程的集合,同一个进程组的各进程接受来自同一个终端的各种信号。

#include <unistd.h>

pid_t getpgrp(void);  // 返回调用者进程的进程组ID
int setpgid(pid_t pid, pid_t pgid);  // 将pid进程的进程组ID设置为pgid

进程组的核心规则

  • 每个进程组都有一个组长进程,组长进程的进程组ID等于该进程ID
  • 只要该进程组中有一个进程存在,该进程组就存在,与组长进程是否终止无关
  • 一个进程只能为自己或自己的子进程设置进程组ID

3.2 会话

会话是一个或多个进程组的集合。进程调用setsid函数可以创建一个新的会话:

#include <unistd.h>

pid_t setsid(void);

setsid函数的行为

  1. 该进程变成新会话的会话首进程(Session Leader)
  2. 该进程成为一个新进程组的组长
  3. 该进程没有控制终端。如果在调用setsid之前该进程有控制终端,这种联系会被切断

会话的层级结构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                           会话与进程组结构                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  会话(Session)                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                       会话首进程                                      │   │
│  │                          │                                          │   │
│  │           ┌──────────────┼──────────────┐                          │   │
│  │           │              │              │                          │   │
│  │           ▼              ▼              ▼                          │   │
│  │  ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐       │   │
│  │  │   进程组A        │ │   进程组B        │ │   进程组C        │       │   │
│  │  │  组长            │ │  组长            │ │  组长            │       │   │
│  │  │  ├─进程1        │ │  ├─进程4        │ │  ├─进程7        │       │   │
│  │  │  ├─进程2        │ │  ├─进程5        │ │  └─进程8        │       │   │
│  │  │  └─进程3        │ │  └─进程6        │ │                  │       │   │
│  │  └─────────────────┘ └─────────────────┘ └─────────────────┘       │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  💡 setsid的作用:使调用进程成为新会话的会话首进程和新进程组的组长,          │
│     并确保该进程没有控制终端                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

四、编写守护进程

4.1 创建守护进程的标准步骤

将普通进程转换为守护进程通常需要执行以下7个步骤:

步骤 操作 目的
1 fork() + 父进程退出 形式上脱离控制终端,确保子进程不是进程组组长
2 在子进程中调用 setsid() 创建新会话,使进程成为会话首进程和新进程组组长,脱离控制终端
3 再次 fork() + 父进程退出 确保守护进程不会意外获取控制终端,且父进程(会话首进程)终止后子进程不再是会话首进程
4 chdir("/") 改变工作目录为根目录,防止占用可卸载的文件系统
5 umask(0) 重设文件权限掩码,增加守护进程的灵活性
6 关闭所有打开的文件描述符 释放从父进程继承的、不会被使用的资源
7 重定向标准输入/输出/错误到/dev/null 防止守护进程意外输出到终端
8 调用 openlog() 建立与syslogd的连接,用于日志记录

💡 为什么需要第二次fork?

第一次fork后,setsid使进程成为会话首进程。会话首进程有权重新请求一个控制终端。第二次fork确保子进程不再是会话首进程,从而保证它永远不会自动获取控制终端。

4.2 daemon_init函数

本书通过daemon_init函数提供了一个完整的守护进程初始化实现。调用它(通常从服务器程序)可使一个进程变成守护进程。

#include "unp.h"
#include <syslog.h>

#define MAXFD 64

extern int daemon_proc;  /* defined in error.c */

int daemon_init(const char *pname, int facility)
{
    int i;
    pid_t pid;

    // 步骤1: 第一次fork,父进程退出
    if ((pid = Fork()) < 0)
        return (-1);
    else if (pid)
        _exit(0);           /* parent terminates */

    /* child 1 continues... */

    // 步骤2: 成为会话首进程
    if (setsid() < 0)       /* become session leader */
        return (-1);

    // 步骤3: 忽略SIGHUP信号
    Signal(SIGHUP, SIG_IGN);

    // 步骤4: 第二次fork,确保不是会话首进程
    if ((pid = Fork()) < 0)
        return (-1);
    else if (pid)
        _exit(0);           /* child 1 terminates */

    /* child 2 continues... */

    // 步骤5: 设置标志,供错误处理函数使用
    daemon_proc = 1;        /* for err_XXX() functions */

    // 步骤6: 改变工作目录到根目录
    chdir("/");             /* change working directory */

    // 步骤7: 关闭所有打开的文件描述符
    for (i = 0; i < MAXFD; i++)
        close(i);

    // 步骤8: 重定向标准输入、输出、错误到/dev/null
    open("/dev/null", O_RDONLY);   // fd 0
    open("/dev/null", O_RDWR);     // fd 1
    open("/dev/null", O_RDWR);     // fd 2

    // 步骤9: 打开syslog连接
    openlog(pname, LOG_PID, facility);

    return (0);             /* success */
}

参数说明

  • pname:程序名称,会出现在日志消息中
  • facility:标识消息发送进程的类型,决定消息的处理方式
  • LOG_PID:将进程ID添加到每条日志消息中

💡 daemon_proc标志的作用:该标志用于区分程序是以守护进程方式运行还是以普通进程方式运行。在错误处理函数中,如果daemon_proc为1,则使用syslog记录错误;否则输出到标准错误。这样省得从头到尾修改程序代码。

4.3 一个更简洁的守护进程模板

如果只需要基本功能,可以参考以下更简洁的守护进程实现:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

void daemonize(void)
{
    pid_t pid;

    // 1. 创建子进程,父进程退出
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    } else if (pid != 0) {
        exit(1);           // 父进程退出
    }

    // 2. 设置会话ID,彻底脱离终端
    setsid();

    // 3. 改变工作目录到根目录
    if (chdir("/") < 0) {
        perror("chdir");
        exit(1);
    }

    // 4. 设置文件权限掩码
    umask(0);

    // 5. 重定向标准输入/输出/错误到/dev/null
    close(0);
    open("/dev/null", O_RDWR);
    dup2(0, 1);
    dup2(0, 2);
}

4.4 守护进程流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                        守护进程创建完整流程图                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  调用程序                                                                   │
│     │                                                                       │
│     ▼                                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 第1步:fork()                                                        │   │
│  └─────────────┬───────────────────────────────────────────────────────┘   │
│                │                                                           │
│        ┌───────┴───────┐                                                   │
│        │               │                                                   │
│        ▼               ▼                                                   │
│    父进程          子进程                                                   │
│    退出(exit)     继续运行                                                  │
│        │          │                                                        │
│        │          ▼                                                        │
│        │   ┌─────────────────────────────────────────────────────────┐    │
│        │   │ 第2步:setsid() → 创建新会话,成为会话首进程             │    │
│        │   └─────────────────────────┬───────────────────────────────┘    │
│        │                             │                                     │
│        │                             ▼                                     │
│        │   ┌─────────────────────────────────────────────────────────┐    │
│        │   │ 第3步:忽略SIGHUP信号(signal(SIGHUP, SIG_IGN))        │    │
│        │   └─────────────────────────┬───────────────────────────────┘    │
│        │                             │                                     │
│        │                             ▼                                     │
│        │   ┌─────────────────────────────────────────────────────────┐    │
│        │   │ 第4步:第二次fork()                                      │    │
│        │   └─────────────────────────┬───────────────────────────────┘    │
│        │                             │                                     │
│        │                     ┌───────┴───────┐                             │
│        │                     │               │                             │
│        │                     ▼               ▼                             │
│        │                  父进程          子进程                           │
│        │               (会话首进程)       (真正的守护进程)                  │
│        │                  退出               │                             │
│        │                   │                 │                             │
│        │                   │                 ▼                             │
│        │                   │   ┌─────────────────────────────────────┐    │
│        │                   │   │ 第5步:daemon_proc = 1              │    │
│        │                   │   │         chdir("/")                  │    │
│        │                   │   │         umask(0)                    │    │
│        │                   │   │         关闭所有文件描述符           │    │
│        │                   │   │         重定向stdin/stdout/stderr   │    │
│        │                   │   │         openlog()                   │    │
│        │                   │   └─────────────────────────────────────┘    │
│        │                   │                 │                            │
│        │                   │                 ▼                            │
│        │                   │          ┌─────────────────┐                 │
│        │                   │          │ 守护进程核心逻辑  │                 │
│        │                   │          └─────────────────┘                 │
│        │                   │                 │                            │
│        │                   │                 ▼                            │
│        │                   │          ┌─────────────────┐                 │
│        │                   │          │ 退出时closelog() │                │
│        │                   │          └─────────────────┘                 │
│        │                   │                                               │
│        └───────────────────┴───────────────────────────────────────────────┘

五、syslog日志系统

守护进程没有控制终端,发生某些事件时需要输出消息。这些消息可能是通告性消息,也可能是需要系统管理员处理的紧急事件消息。syslog函数是输出这些消息的标准方法,该函数把消息发送给syslogd守护进程。

5.1 syslogd守护进程

Unix中的syslogd守护进程通常由某个系统初始化脚本启动,在系统工作期间一直运行。syslogd在启动时执行以下步骤:

步骤 操作 说明
1 读取配置文件 通常为/etc/syslog.conf,指定日志消息的处理方式
2 创建Unix域套接字 捆绑路径/var/run/log(或/dev/log),用于接收本地日志
3 创建UDP套接字 捆绑端口514(syslog服务端口),用于接收远程日志
4 打开/dev/klog 接收内核错误消息
5 进入无限循环 调用select等待描述符可读,处理日志消息

5.2 syslog函数族

#include <syslog.h>

void openlog(const char *ident, int options, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);

参数详解

函数 参数 说明
openlog ident 程序名称,添加到每条消息前
options 控制syslog行为的标志(如LOG_PIDLOG_CONSLOG_NDELAY等)
facility 标识消息发送进程类型(LOG_DAEMONLOG_USERLOG_LOCAL0等)
syslog priority 设施和严重级别的组合(如`LOG_ERR
format 类似printf的格式化字符串
setlogmask maskpri 设置日志掩码,过滤不需要的日志级别

syslog设施值(facility)

常量 说明
LOG_AUTH 安全/授权消息
LOG_CRON cron守护进程
LOG_DAEMON 系统守护进程
LOG_KERN 内核消息
LOG_LOCAL0 ~ LOG_LOCAL7 本地使用
LOG_LPR 行式打印机系统
LOG_MAIL 邮件系统
LOG_NEWS 新闻系统
LOG_SYSLOG syslogd内部消息
LOG_USER 用户级消息(默认)
LOG_UUCP UUCP系统

syslog严重级别(level)

优先级 常量 说明
最高 LOG_EMERG 系统不可用
LOG_ALERT 需要立即采取行动
LOG_CRIT 临界条件
LOG_ERR 错误条件
LOG_WARNING 警告条件
LOG_NOTICE 正常但重要的条件
LOG_INFO 信息性消息
最低 LOG_DEBUG 调试级别消息

5.3 syslog函数使用示例

#include <syslog.h>

int main(int argc, char *argv[])
{
    // 打开日志连接,消息中自动添加进程ID
    openlog("my_daemon", LOG_PID | LOG_CONS, LOG_DAEMON);

    // 记录不同级别的消息
    syslog(LOG_INFO, "Daemon started successfully");
    syslog(LOG_WARNING, "Disk space low: %d%% used", 95);
    syslog(LOG_ERR, "Cannot open configuration file: %s", config_file);

    // 关闭日志连接
    closelog();

    return 0;
}

5.4 syslog系统架构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                         syslog系统架构                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  应用程序/守护进程                  syslogd守护进程                          │
│  ┌─────────────────┐              ┌─────────────────────────────────────┐ │
│  │ openlog()       │              │          syslogd                     │ │
│  │ syslog()        │──Unix域套接字─→│  ┌─────────────────────────────┐  │ │
│  │ closelog()      │  (/dev/log)   │  │  select循环等待3个描述符     │  │ │
│  └─────────────────┘              │  │  1. Unix域套接字(本地)      │  │ │
│                                   │  │  2. UDP套接字(远程)         │  │ │
│                                   │  │  3. /dev/klog(内核)         │  │ │
│                                   │  └──────────────┬──────────────┘  │ │
│                                   │                 │                  │ │
│                                   │                 ▼                  │ │
│                                   │  ┌─────────────────────────────┐  │ │
│                                   │  │ 根据/etc/syslog.conf处理消息 │  │ │
│                                   │  └──────────────┬──────────────┘  │ │
│                                   └─────────────────┼──────────────────┘ │
│                                                     │                    │
│                                                     ▼                    │
│                                   ┌─────────────────────────────────────┐│
│                                   │ 输出目的地                           ││
│                                   │ • 普通文件(如/var/log/messages)    ││
│                                   │ • 控制台(/dev/console)            ││
│                                   │ • 指定用户                          ││
│                                   │ • 远程syslog服务器                  ││
│                                   └─────────────────────────────────────┘│
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

六、inetd守护进程

6.1 为什么需要inetd?

在传统的Unix系统中,许多网络服务(如Telnet、FTP)都需要独立的服务器程序。这些服务器程序各自创建套接字、绑定端口、调用listen和accept,然后处理连接。如果一个系统需要提供多个服务,就需要同时运行多个服务器程序,每个都占用内存和系统资源。

inetd(Internet超级服务器)解决了这个问题。它是一个特殊的守护进程,负责监听多个网络端口,当请求到达时,启动相应的实际服务器来处理请求。

6.2 inetd的特点

特点 说明
集中管理 一个进程监听所有配置的服务端口
按需启动 仅在请求到达时才启动实际服务器
资源节约 减少系统空闲时的资源消耗
简化编程 由inetd处理普通守护进程的大部分启动细节

6.3 inetd的配置文件

inetd的配置文件是/etc/inetd.conf,每行配置一个服务。典型的配置行格式:

<service_name> <socket_type> <protocol> <wait> <user> <server_program> <server_args>

字段说明

字段 说明 示例
service_name 服务名称,需在/etc/services中定义 telnetftp
socket_type 套接字类型:stream(TCP)或dgram(UDP) stream
protocol 协议:tcpudp tcp
wait wait(单线程服务)或nowait(多线程服务) nowait
user 启动服务的用户ID root
server_program 实际服务器程序路径 /usr/sbin/in.telnetd
server_args 传递给服务器程序的参数 可选

6.4 inetd的工作流程

┌─────────────────────────────────────────────────────────────────────────────┐
│                         inetd工作流程图                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 系统启动                                                                │
│     │                                                                       │
│     ▼                                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ inetd守护进程启动,读取/etc/inetd.conf配置文件                        │   │
│  └─────────────────────────────┬───────────────────────────────────────┘   │
│                                 │                                           │
│                                 ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ inetd为每个配置的服务创建套接字,绑定端口,调用listen                 │   │
│  │                                                                      │   │
│  │  例如:                                                              │   │
│  │  ├─ Telnet 服务:创建套接字,绑定端口23                              │   │
│  │  ├─ FTP 服务:创建套接字,绑定端口21                                 │   │
│  │  └─ 其他服务...                                                     │   │
│  └─────────────────────────────┬───────────────────────────────────────┘   │
│                                 │                                           │
│                                 ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ inetd调用select,同时监听所有套接字                                   │   │
│  └─────────────────────────────┬───────────────────────────────────────┘   │
│                                 │                                           │
│                                 ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 客户端请求到达某个端口(如Telnet的23端口)                            │   │
│  │                                                                      │   │
│  │ inetd检测到套接字可读 → accept()                                    │   │
│  └─────────────────────────────┬───────────────────────────────────────┘   │
│                                 │                                           │
│                                 ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ inetd执行fork()创建子进程                                            │   │
│  │                                                                      │   │
│  │ 子进程:                                                             │   │
│  │ 1. 将已连接套接字复制到标准输入(fd 0)和标准输出(fd 1)             │   │
│  │ 2. 关闭其他文件描述符                                                │   │
│  │ 3. exec执行实际服务器程序(如telnetd)                                │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                 │                                           │
│                                 ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 实际服务器程序从标准输入读取请求,向标准输出写入响应                   │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  💡 设计要点:inetd使服务器程序可以专注于业务逻辑,而无需处理套接字操作    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.5 TCP Wrapper机制

TCP Wrapper(tcpd)是一种访问控制机制,可以实现对主机网络连接的控制。当一个请求到达由inetd管理的服务端口时,inetd将该请求转发给名为tcpd的程序。tcpd根据配置文件/etc/hosts.allow/etc/hosts.deny判断是否允许服务该请求。如果请求被允许,则相应的服务器程序(如ftpd、telnetd)将被启动。

💡 TCP Wrapper的工作原理

  • /etc/hosts.allow:允许访问的规则
  • /etc/hosts.deny:拒绝访问的规则
  • tcpd先检查allow文件,后检查deny文件,匹配第一个规则即停止

七、xinetd——新一代超级守护进程

7.1 xinetd概述

xinetd(eXtended InterNET services daemon)是新一代网络守护进程服务程序,又叫超级守护进程,经常用来管理多种轻量级的Internet服务。它取代了早期的inetd,在Red Hat 7、Mandrake 7.2等发行版中被作为标准服务管理工具。

7.2 xinetd vs inetd对比

对比维度 inetd xinetd
配置文件格式 简单的单行配置 结构化配置(服务块)
访问控制 有限(依赖tcp_wrappers) 内置强大的访问控制
日志功能 基础 完善,支持详细日志
DoS防护 有限 内置防护机制
IPv6支持 有限 原生支持
UDP控制 较弱 完善
RPC支持 较差 仍有限(可通过portmap共存)
资源管理 基础 支持连接数、并发数限制

7.3 xinetd的配置

xinetd的主配置文件通常为/etc/xinetd.conf,服务配置文件放在/etc/xinetd.d/目录下,每个服务一个独立配置文件。

xinetd配置模板

service <service_name>
{
    <attribute> = <value>
    ...
}

常用配置属性

属性 说明 示例
disable 是否禁用服务 yesno
socket_type 套接字类型 stream(TCP)或dgram(UDP)
protocol 协议类型 tcpudp
wait 等待模式 yes(单线程)或no(多线程)
user 运行用户 root
server 服务器程序路径 /usr/sbin/in.telnetd
server_args 服务器程序参数 -c -s /tftpboot
only_from 允许访问的地址 192.168.1.0/24
no_access 禁止访问的地址 10.0.0.0/8
access_times 允许访问的时间段 08:00-17:00
log_type 日志类型 FILE /var/log/service.log
per_source 每个源IP的最大连接数 10
cps 每秒连接数限制 100 2(最多100个/秒,超过等待2秒)
bind 绑定特定网卡 192.168.1.1
instances 最大实例数 50

7.4 xinetd配置示例

# /etc/xinetd.d/telnet 示例配置文件
service telnet
{
    disable         = no
    socket_type     = stream
    protocol        = tcp
    wait            = no
    user            = root
    server          = /usr/sbin/in.telnetd
    server_args     = -h
    only_from       = 192.168.1.0/24
    no_access       = 192.168.1.100
    access_times    = 09:00-17:00
    log_type        = FILE /var/log/telnet.log
    log_on_success  = HOST PID EXIT
    log_on_failure  = HOST USERID
    per_source      = 5
    cps             = 50 10
    instances       = 20
}

7.5 xinetd的功能特点

  • 强大的访问控制:基于IP、时间段、连接数的精细控制
  • 完善的日志功能:记录连接成功与失败的主机信息
  • 有效的DoS防御:限制并发连接数和连接速率
  • 资源管理:限制同时运行的服务器数目
  • IP绑定:将服务绑定到特定的网络接口
  • 代理功能:可作为其他系统的代理

7.6 inetd与xinetd工作流程对比图

┌─────────────────────────────────────────────────────────────────────────────┐
│                   inetd与xinetd工作流程对比                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  inetd流程                            xinetd流程                            │
│                                                                             │
│  读取/etc/inetd.conf                   读取/etc/xinetd.conf                 │
│       │                                     │                               │
│       ▼                                     ▼                               │
│  创建套接字,绑定端口                   包含/etc/xinetd.d/*                   │
│       │                                     │                               │
│       ▼                                     ▼                               │
│  select监听所有套接字                    创建套接字,绑定端口                │
│       │                                     │                               │
│       ▼                                     ▼                               │
│  检测到请求                            select监听所有套接字                 │
│       │                                     │                               │
│       ▼                                     ▼                               │
│  fork子进程                             检测到请求                          │
│       │                                     │                               │
│       ▼                                     ▼                               │
│  子进程执行tcpd                        检查访问控制规则                      │
│       │                                  (only_from/no_access)              │
│       ▼                                     │                               │
│  tcpd检查hosts.allow/deny                  ▼                               │
│       │                                 检查连接限制                         │
│       ▼                                  (per_source/cps)                   │
│  启动实际服务器                              │                               │
│       │                                     ▼                               │
│       │                                 fork子进程                          │
│       │                                     │                               │
│       │                                     ▼                               │
│       │                                 子进程exec实际服务器                │
│       │                                     │                               │
│       │                                     ▼                               │
│       │                                 记录日志                            │
│                                                                             │
│  💡 xinetd在请求处理前增加了多层安全检查,提供更强的安全性和可控性          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

八、常见问题与注意事项

8.1 守护进程编写注意事项

问题 说明 解决方案
工作目录 守护进程可能占用可卸载的文件系统 使用chdir("/")改变工作目录
文件权限掩码 继承的文件创建屏蔽字可能拒绝某些权限 使用umask(0)重置掩码
文件描述符泄漏 继承的打开文件浪费系统资源 关闭所有打开的文件描述符
信号处理 SIGHUP信号可能导致守护进程意外终止 忽略SIGHUP信号或实现配置重载
日志记录 守护进程没有控制终端 使用syslog进行日志记录
单实例运行 防止多个守护进程实例同时运行 创建PID文件,检查进程是否存在

8.2 syslog使用注意事项

注意点 说明
syslogd的UDP套接字 较新的syslogd实现除非管理员明确要求,否则不创建UDP套接字,以避免DoS攻击
日志文件大小限制 syslogd日志文件的最大文件大小不能超过2GB
消息长度限制 长度超过900字节的消息可能会被截短
facility选择 守护进程应使用LOG_DAEMONLOG_LOCAL0-LOG_LOCAL7
优先级使用 合理使用优先级级别,便于日志过滤和监控

8.3 inetd/xinetd使用注意事项

注意点 说明
服务配置文件 修改配置后需要重启inetd/xinetd服务
xinetd.d目录 每个服务独立配置,便于管理和维护
TCP Wrapper inetd支持tcp_wrappers,需要在编译时启用
xinetd的RPC支持 xinetd对RPC服务支持较弱,可通过portmap共存
日志管理 xinetd支持完善的日志功能,应合理配置日志级别
安全配置 使用only_fromno_accesscps等选项增强安全性

8.4 PID文件的最佳实践

守护进程通常会在/var/run目录下创建PID文件(如/var/run/sshd.pid),用于:

  1. 记录守护进程的进程ID
  2. 防止多个守护进程实例同时运行
  3. 便于管理员通过脚本停止或重启服务
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int create_pid_file(const char *pidfile)
{
    char buf[16];
    int fd;
    int pid;

    fd = open(pidfile, O_RDWR | O_CREAT, 0644);
    if (fd < 0)
        return -1;

    // 尝试加锁(检查是否已有实例运行)
    if (lockf(fd, F_TLOCK, 0) < 0) {
        close(fd);
        return -1;  // 已有实例运行
    }

    // 写入当前进程ID
    sprintf(buf, "%d\n", getpid());
    write(fd, buf, strlen(buf));
    return 0;
}

九、本章小结

9.1 核心知识点回顾

知识点 关键要点
守护进程 后台运行且不与控制终端关联的进程,生存期长
进程组 一个或多个进程的集合,同一进程组接受同一终端信号
会话 一个或多个进程组的集合,会话首进程由setsid创建
setsid 创建新会话,使进程成为会话首进程和进程组组长,切断控制终端
daemon_init 本书提供的守护进程初始化函数,包含创建守护进程的完整步骤
syslog 守护进程的标准日志输出方法,将消息发送给syslogd
syslogd 系统日志守护进程,管理所有系统日志消息
inetd 超级服务器,监听多个端口,按需启动实际服务
xinetd 新一代超级守护进程,扩展了inetd的功能
TCP Wrapper 访问控制机制,基于hosts.allow/hosts.deny实现

9.2 守护进程创建步骤总结

1. fork() + 父进程退出
2. setsid() 创建新会话
3. 忽略SIGHUP信号
4. 第二次fork() + 父进程退出
5. chdir("/")
6. umask(0)
7. 关闭所有文件描述符
8. 重定向stdin/stdout/stderr到/dev/null
9. openlog() 打开syslog连接

9.3 本章思维导图

第十三章 守护进程和inetd超级服务器
├── 守护进程基础
│   ├── 定义:后台运行,不与控制终端关联
│   ├── 启动方式:系统脚本/inetd/cron/at/用户终端
│   └── 运行环境:无控制终端,通常以超级用户特权运行
├── 进程组与会话
│   ├── 进程组:多个进程的集合,共享同一终端信号
│   ├── 组长进程:进程组ID等于其进程ID
│   └── 会话:多个进程组的集合,setsid创建新会话
├── 守护进程编写
│   ├── daemon_init函数实现
│   ├── 标准7步骤流程
│   └── PID文件管理
├── syslog日志系统
│   ├── syslogd守护进程
│   ├── openlog/syslog/closelog函数
│   ├── facility(设施)和priority(优先级)
│   └── /etc/syslog.conf配置
├── inetd超级服务器
│   ├── 按需启动服务,节省资源
│   ├── /etc/inetd.conf配置文件
│   ├── TCP Wrapper访问控制
│   └── 工作流程:监听→接收→fork→exec
└── xinetd超级守护进程
    ├── inetd的增强替代品
    ├── /etc/xinetd.conf + /etc/xinetd.d/
    ├── 结构化配置语法
    ├── 增强功能:访问控制、DoS防护、日志管理
    └── 配置属性:only_from, no_access, cps, per_source

十、实用工具与命令

10.1 守护进程管理命令

命令 用途
ps -axj 显示所有进程状态,-x表示显示没有控制终端的进程
kill -HUP <pid> 重新加载守护进程配置(如syslogd)
kill -TERM <pid> 终止守护进程
systemctl start/stop/status systemd系统下管理守护进程
service <name> start/stop SysV系统下管理服务

10.2 查看守护进程示例

# 查看所有守护进程
ps -axj | grep -v TTY

# 查看syslogd守护进程
ps aux | grep syslogd

# 查看inetd/xinetd守护进程
ps aux | grep inetd
ps aux | grep xinetd

# 查看systemd管理的服务
systemctl list-units --type=service

10.3 日志查看命令

# 查看系统日志
tail -f /var/log/messages
tail -f /var/log/syslog

# 查看xinetd日志
tail -f /var/log/xinetd.log

# 使用journalctl查看systemd日志
journalctl -u my-daemon.service

# 使用logger命令向syslog发送测试消息
logger -t "my_test" "This is a test message"

十一、下一章预告

📌 下一篇:《UNIX网络编程》读书笔记(十四):第十四章 高级I/O函数

第十四章将详细讲解:

  1. 套接字超时设置:三种设置I/O操作超时的方法(alarmselectSO_RCVTIMEO/SO_SNDTIMEO
  2. recvsend函数:包含flags参数的增强版I/O函数
  3. readvwritev函数:分散读和集中写,减少系统调用次数
  4. recvmsgsendmsg函数:最通用的I/O函数,支持辅助数据传递
  5. 辅助数据(Ancillary Data):通过辅助数据传递文件描述符和凭据
  6. 排队的数据量ioctlFIONREAD命令获取待读数据量
  7. sockatmark函数:检测是否处于TCP带外数据的标记位置

学习目标:学完第十四章后,你将能够——

  • 为网络I/O操作设置合理的超时机制
  • 使用readv/writev优化文件传输性能
  • 理解辅助数据的传递机制
  • 正确处理TCP带外数据(OOB)

敬请期待!

参考资料

  1. W. Richard Stevens, Bill Fenner, Andrew M. Rudoff. 《UNIX网络编程 卷1:套接字联网API(第3版)》. 北京:人民邮电出版社
  2. UNIX网络编程卷一 学习笔记 第十三章 守护进程和inetd超级服务器,博客园,https://www.cnblogs.com/gblog6/p/17518068.html
  3. 网络编程–守护进程,CSDN,https://blog.csdn.net/weixin_44706647/article/details/124209182
  4. UNP卷一chapter13 daemon和inetd超级服务器,CSDN,https://blog.csdn.net/TT_love9527/article/details/80387043
  5. 守护进程及守护进程输出,阿里云开发者社区,https://developer.aliyun.com/article/206295
  6. 程序员必知:Xinetd超级守护进程,阿里云开发者社区,https://developer.aliyun.com/article/1549180
  7. xinetd_百度百科,https://wapbaike.baidu.com/item/xinetd
  8. daemon(7) — man-pages-zh_CN,https://manpages.opensuse.org/Tumbleweed/man-pages-zh_CN/daemon.7.zh_CN.html
  9. 守护进程编写标准和实现,腾讯云开发者社区,https://cloud.tencent.cn/developer/article/2347811
  10. syslogd 守护程序,IBM,https://www.ibm.com/docs/zh/aix/7.3.0
  11. 《Unix环境高级编程》第13章 守护进程,CSDN
  12. 《UNIX环境高级编程》第9章 进程关系,CSDN

本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐