当前位置: 首页 > article >正文

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;
}

- 使用 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.kler.cn/a/550889.html

相关文章:

  • 三、tsp学习笔记——屏幕移植
  • 嵌入式 Linux 设备树:为什么需要设备树?
  • Intel i7系列CPU替换为Xeon X79或X99架构的CPU替代方案和对比分析
  • jenkins 2.380配置从节点
  • DC-7靶机渗透测试全过程
  • BUU38 [RoarCTF 2019]Easy Java1
  • label-studio 导入既有的yolo格式标注
  • 【嵌入式Linux应用开发基础】read函数与write函数
  • 【Rust中级教程】1.9. 所有权(简单回顾):所有权的核心思想、如何实现`Copy` trait、值的删除(丢弃)、值删除的顺序
  • 在Linux系统下修改Docker的默认存储路径
  • Vue 组件化开发——基础与实践
  • 基于 SSM 框架和 Vue 的高校共享单车管理系统设计与实现
  • 人工智能 - 主动视觉可能就是你所需要的:在双臂机器人操作中探索主动视觉
  • 如何在微信小程序中使用 Lottie 动画
  • python 脚本命令 与 lauch.json 在 参数方面的不同
  • 如何在 VS Code 中快速使用 Copilot 来辅助开发
  • APP端弱网模拟与网络测试:如何确保应用在各种网络环境下稳定运行
  • gitte远程仓库修改后,本地没有更新,本地与远程仓库不一致
  • 【Python爬虫(11)】从入门到精通:CSS选择器在Python爬虫中的深度解析(豆瓣电影实例)
  • 【git】工作场景下的 工作区 <-> 暂存区<-> 本地仓库 命令实战 具体案例