Linux信号详解~

目录

前言

一、初识信号

二、信号的概念

三、信号的发送与捕捉

3.1 信号的发送

3.1.1 kill 命令

3.1.2 kill 函数

3.1.3 raise函数

3.1.4 abort函数

3.2 信号的捕捉

3.2.1 signal函数

3.2.2 sigaction函数

3.2.3 图示

四、信号的产生

4.1 硬件异常产生信号

4.2 软件条件产生信号

五、Core dump

5.1 core dump介绍

5.2 core dump作用

 六、阻塞信号

6.1 相关概念

6.2 在内核中的表示

6.3 sigprocmask

6.4 sigpending

七、可重入函数


前言

        在日常生活中,有许多方面都涉及到了信号的知识,例如信号弹、上下课铃声、红绿灯、闹钟等等。我们可以仔细想想由信号引发的几个问题:

  1. 我们是如何认识这些信号的?----有人教,随后记住了;
  2. 即使现在没有信号产生,我们也知道信号产生之后应该做什么
  3. 信号产生了,我们可能并不会立即处理这个信号,因为我们可能在做一些更重要的事情,由此可以得出信号产生后到信号处理之间其实会有一段时间窗口,而在这个时间窗口内,我们必须记住信号的到来。

        而在计算机当中,执行的主体就是进程,也就是说进程必须能够识别并处理信号,这是属于进程内置功能的一部分。同样的,一个进程由信号产生,到信号开始被处理,就一定会有时间窗口,而进程具有临时保存哪些信号已经发生了的能力。

一、初识信号

        在linux中运行某一个进程时,我们可以随时按下 ctrl+c 来杀掉前台进程,如下图一个死循环的输出我们用ctrl+c使进程退出:

它为什么能够杀掉前台进程呢?

        在Linux中的一次登录中,一个终端一般会配上一个bash,每一个登录只允许一个进程是前台进程,可以允许多个进程是后台进程。因此如果我们运行时在进程名后面加上&,则代表让它在后台运行,这个时候我们使用ctrl+c就没有用了,必须使用kill -9 + pid 号来杀掉进程。

        所以正常我们在运行一个进程时,在对bash进行输入命令就没有用了,因为这个进程运行时就成为了前台进程。

kill -l 可以查看操作系统拥有的信号:

        ctrl + c 的本质是被进程解释成为收到了2号信号SIGINT,需要注意的是,不同的操作系统可能对信号的编号有所不同,因此在跨平台开发时应当注意信号编号的兼容性。

        普通信号可以不立即处理,实时信号必须立即处理。

信号的处理方式:

        1. 忽略此信号。

        2. 执行该信号的默认处理动作。

        3. 自定义提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。

二、信号的概念

        信号是 Linux 操作系统中用于进程间通信、处理异常等情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知,用于通知该进程或线程某种事件已经发生,需要做出相应的处理。

        信号的产生和我们自己的代码的运行是异步的,这意味着信号的产生与代码的执行没有直接的关联,信号属于软中断。

三、信号的发送与捕捉

3.1 信号的发送

在 Linux 中,进程可以通过向其他进程或自身发送信号的方式进行通信或处理异常情况。下面介绍几种常见的发送信号的方法。

3.1.1 kill 命令

kill [-signal] PID

其中,-signal 可选参数表示要发送的信号类型,如果省略该参数,则默认发送 SIGTERM 信号。PID 表示接收信号的进程 ID。

例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下命令:

kill -SIGINT 123

3.1.2 kill 函数

我们也可以使用系统调用的一些函数来发送信号:

其中,pid 表示接收信号的进程 ID,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下代码:

#include <signal.h>
#include <unistd.h>
 
int main() {
  pid_t pid = 123;
  int sig = SIGINT;
  if (kill(pid, sig) == -1) {
    perror("kill");
    return 1;
  }
  return 0;
}

3.1.3 raise函数

raise 函数是一个简单的发送信号的函数,可以用来向当前进程发送信号。raise 函数的原型如下:

其中,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

例如,要向当前进程发送 SIGTERM 信号,可以执行以下代码:

#include <signal.h>
 
int main() {
  int sig = SIGTERM;
  if (raise(sig) == -1) {
    perror("raise");
    return 1;
  }
  return 0;
}

3.1.4 abort函数

abort函数的作用是引起一个正常函数的终止,它会给自己发送一个6号信号SIGABRT:

3.2 信号的捕捉

        在上文提到过,信号是可以被自定义捕捉的,下面介绍几种常见的捕捉信号的方法。

3.2.1 signal函数

signal 函数可以用来注册信号处理函数。signal 函数的原型如下:

其中,sig 表示要注册的信号类型handler 是一个函数指针,指向信号处理函数。signal 函数返回一个函数指针,指向之前注册的信号处理函数。如果注册信号处理函数失败,则返回 SIG_ERR。

