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中的信号也会全部被阻塞。