Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)
- 互联网
- 2025-09-03 02:18:02

目录
1.1信号描述
1.2信号相关的概念
1.2.1未决:
1.2.2递达:
1.2.3信号处理方式:
1.2.4阻塞信号集(信号屏蔽字):
1.2.5未决信号集:
1.3信号4要素
1.4信号的产生
1.4.1按键产生
1.4.2系统调用产生
1.4.3软件条件产生
1.4.4硬件异常产生信号
1.4.5命令产生。
1.5kill 函数、命令产生信号
1.6alarm函数产生信号
1.7setitimer函数
1.8信号集操作函数
1.8.1操作自定义信号集函数
1.8.2操作信号屏蔽字 mask 的信号集操作函数
例题:创建函数,打印未决信号集中的每一个二进制位
1.9信号捕捉
1.9.1signal函数
1.9.2sigaction函数
1.9.3 信号捕捉特性:
1.10借助信号捕捉,完成子进程回收
1.10.1SIGCHLD 产生条件
1.10.2内核实现信号捕捉过程
1.1信号描述- 信号的共性: 1. 简单。 2. 不能携带大量数据。 3. 满足某一特定条件才发送。
- 信号的特质: - 信号软件层面上的 “中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止,处理信号,处理结束后,再继续执行后续指令。 - 所有的信号,产生、处理都是由内核完成。 - 信号的实现手段导致,信号有很强的延时。对用户而言,依然感觉不到。
1.2信号相关的概念 1.2.1未决- 产生与递达(处理)之间的状态。 该状态主要受 阻塞(屏蔽)影响。
1.2.2递达- 内核产生信号后递送并且成功到达进程。递达的信号,会被内核立即处理。
1.2.3信号处理方式1. 执行默认动作。
2. 忽略(丢弃)。
3. 捕捉(调用用户指定的函数)。
1.2.4阻塞信号集(信号屏蔽字)- 本质:位图。用来记录信号的屏蔽状态。
- 该信号集中的信号,表示成功被设置屏蔽。再次收到该信号,其处理动作将延后至解除屏蔽。此期间该信号一直处于未决态。
1.2.5未决信号集- 本质:位图。用来记录信号的处理状态。
- 该信号集中的信号,表示,信号已经产生,但尚未被处理。
1.3信号4要素- 信号使用之前,“必须”先确定该信号的 4 要素,再使用!
- 四要素内容: 1. 编号 (信号的编号范围1-31) 2. 名称 3. 事件 4. 默认处理动作 - 使用命令 kill -l 查看Linux 系统中,支持的所有信号。1~31 号:常规信号。 - man 7 signal 查看信号 4 要素。 - SIGKILL和SIGSTOP 信号,不允许忽略、捕捉和阻塞,只能执行默认动作。
1.4信号的产生 1.4.1按键产生- Ctrl + c → 2) SIGINT(终止/中断)
- Ctrl + \ → 3) SIGQUIT(退出)
1.4.2系统调用产生- alarm() → 14) SIGALRM
- abort()
- raise()
1.4.3软件条件产生- alarm() → 14) SIGALRM
- setitimer() → 14) SIGALRM
1.4.4硬件异常产生信号- 段错误:内存访问异常 。→ 11) SIGSEGV
- 浮点数例外:除 0。 → 8) SIGFPE
- 总线错误。内存对齐出错。 → 7) SIGBUS
1.4.5命令产生。- kill 命令
1.5kill 函数、命令产生信号kill命令:
#include <signal.h>
int kill(pid_t pid, int sig); // 发送信号给一个指定的进程。
参数: pid: >0: 发送信号给指定进程。 =0: 发送信号给跟调用kill函数的那个进程,处于同一进程组的进程。 <-1: 取绝对值,当做进组id, 发送信号给该进程组的所有组员。 kill -SIGKILL -9527 -1: 发送信号给,有权限发送的所有进程。 sig:信号编号。
返回值: 成功:0 失败:-1, errno
1.6alarm函数产生信号- 一个进程有且只有唯一的一个闹钟
unsigned int alarm(unsigned int seconds); // 设置定时,发送 SIGALRM 信号。
参数:
seconds: 定时的秒数。 采用自然计时法。
返回值: 上次定时剩余时间。 不会出错! alarm(0): 取消闹钟。
- 例题:统计当前使用的计算机, 1s 最多能数多少数。
int main(int argc, char *argv[]) { // 设置 1s 的定时器 alarm(1); int i = 0; for (i=0; ;i++) { printf("%d\n", i); } return 0; }- 使用 time 命令 查看 程序执行消耗的时间。 - 实际时间 = 用户时间 + 内核时间 + 等待时间
- time ./alarm > out ---- 程序优化的瓶颈在 IO
1.7setitimer函数int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数: which:指定计时方式。 ITIMER_REAL: 采用自然计时法。 ———— 发送 SIGLARM ITIMER_VIRTUAL:采用用户空间计时。 ———— 发送 SIGVTALRM ITIMER_PROF:采用内核+用户空间计时。 ———— 发送 SIGPROF new_value: 传入参数。定时时长。 struct itimerval
{ struct timeval
{ time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }it_interval; /* 周期定时秒数 */ struct timeval
{ time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }it_value; /* 第一次定时秒数 */ }; old_value: 传出参数。上次定时剩余时间。
返回值: 成功:0 失败:-1, errno
例如:
struct itimerval new_t; struct itimerval old_t; new_t.it_interval.tv_sec = 0; new_t.it_interval.tv_usec = 0; new_t.it_value.tv_sec = 1; new_t.it_value.tv_usec = 0; // 等价于 alarm(1) int ret = setitimer(ITIMER_REAL, &new_t, &old_t); 1.8信号集操作函数 1.8.1操作自定义信号集函数#include <signal.h>
sigset_t set; 自定义信号集。 // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); 清空自定义信号集
int sigfillset(sigset_t *set); 将自定义信号集,全部置1
int sigaddset(sigset_t *set, int signum); 将一个信号添加到自定义集合中。
int sigdelset(sigset_t *set, int signum); 将一个信号从自定义集合中移除。
上述 4 个函数返回值:成功:0, 失败:-1, errno
int sigismember(const sigset_t *set, int signum); 判断一个信号是否在集合中
返回值:
在:返回 1 --- 真 不在:返回 0 --- 假
1.8.2操作信号屏蔽字 mask 的信号集操作函数//设置屏蔽信号、解除屏蔽。都使用 sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数: how: SIG_BLOCK; 设置阻塞。 SIG_UNBLOCK;解除屏蔽。 SIG_SETMASK;用自定义的set替换mask (不推荐直接用) set:自定义set。 oldset:保存修改前的 mask状态,以便将来恢复。 也可以传 NULL\
返回值: 成功:0, 失败:-1, errno
int sigpending(sigset_t *set); //查看未决信号集函数 sigpending
参set:传出参数。 未决信息号集。
返回值:成功:0, 失败:-1, errno
例题:创建函数,打印未决信号集中的每一个二进制位 void print_pedset(sigset_t *set) { int i = 0; for (i = 1; i < 32; i++) //信号的编号范围1-31 { if (sigismember(set, i)) { putchar('1'); } else { putchar('0'); } } printf("\n"); } int main(int argc, char *argv[]) { // 定义自定义信号集, 保存旧mask, 保存未决信号集 sigset_t set, oldset, pedset; // 清空自定义信号集. sigemptyset(&set); // 将 2 号信号添加到自定义信号集 sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); // 借助自定义信号, 设置 pcb中的 信号屏蔽字中的 2 号信号为屏蔽. int ret = sigprocmask(SIG_BLOCK, &set, &oldset); if (ret == -1) sys_err("sigprocmask error"); while (1) { // 获取当前的未决信号集 ret = sigpending(&pedset); if (ret == -1) sys_err("sigpending error"); // 打印未决信号集 print_pedset(&pedset); sleep(1); } return 0; } 1.9信号捕捉 1.9.1signal函数#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参1:待捕捉的信号编号。 参2:一旦捕捉到该信号,执行的回调函数。
- 捕捉信号测试用例
void sig_catch(int signum) { if (signum == SIGINT) printf("catch you!! %d\n", signum); else if (signum == SIGQUIT) printf("哈哈, %d, 你被我抓住了\n", signum); else if (signum == SIGKILL) printf("byby %d\n", signum); return ; } int main(int argc, char *argv[]) { // 注册信号捕捉函数 signal(SIGINT, sig_catch); signal(SIGQUIT, sig_catch); signal(SIGKILL, sig_catch); while (1); // 模拟当前进程还有很多代码要执行. return 0; } 1.9.2sigaction函数//注册某一个信号的捕捉事件,指定回调函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参1:待捕捉的信号 参2:传入参数,指定新的处理方式。 参3:传出参数,保存旧有的信号处理方式。
返回值: 成功:0, 失败:-1, errno struct sigaction
{ void (*sa_handler)(int); //捕捉函数名,SIG_IGN表忽略;SIG_DFL表示执行默认动作 void (*sa_sigaction)(int, siginfo_t *, void *); //信号传参。 sigset_t sa_mask; //信号捕捉函数,调用期间,所要屏蔽的信号,加入此集合中。 int sa_flags; //通常设置为0,表使用默认属性。 本信号,自动被屏蔽。 void (*sa_restorer)(void); 被废弃!! };
- 重点掌握: 1. sa_handler:指定捕捉函数名。 2. sa_mask: 在信号捕捉函数调用期间,指定屏蔽哪些信号。【注意】:仅在捕捉函数调用期间有效。 3. sa_flags: 默认值0。 本信号,在信号捕捉函数调用期间,自动被屏蔽。
- 捕捉信号测试用例
void sig_catch(int signum) { if (signum == SIGINT) { printf("---- catch %d signal\n", signum); sleep(10); // 模拟信号捕捉函数,执行了很长时间. } } int main(int argc, char *argv[]) { struct sigaction act, oldact; act.sa_handler = sig_catch; sigemptyset(&(act.sa_mask)); // 清空 sa_mask屏蔽字 sigaddset(&act.sa_mask, SIGQUIT); // 信号SIGINT捕捉函数执行期间, SIGQUIT信号会被屏蔽 act.sa_flags = 0; // 本信号自动屏蔽. // 注册信号捕捉函数. int ret = sigaction(SIGINT, &act, &oldact); if (ret == -1) sys_err("sigaction error"); while(1); // 模拟程序还有很多事要做. return 0; } 1.9.3 信号捕捉特性:1. 捕捉函数执行期间, 信号屏蔽字,由原来pcb 中的 mask 改换为 sa_mask,捕捉函数执行结束,恢复回mask。 2. 捕捉函数执行期间,本信号,自动被屏蔽(sa_flags = 0) 3. 捕捉函数执行期间,被屏蔽的信号,多次发送,解除屏蔽后只处理一次
1.10借助信号捕捉,完成子进程回收 1.10.1SIGCHLD 产生条件- 子进程运行状态发生变化,就会给父进程发送 SIGCHLD
回收子进程代码实现:
void catch_child(int signum) { pid_t wpid; int status; //if ((wpid = wait(NULL)) != -1) // 这样回收产生僵尸子进程 while ((wpid = waitpid(-1, &status, 0)) != -1) { if (WIFEXITED(status)) { printf("-------------------catch child pid = %d, ret = %d\n", wpid, WEXITSTATUS(status)); } } } int main(int argc, char *argv[]) { int i; pid_t pid; for (i = 0; i<15;i++) { if ((pid = fork())== 0) break; } if (15 == i) { // 父进程 struct sigaction act; act.sa_handler = catch_child; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGCHLD, &act, NULL); printf("I am parent, pid = %d\n", getpid()); while (1); // 模拟程序执行很长时间 } else { // 子进程 printf("I am child, pid = %d\n", getpid()); return i; } return 0; } ``` 1.10.2内核实现信号捕捉过程Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Linux高并发服务器开发第十八天(信号及相关概念信号捕捉)”
上一篇
单片机的原理