30天速通Linux 第六章信号及信号处理

学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwd=h9m2 提取码: h9m2

一. 信号的基本概念

信号是 Linux 进程之间的一种通信机制,本质是软中断 。read/write 等系统调用都可能被信号中断。

  • 每个信号用 SIGxxx 整型宏表示,定义在 <signal.h>;kill -l 看列表,man 7 signal 看说明。
  • 使用信号的目的:① 让进程知道发生了某事件;② 执行自己代码中的信号处理程序。

信号的 3 个来源 :用户按键(Ctrl+C、Ctrl+\)/进程执行出错(除 0、非法访问)/其他进程用 kill、sigqueue 发送。

信号的 3 种状态:

状态 含义
递送 delivery 进程对信号采取动作(执行处理函数或忽略)
未决 pending 产生到递送之间
阻塞 block 进程指定阻塞该信号,保持未决直到解除阻塞

若处理方式是"忽略",信号永远不会递送/未决,直接丢弃。

二. 信号的分类

分类 可靠信号 不可靠信号
取值 SIGRTMIN ~ SIGRTMAX 小于 SIGRTMIN
行为 来不及处理时排队,不丢失 来不及处理则丢弃,只留一个
  • Linux 共 64 种信号:前 32 种非实时(不支持排队 = 不可靠),后 32 种实时(支持排队 = 可靠)。
  • 按来源还分:同步信号 (进程自身操作产生,如被零除)/异步信号(进程外事件,如击键)。

常用信号(man 7 signal 完整列表)

信号 说明 默认处理
SIGINT Ctrl+C 终止
SIGQUIT Ctrl+\ 终止 + core
SIGKILL 不可捕捉、不可忽略 终止
SIGSTOP 不可捕捉、不可忽略 暂停
SIGSEGV 内存非法访问 终止 + core
SIGPIPE 往已关闭的管道/socket 写 终止
SIGCHLD 子进程停止或终止 忽略
SIGALRM alarm/setitimer 定时到期 终止
SIGUSR1/2 用户自定义 终止
SIGTSTP Ctrl+Z 挂起 暂停
SIGCONT 让暂停的进程继续 继续/忽略

三. 信号的处理

3.1 三种处理方式

方式 说明
默认处理 多数导致进程终止
忽略 signal(SIGINT, SIG_IGN);SIGKILL/SIGSTOP 不能被忽略
捕捉处理 注册处理函数(最常用);SIGKILL/SIGSTOP 不能被捕捉

3.2 signal(老机制,有缺陷)

c 复制代码
#include <signal.h>
result = signal(int signum, void (*action)(int));
  • action:SIG_DFL(默认)/SIG_IGN(忽略)/函数名。
  • 成功返回之前的处理函数指针,失败返回 -1。
c 复制代码
void sigHandler(int signum)
{
    printf("The sign no is:%d\n", signum);
    signal(SIGINT, sigHandler);   // ⚠ 必须重新注册,否则恢复默认
}

int main()
{
    signal(SIGINT, sigHandler);
    signal(SIGQUIT, SIG_IGN);     // 忽略 ^\
    while (1) sleep(1);
}

⚠ signal 的三个问题:① 处理函数执行中又来同类 信号(多余丢失);② 又来异类 信号(中断当前处理);③ 阻塞系统调用(如 read)被信号打断后如何继续。→ 用 sigaction 替代。

3.3 sigaction(推荐)

c 复制代码
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
    void     (*sa_handler)(int);                        // 老式处理函数
    void     (*sa_sigaction)(int, siginfo_t *, void *); // 带附加信息(可获知信号产生原因)
    sigset_t   sa_mask;      // 处理期间额外阻塞的信号集
    int        sa_flags;     // 行为选项
};
sa_flags 作用
SA_RESTART 被信号打断的阻塞系统调用(read/write/ioctl)自动重启 ;不设则返回 -1,errno=EINTR
SA_SIGINFO 用 sa_sigaction(3 参数)而不是 sa_handler
SA_NODEFER 处理时不阻塞新信号(此时 sa_mask 失效)
SA_RESETHAND 处理完自动撤消注册(即 signal 的老行为)

信号集操作函数

c 复制代码
int sigemptyset(sigset_t *set);                   // 清空
int sigfillset(sigset_t *set);                    // 填满所有信号
int sigaddset(sigset_t *set, int signum);         // 添加
int sigdelset(sigset_t *set, int signum);         // 移除
int sigismember(const sigset_t *set, int signum); // 是否包含

sigprocmask:全程阻塞(与 sa_mask 的区别是它对所有场合生效)

c 复制代码
int sigprocmask(int how, const sigset_t *newset, sigset_t *oldset);
how 含义
SIG_BLOCK 把 newset 加入原阻塞集
SIG_UNBLOCK 从原阻塞集移除 newset
SIG_SETMASK 把阻塞集设为 newset

示例:可靠信号排队、不可靠信号丢失

c 复制代码
sem_t 信号集设 SIGINT(不可靠) + SIGRTMIN(可靠) → sigprocmask(SIG_BLOCK) 屏蔽
→ sigaction 注册(SA_SIGINFO)→ sleep(60) 期间用 kill -SIGRTMIN <pid> 发多次
→ sigprocmask(SIG_SETMASK, &oldmask) 解除阻塞
结果:6 个实时信号全部被处理,普通 SIGINT 只处理 1 次

四. 信号发送函数

