Advertisement

Linux高并发服务器开发 第十八天(信号及相关概念 信号捕捉)

阅读量:

目录

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;
    
 }
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/ZA56UJqhB78GPvcQeaHRfWlxwIFb.png)

- 使用 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);
    
    
    
    
    cpp
    
    

1.8信号集操作函数

1.8.1操作自定义信号集函数

#include <signal.h>

sigset_t set; 自定义信号集。 // typedef unsigned long sigset_t;

int sigempty set(sigset_t *set); 清空自定义信号集

int sigfill set(sigset_t *set); 将自定义信号集,全部置1

int sigadd set(sigset_t *set, int signum); 将一个信号添加到自定义集合中。

int sigdel set(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;
    
 }
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/n7qtvfhsCY6JiHbVPyKGoT8a1ZlW.png)

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;
    
 }
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/gvwrYAqjiOMIbdQV4pStW18KL7hy.png)

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;
    
 }
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/4XuZjf8mvzCqbY9hs0AIRiDBxSTl.png)

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;
    
 }
    
复制代码
cpp
复制代码
#### 1.10.2内核实现信号捕捉过程
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/wEGv8aoUFtb1LpZrycmBPA63Hjqe.png)

全部评论 (0)

还没有任何评论哟~