liunx之信号介绍(3),各种中断的介绍和系统调用的本质以及用户态与内核态的具体介绍

1.硬件中断

CPU与外设之间不会发生数据的拷贝,但二者之间可以发生信号的传递。

CPU在硬件上有一个部位叫针角,能用电信号间接地与各种外设进行沟通,所有的硬件都要有一个叫控制器的设备,我们给硬件下的指令本质都是给控制器下发的。

外部设备和CPU之间交流的媒介设备叫中断控制器,其能用于接收外设发出的中断信息同时通知CPU的特定针角。

每个设备有其对应的CPU针角编号,其在中断控制器中都有一个特定的中断号。

所有的硬件设备内部也是有寄存器存在的(是控制器的一部分)。

总结过程有:外部设备向中断控制器发送信号,中断控制器找到对应的中断号,存进寄存器中之后通知指定CPU的针角,最后CPU就通过中断控制器的寄存器获得对应的中断号。

2.时钟中断

硬件部分只让CPU知道了数据从何而来,但CPU并不知道如何处理这些数据。

中断向量表(IDT):是一个函数指针数组。

下标就是中断号。因此CPU就是拿着中断号在这个表中查找对应的函数方法并调用,也就能让CPU执行对应的中断处理方法。

意义:此时OS就不用再关注外部设备,因为此时外部设备好了会直接发信号提醒OS。(CPU接收到中断信号就自动去IDT中找方法调用,而IDT就是OS的一部分)

可以发现硬件中断的方式很像进程池,由此推断出通信有很大程度模仿了硬件中断,而信号更是说明了其本质是用软件的方式模拟硬件中断的。当没有中断到来时,OS是什么都没有做,处于暂停状态的。

IDT中有一个功能叫进程调度。外设中有一个叫时钟源,其会以固定的,特定的频率向CPU发送特定的中断,这个中断对应的中断号就是进行进程调度的,因此OS时钟源的驱动下进行调度的。所以OS就是基于中断进行工作的软件。

随着时代的发展,时钟源被嵌入进了CPU中进行进程调度(函数名叫schedule)的中断,这个操作整体叫主频。

时间片耗尽:每一次时钟源刺激时,OS会对当前进程存储时间片次数的成员--,一旦值变成了0时OS会对当前进程进行重新调度或进程切换。

时间能推导出时间戳,时间戳能推导出历史总频率(total),每次时钟源刺激,total++,由于三者之间可以互推,同时时钟源能在离线时运作一段时间,因此能在一定程度上保障计算机在离线情况下也知道准确的时间。

总结还是能发现OS的运作时基于中断的,硬件中断就是由外部设备触发来让中断系统执行流程的操作,OS的本质就是一死循环,想要用的功能全往IDT中添加即可。中断也能视为就是让CPU停下当前做的事去做别的事,上面的所有步骤其实可以说就是在指挥CPU做事。

3.软中断

软中断就是没有外部硬件参与的中断。

异常中断:

CPU在处理进程的代码时发生异常,就会生成一种由CPU内部发出的中断,也就是CPU发现异常时自己就会生成一个中断号传给OS进行中断服务。

可以说代码出异常了CPU是最先知道的,然后发送中断号给OS,因此OS是通过中断来知道硬件是否发生异常的。

缺页中断:虚拟地址合法但物理地址不存在,CPU发送中断号调用page_fault进行中断服务。

软中断的核心在于没有外部硬件干涉而非不涉及硬件,上面所涉及的中断就是CPU处理软件时硬件出现异常而发生的中断。

纯软件触发的中断:

CPU的指令集:各种最基本操作的集合(汇编语言)。

各种语言代码的本质就是被编译成了指令集+数据。

指令集中有一个指令x86环境下是(int),x86_64环境下是(syscall),能让CPU主动触发中断。

所有的系统调用函数是存储在一个系统调用表中的。

IDT中有一个中断服务就是专门用于进行调用系统调用的,也就是指向一个函数指针数组,里面的每一个元素就是一个系统调用,每一个元素的下标叫系统调用号。

syscall的作用就是:

(1)获取系统调用号

(2)进行安全检测后调用系统调用

系统调用的具体过程(以调用open为例):

(1)move eax 5 (5是open的系统调用号)

(2)syscall,然后syscall调用IDT对应的中断服务,此处为调用系统调用表

(3)move n eax,用n(系统调用号)来调用对应的系统调用