前提:发送进程的用户 ID 与接收进程相同,或发送者是超级用户。

函数 原型 作用
kill int kill(pid_t pid, int sig); 向指定进程发信号
raise int raise(int sig); 向自身发信号,等价 kill(getpid(), sig)
sigqueue int sigqueue(pid_t pid, int sig, const union sigval value); 发信号并可附带数据
c 复制代码
union sigval { int sival_int; void *sival_ptr; };

sigqueue 的 value 只有在注册时 sa_flags 含 SA_SIGINFO 才会传给处理函数。

五. 可重入函数

  • 可重入 :函数可被多个任务并发调用而不产生数据错误。信号处理函数中必须只用可重入函数。
  • 可重入函数中:不用静态变量、不用 malloc/free、不用标准 I/O 库,用全局变量要小心。
  • 常见可重入函数:read write open close fork exit kill raise signal sigaction wait waitpid sleep lseek stat dup pipe socket send recv 等(完整列表见 man 7 signal 的 Async-signal-safe functions)。

六. 父子进程的信号处理

  • 子进程继承 父进程的信号处理方式,直到调用 exec。
  • 子进程 exec 后,父进程中设为"捕捉"的信号恢复为默认处理,其余不变。
c 复制代码
act.sa_sigaction = intsig_handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &act, NULL);
printf("The parent pid = %d\n", getpid());
pid = fork();
// if (pid == 0) execvp("ls", arg);   // 打开后子进程 SIGINT 变回默认(终止)
while (1);

七. 信号处理的应用

7.1 被中断的系统调用

  • 慢速设备(终端、文件、网络、磁盘)的系统调用会被信号中断;用 sigaction 且设 SA_RESTART 时自动重启 ,否则返回 -1 + EINTR。

7.2 用 SIGCHLD 防止僵尸进程

子进程退出会向父进程发 SIGCHLD(默认被忽略),在处理函数里 wait/waitpid 即可回收。

c 复制代码
void sigchld_handler(int sig)
{
    int status;
    waitpid(-1, &status, WNOHANG);
    if (WIFEXITED(status))       printf("child process exit normally\n");
    else if (WIFSTOPPED(status)) printf("child process is stopped\n");
}

int main()
{
    pid_t pid;
    signal(SIGCHLD, sigchld_handler);
    pid = fork();
    if (pid == 0)       abort();                              // 子进程异常退出
    else if (pid > 0) { sleep(2); printf("parent process\n"); }
    else                exit(0);
}

八. 系统定时信号

8.1 睡眠函数

c 复制代码
unsigned int sleep(unsigned int seconds);   // 秒
void usleep(unsigned long usec);            // 微秒

unsigned int alarm(unsigned int seconds);   // seconds 秒后产生 SIGALRM
int pause(void);                            // 挂起,直到收到一个信号才返回

用 alarm + pause 实现 sleep

c 复制代码
void alarm_handler(int a) { printf("sleep is over"); }

int mysleep(int seconds)
{
    signal(SIGALRM, alarm_handler);
    alarm(seconds);
    pause();
}

⚠ sleep 内部就是用 alarm 实现的,不要和 alarm 混用。

8.2 三个计时器

计时器 计算内容 信号
ITIMER_REAL 真实 程序运行的实际时间 SIGALRM
ITIMER_VIRTUAL 虚拟 用户态消耗的时间 SIGVTALRM
ITIMER_PROF 实用 用户态 + 内核态 SIGPROF

例:用户态 10s + 内核态 8s + 睡眠 7s → 真实 25s、虚拟 10s、实用 18s。

c 复制代码
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);

struct itimerval {
    struct timeval it_interval;   // 重复间隔
    struct timeval it_value;      // 初始倒计时
};
struct timeval { long tv_sec; long tv_usec; };   // 秒 / 微秒
c 复制代码
signal(SIGALRM, alarmsign_handler);
struct itimerval tval;
timeChange(1,    &tval.it_value);      // 初始 1 毫秒
timeChange(1000, &tval.it_interval);   // 每 1000 毫秒触发一次
setitimer(ITIMER_REAL, &tval, NULL);
while (getchar() != '#');              // 输入 # 退出
相关推荐
天启HTTP2 小时前
爬虫防封核心原理:IP轮换机制与风控规避逻辑
linux·服务器·网络·爬虫·tcp/ip
运维行者_2 小时前
网络性能监控怎么做?从自动发现到根因分析的4个环节
运维·服务器·网络·人工智能·支持向量机
此时不提桶,更待何时3 小时前
06-14-A-Kafka集群运维与迁移实战详解
运维·kafka
广州浮点FLOATLIC4 小时前
许可证服务器迁移后软件打不开:研发 IT 怎样定位连接问题
linux·服务器·数据库
IT研究所5 小时前
AI-ITR平台如何减少客户问题反复升级?
大数据·运维·人工智能·低代码·自然语言处理·安全架构·企微
BLUcoding5 小时前
接口服务公网超时排查记录:SecureRandom 阻塞问题分析与解决
java·linux·springboot·aes·securerandom
AIgorithmGEEK5 小时前
[Linux]从手写报头到内核套路:序列化、反序列化与自定义协议全链路
linux·运维·服务器·网络·序列化·反序列化
Nil2085 小时前
leetcode 139单词拆分
linux·运维·服务器
Starry-sky(jing)5 小时前
BUG: unable to handle kernel paging request 完整排查:dmesg 四要素与三路定罪
linux·运维·服务器·内核·排障