例如,要注册 SIGINT 信号的处理函数,自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值,可以执行以下代码:

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
void sigcb(int signo)
{
    cout << "process get a SIGINT signal: " << signo <<endl;
    // exit(1);
}
int main()
{
    if (signal(SIGINT, sigcb) == SIG_ERR) {
        perror("signal");
        return 1;
    }
    while(true)
    {
        cout<<"I am a process,pid: "<<getpid()<<endl;
        sleep(1);
    }

    return 0;
}

当我们按下ctrl+c时,可以看到程序输出,最后我们使用ctrl+\退出程序,运行结果如下:

3.2.2 sigaction函数

在Linux中,sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回 -1。

其中,sig 表示要注册的信号类型act 是一个指向 struct sigaction 结构体的指针,表示新的信号处理函数和信号处理选项,oldact 是一个指向 struct sigaction 结构体的指针,用于获取之前注册的信号处理函数和信号处理选项。

struct sigaction 结构体的定义如下:

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};
  • 其中,sa_handler 字段指定信号处理函数的地址。如果设置为SIG_IGN,则表示忽略该信号。如果设置为SIG_DFL,则表示使用默认处理器,也可以自己设置需处理的函数逻辑。
  • sa_sigaction 字段指定一个信号处理器函数,这个函数包含三个参数:一个整数表示信号编号,一个指向siginfo_t结构体的指针,和一个指向void类型的指针。
  • sa_mask字段指定了在执行信号处理函数期间要阻塞哪些信号。
  • 后面两个字段本章不做详细解释。

例如,要注册 SIGINT 信号的处理函数,自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值,可以执行以下代码:

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
void sigcb(int signo)
{
    cout << "process get a SIGINT signal: " << signo <<endl;
    // exit(1);
}
int main()
{
    struct sigaction newact = {
        newact.sa_handler = sigcb
    };
    struct sigaction oldact;
 
    if (sigaction(SIGINT, &newact, &oldact) == -1) 
    {
        perror("sigaction");
        return 1;
    }
    while(true)
    {
        cout<<"I am a process,pid: "<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

3.2.3 图示

当我们的进程从内核态返回到用户态的时候,进行信号的检测和处理:

四、信号的产生

4.1 硬件异常产生信号

        硬件异常产生信号指硬件发现进程的某种异常,而硬件是被操作系统管理。硬件会将异常通知给系统,系统就会向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

4.2 软件条件产生信号

        alarm函数相当于设置一个闹钟,告诉内核多少秒后,发送一个SIGALRM信号给当前进程,它的返回值是一个闹钟的剩余时间。

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
int main()
{
    int n=0;
    alarm(1);    //1秒后给进程发送SIGALRM信号
    while(true)
    {
        cout<<n<<endl;
        n++;
    }
    return 0;
}

五、Core dump

5.1 core dump介绍

        当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。但默认云服务器上面的core功能是被关闭的,我们可以使用ulimit-a 查看, ulimit-c +字节数 设置core文件大小:

 

        子进程的status可以当作位图来看,因此我们可以手写一段代码来提取出Core Dump的值,原理就是通过使用(status >> 8) & 0xFF 获取子进程的退出码(高8位),通过使用(status & 0x7F)获取子进程的退出信号(低7位),最后使用((status >> 7) & 1) 表达式用于判断是否发生了核心转储(第8位即core dump标志)。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int cnt = 500;
        while(cnt)
        {
            cout << "i am a child process, pid: " << getpid() << "cnt: " << cnt << endl;
            sleep(1);
            cnt--;
        }

        exit(0);
    }

    // father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        cout << "child quit info, rid: " << rid << " exit code: " << 
            ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) <<
                " core dump: " << ((status>>7)&1) << endl; 
    }
    return 0;
}

对于8号信号,默认云服务器上面的core功能是被关闭的,可以看到它的core dump为0,使用ulimit -c 设置文件大小以后,再运行可以看到core dump标志位变为1并且生成了core.pid号的文件:

5.2 core dump作用

        假设我们写一段除0的错误代码:

#include <iostream>
using namespace std;

int main()
{
    int a = 10;
    int b = 0;

    a /= b;

    cout << " a = " << a << endl;
    return 0;
}

 运行后可以结合gdb来进行事后调试:

 六、阻塞信号

6.1 相关概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

6.2 在内核中的表示

在task_struct结构中信号的构成实质是两个位图和一个数组,我们看的顺序也是横着从左往右看。

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
  • 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号被阻塞未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
  • 当一个信号被阻塞时,它仍然可以被发送到进程,并且会被添加到未决信号集合中。阻塞仅仅阻止信号的传递,即阻止信号的处理,但不阻止信号的接收。
  • 如果一个信号被设置为忽略,那么即使该信号被发送到进程,它也不会被添加到未决信号集合中,因为忽略的信号不会对进程产生任何影响。

