linux信号

在这里插入图片描述

在生活中有哪些信号的例子呢?
1 起跑的时候的发令枪(听到枪响之后就开始跑步)
2 闹钟
3 红绿灯
4 低电量通知

我们以红绿灯为例子:
我们能识别红绿灯是不是有人教过我们的啊 ,我们生下来是不理解红绿灯这个概念的。是因为小学老师说,红灯停 ,绿灯行。我们长期以往学到了。
当我们看到红绿灯的时候,我们不一定会马上处理这个信号。就比如我们点了一个外卖,但是我们现在正在打王者荣耀,这时候就喊外卖员把外卖放在门口,我们将团战打完了我们再去拿外卖。
再上诉的例子中,我们信号到来,然后再到信号被处理。中间是有一个时间窗口,我们必须要记住这个信号。

信号的处理动作呢? 是有默认动作 , 自定义动作 ,还有忽略动作(这个忽略动作其实就是在处理信号,但是没有执行体)
在这里插入图片描述
我们把上面的概念迁移到进程中。这里我们需要有一个共识 , 信号是给进程发的。
进程是如何识别信号的呢?我们程序员先教进程遇到某某某信号该怎么处理。我们要让机器能够认识信号,并且识别到了信号要有对应的方法体。
进程本身就是被程序员编写的属性和逻辑的集合 — 程序员编码完成的。当进程收到信号的时候 , 进程可能正在执行更重要的代码,所以信号不一定会被马上处理。进程本身就需要对信号具有保存能力。
进程再处理信号的时候,一般会有三个默认的动作, 默认,自定义 ,忽略(信号被捕捉)

查看所有的信号

kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

信号的保存

信号发送给进程,进程需要保存信号。那么进程应该怎么保存信号呢?我们目前学习的信号是1 -31 是普通信号。后面的信号在了解。
可以通过 查看信号手册

man 7 signal

       Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at terminal
       SIGTTIN   21,21,26    Stop    Terminal input for background process
       SIGTTOU   22,22,27    Stop    Terminal output for background process

首先我们要保存31个信号采用的是位图的方式
那我们应该怎么保存信号呢?,就我们有32 个字节。每一个自己都代表一种信号,若我们收到了第一种信号就把第一个比特位的值由改为1 . 这样就保存了1号信号。
在这里插入图片描述
信号的发送
在这里插入图片描述

在这里插入图片描述

以上就是信号的预备知识

在这里插入图片描述

信号的产生

1 终端产生信号

有一段代码理解一下

#include <stdio.h>
#include <signal.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

