☰
30天速通Linux 第六章信号及信号处理
2026/10/8 23:15:55 网站建设 项目流程

学习资料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完整列表)

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

三. 信号的处理

3.1 三种处理方式

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

3.2signal(老机制,有缺陷)

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

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

3.3sigaction(推荐)

intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);structsigaction{void(*sa_handler)(int);// 老式处理函数void(*sa_sigaction)(int,siginfo_t*,void*);// 带附加信息(可获知信号产生原因)sigset_tsa_mask;// 处理期间额外阻塞的信号集intsa_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的老行为)

信号集操作函数

intsigemptyset(sigset_t*set);// 清空intsigfillset(sigset_t*set);// 填满所有信号intsigaddset(sigset_t*set,intsignum);// 添加intsigdelset(sigset_t*set,intsignum);// 移除intsigismember(constsigset_t*set,intsignum);// 是否包含

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

intsigprocmask(inthow,constsigset_t*newset,sigset_t*oldset);
how含义
SIG_BLOCK把newset加入原阻塞集
SIG_UNBLOCK从原阻塞集移除newset
SIG_SETMASK把阻塞集设为newset

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

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

四. 信号发送函数

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

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

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

五. 可重入函数

  • 可重入:函数可被多个任务并发调用而不产生数据错误。信号处理函数中必须只用可重入函数。
  • 可重入函数中:不用静态变量、不用malloc/free、不用标准 I/O 库,用全局变量要小心。
  • 常见可重入函数:readwriteopencloseforkexitkillraisesignalsigactionwaitwaitpidsleeplseekstatduppipesocketsendrecv等(完整列表见man 7 signal的 Async-signal-safe functions)。

六. 父子进程的信号处理

  • 子进程继承父进程的信号处理方式,直到调用exec。
  • 子进程exec后,父进程中设为"捕捉"的信号恢复为默认处理,其余不变。
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即可回收。

voidsigchld_handler(intsig){intstatus;waitpid(-1,&status,WNOHANG);if(WIFEXITED(status))printf("child process exit normally\n");elseif(WIFSTOPPED(status))printf("child process is stopped\n");}intmain(){pid_tpid;signal(SIGCHLD,sigchld_handler);pid=fork();if(pid==0)abort();// 子进程异常退出elseif(pid>0){sleep(2);printf("parent process\n");}elseexit(0);}

八. 系统定时信号

8.1 睡眠函数

unsignedintsleep(unsignedintseconds);// 秒voidusleep(unsignedlongusec);// 微秒unsignedintalarm(unsignedintseconds);// seconds 秒后产生 SIGALRMintpause(void);// 挂起,直到收到一个信号才返回

用alarm+pause实现sleep

voidalarm_handler(inta){printf("sleep is over");}intmysleep(intseconds){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。

intgetitimer(intwhich,structitimerval*value);intsetitimer(intwhich,conststructitimerval*value,structitimerval*ovalue);structitimerval{structtimevalit_interval;// 重复间隔structtimevalit_value;// 初始倒计时};structtimeval{longtv_sec;longtv_usec;};// 秒 / 微秒
signal(SIGALRM,alarmsign_handler);structitimervaltval;timeChange(1,&tval.it_value);// 初始 1 毫秒timeChange(1000,&tval.it_interval);// 每 1000 毫秒触发一次setitimer(ITIMER_REAL,&tval,NULL);while(getchar()!='#');// 输入 # 退出

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询