【Linux进程】进程信号

embedded/2024/12/26 11:30:35/

目录

1. 信号

2. 信号的产生

2.1 终端按键

 自定义信号处理

2.2 系统调用

kill

 raise

abort 

2.3 硬件异常

2.4 软件条件产生

思考

总结


在这里插入图片描述

1. 信号

        在Linux中存在着一种通信的方式,与管道和System V IPC不同,更准确的说是一种通知机制,告知进程某件事情发生了——信号;用于通知某种条件或异常情况;

 联系日常生活中的信号:红绿灯、警报、电话铃声...我们为什么会认识红绿灯?因为有人从小就告诉你;认识他;知道不同颜色要做什么;

信号有以下特点:

  • 信号在没有产生的时候,其实我们已经知道,怎么处理这个信号
  • 信号什么时候来我们并不知道,信号来的时候相对于正在进行的工作是异步产生的;
  • 信号来的时候,并不一定要立即处理,而是在合适的时候处理;
  • 那么就需要有一种能力——将到来的信号暂时保存;

 这些特点也是进程中的信号所具备的;什么是信号?一种向目标进程发送通知消息的一种机制;

 在Linux中的信号

2. 信号的产生

 热键产生的信号

Ctrl + C 终止前台进程;这里补充一点基础知识;

  • 进程在运行时,分为前台进程和后台进程:前台(只能有一个)后台(./xxx &,后台运行)可以有多个;
  • jobs指令可以查看后台运行的进程
  • fg + 后台进程编号:把进程移到前台运行

示例:

 切到前台:

 光标卡住了,并没有返回shell正常状态;此时可以使用Ctrl + C终止进程

此外还有:

        ctrl+Z 停止止前台进程,暂停后的几进程就会被放在后台挂起;但是前台进程一般不能被暂停,如果暂停就必须立即把暂停的前提进程放到后台;然后用一个后台进程切换到前台运行,确保前台有进程在运行;如果想让后台暂停的进程重新运行,只需bg +进程工作编号就可以让暂停的后台进程继续运行;