OS其实不提供任何的系统调用接口,OS只提供系统调用号,我们使用的系统调用接口其实都是由c标准库封装的,因此所有语言其实都与c语言有关。

所有异常的本质也是系统调用,因此它们也是CPU发现问题后主动调用IDT表中的功能进行处理的。

CPU内部软中断的种类

(1)由代码引发的,例syscall,叫陷阱

(2)由硬件引发的,例野指针,叫异常

4.用户态与内核态

系统调用的过程也是发生在虚拟地址空间中的,所有的系统调用都是地址空间之间的跳转。

OS作为软件,其也在虚拟地址空间中有自己的区域,其甚至有自己的页表,叫内核页表。

用户页表每个进程一个,因此可以有多份,但内核页表是所有进程共用的,因此其是有一份。在虚拟地址空间中0,3GB是用户空间,3GB,4GB是内核空间。

用户态:以用户身份访问0,3GB的空间

内核态:以内核的身份允许用户通过系统调用的方式,访问OS3GB,4GB的空间

CPU中有一个寄存器叫CS,其的低两bit位叫权限标志位,它们的和为0是说明其处于内核态,和为3说明处于用户态。

int 0x80和syscall的功能就是在移动到内核区的同时将权限标志位设为0之后进入内核态,并提供系统调用号。

段描述符表:存储着每个区间的访问权限。

重新理解信号传递的过程:

从用户到内核,先访问3GB,4GB的内和空间,到内核页表之后CPU进行权限标志位的切换(这里切换工作就是段描述符表的辅助),此时就可以进行对OS功能的调度。

在调度返回时能进行对信号的检查,如果为调用用户的自定义功能,就要就要回到用户层,此时就要将权限为从0变成3从而调用自定义功能。

自定义函数返回时调用sigreturn时再切换一次权限位,又调用sys_sigreturn就又切换一次权限位,最终回到用户层上一次运行时的代码处。

总结信号从产生到处理的过程(以ctrl + c为例:

ctrl + c时,键盘发送中断,经过CPU处理发送给OS,OS接收后识别到为ctrl + c就调用对应函数修改pending表,在修改pending表后,CPU检查到当前进程有信号需要处理就又触发一次中断去处理信号,处理完后就返回主流程。因此在内核层执行完一段代码返回时就会检查一次信号,导致上面过程中一共发生了两次中断。

另一种修改信号行动的方式

复制代码
int sigaction(int signum,const struct sigaction*act,struct sigaction*oldcat);

signum是一个信号的编号,struct sigaction是存自定义捕捉方法的结构体,act是我们传的新方法,oldcat是OS传过来的老方法。

cpp 复制代码
//省略版
struct sigaction
{
    //自定义函数
    void(*sa_handler)(int);
    //处理时要被屏蔽的信号的集合
    sigset_t sa_mask;
}

在一个信号被递达时其同时也处于block被设为1的状态(暂时的),抵达完后再恢复为0,目前是为了防止处理该信号时,同信号又多次发送造成信号递归的情况(也就是在信号处理的过程中该信号的pending值又被修改为了1,但由于此时block处于1导致其不会被递达)。而sa_mask就是让signum的信号递归时,sa_mask中的信号也会全部被阻塞。

相关推荐
stanleyrain1 小时前
linux上无感操作Windows上的文件夹
linux·运维·windows
程序员Aries1 小时前
tcp-server 项目实现流程、细节与 muduo 对比分析
linux·网络协议·tcp/ip
染翰1 小时前
Linux 配置:应用用户执行 sudo su root 免密(运维标准配置)
linux·运维·服务器
茫忙然1 小时前
Claude Code 接入 DeepSeek 或 多模型 教程(Linux)
java·linux·数据库
hexu_blog2 小时前
Linux centos 安装向量数据库milvus
linux·centos·milvus
code monkey.3 小时前
【Linux之旅】Linux 应用层自定义协议与序列化:从粘包问题到网络计算器
linux·网络·c++
草莓熊Lotso3 小时前
【Linux网络】深入理解 HTTP 协议(二):从协议格式到手写工业级 HTTP 服务器
linux·运维·服务器·网络·c++·http
剑神一笑10 小时前
Linux pgrep 命令详解:按名称查找进程 PID 的高效方法
linux·运维·chrome
剑神一笑10 小时前
Linux killall 命令详解:按进程名批量终止进程的原理与实践
linux·运维·chrome