|

wf5b45bqngh64021294050.gif
3 B& x# F& P/ P& b1 T. A0 ]点击上方蓝色字体,关注我们8 [0 Q% i4 z* _( D3 [; V
0 B" p, I" L* i9 i7 O/ ^6 S3 l
在命令输出中,如果 TTY 一栏显示为问号(?),这表示该进程没有控制终端,通常意味着它是一个守护进程。同时,COMMAND 一栏中用中括号([])括起来的进程表示内核线程。. Z* `0 ?; M E1 P8 z7 Q% W& D
$ p: C: Z. V7 M3 A1 W% K% X6 U这些线程是在内核空间中创建的,没有对应的用户空间代码,因此不具备程序文件名和命令行信息,通常以字母 k 开头,表示它们是内核线程(Kernel)。. u) e4 F6 k8 G: T- r
1
; n5 n9 y+ k' V8 A0 x编写守护进程的步骤8 Q3 u+ e# x& Y) D N/ ]7 W
编写守护进程通常包括以下几个关键步骤,以确保其能够在后台独立运行,并完成预定的任务。8 k. `5 t/ b+ w* g1 b6 q5 N
& }" Z1 V- d& G( C1、创建子进程并终止父进程& |' ^( M5 D# L' k1 f/ A
使用 fork() 创建子进程后,父进程应调用 exit() 终止自身。这一过程实现了以下几点:
1 T' W, M2 ?4 l' O' x# x; W; ~8 P如果守护进程是通过简单的 shell 命令启动,父进程的退出将使 shell 认为命令已执行完毕。子进程继承了父进程的进程组 ID,但它有自己独立的进程 ID,确保子进程不是进程组的组长,为后续调用 setsid() 准备条件。
( N! T5 i* _# Z# T- y {% F/ b5 ] }
2、子进程调用 setsid() 创建会话
9 y7 P g% A/ d) {3 p4 g在子进程中调用 setsid() 是关键步骤。这将:- z. r# J7 k. g% X, t! U% r4 I1 }
创建一个新的会话,子进程成为新会话的首领。创建新的进程组,子进程成为组长。摆脱原有会话、进程组和控制终端的控制,实现完全独立。
1 ^1 t( z9 q) Z" \9 a: P尽管子进程在 fork() 时继承了父进程的控制权,但 setsid() 能确保其完全脱离。
8 ^/ k& V. E, P) I) y. _# D' J5 I& z% I' }# `) ^
3、更改工作目录为根目录
7 I9 M: K" \& u子进程会继承父进程的当前工作目录,而该目录可能会导致文件系统无法卸载。通常,守护进程会将工作目录更改为根目录(/),以避免这种问题。也可以根据需要选择其他目录。9 o( l# j0 O3 K1 h7 L
, _ G5 y1 Z" _& Z4、重设文件权限掩码(umask)
8 p' t; H3 M2 \6 \: b1 e文件权限掩码 umask 控制新建文件的默认权限。由于子进程继承了父进程的 umask,建议将其设置为 0,以确保子进程拥有最大权限,增强守护进程的灵活性。设置 umask 的方法是调用 umask(0)。
* D8 F0 w% N4 h3 _" ~4 N& g8 [) V. L# u1 J/ l
5、关闭不再需要的文件描述符
2 a$ v' g- W/ G; \' W& x. C子进程会继承父进程打开的所有文件描述符,这可能导致不必要的资源消耗。应关闭不再需要的文件描述符,以确保守护进程不再持有任何继承自父进程的描述符,从而减少资源浪费。" ?5 t$ Y" [, m. J8 Q. F
" R7 E& u. G9 Y8 R" c6、将文件描述符 0、1、2 定位到 /dev/null
% @; o/ O3 X0 i- F守护进程的标准输入、标准输出和标准错误通常会重定向到 /dev/null,这样守护进程的输出就不会显示在任何地方,同时也不会试图从交互式用户那里接收输入。/ X9 _) A! I1 h1 Y d I
4 R; \+ @) D; C7 d
7、其他处理:忽略 SIGCHLD 信号
* S+ Q5 Z( l [' h处理 SIGCHLD 信号不是绝对必要的,但对于某些并发服务器进程尤其重要。通过将 SIGCHLD 信号的处理方式设置为 SIG_IGN,可以避免僵尸进程的产生。这样,当子进程结束时,内核将其交给 init 进程处理,减少了父进程的负担,从而提高了服务器的并发性能。
) A- u# ?6 L1 q2 h! R2% |7 z" H- r U5 e
守护进程的使用和案例设计
- a% b# Y9 \* w( |" h& f为了深入理解如何创建和使用守护进程,我们将创建一个多功能的守护进程,具备以下功能:
1 U7 s% W# k- Z k! c* @& L资源监控功能:守护进程每隔 30 秒获取系统的 CPU、内存和磁盘使用信息,并将其写入 /var/log/resource_monitor.log。定时清理功能:每隔 10 分钟,清理 /tmp 目录下的所有文件。信号处理功能:守护进程能够捕获 SIGTERM 信号,安全退出,并能够处理 SIGHUP 信号重新加载配置文件。
1 b. ]1 K; U! e' W) R7 P8 t& f0 n0 d
2.1、案例功能分析. C! f" q4 o* V# B7 d; D5 ^; ?+ A
系统资源监控:+ S& J9 |1 `, |" F0 S
使用系统命令 stat 和 vmstat 来获取 CPU 和内存信息。使用 df 命令获取磁盘使用情况。每次获取的信息都写入 /var/log/resource_monitor.log,便于运维人员检查系统的健康状态。" {/ E! p( ?3 |
o6 B; b: y7 b1 ^' o定时清理任务:
/ ~# \6 `7 U7 Y! Q9 q$ I每隔 10 分钟调用一个函数清理 /tmp 目录下的文件。使用系统函数 unlink() 删除文件。1 |4 \+ e" n2 u+ e! ~! f
/ ^( \2 U) {5 C
信号处理:
3 ]2 u) g8 K$ d4 ~# L/ K捕获 SIGTERM 信号,干净地终止守护进程并进行资源释放。捕获 SIGHUP 信号,重新加载配置文件(如改变日志文件的路径)。
1 Z) ^' y2 n: ?& q* m5 P3 `$ \9 n
7 W& p; m% r* ?" P5 ]2.2、守护进程代码结构
( C- }; b# w3 C0 d" v1 }/ ]' P# J. ndaemonize():负责将进程变为守护进程的常规步骤。monitor_resources():负责监控系统资源并将其写入日志。cleanup_tmp():每隔 10 分钟清理一次 /tmp 目录中的文件。handle_signal():处理 SIGTERM 和 SIGHUP 信号。reload_config():当捕获 SIGHUP 时,重新加载配置文件。
% i) C3 \& j' h+ b+ E. ]3 G( ~7 C$ H
* A& [6 V% |+ @; g. z) _; h; c2.3、代码实现
% T4 l0 ]+ Y {5 h; M% a#define LOG_FILE "/var/log/resource_monitor.log"#define CONFIG_FILE "/etc/daemon_config.conf"#define TMP_DIR "/tmp"% y. W- R. p, s' j3 z6 g& C& Z
// 定义轮询时间#define MONITOR_INTERVAL 30 // 资源监控间隔 30 秒#define CLEANUP_INTERVAL 600 // 清理间隔 10 分钟3 W$ O9 K) _: E5 }, d; l
int keep_running = 1;FILE *log_fp = NULL;
% ]$ P; E+ g: s/ `// 守护进程初始化函数void daemonize() { pid_t pid;( H8 w, V$ g7 t. F6 P( W) \
// 1. 创建子进程并终止父进程 pid = fork(); if (pid 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS); // 父进程退出) c2 U( z5 ~) [7 T4 f9 r$ ?8 i
// 2. 创建新的会话 if (setsid() 0) exit(EXIT_FAILURE);" h' w& z- M7 _, t0 [& ?
// 3. 忽略 SIGCHLD 信号 signal(SIGCHLD, SIG_IGN);1 P% ^" i4 b0 ?/ K+ P/ z
// 4. 再次 fork,防止守护进程重新获得终端 pid = fork(); if (pid 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS);
; r$ \( K* ~# ]6 T& f4 K$ Z# R) d) w& s // 5. 更改工作目录到根目录 chdir("/");
: W6 v" Y) I9 ^+ F( i6 A' d // 6. 重设文件权限掩码 umask(0);3 D) W8 M2 w g8 ^
// 7. 关闭不再需要的文件描述符 close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO);
* v4 Z" b* S' r. u+ K* A. @ // 8. 重定向标准输入、输出、错误到 /dev/null open("/dev/null", O_RDONLY); open("/dev/null", O_WRONLY); open("/dev/null", O_WRONLY);
% Z& ]7 g+ Z5 S // 打开系统日志 openlog("resource_daemon", LOG_PID, LOG_DAEMON);}) W) \/ t) c4 [5 x1 v
// 捕获信号的处理函数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; }}, s' A0 j ^5 L
// 资源监控功能void monitor_resources() { FILE *fp; char buffer[128];' B+ K5 S. z9 m( |
// 记录当前时间 time_t now = time(NULL); fprintf(log_fp, "Timestamp: %s", ctime(&now));
5 W) l# S% |/ w4 F* g: D$ p) D' K // 记录 CPU 和内存使用情况 fp = popen("vmstat 1 2 | tail -1", "r"); if (fp != NULL) { fgets(buffer, sizeof(buffer) - 1, fp); fprintf(log_fp, "CPU/Memory Usage: %s
6 P l2 E* u( g* V- _6 b0 ~( e, P2 f", buffer); pclose(fp); }
: N# p5 `) L' T. t' ~! L // 记录磁盘使用情况 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); }6 ~) @- b8 K& L4 O6 y6 ^
fflush(log_fp); // 确保日志刷新到文件}! q: o; t2 `0 o% \$ r' W
// 定时清理 /tmp 目录void cleanup_tmp() { DIR *dir; struct dirent *entry; char file_path[256];2 J4 G* c2 P! f3 t
dir = opendir(TMP_DIR); if (dir == NULL) { syslog(LOG_ERR, "Failed to open /tmp directory"); return; }# a, }9 f1 z: Z0 j& S3 [
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); } } }; X1 L* z5 ]& k @3 I
closedir(dir);}& C9 V- m2 x. a% B- a
int main() { daemonize();4 g* R: N/ i+ q, X
// 打开日志文件 log_fp = fopen(LOG_FILE, "a"); if (log_fp == NULL) { syslog(LOG_ERR, "Failed to open log file"); exit(EXIT_FAILURE); }* P8 Z R! s$ E( |6 }1 U8 Y7 h
// 捕获信号处理 signal(SIGTERM, handle_signal); // 用于进程关闭 signal(SIGHUP, handle_signal); // 用于重新加载配置" }! e( F1 j2 L; l$ F
time_t last_cleanup = time(NULL);( K' Y b _% e# t3 {* S& @
// 主循环 while (keep_running) { monitor_resources(); // 监控系统资源
7 z3 B' s9 _: F/ s // 检查是否需要清理 tmp 目录 if (difftime(time(NULL), last_cleanup) >= CLEANUP_INTERVAL) { cleanup_tmp(); last_cleanup = time(NULL); }$ w5 O* `: I0 o. e1 d
// 等待 30 秒后继续 sleep(MONITOR_INTERVAL); }
: k& ^# A& K! p2 I# o // 清理资源并退出 if (log_fp) { fclose(log_fp); } closelog();
/ v( x& _5 v& v6 Y" S return 0;}4 L* \; K! \9 E; b& R2 k. Q9 s
2.4、代码详解! h& f% e( E. Z3 L2 J8 k
守护进程初始化 (daemonize):9 I) j) u2 Q+ d. _
将进程变为守护进程,使用了双 fork() 技术,确保进程在后台运行并与终端脱离关系。使用 syslog 系统日志服务记录进程启动、关闭等信息。( L Z& O/ |6 }5 @
- L# O# L: Z& n0 L5 F' i4 d0 ]! {( k信号处理 (handle_signal):2 B/ k$ K" g& v' S' B, u }
通过 signal() 函数捕获 SIGTERM 和 SIGHUP 信号。SIGTERM 信号用于干净地终止守护进程。SIGHUP 信号用于重新加载配置文件,这里模拟了重新打开日志文件的过程。( K- U" g f. b$ O$ O
# y9 j* Q1 j, F. W% _; o
资源监控 (monitor_resources):
% w2 i+ r# N- `, [使用 vmstat 命令监控 CPU 和内存使用情况,df 命令获取磁盘使用状态。每次监控结果都记录到日志文件中。6 ~4 Z2 `: E6 x( F1 V, O9 i' P
3 ]( O( e" ]) x/ ?' f0 \2 N定时清理 (cleanup_tmp):
8 V8 L3 m Y. y5 B每隔 10 分钟清理 /tmp 目录下的文件。仅删除常规文件,忽略目录等。# z3 H$ k C+ z3 C6 p8 L+ n4 G
. r; A, x( w+ ~0 u: n" V主循环:+ ?6 A* i+ f8 I6 c, f6 a
守护进程每 30 秒调用监控和清理函数,保持持续运行状态。
7 l/ c8 [3 \9 m. h* W# v$ C
% Q7 n9 Y) M4 ?0 w6 U# h0 c" O4 M3. ?- w4 g" o* j
编译和运行守护进程. ~( n. u. R$ [- F* e5 h' w
将上述代码保存为 resource_monitor.c,使用以下命令进行编译和运行:+ Z( y/ ]$ P1 i1 v# ]: h2 q
+ {0 G$ x4 l X) S+ F& }
gcc resource_monitor.c -o resource_monitorsudo ./resource_monitor) j. s& T+ c: x, D/ B. u
注意,守护进程需要写入 /var/log/resource_monitor.log 文件,因此需要使用 sudo 权限运行。% ]' f: D+ P; ]- x$ d0 Z; S
4% f$ J3 H- m6 V7 C# q& j* [
检查守护进程& Q e, v8 r! H' G
查看日志文件内容:
1 O1 F3 G" X3 l* m5 ^ V! h$ B- M0 s& M: I
cat /var/log/resource_monitor.log
' D. A# E- v/ d9 s6 @! }% R$ a; R查看守护进程状态:" }% M' a7 s! H; w B
/ Y9 |$ E: D" T( p$ b* Qps -ef | grep resource_monitor& ~! h5 f4 j! t3 [8 ?7 Y1 y5 u
可以使用 kill 命令根据守护进程的 PID 将其终止:
6 d9 s- N4 d& R1 K9 F2 g
9 D2 F" o) @5 {! g( i# bkill
. K# n' n: p! {
sztz10n3dn464021294150.jpg
, Y' w; Z' y% t: M) A
1t5m4nwzwom64021294250.gif
2 Z) I* h0 a6 @3 e+ I. \$ }* e2 D
点击阅读原文,更精彩~ |
|