上述操作指令,其中shell对于Ctrl+C、Ctrl+Z没有影响;而shell会根据情况进行前后台切换;

 梳理一下指令:

  • ./xxx & (后台运行)
  • jobs (查看后台进程
  • fg + number (后台运行转到前台运行)
  • ctrl + z (暂停前台进程
  • bg + number (运行暂停的进程

 使用键盘输入,操作系统怎么知道有数据到来?——中断;

 结合上图:

        在CPU上会有一个一个的针脚,这些针脚都有自己的编号,在冯诺依曼体系结构中,从数据层面上讲,外设是不和CPU直接打交道的;从信息层面上讲,外设和CPU有间接关联;

比如:我们在使用键盘时,键盘会发出很多的光电信号,这些信号就会引起CPU针脚光电的信号的强弱变化;假设我们使用键盘连接的是0号针脚,键盘产生的光电信号通过针脚被CPU捕捉,在CPU中存在寄存器(有专门的寄存器记录针脚编号、对应的编号数据);这样就把硬件数据就绪转变成某种数据存放到寄存器中;这个数据被放到寄存器里后,他就可以被程序读取了;当然这些硬件设备并不是直接与CPU相连,它们是通过一块叫8259的 “ 板子” 间接连接的,通过8259这样的设备,将硬件信息转变成光电信号给CPU这些针脚;

        在使用硬件设备时,每个硬件分配的有不同的中断号,就是为了更快做出响应;

        OS在系统中还维护了一张表,他其实就是函数指针数组,数组中的内容都指向特定硬件设备的读取方法每个硬件设备的中断号就是数组下标,CPU每读取一个硬件设备,就立即通过这张表来索引对应方法,这张表也叫中断向量表;这个过程也叫中断。

        硬件设备通过发送信号给CPU,暂时中止当前程序的执行,转而执行特定的中断处理;硬件设备通过中断告知操作系统自己 已准备就绪;这也就回答了OS怎么知道键盘有数据输入这些设计,也恰好符合信号的特点:在信号没有发出前就已经知道对应信号怎么执行;产生也是异步产生;

 信号的本质:用软件模拟中断的行为;

2.1 终端按键

使用Ctrl+C终止进程时本质就是通过发送2号信号终止进程

由此也可以发现,键盘是可以进行两种输入:数据输入、控制输入;
使用kil -2 发送信号进程进程是可以识别2号信号的;它是怎么做到的?每个信号都有对应的编号,这个编号其实就是数组下标;

 进程收到信号,找到这张表,根据信号编号索引找到它指向方法,比如收到2号信号,通过2
下标位置的方法终止进程

 自定义信号处理

#include <signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

signal接口就可以实现自定义处理,一旦设置就是永久有效; 

信号在合适的时候进行处理:

  • 默认行为
  • 忽略
  • 自定义处理——信号的捕捉 

编写一个程序验证一下:

void handler(int signal)
{std::cout << "获得一个" << signal <<"号信号" << std::endl;exit(1); // 不退出时使用Ctrl + C、Ctrl + Z就不会退出、暂停进程了
}int main(){signal(2, hander); // Ctrl + Csignal(19, hander);signal(20, hander);//Ctrl + Z signal(9, hander);// 9号信号不可被自定义捕捉while(true){std::cout << "running...,pid: " << getpid() << std::endl;sleep(1);}return 0;}

 1~31是普通信号;34~64是实时信号

发送信号时可以用信号名,也可以用编号,并且信号名全是大写,那就是宏;信号列表中没有0号信号0;表示没有收到信号,表示进程正常退出;没有32、33号信号

在每个进程中都有一张自己的函数指针数组,数组的下标和信号编号强相关,对于普通信号来讲,进程收到信号之后,进程要表示自己是否收到某种信号?——使用位图并在合适的时候处理;

  • 比特位的位置:决定信号编号
  • 比特位的内容:决定是否收到信号
struct task _struct
{//信号位图uint sigmap;// 其他字段
}

         进程在收到信号后,根据信号编码对位图进行写入,然后通过位图位置去查 函数指针数组,进行相应的操作;信号由谁发?操作系统(OS);OS向目标进程信号,也就是OS向目标进程信号;OS直接修改进程PCB中的信号位图就完成了信号发送;

无论信号有多少种产生方式,永远只能让OS向目标进程发送!!!  因为OS是进程的管理者;

2.2 系统调用

         代码层面上,系统调用也可以向进程发送信号

kill

向任意进程发送任意信号

#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
// The kill() system call can be used to send any signal to any process group or process.

 raise

 向自己发送任意信号

#include <signal.h>int raise(int sig);
// The raise() function sends a signal to the calling process or thread.  In a single-threaded program it is equivalent to kill(getpid(), sig);

abort 

 向自己发送 SIGABRT 信号强制终止

#include <stdlib.h>void abort(void);

2.3 硬件异常

 使用空指针:

int *p = nullptr;// 11号信号
*p = 10;

        向空指针写入(0号地址处写入),在向0号地址处写入数据时,由于0号地址通常是操作系统保留的地址空间,没有分配给用户程序使用,因此写入数据会导致缺页中断的发生;

 除零错误:

int a = 10;
a /= 0;

 状态寄存器中有很多的标志位,有些标志位是来衡量运算结果状态的;

        状态寄存器中有一个比特位,用来表示CPU内的计算是否溢出也叫溢出标记位;一旦出现像除零这种错误,他就会直接把溢出标记位 置为1 溢出标记位可以甄别出来;

        溢出标志位被置为1后,也就标志着CPU内部执行出现异常,此时CPU就会通过它自己的“针脚”发送信号去告知OS,溢出标志位出现了溢出问题;OS就会解释成类似于kill(targetprocess,signore);

void handler(int signal)
{std::cout << "获得一个" << signal <<"号信号" << std::endl;exit(1); // 不退出时使用Ctrl + C、Ctrl + Z就不会退出、暂停进程了
}int main()
{signal(8,handler);int a = 10;a /= 0;return 0;
}

 

        发现了问题,没有循环的情况下,一直在执行自定义的方法为什么?在默认情况下,对于除零错误,OS是直接终止掉的,终止进程也是处理信号的一种方式;

        状态寄存器中的溢出标志位置为1了,寄存器的内容置为1了,寄存器的内容保存到进程上下文;异常是由进程所导致的,OS把进程终止,就不需要再维护进程的上下文了,他就会被其他进程标志位覆盖;进程被杀掉,就不会在被调度,也就不会再收到信号

        这里我们更改了它默认终止进程处理方式,进而转换为自定义处理方法(handler),方法内没有终止进程进程就不会退出,不退出异常就会一直存在;只要CPU调度该进程,CPU就会告诉OS,调度出现错误;OS对于这种问题,OS怎么处理呢?OS就输出一句话就认为自己处理完了;可是进程还没有被杀掉,它依然在被调度;只要他每次被调度,对他的上下文进行恢复时,status寄存器内的溢出标志位还是1,CPU每次调度该进程就会通知OS一次,OS处理就答应一句话;这样就造成了循环打印的现象;

为什么空指针属于硬件异常?

 

        之前经常提到通过页表查找虚拟地址映射的物理地址,这个查找工作就是MMU来做;MMU(Memory Management Unit)是一种硬件设备,用于将虚拟内存地址转换为物理内存地址,现在的MMU一般集成在CPU中 ;

        如果虚拟内存到物理内存转化失败了,也会报错(硬件问题)一旦硬件执行出问题OS就会知道;OS就会把引起问题的进程杀掉,所以如果访问 nullptr 地址时就会引发MMU转化错误,然后
OS就会杀掉该进程,如果进程不退出就会和除零错误一样,每次调度该进程都会发送信号给OS;

2.4 软件条件产生

  • SIGPIPE是一种由软件条件产生的信号,在管道中有所介绍,读端断开后,而写端一直写;此时OS就会发送信号终止写端;
  • 收到信号,不一定是进程出现异常;比如:有些信号是让进程暂停;
  • 进程一旦出现异常,就一定会收到信号
  • 闹钟就是一种软件条件;
#include <unistd.h>unsigned int alarm(unsigned int seconds);

        调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程

        闹钟需要有时间,还需要有唯一的数值去比对;——时间戳;每个闹钟中就要有:时间戳、闹钟id、设置者、处理操作等数据,每设置一个闹钟,OS就创建一个时钟对象;

对于系统中不同时间的时钟,OS也需要进行管理;如何管理?每个闹钟超时的时间点都不一样,要怎么管理?闹钟一旦超时OS就要发送信号了,应该如何管理?使用链表?问题是怎么知道哪个闹钟超时了
        最好的方法就是将所以的闹钟按照时间进行排序;升序排序,只要开头的闹钟没有超时,那么后续的闹钟就都没有超时;使用链表排序效率显然有些低;

        维护有序性,便于维护的数据结构;——堆/优先级队列;我们可以以时间为键值进行建堆,如果堆顶没有超时,那么后续的闹钟也不会超时,如果堆顶超时就把堆顶闹钟移出堆即可;堆内部会自动调整,下次还是判断堆顶是否超时;

思考

为什么电脑一开机就知道当前的时间?
        计算机内部有一个叫做实时时钟(RealTime Clock)的硬件设备,实时时钟会在计算机关闭时继续运行,并保持时间的记录。当计算机重新启动时,操作系统会从实时时钟中读取当前的时间信息,从而确保计算机开机时显示正确的时间;

电脑断电了硬件怎么运行?
        实时时钟通常会内置一个小型的纽扣电池,用来提供持续的电力供应以保持时钟运行,它可以确保即使计算机关闭或断电,实时时钟仍能继续运行并保存时间信息;

OS启动其他程序,那OS如何启动?

        在OS内部存在一个CMOS芯片,内部存储BIOS(程序)设置,包括硬盘驱动器的类型、启动顺序、时钟设置等。当计算机启动时,BIOS(计算机启动时首个运行的软件) 会读取 CMOS 中的设置,依据这些设置找出哪一个驱动器(如硬盘、光盘、USB 等)是启动设备。BIOS 会加载引导程序(Bootloader),引导程序开始执行(加载操作系统的内核文件到内存中),这样操作系统就可以开始加载和运行。操作系统内核开始初始化系统、加载驱动程序和服务,并开始用户空间的程序。

所有用户的行为都是以进程的形式在OS中表现出来的;OS只要把进程调度好,就能完成所有的用户任务;


总结

        以上便是本文的全部内容,希望对你有所帮助,感谢阅读!


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

相关文章

如何查看flink错误信息

flink出错时&#xff0c;可以通过以下步骤查看flink错误信息&#xff1a; 1.打开flink webui界面 2.进入overview或running jobs页面 3.点击出错的job name&#xff0c;出错的jobname一般后面的status会变红 4.在job 详情页面&#xff0c;点击exception&#xff0c;即可查看错…

eth_type_trans 函数

eth_type_trans 是 Linux 内核网络子系统中的一个函数,它主要用于确定接收到的以太网数据包(Ethernet frame)的协议类型,并设置相应的 sk_buff 结构体的协议字段。以下是关于 eth_type_trans 的详细解释: 功能 eth_type_trans 函数的主要功能是根据以太网数据包的目的 M…

Flink中并行度和slot的关系——任务和任务槽

一、任务槽&#xff08;task slots) Flink的每一个TaskManager是一个JVM进程&#xff0c;在其上可以运行多个线程&#xff08;任务task&#xff09;&#xff0c;那么每个线程可以拥有多少进程资源呢&#xff1f;任务槽就是这样一个概念&#xff0c;对taskManager上每个任务运行…

w118共享汽车管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

LightGBM分类算法在医疗数据挖掘中的深度探索与应用创新(上)

一、引言 1.1 医疗数据挖掘的重要性与挑战 在当今数字化医疗时代,医疗数据呈爆炸式增长,这些数据蕴含着丰富的信息,对医疗决策具有极为重要的意义。通过对医疗数据的深入挖掘,可以发现潜在的疾病模式、治疗效果关联以及患者的健康风险因素,从而为精准医疗、个性化治疗方…

Elasticsearch 国产化替代方案之一 Easysearch 的介绍与部署指南

一、前言 在国内数字化转型浪潮和 信创 大背景下&#xff0c;“替代进口”成为许多企业级应用所需要面对的重要课题&#xff0c;搜索领域也不例外。 Elasticsearch&#xff08;简称 ES&#xff09;作为一款业界领先的全文搜索和分析引擎&#xff0c;虽然功能强大&#xff0c;但…

[实战]Docker应用自动重启

场景 Java应用&#xff0c;在凌晨定时任开始时运行一段时间后&#xff0c;会自动重启&#xff0c;导致定时任务失败。该应用使用Docker部署 分析 Docker应用运行一段时间自动重启可能的原因为容器分配的资源&#xff08;如CPU、内存&#xff09;不足&#xff0c;系统可能会杀…

【uni-app】2025最新uni-app一键登录保姆级教程(包含前后端获取手机号方法)(超强避坑指南)

前言&#xff1a; 最近在配置uni-app一键登录时遇到了不少坑&#xff0c;uni-app的配套文档较为混乱&#xff0c;并且有部分更新的内容也没有及时更改在文档上&#xff0c;导致部分开发者跟着uni-app配套文档踩坑&#xff01;而目前市面上的文章质量也层次不齐&#xff0c;有的…