学习资料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 库,用全局变量要小心。 - 常见可重入函数:
readwriteopencloseforkexitkillraisesignalsigactionwaitwaitpidsleeplseekstatduppipesocketsendrecv等(完整列表见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() != '#'); // 输入 # 退出