《UNIX环境高级编程(第三版)》第十章 信号 重难点分步详解
第十章是UNIX进程通信与控制的核心章节,信号作为异步通知机制,是进程响应事件的关键。本章重难点围绕"信号的生命周期""处理逻辑""安全实践"展开,以下按"概念→实践→避坑"的逻辑分步拆解,每个知识点配套可运行代码,帮你吃透信号机制。
一、核心重难点框架(先理清脉络)
本章核心是"信号从产生到处理的完整流程",拆解为7个递进式重难点:
- 信号的基本概念与常见信号(基础铺垫)
- 信号的4种产生方式(触发条件+代码验证)
- 信号处理函数注册(
signalvssigaction,重点) - 信号集与信号屏蔽(
sigset_t+sigprocmask,核心难点) - 可靠/不可靠信号(避坑关键)
- 信号处理的并发安全(实战必懂)
- 定时器与信号联动(
alarm+setitimer,实战常用)
二、重难点1:信号的基本概念(先搞懂"是什么")
1. 核心定义
信号是内核向进程发送的异步通知,告知进程发生特定事件(如终端中断、内存越界),进程需中断当前执行流响应。
2. 3种默认处理动作
- 忽略(
SIG_IGN):进程无响应(如SIGCHLD默认忽略)。 - 终止(
SIG_DFL):进程直接退出(如SIGINT、SIGTERM)。 - 终止并生成Core Dump(
SIG_DFL变种):进程退出并生成core文件(如SIGSEGV内存越界),用于调试。
3. 必须掌握的常见信号
| 信号名 | 编号 | 触发场景 | 默认动作 |
|---|---|---|---|
SIGINT |
2 | 终端Ctrl+C | 终止进程 |
SIGQUIT |
3 | 终端Ctrl+\ | 终止+Core Dump |
SIGTERM |
15 | kill命令默认信号 |
终止进程 |
SIGKILL |
9 | 强制终止进程 | 终止(不可捕获) |
SIGSEGV |
11 | 内存访问越界 | 终止+Core Dump |
SIGCHLD |
17 | 子进程终止/暂停 | 忽略 |
SIGALRM |
14 | 定时器超时(alarm/setitimer) |
终止进程 |
关键说明
SIGKILL(9)和SIGSTOP(19)是特殊信号:不可捕获、不可忽略、不可修改默认动作,仅用于强制终止/暂停进程。- 信号的"异步性":产生时间不确定,进程需随时准备响应。
三、重难点2:信号的产生方式(知道"怎么来")
信号产生主要有4种场景,每种场景配套代码验证,直观理解触发逻辑。
1. 场景1:终端按键触发
- 示例:Ctrl+C(
SIGINT)、Ctrl+\(SIGQUIT)、Ctrl+Z(SIGTSTP,暂停进程)。 - 无需代码,直接在终端操作即可触发。
2. 场景2:系统调用触发(主动发送信号)
核心函数:kill(跨进程发送)、raise(向自身发送)
c
#include "apue.h"
#include <signal.h>
#include <unistd.h>
int main(void) {
pid_t pid;
// fork子进程
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { // 子进程
printf("Child PID: %ld\n", (long)getpid());
raise(SIGSTOP); // 子进程暂停自身(发送SIGSTOP)
exit(0); // 暂停后需父进程唤醒才会执行
}
// 父进程:唤醒子进程 → 终止子进程
sleep(1);
printf("Parent: Send SIGCONT to child\n");
kill(pid, SIGCONT); // 唤醒暂停的子进程
sleep(1);
printf("Parent: Send SIGTERM to child\n");
kill(pid, SIGTERM); // 终止子进程
waitpid(pid, NULL, 0); // 回收子进程
printf("Child terminated\n");
exit(0);
}
运行结果分析
Child PID: 12345
Parent: Send SIGCONT to child
Parent: Send SIGTERM to child
Child terminated
- 关键:
kill支持跨进程发送,pid参数可指定进程/进程组(pid=0发送给当前进程组);raise等价于kill(getpid(), sig)。
3. 场景3:硬件异常触发
- 示例:除数为0(
SIGFPE)、内存访问越界(SIGSEGV)、非法指令(SIGILL)。 - 代码示例(内存越界触发
SIGSEGV):
c
#include "apue.h"
int main(void) {
int *p = NULL;
*p = 10; // 内存越界,触发SIGSEGV
exit(0);
}
运行结果
Segmentation fault (core dumped) // 触发Core Dump
4. 场景4:软件条件触发
- 示例:管道读端关闭后写操作(
SIGPIPE)、定时器超时(SIGALRM)、子进程终止(SIGCHLD)。 - 代码示例(管道破裂触发
SIGPIPE):
c
#include "apue.h"
#include <signal.h>
void sig_pipe(int signo) {
printf("Received SIGPIPE (signo: %d)\n", signo);
}
int main(void) {
int fd[2];
pipe(fd); // 创建管道
close(fd[0]); // 关闭读端
signal(SIGPIPE, sig_pipe); // 注册SIGPIPE处理函数
write(fd[1], "hello", 5); // 写关闭的管道,触发SIGPIPE
exit(0);
}
运行结果
Received SIGPIPE (signo: 13)
四、重难点3:信号处理函数注册(核心操作:"怎么处理")
进程可自定义信号响应逻辑,核心是两个函数:signal(简单但不灵活)和sigaction(推荐,功能强大)。
1. 简单注册:signal函数
函数原型
c
#include <signal.h>
// handler:SIG_IGN(忽略)、SIG_DFL(默认)、自定义函数指针
void (*signal(int sig, void (*handler)(int)))(int);
代码示例(注册SIGINT处理函数)
c
#include "apue.h"
// 自定义SIGINT处理函数(替代默认终止动作)
void sig_int(int signo) {
printf("Received SIGINT (Ctrl+C), signo: %d\n", signo);
}
int main(void) {
// 注册SIGINT的处理函数为sig_int
if (signal(SIGINT, sig_int) == SIG_ERR) {
err_sys("signal error");
}
printf("Wait for SIGINT (press Ctrl+C)...\n");
for (;;) {
pause(); // 暂停进程,等待信号触发
}
exit(0);
}
运行结果
Wait for SIGINT (press Ctrl+C)...
^CReceived SIGINT (Ctrl+C), signo: 2
^CReceived SIGINT (Ctrl+C), signo: 2
缺点
- 跨系统行为不一致(如是否自动重启被中断的系统调用)。
- 不支持信号屏蔽、附加信息传递。
2. 推荐注册:sigaction函数(重点中的重点)
sigaction是POSIX标准推荐接口,支持信号屏蔽、附加信息、系统调用重启,解决signal的所有缺陷。
核心结构体与函数原型
c
#include <signal.h>
struct sigaction {
void (*sa_handler)(int); // 简单处理函数(仅接收信号编号)
void (*sa_sigaction)(int, siginfo_t *, void *); // 带附加信息的处理函数
sigset_t sa_mask; // 处理期间屏蔽的信号集
int sa_flags; // 行为标志(关键!)
void (*sa_restorer)(void); // 已废弃,无需设置
};
// 注册sig信号的处理动作,oldact保存旧动作(可为NULL)
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
关键sa_flags标志(必须掌握)
SA_RESTART:被信号中断的系统调用(如read、pause)自动重启,避免返回-1且errno=EINTR。SA_SIGINFO:启用sa_sigaction,可获取信号附加信息(发送者PID、信号原因)。SA_NODEFER:处理信号时不屏蔽当前信号(默认会屏蔽,避免嵌套触发)。SA_RESETHAND:信号处理后自动恢复为默认动作。
代码示例(带附加信息的处理函数)
c
#include "apue.h"
#include <signal.h>
// 带附加信息的处理函数(需设置SA_SIGINFO)
void sig_info(int signo, siginfo_t *info, void *ctx) {
printf("Received signal: %d\n", signo);
printf("Signal sender PID: %ld\n", (long)info->si_pid); // 发送者PID
printf("Signal code: %d\n", info->si_code); // 信号原因(SI_USER=1:用户发送)
}
int main(void) {
struct sigaction act;
sigemptyset(&act.sa_mask); // 初始化信号集为空(不额外屏蔽信号)
act.sa_flags = SA_SIGINFO; // 启用附加信息传递
act.sa_sigaction = sig_info; // 绑定处理函数
// 注册SIGTERM信号
if (sigaction(SIGTERM, &act, NULL) < 0) {
err_sys("sigaction error");
}
printf("Wait for SIGTERM (send with: kill %ld)...\n", (long)getpid());
for (;;) {
pause();
}
exit(0);
}
运行结果(另一个终端执行kill 12345)
Wait for SIGTERM (send with: kill 12345)...
Received signal: 15
Signal sender PID: 6789 // 发送kill命令的进程PID
Signal code: 1 // SI_USER=1,标识用户主动发送
关键结论
SA_SIGINFO是获取信号来源的核心,适合进程间通信(如传递自定义数据)。sa_mask可在处理信号时屏蔽其他信号,避免嵌套触发(如处理SIGINT时屏蔽SIGTERM)。
五、重难点4:信号集与信号屏蔽(核心难点:"控制信号递达")
信号集(sigset_t)是表示多个信号的集合,核心用途是"信号屏蔽"------进程可阻塞特定信号,直到解除屏蔽后才触发。
1. 信号集操作函数(必须掌握,缺一不可)
c
#include <signal.h>
// 初始化信号集为空(无任何信号)
int sigemptyset(sigset_t *set);
// 初始化信号集为全1(包含所有信号)
int sigfillset(sigset_t *set);
// 向信号集添加信号sig
int sigaddset(sigset_t *set, int sig);
// 从信号集删除信号sig
int sigdelset(sigset_t *set, int sig);
// 检查信号sig是否在信号集中(1=是,0=否,-1=出错)
int sigismember(const sigset_t *set, int sig);
- 注意:
sigset_t的实现依赖系统,必须通过上述函数操作,不可直接赋值。
2. 信号屏蔽字与sigprocmask函数
进程的"信号屏蔽字"决定哪些信号被阻塞,sigprocmask用于修改屏蔽字:
c
#include <signal.h>
// how:修改方式,set:新信号集,oldset:保存旧屏蔽字(可为NULL)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how参数的3种取值
SIG_BLOCK:将set中的信号添加到屏蔽字(阻塞这些信号)。SIG_UNBLOCK:从屏蔽字中删除set中的信号(解除阻塞)。SIG_SETMASK:将屏蔽字设置为set(覆盖原有屏蔽字)。
3. 代码示例(阻塞SIGINT信号)
c
#include "apue.h"
void sig_int(int signo) {
printf("Received SIGINT!\n");
}
int main(void) {
sigset_t newmask, oldmask;
// 注册SIGINT处理函数
if (signal(SIGINT, sig_int) == SIG_ERR) {
err_sys("signal error");
}
// 初始化新信号集,添加SIGINT
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
// 阻塞SIGINT,保存旧屏蔽字
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
err_sys("sigprocmask error");
}
printf("SIGINT blocked, wait 5s (press Ctrl+C now)...\n");
sleep(5); // 期间按Ctrl+C,SIGINT被阻塞,不触发处理函数
// 解除阻塞,恢复旧屏蔽字
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
err_sys("sigprocmask error");
}
printf("SIGINT unblocked, wait for Ctrl+C...\n");
pause(); // 此时按Ctrl+C会触发处理函数
exit(0);
}
运行结果
SIGINT blocked, wait 5s (press Ctrl+C now)...
^C // 阻塞期间按Ctrl+C,无响应
SIGINT unblocked, wait for Ctrl+C...
^CReceived SIGINT! // 解除后触发
关键结论
- 被阻塞的信号会"挂起":不可靠信号(1~31)挂起期间多次产生,仅递达1次;可靠信号(34+)会排队,全部递达。
sigprocmask是保护临界区的核心(如进程执行重要操作时,阻塞无关信号)。
六、重难点5:可靠信号与不可靠信号(避坑关键)
1. 核心区别(表格对比,一目了然)
| 类型 | 编号范围 | 核心特点(坑点) | 示例 |
|---|---|---|---|
| 不可靠信号 | 1~31 | 1. 不支持排队,多次产生可能丢失;2. 可能打断系统调用;3. 处理后可能自动恢复默认动作 | SIGINT、SIGTERM |
| 可靠信号 | 34~64 | 1. 支持排队,多次产生不丢失;2. 不自动恢复默认动作;3. 支持附加信息传递 | SIGRTMIN+0、SIGRTMAX-1 |
2. 不可靠信号的两大问题及解决
问题1:信号丢失
- 场景:进程阻塞
SIGINT期间,多次按Ctrl+C,解除阻塞后仅触发1次。 - 解决:避免高频触发不可靠信号;需确保递达的场景改用可靠信号。
问题2:系统调用被打断
- 场景:
read从终端读取时,收到SIGINT,read返回-1,errno=EINTR。 - 解决:用
sigaction设置SA_RESTART,让被打断的系统调用自动重启。
代码示例(SA_RESTART重启read)
c
#include "apue.h"
#include <signal.h>
void sig_int(int signo) {
printf("Received SIGINT, read will restart!\n");
}
int main(void) {
struct sigaction act;
char buf[1024];
ssize_t n;
sigemptyset(&act.sa_mask);
act.sa_handler = sig_int;
act.sa_flags = SA_RESTART; // 关键:重启被打断的系统调用
if (sigaction(SIGINT, &act, NULL) < 0) {
err_sys("sigaction error");
}
printf("Enter a line (press Ctrl+C during input): ");
n = read(STDIN_FILENO, buf, sizeof(buf)); // 被SIGINT打断后自动重启
if (n < 0) {
err_sys("read error");
}
printf("You entered: %.*s", (int)n, buf);
exit(0);
}
运行结果
Enter a line (press Ctrl+C during input): ^CReceived SIGINT, read will restart!
hello world // 输入内容
You entered: hello world
- 关键:
SA_RESTART仅对部分系统调用有效(如read、write、pause),socket相关调用(如recv)需手动处理EINTR。
七、重难点6:信号处理的并发安全(实战必懂)
信号处理函数是"异步执行"的(可能打断进程任意指令),必须遵循3条安全规则,否则会导致数据错乱、死锁。
1. 核心安全规则
- 仅调用"异步信号安全函数":如
write、_exit、sigprocmask,禁止使用printf、malloc、strcpy(非异步安全,可能修改全局状态)。 - 避免访问全局变量:若必须访问,用
volatile sig_atomic_t类型(确保读写原子性,防止编译器优化)。 - 处理函数要简短:仅做简单操作(如设置标志位),复杂逻辑放在主进程。
2. 代码示例(异步安全的信号处理)
c
#include "apue.h"
#include <signal.h>
// 全局标志位:volatile确保不被编译器优化,sig_atomic_t确保原子读写
volatile sig_atomic_t sig_received = 0;
void sig_handler(int signo) {
sig_received = 1; // 仅设置标志位,无复杂操作
// 使用write(异步安全)替代printf
write(STDOUT_FILENO, "Signal received!\n", 16);
}
int main(void) {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = sig_handler;
act.sa_flags = 0;
if (sigaction(SIGINT, &act, NULL) < 0) {
err_sys("sigaction error");
}
printf("Wait for SIGINT (press Ctrl+C)...\n");
while (!sig_received) {
sleep(1); // 主进程循环检查标志位
}
printf("Main process detected signal, exit!\n");
exit(0);
}
运行结果
Wait for SIGINT (press Ctrl+C)...
^CSignal received!
Main process detected signal, exit!
- 关键:用"标志位通信"实现信号处理函数与主进程的安全交互,避免直接在处理函数中做复杂逻辑。
八、重难点7:定时器与信号(实战常用)
UNIX中定时器通过产生SIGALRM信号触发超时逻辑,核心函数是alarm(简单单次)和setitimer(高精度、循环)。
1. alarm函数(单次定时器)
c
#include <unistd.h>
// 设定seconds秒后产生SIGALRM,返回之前剩余的秒数(0=无之前的定时器)
unsigned int alarm(unsigned int seconds);
代码示例(5秒超时)
c
#include "apue.h"
void sig_alrm(int signo) {
printf("Timeout! 5 seconds passed.\n");
exit(0);
}
int main(void) {
if (signal(SIGALRM, sig_alrm) == SIG_ERR) {
err_sys("signal error");
}
alarm(5); // 5秒后产生SIGALRM
printf("Wait for 5 seconds...\n");
pause(); // 暂停等待信号
exit(0);
}
运行结果
Wait for 5 seconds...
Timeout! 5 seconds passed.
2. setitimer函数(循环/高精度定时器)
支持3种定时器类型,核心是ITIMER_REAL(真实时间,产生SIGALRM):
c
#include <sys/time.h>
// which:定时器类型(ITIMER_REAL/ITIMER_VIRTUAL/ITIMER_PROF)
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
// 定时器参数:it_value(首次超时时间),it_interval(循环周期)
struct itimerval {
struct timeval it_interval; // 循环周期(0=单次)
struct timeval it_value; // 首次超时时间(0=取消)
};
struct timeval {
time_t tv_sec; // 秒
suseconds_t tv_usec; // 微秒(1e6微秒=1秒)
};
代码示例(1秒循环定时器)
c
#include "apue.h"
#include <signal.h>
#include <sys/time.h>
void sig_alrm(int signo) {
static int count = 0;
printf("Timer triggered! Count: %d\n", ++count);
if (count == 3) {
exit(0); // 触发3次后退出
}
}
int main(void) {
struct sigaction act;
struct itimerval timer;
// 注册SIGALRM处理函数
sigemptyset(&act.sa_mask);
act.sa_handler = sig_alrm;
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) < 0) {
err_sys("sigaction error");
}
// 配置定时器:首次1秒后触发,之后每1秒触发1次
timer.it_value.tv_sec = 1; // 首次超时:1秒
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1; // 循环周期:1秒
timer.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL, &timer, NULL) < 0) {
err_sys("setitimer error");
}
// 主进程循环等待信号
for (;;) {
pause();
}
exit(0);
}
运行结果
Timer triggered! Count: 1
Timer triggered! Count: 2
Timer triggered! Count: 3
- 关键:
setitimer比alarm灵活,支持微秒级精度和循环,适合心跳检测、任务调度等场景。
九、章节核心总结(必记要点)
- 信号是异步通知,处理函数必须"简短、异步安全",避免复杂操作。
- 优先用
sigaction替代signal,它支持信号屏蔽、附加信息、系统调用重启,兼容性更好。 - 信号集与
sigprocmask是控制信号递达的核心,用于保护临界区、避免信号嵌套。 - 不可靠信号(1~31)可能丢失,可靠信号(34+)支持排队,高频场景选可靠信号。
- 定时器与信号联动是实战常用场景,
setitimer比alarm更强大,优先使用。
十、实践建议(必做代码练习)
- 实现信号驱动的超时读取:从终端读取输入,5秒内无输入则超时退出(
setitimer+SIGALRM)。 - 验证不可靠信号丢失:阻塞
SIGINT,多次按Ctrl+C,解除后观察触发次数。 - 用
sigwait同步处理信号:将SIGINT和SIGTERM加入信号集,用sigwait等待,避免异步处理的并发问题。 - 父子进程信号通信:父进程发送
SIGUSR1给子进程,子进程收到后回复SIGUSR2给父进程。
中等难度多选题
题目1:关于信号处理函数注册(signal vs sigaction),下列说法正确的有( )
A. signal函数跨UNIX系统行为一致,支持设置信号屏蔽集
B. sigaction可通过sa_mask成员指定信号处理期间需要屏蔽的信号
C. 启用SA_SIGINFO标志后,需绑定sa_sigaction函数以获取信号附加信息(如发送者PID)
D. signal函数支持传递信号附加信息,比sigaction更适合进程间通信场景
E. sigaction的SA_RESTART标志可让被信号中断的系统调用自动重启
题目2:下列关于可靠信号与不可靠信号的描述,正确的有( )
A. 不可靠信号编号范围为1~31,多次产生时可能丢失(不支持排队)
B. 可靠信号编号从34开始(SIGRTMIN及以上),多次产生时会排队递达
C. 不可靠信号处理完成后,可能自动恢复为默认动作(如早期signal实现)
D. 可靠信号的默认处理动作仅为"终止进程",无其他可能
E. 无论是可靠还是不可靠信号,SIGKILL(9)和SIGSTOP(19)均不可捕获、不可忽略
题目3:关于信号集与信号屏蔽操作,下列说法正确的有( )
A. sigfillset函数用于初始化信号集为空(不包含任何信号)
B. sigprocmask的SIG_UNBLOCK参数表示从当前屏蔽字中删除set中的信号(解除阻塞)
C. 被阻塞的不可靠信号在阻塞期间多次触发,解除阻塞后仅递达1次
D. sigdelset函数用于向信号集中添加指定信号
E. 信号屏蔽字仅对当前进程有效,子进程不会继承父进程的信号屏蔽字
题目4:下列关于定时器函数alarm和setitimer的差异,说法正确的有( )
A. alarm仅支持秒级精度,且多次调用时后一次会覆盖前一次的定时器设置
B. setitimer通过ITIMER_REAL类型产生SIGALRM信号,支持微秒级精度
C. setitimer的it_value成员指定循环触发周期,it_interval成员指定首次超时时间
D. alarm创建的定时器触发后,需重新调用alarm才能再次触发(单次定时器)
E. setitimer支持3种定时器类型,其中ITIMER_VIRTUAL仅统计进程的用户态CPU时间
题目5:为保证信号处理的并发安全,下列做法正确的有( )
A. 在信号处理函数中调用printf输出日志(printf为异步安全函数)
B. 访问全局标志位时,使用volatile sig_atomic_t类型(确保原子读写)
C. 信号处理函数仅执行简单操作(如设置标志位、调用write输出),避免复杂逻辑
D. 利用"标志位通信"实现信号处理函数与主进程的交互,而非直接在处理函数中完成业务逻辑
E. 信号处理函数中可调用malloc动态分配内存,提升灵活性
答案与详解
题目1 答案:B、C、E
详解:
- 选项A错误:signal函数跨系统行为不一致(如是否自动重启被中断的系统调用),且不支持信号屏蔽集设置,仅能简单注册处理函数。
- 选项B正确:sigaction的sa_mask成员是信号集,用于指定"处理当前信号期间需要阻塞的其他信号",避免信号嵌套触发。
- 选项C正确:SA_SIGINFO是sigaction的核心标志,启用后需使用sa_sigaction(而非sa_handler)作为处理函数,该函数可通过siginfo_t结构体获取信号发送者PID、信号原因等附加信息。
- 选项D错误:signal函数不支持传递附加信息,sigaction才是进程间通信场景的首选(如通过附加信息传递自定义数据)。
- 选项E正确:SA_RESTART标志可让被信号中断的系统调用(如read、pause)自动重启,避免返回-1且errno=EINTR,简化错误处理。
题目2 答案:A、B、C、E
详解:
- 选项A正确:不可靠信号(1~31)无排队机制,若在阻塞期间多次产生,解除阻塞后仅递达1次,可能丢失。
- 选项B正确:可靠信号(34~64,SIGRTMIN及以上)支持排队,多次产生时会按顺序递达,不丢失。
- 选项C正确:早期UNIX系统中,signal注册的不可靠信号处理完成后,可能自动恢复为默认动作(如SIGINT处理后再次按Ctrl+C会终止进程),需通过sigaction规避该问题。
- 选项D错误:可靠信号的默认动作与信号类型相关,并非仅"终止进程",例如部分实时信号默认动作是"忽略"。
- 选项E正确:SIGKILL(9)和SIGSTOP(19)是特殊信号,无论可靠与否,均不可捕获、不可忽略、不可修改默认动作,仅用于强制终止/暂停进程。
题目3 答案:B、C、E
详解:
- 选项A错误:sigfillset用于初始化信号集为"全1"(包含所有信号),sigemptyset才是初始化信号集为空。
- 选项B正确:sigprocmask的3种操作中,SIG_UNBLOCK的作用是"从当前屏蔽字中删除set中的信号",解除对这些信号的阻塞。
- 选项C正确:不可靠信号不支持排队,阻塞期间多次触发仅会"挂起1次",解除阻塞后仅递达1次;可靠信号则会全部排队递达。
- 选项D错误:sigdelset用于从信号集中删除指定信号,sigaddset才是向信号集中添加信号。
- 选项E正确:信号屏蔽字是进程的私有属性,fork创建的子进程会继承父进程的文件描述符、环境变量等,但不会继承信号屏蔽字(子进程默认屏蔽字为空)。
题目4 答案:A、B、D、E
详解:
- 选项A正确:alarm仅支持秒级精度,且多次调用时,后一次的seconds会覆盖前一次,例如先调用alarm(5),再调用alarm(3),最终3秒后触发SIGALRM。
- 选项B正确:setitimer的ITIMER_REAL类型对应真实时间,超时后产生SIGALRM信号,其精度为微秒级(struct timeval支持tv_usec字段),优于alarm。
- 选项C错误:setitimer的it_value成员指定"首次超时时间",it_interval成员指定"循环触发周期"(0表示单次定时器)。
- 选项D正确:alarm是单次定时器,触发后需重新调用才能再次生效;setitimer通过设置it_interval可实现循环触发。
- 选项E正确:setitimer支持3种类型:ITIMER_REAL(真实时间)、ITIMER_VIRTUAL(仅统计进程用户态CPU时间)、ITIMER_PROF(统计用户态+内核态CPU时间)。
题目5 答案:B、C、D
详解:
- 选项A错误:printf不是异步安全函数(可能修改全局缓冲区状态),信号处理函数中调用可能导致数据错乱,应避免使用。
- 选项B正确:volatile sig_atomic_t类型的变量确保"读写原子性",且不会被编译器优化,是信号处理函数中访问全局变量的唯一安全类型。
- 选项C正确:信号处理函数是异步执行的,需保持"简短、无阻塞",仅执行设置标志位、调用异步安全函数等简单操作,复杂逻辑应放在主进程中。
- 选项D正确:"标志位通信"是信号处理的常用安全模式(如信号处理函数设置volatile sig_atomic_t flag=1,主进程循环检查flag),避免直接在处理函数中处理业务。
- 选项E错误:malloc不是异步安全函数(内部维护全局内存管理链表),信号处理函数中调用可能导致内存泄漏或崩溃,需避免动态内存分配。