[Linux][OS][详解信号的产生]

目录

1.信号概念

硬件层面

[2. 产生!](#2. 产生!)

[1. 键盘组合键](#1. 键盘组合键)

[2. kill 命令 kill -signo pid](#2. kill 命令 kill -signo pid)

[3. 系统调用](#3. 系统调用)

[4. 硬件异常--会自动退出](#4. 硬件异常--会自动退出)

软件条件--闹钟

发送


信号和信号量没有任何的关系,就像老婆和老婆饼,上一篇文章我们讲到了信号量,这篇文章我们将来对信号进行讲解~

信号部分讲解思路:

  1. 信号的概念搞定--输出一堆的结论,支撑我们对信号的理解
  2. 信号的产生--重点
  3. 信号的保存

1.信号概念

日常生活中的信号,例如:信号弹,铃声,红绿灯,闹钟...

1.你怎么认识这些信号的?有人教我->我记住了

2.即便是我们现在没有信号产生,我也知道信号产生之后,我该干什么

3.信号产生了,我们++可能并不立即++ 处理这个信号。在合适的时候,因为我们可能正在做更重要的事情--所以,信号产生后--时间窗口--信号处理时--在这个++时间窗口内,你必须记住信号的到来!++


认识信号:

  1. 识别信号
  2. ++知道信号的处理方法++

和进程联系起来:

  1. 进程 必须 识别+处理信号--信号没有产生,也要具备处理信号的能力--信号的处理能力,属于进程内置功能的一部分
  2. 进程++即便是没有收到信号++,也能知道哪些信号怎么处理
  3. 当进程真的收到了一个具体的信号的时候,进程可能并不会立即处理这个型号,++合适的时候处理++
  4. 一个进程必须当信号产生,到信号开始被处理,就一定会有时间窗口,进程具有临时保存哪些信号已经发生了的能力

./后,不接受指令了,叫做前台进程

  • 当你运行./myprogram时,这个程序会变成前台进程。此时,bash(或你正在使用的shell)将控制权交给这个程序,所以你++不能再在同一个终端中输入其他命令++,直到这个程序运行结束。

后面加上& ,接受指令,++不再受 ctrl c 控制了++,要 kill ,启动的就是后台进程了

  • 允许你在同一个终端中继续输入其他命令。
  • 后台进程通常不会直接接收键盘输入,但它们的输出(标准输出和标准错误输出)仍然会显示在终端上,除非你将其重定向到文件或其他地方。

Linux 中,一次登录中,一个终端,一般会配上一个 bash ,每一个登录,只允许一个进程是前台进程,可以允许多个进程是后台进程 (前台获取键盘输入?后台的前台是默认的 bash,所以会接受指令

  • "只允许一个进程是前台进程" :这意味着在任何给定时刻,命令行界面只能有一个进程直接与用户交互,接收用户的键盘输入。
  • "可以允许多个进程是后台进程":这意味着用户可以同时运行多个任务,这些任务在后台执行,不会干扰前台进程,也不会直接接收用户的输入。
  • "前台获取键盘输入?" :是的,前台进程可以获取键盘输入。这是用户与前台进程交互的方式。
  • "后台的前台是默认的 bash,所以会接受指令" :当你没有运行其他前台进程时,默认的前台进程是bash(或shell)。在这种情况下,bash作为命令行解释器,可以接收和执行用户的指令。当你在bash中启动一个后台进程后,bash仍然保持在前台,能够接受新的指令。

对于后台:

ps ajx | grep myprocess 
pidof myprocess | xargs kill -9

ctrl + c 为什么能够杀掉我们前台进程呢?

键盘输入首先是被前台进程收到的,ctrl c 本质是被进程解释成为收到了信号,2 号信号

kill -l 查看信号,在 linux 中信号就是数字,内核中以宏定义了

1-30 普通信号

31-64 实时信号,需要立即处理的

**信号的处理方式:**三选一

  1. 默认动作
  2. 忽略
  3. 自定义动作(信号的捕捉)

**验证:**进程收到 2 号信号的默认动作,就是终止自己

man signal 修改特定进程对于信号的处理动作,自定义捕捉

void myhandler(int signo)
{
    cout << "process get a signal: " << signo <<endl;
    // exit(1);
}
signal(2, myhandler);
  1. signal 只需要设置一次,往后都有效

  2. signal 是在++后续执行中触发++ ,信号的产生和代码运行是异步的,叫做软中断

    #include<iostream>
    #include<unistd.h>
    #include<cstdio>
    #include<signal.h>

    void handler(int signo)
    {
    cout<<"捕捉到信号:"<<signo<<endl;
    }

    int main()
    {
    signal(2,handler);
    int cnt=0;
    while(true)
    {
    printf("我是一个进程,我正在运行%d\n",cnt++);
    sleep(1);
    }
    return 0;
    }

硬件层面

键盘数据是如何输入给内核的,ctrl c 又是如何变成信号的--谈谈硬件

键盘被摁下,肯定是 OS 先知道!

OS 怎么知道键盘上有数据了??键盘上的数据存储到 os 缓冲区

外设有数据了, CPU 硬件中断进行识别,记录中断号

CPU 中的寄存器凭什么能保存数据呢?

充放电的过程,高低电频被解释为 01 数据,同时 CPU 中的中断信息被操作系统读取,把外设数据拷贝到缓冲区中,有了中断,os 就不用轮询检查外设了

我们学的信号,就是用软件方式,对进程模拟的硬件中断

数据拷贝到缓冲区前,os 会判断数据,控制(ctrl c 转化成为 2 号信号发送给进程)

os 就是不断接收外部中断来接受外设


不同的文件传的信号涉及到的是不同的缓冲区

2. 产生!

1. 键盘组合键

ctrl c2

ctrl \3 号信号

++不是所有的信号都是可以被 signal 捕捉的,例如:9 19++

测试!杀进程和暂停进程不能被捕捉,os 为了安全起见

2. kill 命令 kill -signo pid

3. 系统调用

man kill

man raise给自己发送信号

==kill(getpid(),2)

man abort给自己发 6 号信号,终止

1 2 3 都是信号产生的方式!但是无论信号如何产生,最终一定是谁发送给进程的?OS

为什么?OS 是进程的管理者!

4. 硬件异常--会自动退出

常见例子:

  1. /0
  2. 野指针

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号

  • 例如:当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程
  • 例如:当前进程访问了非法内存地址,,++MMU会产生异常++,内核将这个异常解释为SIGSEGV信号发送给进程

由此看出,在C/C++中,除零,内存越界等异常,在系统层面上,是被当成信号处理的

测试对于自己报错的解释:

void handler(int signo)
{
    cout<<"进程捕捉到了一个信号,信号编号是:"<<signo<<endl;
}

int main(int argc,char* argv[])
{
    signal(SIGFPE,handler);
    int cnt=0;
    while(true)
    {
         printf("cnt:%d,pid:%d\n",cnt++,getpid());
         int a=10;
         a/=0;       
    }
     return 0;
}

捕捉之后为什么会死循环呢?

系统是为了让用户可以查看到错误信息的捕捉,但捕捉后 CPU 状态寄存器一直在获取异常信息,因为异常只影响进程,++CPU 把错误消息一直在给 OS 传,一直在被调度++

上层用来宽容度保存资源和日志,所以交给了上层对异常进行处理和退出

补充:右边监视窗口的打开 ps ajx | grep myprocess

为什么/0,野指针会让进程崩溃了?Os 会给进程发送信号

OS 如何知道的呢?

CPU 上的 eip/pc 扫描函数

还有状态寄存器,存在溢出标志位

原理:寄存器知道进程上下文,虽然我们修改的是 CPU 内部的状态寄存器,但是进程只影响你自己

操作系统检测到了硬件错误之后,一直发送

很多异常都是硬件引起的

CPU 也是硬件!操作系统是硬件的管理者

MMU 内存管理单元,集成在了 CPU 内部,CPU 读到的是虚拟地址

都在 CPU 中三位一体:页表 虚拟地址 MMU->物理地址

地址转化失败:虚拟到物理转化失败(野指针),转化失败 CPU 会进行物理报错--信号

CPU 怎么区别溢出和越界

  • CPU 通过不同的异常处理机制来区分溢出和越界。溢出通常通过算术指令的状态标志来指示,而越界通过内存访问异常来指示。

上层语言是如何知道异常的呢?可能是产生了信号

软件条件--闹钟

异常,只会由硬件产生吗??不是,eg 管道只写不读报错 13)

man alarm

会返回上一次闹钟的剩余时间 n,

测试

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

void catchSig(int signo) {
    cout << "进程捕捉到了一个信号,信号编号是: " << signo << endl;
    alarm(1); // 重新设置闹钟信号,延迟时间为 1 秒
}

int main(int argc, char* argv[]) {
    signal(SIGALRM, catchSig); // 设置信号处理函数
    alarm(5); // 设置初始的闹钟信号,延迟时间为 5 秒

    int cnt = 0; // 初始化计数器

    while (true) {
        cnt++;
        if (cnt == 3) {
            int n = alarm(0); // 取消现有的闹钟信号,并返回剩余时间
            cout << "取消闹钟信号,剩余时间: " << n << " 秒" << endl;
        }
        sleep(1); // 让程序暂停 1 秒
    }

    return 0;
}

1.重定义信号 2.alarm 查看返回值

操作系统中存在大量的闹钟,对闹钟的管理转化为对的增删查改,struct alarm* head,还是**先组织在描述,**进行抽象

时间戳--记录当前时间,超时时间,++OS 周期性的检查这些闹钟,循环 PCB 对比比较++

对于堆和优先队列的回忆:

堆(Heap)

堆是一种特殊的完全二叉树,它满足以下性质:

  • 最大堆(Max Heap) :对于任何节点,其值都大于或等于其子节点的值。根节点是堆中的最大值。
  • 最小堆(Min Heap) :对于任何节点,其值都小于或等于其子节点的值。根节点是堆中的最小值。

堆通常用于实现优先队列,也可以用于排序算法(如堆排序)。

优先队列(Priority Queue)

优先队列是一种抽象数据类型,它类似于队列或栈,但是每个元素都关联有一个优先级。在优先队列中,元素按照优先级进行排序,具有最高优先级的元素先被处理。

特性

  • 先进先出**(FIFO)**:对于相同优先级的元素,它们按照进入队列的顺序被处理。
  • 优先级处理:具有更高优先级的元素可以先于低优先级的元素被处理。
  • 堆是实现优先队列的一种常见方式。例如通过最大堆,可以实现一个最大优先队列

信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?

man 7 signal //信号手册

Term,Core都是终止进程,有什么区别?

其实这有关于,进程退出时,核心转储问题

默认云服务器上面的 core 功能是被关闭的,防止 core dump 冲击内存,core dump 会占内存,可以通过下面命令进行打开

打开系统的 core dump 功能,一旦进程出异常,OS 会将进程在内存中的运行信息,给我 dump(转储)到进程的当前目录(磁盘)形成 core pid 文件:核心转储(core dump)

调试时显示:运行时错误,哪一行,直接复现问题之后,直接定位到出错行

测试:

int main()
{
    //核心转储
    while(true)
    {
        int a[10];
        a[10000]=10;
    }
    return 0;
}

**操作:**打开 core 功能后,gdb 加载,先运行,在 core-file,事后调试

core ==term +core dump,Core退出的可以被核心转储的以便于后序快递定位问题

发送

对于普通信号而言,对于进程而言,自己有还是没有,收到哪一个信号,是给进程的 PCB 发来实现管理

task_struct{
    int signal;//0000 0000... //普通信号,位图管理信号
}
  1. 比特位的内容是 0 还是 1,表面是否收到
  2. 比特位的位置(第几个),表示信号的编号
  3. 所谓的"发信号",本质就是 os 去修改 task_struct 的信号位图对应的比特位,"写信号"!

意味 OS 是进程的管理者,只有他有资格才能修改 task_struct 内部的属性!!!

(操作系统进行操作还要考虑上层软件哦

信号保存为什么?

进程收到信号之后,可能不会立即处理这个信号,信号不会被处理,就要有一个时间窗口

34-64 实时信号,有一个实时队列

连续发了好几个 2 号信号,位图怎么处理?都是一样的信号,没必要保存

注意:kill -9还是可以杀死进程,无论你怎么修改,无法对9号信号设定捕捉,即使你做了,OS也不会给你设置。

下篇文章将继续对信号进行讲解~

相关推荐
爱吃青椒不爱吃西红柿‍️19 分钟前
华为ASP与CSP是什么?
服务器·前端·数据库
IT果果日记21 分钟前
ubuntu 安装 conda
linux·ubuntu·conda
Python私教24 分钟前
ubuntu搭建k8s环境详细教程
linux·ubuntu·kubernetes
做人不要太理性26 分钟前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.35 分钟前
2、桥接模式
c++·桥接模式
羑悻的小杀马特37 分钟前
环境变量简介
linux
chnming198739 分钟前
STL关联式容器之map
开发语言·c++
程序伍六七1 小时前
day16
开发语言·c++
小陈phd1 小时前
Vscode LinuxC++环境配置
linux·c++·vscode
运维&陈同学1 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列