主要探讨四个主题:信号处理、守护进程、进程组和会话。这些都是Linux/Unix进程管理中的重要概念。下面将分别深入探讨它们的工作原理和机制。
一、信号处理(Signal)
1.1 信号的基本概念
信号是Linux系统中用于进程间通信的一种机制,它是一种异步的通知,用来通知进程发生了某个事件。信号可以由内核、其他进程或进程自身发送。
常见信号列表:
信号编号 信号名 默认动作 说明
1 SIGHUP 终止 终端挂起或控制进程终止
2 SIGINT 终止 来自键盘的中断(Ctrl+C)
3 SIGQUIT 终止+核心转储 来自键盘的退出(Ctrl+\)
9 SIGKILL 终止 强制终止信号,不可捕获或忽略
10 SIGUSR1 终止 用户自定义信号1
12 SIGUSR2 终止 用户自定义信号2
14 SIGALRM 终止 由alarm函数设置的定时器超时
15 SIGTERM 终止 终止信号,可被捕获
17 SIGCHLD 忽略 子进程状态改变
20 SIGTSTP 停止 来自键盘的停止(Ctrl+Z)
1.2 信号的处理机制
当信号发生时,内核会在进程的进程表项中设置一个标志位,这个过程称为“递送”。信号产生和递送之间的时间间隔内,信号是“未决”的。
进程可以选择以下三种方式处理信号:
忽略信号:除了SIGKILL和SIGSTOP,其他信号都可以被忽略。
捕捉信号:为信号指定一个处理函数,当信号发生时,执行该处理函数。
执行默认动作:大多数信号的默认动作是终止进程。
1.3 信号处理函数
使用sigaction函数可以设置信号处理方式。示例:
c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signo) {
if (signo == SIGINT) {
printf(“Received SIGINT\n”);
} else if (signo == SIGTERM) {
printf(“Received SIGTERM\n”);
}
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// 注册信号处理函数
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
// 无限循环,等待信号
while (1) {
pause();
}
return 0;
}
1.4 信号的阻塞与未决
进程可以阻塞信号,被阻塞的信号将保持在未决状态,直到进程解除阻塞。使用sigprocmask可以设置进程的信号屏蔽字。
c
#include <signal.h>
int main() {
sigset_t newset, oldset;
// 初始化信号集
sigemptyset(&newset);
sigaddset(&newset, SIGINT); // 阻塞SIGINT
// 设置信号屏蔽字
sigprocmask(SIG_BLOCK, &newset, &oldset);
// 在这段代码中,SIGINT被阻塞
// 即使按下Ctrl+C,也不会中断
// 恢复原来的信号屏蔽字
sigprocmask(SIG_SETMASK, &oldset, NULL);
return 0;
}
二、守护进程(Daemon)
2.1 守护进程的特点
守护进程是运行在后台的一种特殊进程,它独立于控制终端,通常周期性地执行某种任务或等待处理某些事件。守护进程通常在系统启动时开始运行,直到系统关闭。
守护进程的特点:
在后台运行
脱离控制终端,避免被终端信号打断
独立于用户会话,通常以root权限运行
通常用来提供服务
2.2 编写守护进程的步骤
创建子进程,父进程退出:使子进程成为孤儿进程,被init进程收养。
在子进程中创建新会话:调用setsid,使子进程成为会话组长,脱离终端。
改变当前工作目录:通常改为根目录,避免占用可卸载的文件系统。
重设文件权限掩码:umask(0),确保守护进程创建文件时有足够的权限。
关闭不需要的文件描述符:释放从父进程继承的打开文件。
处理信号:通常忽略某些信号,如SIGHUP。
2.3 守护进程示例
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void daemonize() {
pid_t pid;
// 1. 创建子进程,父进程退出
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
} else if (pid > 0) {
exit(0); // 父进程退出
}
// 2. 在子进程中创建新会话
if (setsid() < 0) {
perror("setsid");
exit(1);
}
// 3. 改变当前工作目录为根目录
if (chdir("/") < 0) {
perror("chdir");
exit(1);
}
// 4. 重设文件权限掩码
umask(0);
// 5. 关闭标准输入、输出、错误输出
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 6. 处理信号
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
}
int main() {
daemonize();
// 守护进程的具体工作
while (1) {
// 在这里执行守护进程的任务
// 例如,每隔一段时间执行某个操作
sleep(10);
}
return 0;
}
三、进程组和会话
3.1 进程组(Process Group)
进程组是一个或多个进程的集合,每个进程组有一个唯一的进程组ID。进程组中的进程通常与同一个作业(job)相关联,可以接收相同的信号。
每个进程属于一个进程组。
进程组ID等于组长的进程ID。
组长进程可以创建新的进程组,并将其他进程加入该组。
3.2 会话(Session)
会话是一个或多个进程组的集合,通常由一次登录形成一个会话。每个会话有一个唯一的会话ID,并且有一个会话首进程(session leader),即创建会话的进程。
每个会话可以有一个控制终端(controlling terminal)。
与控制终端建立连接的会话首进程称为控制进程(controlling process)。
一个会话中的进程组可以分为一个前台进程组和多个后台进程组。
3.3 相关函数
setsid():创建一个新会话,并成为会话首进程和进程组长,同时脱离控制终端。
setpgid(pid_t pid, pid_t pgid):将指定进程加入指定进程组,或创建新的进程组。
getpgrp():返回当前进程的进程组ID。
getsid(pid_t pid):返回指定进程的会话ID。
3.4 示例:创建新会话和进程组
c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
// 创建子进程
pid = fork();
if (pid < 0) {
perror("fork");
return 1;
} else if (pid == 0) {
// 子进程中创建新会话
if (setsid() < 0) {
perror("setsid");
return 1;
}
printf("Child: PID=%d, PGID=%d, SID=%d\n",
getpid(), getpgrp(), getsid(0));
// 子进程的工作
sleep(10);
} else {
// 父进程
printf("Parent: PID=%d, PGID=%d, SID=%d\n",
getpid(), getpgrp(), getsid(0));
sleep(5);
}
return 0;
}
四、深入探讨工作原理和机制
4.1 信号处理的底层机制
当信号被发送给进程时,内核会在进程的进程控制块(PCB)中设置一个标志位。当进程从内核态返回到用户态时,内核会检查进程的未决信号,并调用相应的信号处理函数。信号处理函数在用户态执行,但内核为其设置了特殊的栈帧,使得信号处理函数返回后,进程可以继续原来的执行。
4.2 守护进程与系统服务
现代Linux系统使用systemd来管理守护进程。systemd提供了更强大的服务管理功能,包括依赖关系、并行启动、按需启动等。编写systemd服务需要创建一个.service文件,放在/etc/systemd/system/目录下。
4.3 进程组和会话与终端的关系
终端驱动程序会将输入和信号发送给前台进程组。当用户按下Ctrl+C时,终端驱动程序会向前台进程组发送SIGINT信号。同样,Ctrl+Z发送SIGTSTP信号。这使得用户可以通过终端控制作业的执行。
4.4 实际应用:实现一个简单的shell
一个简单的shell需要管理作业(jobs),包括前台作业和后台作业。这需要使用进程组和会话。当shell运行一个前台作业时,它会将作业放入一个新的进程组,并设置为前台进程组。当作业结束时,shell会重新获得控制权。
下面对进程信号处理、守护进程、进程组与会话:深度解析
一、信号处理(Signal)深度解析
1.1 信号的本质与分类
1.1.1 信号的完整生命周期
信号生命周期(从产生到处理):
┌─────────────────────────────────────────┐
│ 信号产生(Generation) │
│ 1. 硬件异常(除零、内存访问错误) │
│ 2. 软件事件(定时器到期、子进程终止) │
│ 3. 终端事件(Ctrl+C, Ctrl+Z) │
│ 4. 其他进程发送(kill, sigqueue) │
└───────────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 信号递送(Delivery) │
│ 内核检查目标进程的信号屏蔽字 │
│ 如果信号被阻塞,标记为未决(Pending) │
│ 如果未被阻塞,立即递送 │
└───────────────────┬─────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 信号处理(Handling) │
│ 1. 忽略(Ignore) │
│ 2. 捕获(Catch):执行用户处理函数 │
│ 3. 默认(Default):执行系统默认动作 │
└─────────────────────────────────────────┘
关键状态:
- 产生(Generated):信号已发生但未处理
- 未决(Pending):信号已产生但被阻塞
- 递送(Delivered):信号已发送给进程
- 捕获(Caught):信号被用户处理函数处理
1.1.2 Linux信号分类表
// 标准信号(1-31)和实时信号(34-64)
#define _NSIG 65 // 信号总数
// 信号宏定义(部分)
#define SIGHUP 1 // 终端挂起或控制进程终止
#define SIGINT 2 // 键盘中断(Ctrl+C)
#define SIGQUIT 3 // 键盘退出(Ctrl+\)
#define SIGILL 4 // 非法指令
#define SIGTRAP 5 // 跟踪/断点陷阱
#define SIGABRT 6 // 异常终止
#define SIGBUS 7 // 总线错误
#define SIGFPE 8 // 浮点异常
#define SIGKILL 9 // 强制终止(不可捕获)
#define SIGUSR1 10 // 用户自定义信号1
#define SIGSEGV 11 // 段错误
#define SIGUSR2 12 // 用户自定义信号2
#define SIGPIPE 13 // 管道破裂
#define SIGALRM 14 // 定时器超时
#define SIGTERM 15 // 终止信号
#define SIGCHLD 17 // 子进程状态改变
#define SIGCONT 18 // 继续执行(如果停止)
#define SIGSTOP 19 // 停止进程(不可捕获)
#define SIGTSTP 20 // 终端停止信号(Ctrl+Z)
#define SIGTTIN 21 // 后台进程尝试读终端
#define SIGTTOU 22 // 后台进程尝试写终端
#define SIGURG 23 // 紧急数据到达socket
#define SIGXCPU 24 // CPU时间超限
#define SIGXFSZ 25 // 文件大小超限
#define SIGVTALRM 26 // 虚拟定时器超时
#define SIGPROF 27 // 性能分析定时器超时
#define SIGWINCH 28 // 窗口大小改变
#define SIGIO 29 // I/O就绪
#define SIGPWR 30 // 电源故障
#define SIGSYS 31 // 非法系统调用
// 实时信号(34-64)
#define SIGRTMIN 34
#define SIGRTMAX 64
// 信号标志
#define SA_NOCLDSTOP 0x00000001 // SIGCHLD不报告停止
#define SA_NOCLDWAIT 0x00000002 // 不创建僵尸进程
#define SA_SIGINFO 0x00000004 // 使用sa_sigaction而不是sa_handler
#define SA_ONSTACK 0x08000000 // 使用备用信号栈
#define SA_RESTART 0x10000000 // 系统调用自动重启
#define SA_NODEFER 0x40000000 // 不阻塞当前信号
#define SA_RESETHAND 0x80000000 // 处理一次后恢复默认
1.2 信号处理的内核机制
1.2.1 内核中的信号数据结构
// Linux内核信号相关数据结构
// task_struct中的信号相关字段
struct task_struct {
// ... 其他字段
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked; // 阻塞的信号集
sigset_t real_blocked;
struct sigpending pending; // 未决信号队列
// ... 其他字段
};
// 信号描述符(每个进程共享)
struct signal_struct {
atomic_t count; // 引用计数
struct sigpending shared_pending; // 共享未决信号
// 资源限制
unsigned long rlim[RLIM_NLIMITS];
// 进程组和会话
struct pid *leader_pid;
struct tty_struct *tty;
// 统计信息
unsigned long long cutime, cstime; // 子进程用户/系统时间
unsigned long long utime, stime; // 本进程用户/系统时间
// 信号处理
int group_exit_code;
int group_stop_count;
unsigned int flags;
};
// 信号处理描述符
struct sighand_struct {
atomic_t count; // 引用计数
struct k_sigaction action[_NSIG]; // 信号处理数组
spinlock_t siglock; // 保护信号处理
wait_queue_head_t signalfd_wqh;
};
// 未决信号队列
struct sigpending {
struct list_head list; // 未决信号链表
sigset_t signal; // 信号位图
};
// 内核信号动作
struct k_sigaction {
struct sigaction sa;
#ifdef __ARCH_HAS_KA_RESTORER
__sigrestore_t ka_restorer;
#endif
};
// 用户空间sigaction结构
struct sigaction {
union {
__sighandler_t sa_handler; // 简单处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); // 扩展处理函数
} __sigaction_handler;
unsigned long sa_flags;
__sigrestore_t sa_restorer;
sigset_t sa_mask; // 执行处理函数时阻塞的信号
};
1.2.2 信号递送的核心流程
// 内核信号递送核心代码(简化版)
void signal_deliver(struct task_struct *tsk, struct k_sigaction *ka,
int sig, siginfo_t *info, sigset_t *oldset) {
struct pt_regs *regs = task_pt_regs(tsk);
// 1. 设置用户栈帧
if (ka->sa.sa_flags & SA_SIGINFO) {
// 使用siginfo版本
if (setup_rt_frame(sig, ka, info, oldset, regs) < 0)
return;
} else {
// 简单版本
if (setup_frame(sig, ka, oldset, regs) < 0)
return;
}
// 2. 设置信号处理函数地址
regs->ARM_ip = (unsigned long)ka->sa.sa_handler;
// 3. 设置返回地址(从信号处理返回后执行的地址)
regs->ARM_lr = (unsigned long)ka->sa.sa_restorer;
// 4. 设置程序计数器指向信号处理函数
regs->ARM_pc = regs->ARM_ip;
// 5. 清除阻塞标志(除非指定SA_NODEFER)
if (!(ka->sa.sa_flags & SA_NODEFER)) {
sigdelset(&tsk->blocked, sig);
}
// 6. 如果指定SA_ONESHOT,恢复默认处理
if (ka->sa.sa_flags & SA_RESETHAND) {
ka->sa.sa_handler = SIG_DFL;
}
}
// 设置信号栈帧(x86-64架构示例)
int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
sigset_t *set, struct pt_regs *regs) {
struct rt_sigframe __user *frame;
void __user *restorer;
int err = 0;
// 在用户栈上分配帧空间
frame = get_sigframe(ka, regs, sizeof(*frame));
if (!access_ok(frame, sizeof(*frame)))
return -EFAULT;
// 设置ucontext
err |= __put_user(0, &frame->uc.uc_flags);
err |= __put_user(NULL, &frame->uc.uc_link);
err |= save_altstack(&frame->uc.uc_stack, regs->sp);
// 保存寄存器状态
err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, set->sig[0]);
// 保存siginfo
err |= copy_siginfo_to_user(&frame->info, info);
// 设置返回地址
restorer = ka->sa.sa_restorer;
if (!restorer)
restorer = VDSO_SYMBOL(current->mm->context.vdso, rt_sigreturn);
err |= __put_user(restorer, &frame->pretcode);
// 设置用户栈指针和指令指针
regs->sp = (unsigned long)frame;
regs->ip = (unsigned long)ka->sa.sa_handler;
regs->ax = sig; // 信号编号作为第一个参数
regs->si = (unsigned long)&frame->info; // siginfo作为第二个参数
regs->dx = (unsigned long)&frame->uc; // ucontext作为第三个参数
return err;
}
1.3 高级信号处理技术
1.3.1 信号掩码与阻塞
// 信号掩码操作完整示例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void print_signal_mask(const char *msg) {
sigset_t curr_mask;
// 获取当前信号掩码
if (sigprocmask(SIG_BLOCK, NULL, &curr_mask) == -1) {
perror("sigprocmask");
return;
}
printf("%s: ", msg);
for (int i = 1; i < NSIG; i++) {
if (sigismember(&curr_mask, i)) {
printf("%d ", i);
}
}
printf("\n");
}
int main() {
sigset_t new_mask, old_mask, pending_mask;
printf("PID: %d\n", getpid());
// 初始化信号集
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGINT);
sigaddset(&new_mask, SIGQUIT);
sigaddset(&new_mask, SIGUSR1);
// 阻塞SIGINT, SIGQUIT, SIGUSR1
if (sigprocmask(SIG_BLOCK, &new_mask, &old_mask) == -1) {
perror("sigprocmask");
return 1;
}
print_signal_mask("Blocked signals");
// 睡眠期间,发送的信号将被阻塞
printf("Sleeping for 10 seconds...\n");
printf("Try sending signals: kill -INT %d\n", getpid());
printf(" kill -QUIT %d\n", getpid());
printf(" kill -USR1 %d\n", getpid());
sleep(10);
// 检查未决信号
sigpending(&pending_mask);
printf("Pending signals: ");
for (int i = 1; i < NSIG; i++) {
if (sigismember(&pending_mask, i)) {
printf("%d ", i);
}
}
printf("\n");
// 恢复原来的信号掩码
if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
perror("sigprocmask");
return 1;
}
print_signal_mask("Restored signal mask");
// 现在信号将被递送
printf("Signals will be delivered now...\n");
sleep(5);
return 0;
}
1.3.2 信号处理函数的安全实践
// 异步信号安全的信号处理函数
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
// 全局标志,由信号处理函数设置
volatile sig_atomic_t flag = 0;
// 异步信号安全的处理函数
void signal_handler(int sig) {
// 只设置标志,不做其他事情
flag = 1;
// 可以安全地使用write
const char msg[] = "Signal received\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}
// 非安全的处理函数(演示问题)
void unsafe_handler(int sig) {
// printf不是异步信号安全的!
printf("Received signal %d\n", sig); // 危险!
// malloc/free也不是安全的
char *buf = malloc(100); // 危险!
if (buf) {
sprintf(buf, "Signal %d", sig); // sprintf也不是安全的
free(buf); // 危险!
}
}
// 主程序:安全的信号处理模式
int main() {
struct sigaction sa;
// 设置信号处理
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
// 主循环:检查标志而不是在信号处理函数中做复杂工作
while (1) {
// 阻塞所有信号以避免竞态条件
sigset_t all_signals, old_signals;
sigfillset(&all_signals);
sigprocmask(SIG_BLOCK, &all_signals, &old_signals);
if (flag) {
// 重置标志
flag = 0;
// 在这里执行实际工作(在信号被阻塞的情况下)
printf("Processing signal...\n");
// 可以安全地使用非异步安全的函数
}
// 恢复信号掩码
sigprocmask(SIG_SETMASK, &old_signals, NULL);
// 休眠(可能被信号中断)
pause(); // 等待信号
}
return 0;
}
1.3.3 实时信号与排队信号
// 实时信号(排队信号)示例
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 实时信号处理函数
void rt_signal_handler(int sig, siginfo_t *info, void *context) {
// 可以获取发送者的PID和数据
printf("Received real-time signal %d\n", sig);
printf(" From PID: %d\n", info->si_pid);
printf(" Value: %d\n", info->si_value.sival_int);
printf(" Code: %d\n", info->si_code);
// 可以使用用户数据
if (info->si_code == SI_QUEUE) {
printf(" This is a queued signal with data\n");
}
}
int main() {
struct sigaction sa;
union sigval value;
printf("PID: %d\n", getpid());
// 设置实时信号处理
sa.sa_sigaction = rt_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_RESTART; // 重要:必须使用SA_SIGINFO
// 注册实时信号处理
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
if (sigaction(SIGRTMIN + 1, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
// 如果是父进程,发送排队信号
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid > 0) {
// 父进程:发送多个排队信号
sleep(1); // 给子进程时间设置信号处理
for (int i = 0; i < 5; i++) {
value.sival_int = i * 100;
printf("Parent: sending signal with value %d\n", value.sival_int);
// 发送排队信号(实时信号可以排队)
if (sigqueue(pid, SIGRTMIN, value) == -1) {
perror("sigqueue");
}
usleep(100000); // 100ms间隔
}
// 等待子进程
wait(NULL);
} else {
// 子进程:接收信号
printf("Child: waiting for signals...\n");
// 实时信号会被排队,所以会按顺序处理
while (1) {
pause(); // 等待信号
}
}
return 0;
}
二、守护进程(Daemon)深度解析
2.1 守护进程的完整创建流程
2.1.1 传统UNIX守护进程创建
// 完整的守护进程创建函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
int become_daemon(int flags) {
int maxfd, fd;
// 1. 创建子进程,父进程退出
switch (fork()) {
case -1: return -1; // 失败
case 0: break; // 子进程继续
default: _exit(EXIT_SUCCESS); // 父进程退出
}
// 2. 成为新会话的首进程,脱离控制终端
if (setsid() == -1)
return -1;
// 3. 确保不是会话首进程(防止获取控制终端)
switch (fork()) {
case -1: return -1;
case 0: break;
default: _exit(EXIT_SUCCESS);
}
// 4. 清除文件创建掩码
if (!(flags & BD_NO_UMASK0))
umask(0);
// 5. 改变工作目录到根目录
if (!(flags & BD_NO_CHDIR))
if (chdir("/") == -1)
return -1;
// 6. 关闭所有打开的文件描述符
if (!(flags & BD_NO_CLOSE_FILES)) {
maxfd = sysconf(_SC_OPEN_MAX);
if (maxfd == -1) // 如果限制不确定,假设为1024
maxfd = 1024;
for (fd = 0; fd < maxfd; fd++)
close(fd);
}
// 7. 重新打开标准文件描述符到/dev/null
if (!(flags & BD_NO_REOPEN_STD_FDS)) {
close(STDIN_FILENO);
fd = open("/dev/null", O_RDWR);
if (fd != STDIN_FILENO) // fd应该是0
return -1;
if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
return -1;
if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
return -1;
}
return 0;
}
// 守护进程标志
#define BD_NO_CHDIR 01 // 不改变工作目录
#define BD_NO_CLOSE_FILES 02 // 不关闭所有打开的文件
#define BD_NO_REOPEN_STD_FDS 04 // 不重新打开stdin/stdout/stderr
#define BD_NO_UMASK0 010 // 不清除umask
#define BD_MAX_CLOSE 8192 // 如果sysconf返回不确定值
2.1.2 现代守护进程的最佳实践
// 使用双重fork和syslog的现代守护进程
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
// 信号处理函数
static void signal_handler(int sig) {
switch (sig) {
case SIGHUP:
syslog(LOG_INFO, "Received SIGHUP, reloading configuration");
// 重新加载配置
break;
case SIGTERM:
syslog(LOG_INFO, "Received SIGTERM, exiting");
closelog();
exit(EXIT_SUCCESS);
default:
syslog(LOG_WARNING, "Received unexpected signal %d", sig);
}
}
// 设置信号处理
static void setup_signal_handlers(void) {
struct sigaction sa;
// 设置SIGHUP处理(重新加载配置)
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGHUP, &sa, NULL) == -1) {
syslog(LOG_ERR, "Failed to set up SIGHUP handler: %s", strerror(errno));
exit(EXIT_FAILURE);
}
// 设置SIGTERM处理(优雅退出)
if (sigaction(SIGTERM, &sa, NULL) == -1) {
syslog(LOG_ERR, "Failed to set up SIGTERM handler: %s", strerror(errno));
exit(EXIT_FAILURE);
}
// 忽略不必要的信号
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) == -1) {
syslog(LOG_ERR, "Failed to ignore SIGPIPE: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
syslog(LOG_ERR, "Failed to ignore SIGUSR1: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if (sigaction(SIGUSR2, &sa, NULL) == -1) {
syslog(LOG_ERR, "Failed to ignore SIGUSR2: %s", strerror(errno));
exit(EXIT_FAILURE);
}
}
// 创建PID文件
static int create_pidfile(const char *pidfile) {
int fd;
char pid_str[20];
fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
syslog(LOG_ERR, "Cannot open PID file %s: %s", pidfile, strerror(errno));
return -1;
}
snprintf(pid_str, sizeof(pid_str), "%ld\n", (long)getpid());
if (write(fd, pid_str, strlen(pid_str)) != (ssize_t)strlen(pid_str)) {
syslog(LOG_ERR, "Cannot write to PID file %s: %s", pidfile, strerror(errno));
close(fd);
return -1;
}
close(fd);
return 0;
}
// 守护进程主函数
int daemonize(const char *name, const char *pidfile, int facility) {
pid_t pid, sid;
// 第一次fork
pid = fork();
if (pid < 0) {
// fork失败
return -1;
} else if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程继续
// 创建新会话,脱离控制终端
sid = setsid();
if (sid < 0) {
return -1;
}
// 第二次fork,确保不是会话首进程,防止获取控制终端
pid = fork();
if (pid < 0) {
return -1;
} else if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 孙子进程继续(真正的守护进程)
// 清除文件创建掩码
umask(0);
// 改变工作目录到根目录
if (chdir("/") < 0) {
return -1;
}
// 关闭标准文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 重新打开stdin, stdout, stderr到/dev/null
open("/dev/null", O_RDONLY); // stdin
open("/dev/null", O_RDWR); // stdout
open("/dev/null", O_RDWR); // stderr
// 初始化syslog
openlog(name, LOG_PID, facility);
// 创建PID文件
if (pidfile && create_pidfile(pidfile) < 0) {
syslog(LOG_ERR, "Cannot create PID file");
return -1;
}
// 设置信号处理
setup_signal_handlers();
syslog(LOG_INFO, "Daemon %s started with PID %ld", name, (long)getpid());
return 0;
}
// 示例守护进程主程序
int main(int argc, char *argv[]) {
const char *daemon_name = "mydaemon";
const char *pidfile = "/var/run/mydaemon.pid";
// 成为守护进程
if (daemonize(daemon_name, pidfile, LOG_DAEMON) < 0) {
fprintf(stderr, "Failed to daemonize\n");
exit(EXIT_FAILURE);
}
// 守护进程主循环
syslog(LOG_INFO, "Daemon is running");
while (1) {
// 守护进程的工作
// 例如:检查配置、处理请求等
sleep(10); // 示例:每10秒循环一次
syslog(LOG_DEBUG, "Daemon heartbeat");
}
// 理论上不会到达这里
closelog();
return 0;
}
2.2 systemd时代的守护进程
2.2.1 systemd服务单元文件
# /etc/systemd/system/mydaemon.service
[Unit]
Description=My Custom Daemon
Documentation=man:mydaemon(8)
After=network.target
Requires=network.target
[Service]
Type=notify # 使用sd_notify()通知systemd
ExecStart=/usr/sbin/mydaemon # 守护进程二进制文件
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID
KillSignal=SIGTERM
KillMode=process # 只杀死主进程
Restart=on-failure # 失败时重启
RestartSec=5s # 重启前等待5秒
TimeoutStopSec=30s # 停止超时30秒
# 安全设置
User=daemonuser
Group=daemongroup
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/mydaemon /var/log/mydaemon
# 资源限制
LimitNOFILE=65536
LimitNPROC=512
LimitMEMLOCK=infinity
# 环境变量
Environment="MY_DAEMON_DEBUG=0"
EnvironmentFile=-/etc/default/mydaemon
[Install]
WantedBy=multi-user.target
2.2.2 使用sd-daemon库的现代守护进程
// 使用systemd的sd-daemon库的守护进程
#define _GNU_SOURCE
#include <systemd/sd-daemon.h>
#include <systemd/sd-event.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
// 事件回调函数
static int signal_handler(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
switch (si->ssi_signo) {
case SIGTERM:
sd_notify(0, "STOPPING=1");
sd_event_exit(sd_event_source_get_event(es), 0);
break;
case SIGHUP:
sd_notify(0, "RELOADING=1");
// 重新加载配置
sd_notify(0, "READY=1");
break;
default:
break;
}
return 0;
}
// 定时器回调函数
static int timer_handler(sd_event_source *es, uint64_t usec, void *userdata) {
static int count = 0;
count++;
syslog(LOG_INFO, "Timer tick %d", count);
// 发送watchdog通知(如果启用了WatchdogSec)
sd_notify(0, "WATCHDOG=1");
return 0;
}
int main(int argc, char *argv[]) {
sd_event *event = NULL;
sd_event_source *signal_source = NULL;
sd_event_source *timer_source = NULL;
sigset_t ss;
int r;
// 初始化syslog
openlog("systemd-daemon", LOG_PID, LOG_DAEMON);
// 创建事件循环
r = sd_event_default(&event);
if (r < 0) {
syslog(LOG_ERR, "Failed to create event loop: %s", strerror(-r));
goto finish;
}
// 设置信号处理
sigemptyset(&ss);
sigaddset(&ss, SIGTERM);
sigaddset(&ss, SIGHUP);
sigaddset(&ss, SIGINT);
r = sd_event_add_signal(event, &signal_source, &ss, signal_handler, NULL);
if (r < 0) {
syslog(LOG_ERR, "Failed to add signal handler: %s", strerror(-r));
goto finish;
}
// 添加定时器(每秒触发一次)
r = sd_event_add_time(event, &timer_source, CLOCK_MONOTONIC,
UINT64_MAX, 1000000, timer_handler, NULL);
if (r < 0) {
syslog(LOG_ERR, "Failed to add timer: %s", strerror(-r));
goto finish;
}
// 通知systemd服务已启动
sd_notify(0,
"READY=1\n"
"STATUS=Daemon is running\n"
"MAINPID=%lu",
(unsigned long) getpid());
syslog(LOG_INFO, "Daemon started with PID %ld", (long)getpid());
// 运行事件循环
r = sd_event_loop(event);
if (r < 0) {
syslog(LOG_ERR, "Event loop failed: %s", strerror(-r));
}
finish:
// 清理
if (signal_source)
sd_event_source_unref(signal_source);
if (timer_source)
sd_event_source_unref(timer_source);
if (event)
sd_event_unref(event);
syslog(LOG_INFO, "Daemon stopped");
closelog();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
三、进程组(Process Group)与会话(Session)
3.1 进程组与会话的基本概念
3.1.1 关系层次
进程、进程组、会话、终端的关系:
┌─────────────────────────────────────────┐
│ 终端(Terminal) │ ← 物理或伪终端
│ /dev/tty1, pts/0, etc. │
└───────────────────┬─────────────────────┘
│ 控制关系
▼
┌─────────────────────────────────────────┐
│ 会话(Session) │ ← 一次登录形成一个会话
│ - 会话ID(SID) │
│ - 控制进程(Session Leader) │
│ - 控制终端(Controlling Terminal) │
│ - 前台进程组(Foreground Process Group)│
│ - 后台进程组(Background Process Group)│
└───────────────────┬─────────────────────┘
│ 包含关系
▼
┌─────────────────────────────────────────┐
│ 进程组(Process Group) │ ← 一个作业(job)
│ - 进程组ID(PGID) │
│ - 组长进程(Process Group Leader) │
└───────────────────┬─────────────────────┘
│ 包含关系
▼
┌─────────────────────────────────────────┐
│ 进程(Process) │
│ - 进程ID(PID) │
│ - 父进程ID(PPID) │
│ - 实际用户ID(RUID) │
│ - 有效用户ID(EUID) │
└─────────────────────────────────────────┘
3.1.2 内核数据结构
// 内核中的进程组和会话数据结构
// task_struct中的相关字段
struct task_struct {
// ... 其他字段
pid_t pid; // 进程ID
pid_t tgid; // 线程组ID(POSIX线程)
// 进程组和会话
struct pid_link pids[PIDTYPE_MAX]; // PID链接
struct task_struct *group_leader; // 线程组领导者
// ... 其他字段
};
// PID类型枚举
enum pid_type {
PIDTYPE_PID, // 进程ID
PIDTYPE_TGID, // 线程组ID
PIDTYPE_PGID, // 进程组ID
PIDTYPE_SID, // 会话ID
PIDTYPE_MAX
};
// PID结构
struct pid {
atomic_t count;
unsigned int level;
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
struct upid numbers[1];
};
// 用户空间可见的PID
struct upid {
int nr; // PID数值
struct pid_namespace *ns; // 命名空间
struct hlist_node pid_chain;
};
3.2 进程组操作与会话管理
3.2.1 创建新会话和进程组
// 创建新会话和进程组的完整示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void print_ids(const char *name) {
printf("%s: PID=%ld, PPID=%ld, PGID=%ld, SID=%ld\n",
name,
(long)getpid(),
(long)getppid(),
(long)getpgrp(),
(long)getsid(0));
// 获取控制终端
int tty = tcgetpgrp(STDIN_FILENO);
if (tty == -1) {
printf("%s: No controlling terminal\n", name);
} else {
printf("%s: Controlling terminal PGID=%ld\n", name, (long)tty);
}
}
int main() {
pid_t pid;
printf("Original process:\n");
print_ids("Parent");
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程
// 创建新会话
if (setsid() == -1) {
perror("setsid");
exit(1);
}
printf("\nAfter setsid():\n");
print_ids("Child (new session leader)");
// 创建孙子进程(在新的会话中)
pid_t grandchild_pid = fork();
if (grandchild_pid == -1) {
perror("fork in child");
exit(1);
}
if (grandchild_pid == 0) {
// 孙子进程
// 创建新进程组(孙子进程成为组长)
if (setpgid(0, 0) == -1) {
perror("setpgid");
exit(1);
}
printf("\nGrandchild in new process group:\n");
print_ids("Grandchild (new PG leader)");
// 孙子进程的工作
sleep(5);
exit(0);
} else {
// 子进程等待孙子进程
wait(NULL);
}
exit(0);
} else {
// 父进程
sleep(1);
printf("\nParent after child created new session:\n");
print_ids("Parent");
// 父进程等待子进程
wait(NULL);
}
return 0;
}
3.2.2 作业控制(Job Control)实现
// 简单的shell作业控制实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <termios.h>
#include <string.h>
#define MAX_JOBS 100
// 作业状态
typedef enum {
JOB_RUNNING,
JOB_STOPPED,
JOB_DONE
} job_state_t;
// 作业结构
typedef struct {
pid_t pgid; // 进程组ID
int job_id; // 作业ID
job_state_t state; // 状态
char *command; // 命令字符串
} job_t;
job_t jobs[MAX_JOBS];
int next_job_id = 1;
// 添加作业
int add_job(pid_t pgid, const char *command) {
for (int i = 0; i < MAX_JOBS; i++) {
if (jobs[i].state == JOB_DONE || jobs[i].pgid == 0) {
jobs[i].pgid = pgid;
jobs[i].job_id = next_job_id++;
jobs[i].state = JOB_RUNNING;
jobs[i].command = strdup(command);
return jobs[i].job_id;
}
}
return -1;
}
// 更新作业状态
void update_job_status(pid_t pgid, job_state_t state) {
for (int i = 0; i < MAX_JOBS; i++) {
if (jobs[i].pgid == pgid) {
jobs[i].state = state;
break;
}
}
}
// 列出所有作业
void list_jobs() {
printf("Job List:\n");
printf("ID\tPGID\tState\tCommand\n");
for (int i = 0; i < MAX_JOBS; i++) {
if (jobs[i].pgid != 0 && jobs[i].state != JOB_DONE) {
const char *state_str;
switch (jobs[i].state) {
case JOB_RUNNING: state_str = "Running"; break;
case JOB_STOPPED: state_str = "Stopped"; break;
default: state_str = "Unknown"; break;
}
printf("[%d]\t%ld\t%s\t%s\n",
jobs[i].job_id,
(long)jobs[i].pgid,
state_str,
jobs[i].command);
}
}
}
// 前台运行命令
pid_t run_foreground(const char *command) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
// 创建新进程组,子进程成为组长
setpgid(0, 0);
// 将新进程组设置为前台进程组
tcsetpgrp(STDIN_FILENO, getpgrp());
// 恢复默认信号处理
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGTTIN, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
// 执行命令
execl("/bin/sh", "sh", "-c", command, (char *)NULL);
// 如果exec失败
perror("execl");
exit(1);
} else if (pid > 0) {
// 父进程(shell)
// 等待子进程的进程组
int status;
waitpid(-pid, &status, WUNTRACED);
// 将控制终端返回给shell
tcsetpgrp(STDIN_FILENO, getpgrp());
// 检查子进程是否停止
if (WIFSTOPPED(status)) {
// 子进程被停止(如Ctrl+Z)
printf("\nJob stopped\n");
add_job(pid, command);
update_job_status(pid, JOB_STOPPED);
} else if (WIFSIGNALED(status)) {
// 子进程被信号终止
printf("\nJob terminated by signal %d\n", WTERMSIG(status));
}
return pid;
}
return -1;
}
// 后台运行命令
pid_t run_background(const char *command) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
// 创建新进程组
setpgid(0, 0);
// 后台进程应该忽略终端信号
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
// 重定向标准输入从/dev/null
int null_fd = open("/dev/null", O_RDONLY);
if (null_fd != -1) {
dup2(null_fd, STDIN_FILENO);
close(null_fd);
}
// 执行命令
execl("/bin/sh", "sh", "-c", command, (char *)NULL);
perror("execl");
exit(1);
} else if (pid > 0) {
// 父进程(shell)
int job_id = add_job(pid, command);
printf("[%d] %ld\n", job_id, (long)pid);
return pid;
}
return -1;
}
// 继续运行作业(前台或后台)
int continue_job(int job_id, int foreground) {
job_t *job = NULL;
// 查找作业
for (int i = 0; i < MAX_JOBS; i++) {
if (jobs[i].job_id == job_id && jobs[i].state == JOB_STOPPED) {
job = &jobs[i];
break;
}
}
if (!job) {
printf("No such stopped job: %d\n", job_id);
return -1;
}
// 发送SIGCONT信号继续作业
if (kill(-job->pgid, SIGCONT) == -1) {
perror("kill");
return -1;
}
if (foreground) {
// 前台继续
job->state = JOB_RUNNING;
// 将作业的进程组设置为前台进程组
tcsetpgrp(STDIN_FILENO, job->pgid);
// 等待作业完成
int status;
waitpid(-job->pgid, &status, WUNTRACED);
// 将控制终端返回给shell
tcsetpgrp(STDIN_FILENO, getpgrp());
if (WIFSTOPPED(status)) {
job->state = JOB_STOPPED;
printf("\nJob %d stopped again\n", job_id);
} else {
job->state = JOB_DONE;
printf("\nJob %d completed\n", job_id);
}
} else {
// 后台继续
job->state = JOB_RUNNING;
printf("Continued job %d in background\n", job_id);
}
return 0;
}
int main() {
char command[256];
struct termios original_termios;
// 保存原始终端设置
tcgetattr(STDIN_FILENO, &original_termios);
printf("Simple Shell with Job Control\n");
printf("Commands:\n");
printf(" command & - Run in background\n");
printf(" jobs - List jobs\n");
printf(" fg <job_id> - Bring job to foreground\n");
printf(" bg <job_id> - Continue job in background\n");
printf(" exit - Exit shell\n");
while (1) {
printf("\nshell> ");
fflush(stdout);
if (!fgets(command, sizeof(command), stdin)) {
break;
}
// 去掉换行符
command[strcspn(command, "\n")] = '\0';
if (strlen(command) == 0) {
continue;
}
if (strcmp(command, "exit") == 0) {
break;
} else if (strcmp(command, "jobs") == 0) {
list_jobs();
} else if (strncmp(command, "fg ", 3) == 0) {
int job_id = atoi(command + 3);
continue_job(job_id, 1);
} else if (strncmp(command, "bg ", 3) == 0) {
int job_id = atoi(command + 3);
continue_job(job_id, 0);
} else if (command[strlen(command) - 1] == '&') {
// 后台运行
command[strlen(command) - 1] = '\0';
run_background(command);
} else {
// 前台运行
run_foreground(command);
}
}
// 恢复原始终端设置
tcsetattr(STDIN_FILENO, TCSANOW, &original_termios);
return 0;
}
3.3 终端与控制进程
3.3.1 终端控制原语
// 终端控制完整示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
#include <sys/ioctl.h>
// 保存和恢复终端设置
struct termios original_termios;
void restore_terminal() {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios);
}
void setup_terminal() {
struct termios new_termios;
// 获取当前终端设置
if (tcgetattr(STDIN_FILENO, &original_termios) == -1) {
perror("tcgetattr");
exit(1);
}
// 设置atexit处理函数
atexit(restore_terminal);
// 配置新终端设置
new_termios = original_termios;
// 禁用规范模式(逐行处理)和回显
new_termios.c_lflag &= ~(ICANON | ECHO);
// 设置最小读取字符数和超时
new_termios.c_cc[VMIN] = 1; // 至少读取1个字符
new_termios.c_cc[VTIME] = 0; // 无超时
// 应用新设置
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) == -1) {
perror("tcsetattr");
exit(1);
}
}
// 获取终端大小
void get_terminal_size(int *rows, int *cols) {
struct winsize ws;
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) {
// 如果ioctl失败,使用默认值
*rows = 24;
*cols = 80;
} else {
*rows = ws.ws_row;
*cols = ws.ws_col;
}
}
// 终端信号处理
void sigwinch_handler(int sig) {
int rows, cols;
// 获取新的终端大小
get_terminal_size(&rows, &cols);
printf("\rTerminal resized: %dx%d\n", cols, rows);
fflush(stdout);
}
// 简单的终端应用程序
int main() {
char ch;
int rows, cols;
// 设置信号处理
signal(SIGWINCH, sigwinch_handler);
// 设置终端
setup_terminal();
// 获取初始终端大小
get_terminal_size(&rows, &cols);
printf("Terminal size: %dx%d\n", cols, rows);
printf("Press 'q' to quit, any other key to continue\n");
while (1) {
// 读取单个字符(非阻塞模式)
if (read(STDIN_FILENO, &ch, 1) == 1) {
if (ch == 'q' || ch == 'Q') {
break;
}
printf("You pressed: %c (0x%02x)\r\n",
isprint(ch) ? ch : ' ',
(unsigned char)ch);
// 如果是Ctrl+C,不会中断程序(因为我们处理了终端设置)
if (ch == 0x03) { // Ctrl+C
printf("Ctrl+C pressed (but ignored)\r\n");
}
// 如果是Ctrl+Z,发送SIGTSTP给自己
if (ch == 0x1a) { // Ctrl+Z
printf("Ctrl+Z pressed, stopping...\r\n");
fflush(stdout);
kill(getpid(), SIGTSTP);
// 当进程继续时,从这里恢复
printf("Process continued\r\n");
}
}
}
printf("\nExiting...\n");
return 0;
}
四、高级主题与最佳实践
4.1 信号处理的最佳实践
4.1.1 信号处理中的竞态条件
// 避免信号处理中的竞态条件
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
// 使用sig_atomic_t保证原子性
volatile sig_atomic_t flag = 0;
volatile sig_atomic_t processed_flag = 1; // 初始为已处理
// 信号处理函数 - 只设置标志
void signal_handler(int sig) {
if (processed_flag) {
flag = 1;
processed_flag = 0;
}
}
// 线程安全的信号处理
void *worker_thread(void *arg) {
sigset_t mask;
// 在这个线程中阻塞所有信号
sigfillset(&mask);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
while (1) {
// 在这里执行工作,不会被信号中断
usleep(100000); // 100ms
// 检查是否需要处理信号(通过线程间通信)
// ...
}
return NULL;
}
int main() {
struct sigaction sa;
pthread_t thread;
sigset_t mask, oldmask;
// 设置信号处理
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
// 创建工作者线程
pthread_create(&thread, NULL, worker_thread, NULL);
// 在主线程中处理信号
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
while (1) {
// 等待信号
sigsuspend(&oldmask);
// 当信号处理函数返回后,检查标志
if (flag && !processed_flag) {
// 处理信号
printf("Processing signal...\n");
// 重置标志
flag = 0;
processed_flag = 1;
}
}
pthread_join(thread, NULL);
return 0;
}
4.2 现代守护进程架构
4.2.1 基于事件循环的守护进程
// 使用libevent的现代守护进程
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <signal.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
static void signal_cb(evutil_socket_t fd, short event, void *arg) {
struct event_base *base = arg;
syslog(LOG_INFO, "Received signal, shutting down");
event_base_loopbreak(base);
}
static void accept_cb(struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr,
int socklen,
void *arg) {
struct event_base *base = arg;
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
syslog(LOG_ERR, "Error creating bufferevent");
return;
}
// 设置回调
bufferevent_setcb(bev, NULL, NULL, NULL, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main(int argc, char *argv[]) {
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event;
struct sockaddr_in sin;
// 成为守护进程
daemon(1, 0);
// 打开syslog
openlog("libevent-daemon", LOG_PID, LOG_DAEMON);
syslog(LOG_INFO, "Daemon started");
// 初始化libevent
base = event_base_new();
if (!base) {
syslog(LOG_ERR, "Could not initialize libevent");
return 1;
}
// 设置信号处理
signal_event = evsignal_new(base, SIGINT, signal_cb, base);
evsignal_add(signal_event, NULL);
signal_event = evsignal_new(base, SIGTERM, signal_cb, base);
evsignal_add(signal_event, NULL);
// 设置监听socket
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
listener = evconnlistener_new_bind(base, accept_cb, base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
-1, (struct sockaddr*)&sin, sizeof(sin));
if (!listener) {
syslog(LOG_ERR, "Could not create listener");
return 1;
}
syslog(LOG_INFO, "Daemon listening on port 8888");
// 运行事件循环
event_base_dispatch(base);
// 清理
evconnlistener_free(listener);
event_base_free(base);
syslog(LOG_INFO, "Daemon stopped");
closelog();
return 0;
}
五、总结
信号处理、守护进程、进程组和会话是Linux进程管理中的核心概念。理解这些概念对于编写健壮的、功能丰富的应用程序至关重要。在实际开发中,我们经常需要处理信号、编写守护进程,并通过进程组和会话来管理作业。
16

被折叠的 条评论
为什么被折叠?



