电子产业一站式赋能平台

PCB联盟网

搜索
查看: 92|回复: 0
收起左侧

Linux守护进程

[复制链接]

724

主题

724

帖子

5176

积分

四级会员

Rank: 4

积分
5176
发表于 2024-11-25 08:03:00 | 显示全部楼层 |阅读模式

wf5b45bqngh64021294050.gif

wf5b45bqngh64021294050.gif
: {% R2 e( A5 |  Z4 i) J1 D. x
点击上方蓝色字体,关注我们( `+ `2 g! L$ R( y
6 L% K9 s# B2 `! m0 n
在命令输出中,如果 TTY 一栏显示为问号(?),这表示该进程没有控制终端,通常意味着它是一个守护进程。同时,COMMAND 一栏中用中括号([])括起来的进程表示内核线程。  |8 G2 T4 l/ t0 a$ E$ k* T
: P% ^/ m; c( u4 V
这些线程是在内核空间中创建的,没有对应的用户空间代码,因此不具备程序文件名和命令行信息,通常以字母 k 开头,表示它们是内核线程(Kernel)。, M7 w4 A, C" G) u
1* p/ I: v! i: B% y) M7 a1 y
编写守护进程的步骤
8 I( R! j; D  _5 t7 _) x编写守护进程通常包括以下几个关键步骤,以确保其能够在后台独立运行,并完成预定的任务。
8 v. N+ v$ l8 M7 Y
7 n( n7 X. r# g0 ~7 z. a1、创建子进程并终止父进程
& ~- [; c# C# x6 ^) z* k* q使用 fork() 创建子进程后,父进程应调用 exit() 终止自身。这一过程实现了以下几点:! M4 @6 Z1 K: S/ Z8 n
  • 如果守护进程是通过简单的 shell 命令启动,父进程的退出将使 shell 认为命令已执行完毕。
  • 子进程继承了父进程的进程组 ID,但它有自己独立的进程 ID,确保子进程不是进程组的组长,为后续调用 setsid() 准备条件。
    9 q' d) X5 L; M" ^- J& k* ?2 ^
    $ T  C  f3 N0 l& J# {! p; l/ V# `
    2、子进程调用 setsid() 创建会话
    5 \2 e: @6 j, S0 ~; L0 Z$ \在子进程中调用 setsid() 是关键步骤。这将:
    / @7 t( r' h" S% l9 y8 E2 N
  • 创建一个新的会话,子进程成为新会话的首领。
  • 创建新的进程组,子进程成为组长。
  • 摆脱原有会话、进程组和控制终端的控制,实现完全独立。# R: |8 p4 q" D3 b; J" Z; p
    尽管子进程在 fork() 时继承了父进程的控制权,但 setsid() 能确保其完全脱离。
    - P! f  P/ V0 |$ s+ H

    7 G# y" |3 T; t' m, ~, b1 x( h  Y: M3、更改工作目录为根目录
    ' r5 P  y1 @  B, l子进程会继承父进程的当前工作目录,而该目录可能会导致文件系统无法卸载。通常,守护进程会将工作目录更改为根目录(/),以避免这种问题。也可以根据需要选择其他目录。+ W% u- x' d# }% |( n5 N

    . i, E+ C) e$ M! W4、重设文件权限掩码(umask): \: M7 g! P, \. q% C1 `) N
    文件权限掩码 umask 控制新建文件的默认权限。由于子进程继承了父进程的 umask,建议将其设置为 0,以确保子进程拥有最大权限,增强守护进程的灵活性。设置 umask 的方法是调用 umask(0)。% Q% l3 i  R' U7 Z$ L
    - N3 `5 a) w; x4 t
    5、关闭不再需要的文件描述符
    3 _- o' P( J# B- o) ]子进程会继承父进程打开的所有文件描述符,这可能导致不必要的资源消耗。应关闭不再需要的文件描述符,以确保守护进程不再持有任何继承自父进程的描述符,从而减少资源浪费。
    1 K( n# T( F& E+ @# C6 u
    2 U7 W9 E* M' p1 w1 ]3 u2 F- h6、将文件描述符 0、1、2 定位到 /dev/null
    " W" L; t* k* Q) i8 f* H4 \" u守护进程的标准输入、标准输出和标准错误通常会重定向到 /dev/null,这样守护进程的输出就不会显示在任何地方,同时也不会试图从交互式用户那里接收输入。9 v4 r5 r6 I. X+ H$ C$ {

    / ?8 q; A: j: R6 j7、其他处理:忽略 SIGCHLD 信号
    8 l$ ]" Q2 _  ~. ]$ u" H9 b% {处理 SIGCHLD 信号不是绝对必要的,但对于某些并发服务器进程尤其重要。通过将 SIGCHLD 信号的处理方式设置为 SIG_IGN,可以避免僵尸进程的产生。这样,当子进程结束时,内核将其交给 init 进程处理,减少了父进程的负担,从而提高了服务器的并发性能。
    0 U1 t9 A' W8 F2
    . M  p0 `7 a: {8 o; W守护进程的使用和案例设计: [/ Z2 v$ z( m' w" l
    为了深入理解如何创建和使用守护进程,我们将创建一个多功能的守护进程,具备以下功能:
    ; a! g9 e7 ^, \; q
  • 资源监控功能:守护进程每隔 30 秒获取系统的 CPU、内存和磁盘使用信息,并将其写入 /var/log/resource_monitor.log。
  • 定时清理功能:每隔 10 分钟,清理 /tmp 目录下的所有文件。
  • 信号处理功能:守护进程能够捕获 SIGTERM 信号,安全退出,并能够处理 SIGHUP 信号重新加载配置文件。1 a* v: g$ B: C( Z  N
    6 n8 c6 C9 P  T5 x
    2.1、案例功能分析
    * [4 O, [: q# t系统资源监控( G' M5 d$ b/ z; _/ J1 u1 R, r
  • 使用系统命令 stat 和 vmstat 来获取 CPU 和内存信息。
  • 使用 df 命令获取磁盘使用情况。
  • 每次获取的信息都写入 /var/log/resource_monitor.log,便于运维人员检查系统的健康状态。: R1 M. {9 F: Y! I$ j3 X( {+ Z

    ; B" ~$ p# P9 L  V# m2 R定时清理任务
    ( u7 v! R2 J- p* J
  • 每隔 10 分钟调用一个函数清理 /tmp 目录下的文件。
  • 使用系统函数 unlink() 删除文件。
      O, z; C! @/ r6 K- c

    / O; C9 ?" @7 ?! P0 |  T信号处理
    7 s" v8 Y$ `) U  o* r/ X% G7 e7 U
  • 捕获 SIGTERM 信号,干净地终止守护进程并进行资源释放。
  • 捕获 SIGHUP 信号,重新加载配置文件(如改变日志文件的路径)。
    3 m! `! U3 ~' E1 S" {4 n" `3 ?

    & L; M+ Z$ A1 g+ h7 s2.2、守护进程代码结构" s- L+ k+ Q8 ?8 D" v; X
  • daemonize():负责将进程变为守护进程的常规步骤。
  • monitor_resources():负责监控系统资源并将其写入日志。
  • cleanup_tmp():每隔 10 分钟清理一次 /tmp 目录中的文件。
  • handle_signal():处理 SIGTERM 和 SIGHUP 信号。
  • reload_config():当捕获 SIGHUP 时,重新加载配置文件。! \4 {6 ]) {- N' ?. L, V( X4 A2 \7 O
    2 Z( l' v1 V/ i" b9 p
    2.3、代码实现
    0 u6 t; O  o# q& ], c
  • #define LOG_FILE "/var/log/resource_monitor.log"#define CONFIG_FILE "/etc/daemon_config.conf"#define TMP_DIR "/tmp"; a! [2 g* r- B3 `! |7 I7 s
    // 定义轮询时间#define MONITOR_INTERVAL 30  // 资源监控间隔 30 秒#define CLEANUP_INTERVAL 600 // 清理间隔 10 分钟1 E6 _! K  r' |' B! T8 W
    int keep_running = 1;FILE *log_fp = NULL;
    % e' R- X6 s$ c3 f; Q$ Z0 C// 守护进程初始化函数void daemonize() {    pid_t pid;3 o9 N0 b# A( e; T' h. v6 X
        // 1. 创建子进程并终止父进程    pid = fork();    if (pid 0) exit(EXIT_FAILURE);    if (pid > 0) exit(EXIT_SUCCESS);  // 父进程退出' n3 w% r8 G0 L0 ~4 ]0 I* `5 F3 \, {
        // 2. 创建新的会话    if (setsid() 0) exit(EXIT_FAILURE);% b& i$ d% b# ^0 F1 A5 M
        // 3. 忽略 SIGCHLD 信号    signal(SIGCHLD, SIG_IGN);
    4 r0 C# P  h: e9 \( H% \    // 4. 再次 fork,防止守护进程重新获得终端    pid = fork();    if (pid 0) exit(EXIT_FAILURE);    if (pid > 0) exit(EXIT_SUCCESS);
    , u/ f: r+ V$ u7 {2 z    // 5. 更改工作目录到根目录    chdir("/");, l8 b" C5 d1 _- y+ |" t3 i
        // 6. 重设文件权限掩码    umask(0);3 R5 X3 f+ l! T+ C( o% o, n; z
        // 7. 关闭不再需要的文件描述符    close(STDIN_FILENO);    close(STDOUT_FILENO);    close(STDERR_FILENO);
    6 }' L& H- l( _    // 8. 重定向标准输入、输出、错误到 /dev/null    open("/dev/null", O_RDONLY);    open("/dev/null", O_WRONLY);    open("/dev/null", O_WRONLY);
    # @( i- Y# A% H% K    // 打开系统日志    openlog("resource_daemon", LOG_PID, LOG_DAEMON);}
    1 }# d7 h- E- {2 x% ?! S// 捕获信号的处理函数void handle_signal(int signal) {    switch (signal) {        case SIGHUP:            syslog(LOG_INFO, "Reloading configuration file...");            // 重新加载配置文件            if (log_fp) {                fclose(log_fp);            }            log_fp = fopen(LOG_FILE, "a");            if (log_fp == NULL) {                syslog(LOG_ERR, "Failed to open log file");                exit(EXIT_FAILURE);            }            break;        case SIGTERM:            syslog(LOG_INFO, "Daemon is shutting down...");            if (log_fp) {                fclose(log_fp);            }            closelog();            keep_running = 0;  // 设置标志位,结束主循环            break;    }}
    " U* t3 V, F6 H& ~6 j$ a6 h' b- p// 资源监控功能void monitor_resources() {    FILE *fp;    char buffer[128];
    / ?! a9 u7 e: K2 {0 P    // 记录当前时间    time_t now = time(NULL);    fprintf(log_fp, "Timestamp: %s", ctime(&now));
    " m$ z4 h3 @2 T    // 记录 CPU 和内存使用情况    fp = popen("vmstat 1 2 | tail -1", "r");    if (fp != NULL) {        fgets(buffer, sizeof(buffer) - 1, fp);        fprintf(log_fp, "CPU/Memory Usage: %s4 m, f+ r3 F6 ^3 t: L$ n1 c
    ", buffer);        pclose(fp);    }. b( h, f+ h$ _, Q2 J
        // 记录磁盘使用情况    fp = popen("df -h /", "r");    if (fp != NULL) {        while (fgets(buffer, sizeof(buffer) - 1, fp) != NULL) {            fprintf(log_fp, "Disk Usage: %s", buffer);        }        pclose(fp);    }
    1 s4 ], N5 z& H& `8 m' I    fflush(log_fp);  // 确保日志刷新到文件}
    * |, U1 Q  y1 ^: K// 定时清理 /tmp 目录void cleanup_tmp() {    DIR *dir;    struct dirent *entry;    char file_path[256];
    . I9 I4 y- ]; n8 S    dir = opendir(TMP_DIR);    if (dir == NULL) {        syslog(LOG_ERR, "Failed to open /tmp directory");        return;    }
    4 x' s  ~4 }* ~0 Z9 }2 z( R    while ((entry = readdir(dir)) != NULL) {        if (entry->d_type == DT_REG) {  // 只删除常规文件            snprintf(file_path, sizeof(file_path), "%s/%s", TMP_DIR, entry->d_name);            if (unlink(file_path) == 0) {                syslog(LOG_INFO, "Deleted file: %s", file_path);            } else {                syslog(LOG_ERR, "Failed to delete file: %s", file_path);            }        }    }( ?0 C( a9 ]" _  x1 u! F2 D. ^
        closedir(dir);}* a5 y; k6 `/ Z7 o! o1 Y
    int main() {    daemonize();: N' k& t: s" I- @
        // 打开日志文件    log_fp = fopen(LOG_FILE, "a");    if (log_fp == NULL) {        syslog(LOG_ERR, "Failed to open log file");        exit(EXIT_FAILURE);    }
    ) g; b5 D# w+ y  e5 r    // 捕获信号处理    signal(SIGTERM, handle_signal);  // 用于进程关闭    signal(SIGHUP, handle_signal);   // 用于重新加载配置
    / s1 t1 G: m; q. E* D6 _    time_t last_cleanup = time(NULL);
    4 S& x0 T: N' Q2 Q. L( t9 `. b) O3 N    // 主循环    while (keep_running) {        monitor_resources();  // 监控系统资源0 ^' _% ~, t4 k7 U1 D9 W+ k
            // 检查是否需要清理 tmp 目录        if (difftime(time(NULL), last_cleanup) >= CLEANUP_INTERVAL) {            cleanup_tmp();            last_cleanup = time(NULL);        }
    $ Q2 S/ q4 _4 M        // 等待 30 秒后继续        sleep(MONITOR_INTERVAL);    }3 v+ N4 y; J" y+ c' R
        // 清理资源并退出    if (log_fp) {        fclose(log_fp);    }    closelog();
    9 Q. {% }8 c0 q* X" F. B, ?  U5 R6 t    return 0;}, d8 k0 {: [2 V, K: F
    2.4、代码详解) O* k  D5 N1 e0 }4 p( }
    守护进程初始化 (daemonize)
    : w$ b/ {7 `/ t- K6 q  q7 Q
  • 将进程变为守护进程,使用了双 fork() 技术,确保进程在后台运行并与终端脱离关系。
  • 使用 syslog 系统日志服务记录进程启动、关闭等信息。7 q+ @, i  l" \6 D7 N: l6 ~
    / n( V7 Q% o6 q8 f9 F
    信号处理 (handle_signal)
    6 \6 R% _- U+ \+ W
  • 通过 signal() 函数捕获 SIGTERM 和 SIGHUP 信号。
  • SIGTERM 信号用于干净地终止守护进程。
  • SIGHUP 信号用于重新加载配置文件,这里模拟了重新打开日志文件的过程。. e+ E2 P; A" p2 h4 j2 n
    $ b( }3 K9 P/ [7 T# l
    资源监控 (monitor_resources)4 d$ r' p) Z; _2 ~! X8 m
  • 使用 vmstat 命令监控 CPU 和内存使用情况,df 命令获取磁盘使用状态。
  • 每次监控结果都记录到日志文件中。/ u8 w! y- A, R. n4 ]  t
    1 k- i4 O; c- M- |3 N5 @
    定时清理 (cleanup_tmp)
    7 C  h8 o2 N" q: M" ?" |) i
  • 每隔 10 分钟清理 /tmp 目录下的文件。
  • 仅删除常规文件,忽略目录等。) C3 w' X. Q# [: V, t2 ]
    $ l; _6 a* \5 g: O. o: [
    主循环
    # @3 C) X" x/ }7 X: C
  • 守护进程每 30 秒调用监控和清理函数,保持持续运行状态。
    + J, [3 d8 s: q
    * S5 b, g) y. N% J
    3+ h' e/ x. H& Q" K
    编译和运行守护进程, A6 h$ C, K8 Q" W5 m- P$ I" u( b
    将上述代码保存为 resource_monitor.c,使用以下命令进行编译和运行:. M: e4 O- J: k

    7 N. h9 r' h7 m4 s" z3 g8 `
  • gcc resource_monitor.c -o resource_monitorsudo ./resource_monitor2 G$ o, [& K( W( `% p
    注意,守护进程需要写入 /var/log/resource_monitor.log 文件,因此需要使用 sudo 权限运行。# |9 `4 u9 M; M+ c5 T3 j
    4
    : g0 r. s- J9 M! w5 z1 Z2 M检查守护进程3 k4 s/ j' k4 H! r6 }
    查看日志文件内容:) d' l8 H, b4 c$ f  k2 x. h6 I' W

    : T3 @5 f+ k" l3 J
  • cat /var/log/resource_monitor.log
    % _1 O" T* r; {1 Q查看守护进程状态:
    , [$ ]" I% P% I% m) B- @; a  k! n, d, B! }. s7 h& L
  • ps -ef | grep resource_monitor
    . F( t+ `" r, T% f) _可以使用 kill 命令根据守护进程的 PID 将其终止:
    0 Q, G9 @1 c# A* x
    ! s) @! t. h% g, [
  • kill
    . y9 k; V  b# M0 S" ]8 ]

    sztz10n3dn464021294150.jpg

    sztz10n3dn464021294150.jpg
    . e2 ~3 g% e' C! W

    1t5m4nwzwom64021294250.gif

    1t5m4nwzwom64021294250.gif
    - H0 {$ @; Q$ X/ ?0 w
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表