第十章_信号_《UNIX环境高级编程(第三版)》_笔记

《UNIX环境高级编程(第三版)》第十章 信号 重难点分步详解

第十章是UNIX进程通信与控制的核心章节,信号作为异步通知机制,是进程响应事件的关键。本章重难点围绕"信号的生命周期""处理逻辑""安全实践"展开,以下按"概念→实践→避坑"的逻辑分步拆解,每个知识点配套可运行代码,帮你吃透信号机制。

一、核心重难点框架(先理清脉络)

本章核心是"信号从产生到处理的完整流程",拆解为7个递进式重难点:

  1. 信号的基本概念与常见信号(基础铺垫)
  2. 信号的4种产生方式(触发条件+代码验证)
  3. 信号处理函数注册(signal vs sigaction,重点)
  4. 信号集与信号屏蔽(sigset_t + sigprocmask,核心难点)
  5. 可靠/不可靠信号(避坑关键)
  6. 信号处理的并发安全(实战必懂)
  7. 定时器与信号联动(alarm + setitimer,实战常用)

二、重难点1:信号的基本概念(先搞懂"是什么")

1. 核心定义

信号是内核向进程发送的异步通知,告知进程发生特定事件(如终端中断、内存越界),进程需中断当前执行流响应。

2. 3种默认处理动作

  • 忽略(SIG_IGN):进程无响应(如SIGCHLD默认忽略)。
  • 终止(SIG_DFL):进程直接退出(如SIGINTSIGTERM)。
  • 终止并生成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:被信号中断的系统调用(如readpause)自动重启,避免返回-1errno=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. 处理后可能自动恢复默认动作 SIGINTSIGTERM
可靠信号 34~64 1. 支持排队,多次产生不丢失;2. 不自动恢复默认动作;3. 支持附加信息传递 SIGRTMIN+0SIGRTMAX-1

2. 不可靠信号的两大问题及解决

问题1:信号丢失
  • 场景:进程阻塞SIGINT期间,多次按Ctrl+C,解除阻塞后仅触发1次。
  • 解决:避免高频触发不可靠信号;需确保递达的场景改用可靠信号。
问题2:系统调用被打断
  • 场景:read从终端读取时,收到SIGINTread返回-1errno=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仅对部分系统调用有效(如readwritepause),socket相关调用(如recv)需手动处理EINTR

七、重难点6:信号处理的并发安全(实战必懂)

信号处理函数是"异步执行"的(可能打断进程任意指令),必须遵循3条安全规则,否则会导致数据错乱、死锁。

1. 核心安全规则

  • 仅调用"异步信号安全函数":如write_exitsigprocmask,禁止使用printfmallocstrcpy(非异步安全,可能修改全局状态)。
  • 避免访问全局变量:若必须访问,用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
  • 关键:setitimeralarm灵活,支持微秒级精度和循环,适合心跳检测、任务调度等场景。

九、章节核心总结(必记要点)

  1. 信号是异步通知,处理函数必须"简短、异步安全",避免复杂操作。
  2. 优先用sigaction替代signal,它支持信号屏蔽、附加信息、系统调用重启,兼容性更好。
  3. 信号集与sigprocmask是控制信号递达的核心,用于保护临界区、避免信号嵌套。
  4. 不可靠信号(1~31)可能丢失,可靠信号(34+)支持排队,高频场景选可靠信号。
  5. 定时器与信号联动是实战常用场景,setitimeralarm更强大,优先使用。

十、实践建议(必做代码练习)

  1. 实现信号驱动的超时读取:从终端读取输入,5秒内无输入则超时退出(setitimer+SIGALRM)。
  2. 验证不可靠信号丢失:阻塞SIGINT,多次按Ctrl+C,解除后观察触发次数。
  3. sigwait同步处理信号:将SIGINTSIGTERM加入信号集,用sigwait等待,避免异步处理的并发问题。
  4. 父子进程信号通信:父进程发送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不是异步安全函数(内部维护全局内存管理链表),信号处理函数中调用可能导致内存泄漏或崩溃,需避免动态内存分配。

相关推荐
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之which命令(实操篇)
linux·运维·服务器·前端·chrome·笔记
额呃呃2 小时前
select详细分析
服务器
网创联盟,知识导航2 小时前
沐雨云香港大宽带云服务器 · 配置全览
服务器·阿里云·腾讯云
巧克力味的桃子3 小时前
单链表 - 有序插入并输出学习笔记
笔记·学习
智者知已应修善业3 小时前
【求等差数列个数/无序获取最大最小次大次小】2024-3-8
c语言·c++·经验分享·笔记·算法
fantasy5_53 小时前
Linux 动态进度条实战:从零掌握开发工具与核心原理
linux·运维·服务器
不瘦80斤不改名3 小时前
Python 日志(logging)全解析
服务器·python·php
莫逸风4 小时前
【局域网服务方案】:无需找运营商,低成本拥有高性能服务器
运维·服务器
Huanzhi_Lin5 小时前
图形渲染管线流程笔记
笔记·图形渲染·shader·glsl