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

embedded/2025/2/21 4:28:01/

目录

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内核实现信号捕捉过程


http://www.ppmy.cn/embedded/163457.html

相关文章

Jenkins 给任务分配 节点(Node)、设置工作空间目录

Jenkins 给任务分配 节点(Node)、设置工作空间目录 创建 Freestyle project 类型 任务 任务配置 Node 打开任务-> Configure-> General 勾选 Restrict where this project can be run Label Expression 填写一个 Node 的 Label&#xff0c;输入有效的 Label名字&#x…

小程序之间实现互相跳转的逻辑

1:小程序之间可以实现互相跳转吗 可以实现互相跳转! 2:小程序跳转是否有限制 有限制!限制如下 2.1:需要用户触发跳转 从 2.3.0 版本开始,若用户未点击小程序页面任意位置,则开发者将无法调用此接口自动跳转至其他小程序。 2.2:需要用户确认跳转 从 2.3.0 版本开始…

HarmonyOS的核心特性:分布式技术引领创新

在数字化浪潮汹涌的今天&#xff0c;物联网&#xff08;IoT&#xff09;技术的飞速发展正逐步打破设备间的界限&#xff0c;使万物互联成为可能。HarmonyOS&#xff0c;作为华为自主研发的分布式全场景操作系统&#xff0c;凭借其核心的分布式技术&#xff0c;不仅引领了操作系…

04 redis数据类型

文章目录 redis数据类型string类型hash类型list类型set类型zset类型 &#xff08;sortedset&#xff09;通用命令 redis数据类型 官方命令&#xff1a;&#xff1a;http://www.redis.cn/commands.html Redis 中存储数据是通过 key-value 格式存储数据的&#xff0c;其中 val…

大一的你如何入门TensorFlow

刚刚迈入大学的你&#xff0c;对计算机编程还比较陌生。对于现在主流人工智能技术架构TensorFlow的学习&#xff0c;需要循序渐进。入门 TensorFlow 编程需要结合基础知识学习和实践操作。首先可能需要巩固Python基础&#xff0c;特别是NumPy和数据处理相关的库&#xff0c;因为…

保姆级GitHub大文件(100mb-2gb)上传教程

GLF&#xff08;Git Large File Storage&#xff09;安装使用 使用GitHub desktop上传大于100mb的文件时报错 The following files are over 100MB. lf you commit these files, you will no longer beable to push this repository to GitHub.com.term.rarWe recommend you a…

matlab汽车动力学半车垂向振动模型

1、内容简介 matlab141-半车垂向振动模型 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略

YOLOv11目标检测:解密mAP50与mAP的背后秘密

前言 YOLO(You Only Look Once)作为目标检测领域的“网红”,已经在技术圈掀起了不小的波澜。它通过一次性扫描整个图像,快速定位目标,这种高效的方式让无数应用得以实现——从自动驾驶到安防监控,YOLO都能轻松驾驭。而随着YOLOv11的发布,大家对它的表现充满了好奇,尤其…