6.3 sigprocmask

        函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集/BLOCK表),成功返回0,出错返回-1

how参数:

sigset_t 称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,它用来存储未决和阻塞标志。

set参数:指向一个信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号。如果 set 是一个空指针(NULL),则 how 参数没有效果指向一个信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号,是一个输入型函数。如果 set 是一个空指针(NULL),则 how 参数没有效果。

oldset参数:如果不是空指针(NULL),则进程的当前信号屏蔽字会被存储在 oset 指向的位置。如果 oset 是空指针,则不返回当前的信号屏蔽字,是一个输出型参数。

6.4 sigpending

        sigpending函数读取当前进程的未决信号集,通过set参数传出,即把调用进程所对应的pending表带出来,调用成功则返回0,出错则返回-1

 例如,可以使用下段代码屏蔽2号信号并通过打印pending表来观察:

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;

void PrintPending(sigset_t &pending)
{
    for(int signo=31;signo>=1;signo--)
    {
        //存在打印1,否则为0
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }
        else
        {
            cout<<"0";
        }
    }
    cout<<endl;
}
int main()
{
    
    //阻塞2号新号       --数据预备
    sigset_t bset,oset;
    //函数sigemptyset初始化set所指向的信号集使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
    sigemptyset(&bset); 
    sigemptyset(&oset); 

    //函数sigaddset在该信号集中添加2号信号
    sigaddset(&bset,2); 
    
    //----系统调用,将数据设置进内核----
    sigprocmask(SIG_SETMASK,&bset,&oset);

    //重复打印pending信息便于观察
    sigset_t pending;
    while(true)
    {
        //获取
        int n = sigpending(&pending);
        if(n<0) continue;
        //打印
        PrintPending(pending);
        sleep(1);
    }

    return 0;
}

注意,9号和19号信号是无法被屏蔽的:

七、可重入函数

如果一个函数在被重复进入的情况下不会出错,则是可重入函数,否则是不可重入函数。

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/368303.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++输出地址

下面是一段输出地址的程序。 #include <bits/stdc.h> using namespace std;int main() {int s;cout << &s;//原地址return 0; }假如有一个人&#xff08;的朋友&#xff09;后来了&#xff0c;他也想住进的房间&#xff0c;我们可以这样&#xff1a; #includ…

OfficeWeb365 Readfile 任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

windows下安装go

下载golang Go 官网下载地址&#xff1a; https://golang.org/dl/ Go 官方镜像站&#xff08;推荐&#xff09;&#xff1a; https://golang.google.cn/dl/ 选择安装包 验证有没有安装成功 查看 go 环境 说明 &#xff1a; Go1.11 版本之后无需手动配置环境变量&#xff0c…

Apache POl Excel

目录 介绍 Apache POl的应用场景&#xff1a; 入门使用 通过POI创建Excel文件并且写入文件内容 通过POI读取Excel文件中的内容 介绍 Apache POl是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用POI在Java程序中对Miscrosoft O…

VBA技术资料MF114:批量给Word文档添加页眉

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

Acwing 141 周赛 解题报告 | 珂学家 | 逆序数+奇偶性分析

前言 整体评价 很普通的一场比赛&#xff0c;t2思维题&#xff0c;初做时愣了下&#xff0c;幸好反应过来了。t3猜猜乐&#xff0c;感觉和逆序数有关&#xff0c;和奇偶性有关。不过要注意int溢出。 欢迎关注: 珂朵莉的天空之城 A. 客人数量 题型: 签到 累加和即可 import…

简单说说mysql的日志

今天我们通过mysql日志了解mysqld的错误日志、慢查询日志、二进制日志&#xff0c;redolog, undolog等。揭示它们的作用和用途&#xff0c;让我们工作中更能驾驭mysql。 redo 日志 如果mysql事务提交后发生了宕机现象&#xff0c;那怎么保证数据的持久性与完整性&#xff1f;…

JAVA基础 队列

排队取奶茶 时间限制&#xff1a;1.000S 空间限制&#xff1a;128MB 题目描述 假设有一家奶茶店&#xff0c;现在有一些人在排队等待取奶茶&#xff0c;同时也有人在取奶茶。 请你设计一个程序模拟这种情况下的奶茶队列管理。 假设每个人取奶茶的时间非常短&#xff0c;可…

MySQL亿级数据的查询优化-历史表该如何建

前端时间在知乎上看到一个问题&#xff0c;今天有空整理并测试了一下&#xff1a; 这个问题很具体&#xff0c;所以还是可以去尝试优化一下&#xff0c;我们基于InnoDB并使用自增主键来讲。 比较简单的做法是将历史数据存放到另一个表中&#xff0c;与最近的数据分开。那是不是…