void handler(int sig)
{
    printf("catch a sig : %d\n", sig);
    cout << "catch a sig " << sig << endl;
}
int main()
{
    signal(2, handler); // 前文提到过,信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的,提前了解一下
    while (1)
    {
        cout << "我是赠酒 " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

结果

我是赠酒 14006
我是赠酒 14006
我是赠酒 14006
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2
我是赠酒 14006
我是赠酒 14006
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2
我是赠酒 14006
^Ccatch a sig : 2
catch a sig 2

帮我在键盘中输入ctrl + c的时候。 就会发现终止不了进程, 而是Ccatch a sig : 2 打印类似信息。我们的ctrl+c就是2 号信号。
ctrl +/ 就是3号信号

这里我们写一个小的demo .我想实现一个代码。自己写于一个kill 的效果

test.cc代码

#include <stdio.h>
#include <signal.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

void handler(int sig)
{
    printf("catch a sig : %d\n", sig);
    cout << "catch a sig " << sig << endl;
}
int main()
{
    signal(3, handler); // 前文提到过,信号是可以被自定义捕捉的,siganl函数就是来进行信号捕捉的,提前了解一下
    while (1)
    {
        cout << "我是赠酒 " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

mysignal.cc代码

#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
using namespace std;

void Usage (const char* arr)
{
    cout<<arr<<endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid = atoi(argv[1]);
    pid_t signo = atoi(argv[2]);
    int n = kill(pid,signo);
    if(n!=0)
    {
        perror("kill");
    }
    return 0;
}

结果
在这里插入图片描述

2. 调用系统函数向进程发信号

kill -9 pid

3 由软件产生的信号

函数 alarm() 在 Unix 和类 Unix 系统中用于设置一个定时器(计时器),当定时器到达设定的时间后,操作系统会向当前进程发送 SIGALRM 信号。这个函数是基于信号的简单定时功能,广泛用于实现超时检测和定时任务。

在这里插入图片描述
代码


#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
#include <stdio.h>
using namespace std;
int cnt =0;
void catchSig(int signo)
{
    cout << "我捕捉到了一个信号" << getpid() << endl;
    cout<<cnt<<endl;
    exit(1);
}
int main()
{
    signal(SIGALRM, catchSig);
    alarm(1);
    while(true)
    {
        cnt++;
    }
    return 0;
}

结果

[zk@VM-24-17-centos lesson23]$ ./test2
我捕捉到了一个信号12901
405781115

在这里插入图片描述
在这里插入图片描述
可用堆来对时间排序,把小的放在堆顶。

4 由硬件产生的信号

我们先用一个代码验证一下会报那种信号

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

void catchSig(int signo)
{
    cout<<"我捕捉到了一个信号 "<<signo<<endl;
}
int main()
{
    cout<<"我正在运行中 。。。"<<endl;
    sleep(1);
    int a =10/0;
    return 0;
}

结果

[zk@VM-24-17-centos lesson23]$ ./test1 
我正在运行中 。。。
Floating point exception

查看kill - l,发现报的是 8号信号

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

void catchSig(int signo)
{
    cout<<"我捕捉到了一个信号 "<<signo<<endl;
}
int main()
{
    signal(8,catchSig);
    cout<<"我正在运行中 。。。"<<endl;
    sleep(1);
    int a =10/0;
    return 0;
}

结果:发现一直在报8号信号 ,没有中断进程

我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8
我捕捉到了一个信号 8

这里有一个疑问?明明是我写的代码报的错误,为什么是硬件错误呢?
首先我们计算需要在cpu上去计算 ,cpu上有很多寄存器 ,分别为 eax 到 edx 。将10 放进eax ,将0放入ebx。这样计算。当 CPU 执行除法指令并遇到除数为零的情况时,可能会返回一个未定义的结果或特定值(如最大整数等)。CPU 会在状态寄存器中设置特定的错误或异常标志。这可以是一个特定的异常位,如 溢出标志(OF) 或特定的错误标志。这个标志位的设置通知操作系统或应用程序有一个异常情况发生。依赖于具体的架构和操作系统,操作系统要感知到状态寄存器的异常,会给当前进程发送8号信号。从而触发了我们之前自定义信号的代码?

为什么一直发8号信号

信号处理返回到错误点:
当你的程序中发生除零操作(10/0),产生 SIGFPE 信号,你的信号处理函数 catchSig 被调用。如果这个函数执行完后,程序的控制流返回到了产生信号的那条指令(即除零操作),除非有某种机制来改变程序的状态或修改执行流,否则同样的操作会再次产生 SIGFPE 信号。
信号处理函数行为:
在默认情况下,许多系统上,当信号处理函数被触发后,相关信号的处理动作会重置为默认动作(通常是终止进程)。但如果信号处理器通过 signal 调用设置,而不是 sigaction,并且在处理函数中没有更改任何执行状态,那么当控制权返回到触发信号的代码位置时,相同的错误操作将重复执行,并再次触发信号。
操作系统的信号递送机制:
操作系统在检测到特定的错误(如除零错误)时,会向执行该操作的进程发送信号。如果进程的信号处理不足以解决问题(如简单地打印消息而不改变程序状态或逻辑流),则操作系统在程序继续执行时会不断重新检测到相同的错误条件,并持续发送同一信号。

在这里插入图片描述

硬件产生异常
在这里插入图片描述

另外一个例子


#include<iostream>
using namespace std;

int main()
{
    int* p =100;
    p = nullptr;
    *p=100;
    return 0;
}

会报错,11号信号

11) SIGSEGV

当你访问0位置的时候,这个CPU的mmu就会报错。给你发送11号信号。
在这里插入图片描述
以下是我的疑问??
虚拟内存管理:现代操作系统使用虚拟内存管理技术,将虚拟地址空间映射到物理内存。虚拟内存不是直接等同于物理内存的大小,它可以更大。例如,即使物理内存是4GB,操作系统可以通过虚拟内存技术提供更大的地址空间(如在32位系统中高达64GB),这是通过内存分页和交换(swap)文件或页文件(page file)实现的。
内存分页:操作系统将虚拟内存分为多个页(通常大小为4KB),这些页通过页表映射到物理内存的页框。如果虚拟内存页当前不在物理内存中,则操作系统可以将它们存储到硬盘上的交换空间,当需要时再加载回物理内存。
PCB和页表存储:尽管PCB和页表占用一部分内存,但这通常是非常小的一部分。页表可能相对较大,特别是在大型系统或具有大量内存的系统中,但它们通常仅占用物理内存的一小部分。操作系统设计时会考虑到这些结构的内存占用,并优化其内存使用。
物理内存的利用:你提到的4GB物理内存限制是指可用于用户进程和系统资源的总内存。操作系统内核、PCB、页表等系统资源确实占用了一部分物理内存,但是操作系统通过高效的内存管理技术(如内存分页和虚拟内存管理),确保了系统的正常运行,同时尽可能地为用户进程提供最大的可用内存空间

进程退出的时候的核心转储问题

在这里插入图片描述
在这里插入图片描述
term 是正常结束
core 是段错误·可以生一个文件 ,用gd加载该文件就可以看到在哪有错误。

在这里插入图片描述
在这里插入图片描述
代码想要被调试,需要使用 -g选项

信号的保存

先看看进程信号的执行流程

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

首先,
信号的保存采用的是位图结构,位图结构是什么呢??

00000000000000000000000000000000 来表示0
00000000000000000000000000000001 表示1出现
00000000000000000000000000000010 表示2出现
00000000000000000000000000000100 表示3出现
....
10000000000000000000000000000000 表示31出现

那么一个int就可以表示32个值
这样通过一个数字就可以表示32个数字。可以极大的节省内存空间。
这样在数组中:
第1个数字表示的范围为0 ~ 31;
第2个数字表示的范围为32 ~ 63;
第3个数字表示的范围为64 ~ 95;
第4个数字表示的范围为96 ~ 127;
第5个数字表示的范围为128 ~ 159;

就是这样一个结构,具体就是
如果我想要表示92102这个数字出现过,则用92102除以32,找出在数组上的第几位数字。所以92102这个数字在数组上第2878个数的范围内,接下来找这个数字上的第几位,取92102模上32的余数 ,即在数组上第2878个数二进制的6位,标记为1。标志92102这个数字出现过。

简易的原理就是 一个类里面然后有个数组 ,数组的长度 x32 就是我们能保存的实际数字。

在这里插入图片描述
信号的捕捉

这一段代码的逻辑就可以表示 ,我们对信号的处理。 我们判断信号signo在不在 , 假设signo是1 ,就不需要左移位置 。 就相当于

0000 0000 0000 0000 0000 0000 0000 0001

表示1号信号。然后先判断是否阻塞,阻塞了就不需要继续判断了。
在这里插入图片描述
这里有三个结构 ,分别是pending 机构 block结构 。 然后函数指针结构的数组表示对应信号的处理方法。
在这里插入图片描述同一时间来了很多相同的信号,这个信号只会被记录一次。其他的信号被丢失了。

在这里插入图片描述
在这里插入图片描述
正常情况下 ,我们调用了getpid这种需要内核态作为访问的。访问完成之后就要回到用户态调用内核态资源之后的代码的位置继续执行。但是往往调用系统资源是非常耗时的。操作系统呢就会去检查信号表?检查的顺序呢如下图所示!先检查block ,在检查pending 。在检查handler函数指针表。先检查block是否为1 ,不为1就继续检查pending表 。然后就找对应的handler的函数指针执行相应的代码。
在这里插入图片描述
这是一些常见的概连介绍,cpu里面有一个寄存器,指向pcb 。

在这里插入图片描述
CPU会保存进程的上下文信息
在这里插入图片描述
cpu的寄存器是可以直接指向进程的。
在这里插入图片描述
同时CPU里面还有一个CR3寄存器,表征当前进程的运行级别。这里就需要内核态和用户态之间的切换。
在这里插入图片描述
首先,我们要理解,系统调用其实是在内存的3 - 4G的区域。同时内核区也有一张页表,这张页表是所有进程共享的。也就是只有一张。我们写的代码在代码段里面想调用系统接口。就会到系统去区找方法 ,并通过页表映射到物理内存。执行完成之后返回到系统区,然后在返回代码段。
在这里插入图片描述
这里是详细的流程
在这里插入图片描述
这里分为了两种不同的情况 。 1 是对信号的捕捉有自定义方法 ,另外一种是没有。

问题 : 第三步有必要存在吗?为啥非要先从内核态变为用户态之后再执行函数。

解答: 因为handler是我们自己写在用户区域的。里面执行代码的权限是用户的执行权限,但是如果我们用户再编写代码的时候,加入了一些只有才做系统权限才能运行的代码 ?本来运行不起的,但是如果这里直接用内核区来运行就能直接运行。操作系统不相信任何人,只相信自己的接口。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相关推荐

  1. Linux

    2024-04-26 22:46:01       54 阅读
  2. Linux

    2024-04-26 22:46:01       29 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-26 22:46:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-26 22:46:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-26 22:46:01       82 阅读
  4. Python语言-面向对象

    2024-04-26 22:46:01       91 阅读

热门阅读

  1. C++ 中的 struct 和 Class

    2024-04-26 22:46:01       39 阅读
  2. leetcode961-N-Repeated Element in Size 2N Array

    2024-04-26 22:46:01       35 阅读
  3. 10 内核开发-避免冲突和死锁-读写锁

    2024-04-26 22:46:01       33 阅读
  4. 如何看懂财报 - 财报分析与关键指标

    2024-04-26 22:46:01       36 阅读
  5. 巴西游戏市场海外营销洞察

    2024-04-26 22:46:01       40 阅读
  6. Ubuntu22.04.4 - Redis - 笔记

    2024-04-26 22:46:01       27 阅读
  7. 探索PostegreSQL与MySQL的区别

    2024-04-26 22:46:01       34 阅读
  8. openfeign整合sentinel进行降级

    2024-04-26 22:46:01       34 阅读
  9. 如何实现百万级数据从Excel导入到数据库

    2024-04-26 22:46:01       35 阅读
  10. 字符串简单运算(BigDecimal相关运算)

    2024-04-26 22:46:01       39 阅读
  11. Swift 中如何四舍五入

    2024-04-26 22:46:01       30 阅读
  12. linux文件相关命令

    2024-04-26 22:46:01       31 阅读
  13. MR混合现实实训系统为农学情景实训教学演练

    2024-04-26 22:46:01       28 阅读
  14. Anagrams

    2024-04-26 22:46:01       36 阅读