DockerCompose+SpringBoot+Nginx+Mysql实践

DockerComposeSpringBootNginxMysql实践 1、Spring Boot案例 首先我们先准备一个 Spring Boot 使用 Mysql 的小场景&#xff0c;我们做这样一个示例&#xff0c;使用 Spring Boot 做一个 Web 应 用&#xff0c;提供一个按照 IP 地址统计访问次数的方法&#xff0c;每次请求时…

【C语言/基础梳理/期末复习】动态内存管理(附思维导图)

目录 一、为什么要有动态内存分配 &#xff08;1&#xff09;我们已经掌握的内存方式的特点 &#xff08;2&#xff09;需求 二、malloc和free 2.1.malloc 2.1.1函数原型 2.1.2函数使用 2.1.3应用示例​编辑 2.2free 2.2.1函数原型 2.2.2函数使用 三、calloc和reallo…

Vue3 - 从 vue2 到 vue3 过渡,这一套就够了(案例 + 效果演示)(二)

目录 一、组合式 API 的使用 1.1、watch 函数 1.2、watchEffect 函数 1.3、toRef 和 toRefs 1.3.1、toRef 1.3.2、toRefs 1.4、vue3 的声明周期 一、组合式 API 的使用 1.1、watch 函数 与 vue2.x 中的 watch 配置功能一致&#xff0c;但是多了一些坑&#xff1a; 这…

计算机设计大赛 深度学习 YOLO 实现车牌识别算法

文章目录 0 前言1 课题介绍2 算法简介2.1网络架构 3 数据准备4 模型训练5 实现效果5.1 图片识别效果5.2视频识别效果 6 部分关键代码7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于yolov5的深度学习车牌识别系统实现 该项目较…

platform总线

1、平台总线模型 平台总线模型是Linux系统虚拟出来的总线&#xff0c;而I2C、SPI等物理总线是真实存在的。 平台总线模型将一个驱动分成两个部分&#xff0c;分别是device.c和driver.c&#xff0c;分别用来描述硬件信息和控制硬件。 平台总线通过字符串比较&#xff0c;将name…

MySQL数据库①_MySQL入门(概念+使用)

目录 1. 数据库的概念 1.1 数据库的存储介质 1.2 主流数据库 2. MySQL的基本使用 2.1 链接数据库 2.2 服务器管理 2.3 数据库&#xff0c;服务器和表关系 2.4 简单MySQL语句 3. MySQL架构 4. SQL分类 5. 存储引擎 本篇完。 1. 数据库的概念 数据库是按照数据结构来…

蓝桥杯备战——12.PCF8591芯片的使用

目录 1.芯片简介2.读写时序3.控制字4.代码封装库5.原理图分析6.使用示例 1.芯片简介 截取自NXP的PCF8591芯片数据手册&#xff0c;我把重点关注部分划出来了&#xff0c;请务必自行阅读一遍数据手册&#xff01; 2.读写时序 ①器件地址&#xff1a; Bit0决定是读还是写操作&…

阳光倒灌光催化太阳光(太阳能)模拟器

太阳光&#xff08;太阳能&#xff09;模拟器是一种模拟太阳光照射的设备。由于太阳模拟器本身体积较小&#xff0c;测试过程不受环境、气候、时间等因素影响&#xff0c;从而避免了室外测量的各种因素限制&#xff0c;广泛应用于太阳能电池特性测试&#xff0c;光电材料特性测…

数学建模比赛期间,网上的各种思路靠谱吗

美赛第二天&#xff0c;很多人已经撑不住了。 从昨天起网上就那么多“思路”、“指导”、“代做”、“成品论文”&#xff0c;有免费的&#xff0c;也有收钱的。 有人说那些思路都是滥竽充数&#xff0c;纯坑钱浪费时间 有人说自己啥都不会&#xff0c;看那些思路就有启发 有…

一些你可能用到的函数和头文件

对于排序想必大家应该挺熟悉的&#xff0c;如果要是给一连串打乱的整数让你由小到大排序&#xff0c;常见的方法有冒泡排序法和选择排序法等&#xff0c;今天我就给大家介绍一个十分好用的方法&#xff0c;就是使用 sort 函数来进行快排。 sort 函数是位于头文件 #include <…

正点原子--STM32定时器学习笔记(2)

书接上文&#xff0c;本篇是对基本定时器实验部分进行的总结~ 实验目标&#xff1a;通过TIM6基本定时器定时500ms&#xff0c;让LED0每隔500ms闪烁。 解决思路&#xff1a;使用定时器6&#xff0c;实现500ms产生一次定时器更新中断&#xff0c;在中断里执行“翻转LED0”。 定时…