操作系统运行原理以及用户态和内核态相关知识【Linux操作系统】

发布于:2025-05-29 ⋅ 阅读:(25) ⋅ 点赞:(0)

操作系统的运行原理

硬件中断

之前理解信号处理时我们已经简单了解了信号中断

下面具体来谈一下硬件中断相关过程:
在这里插入图片描述

  • ①操作系统知道外设的数据是否已经准备好了的方法肯定不是轮询查看
    因为外设太多,操作系统太忙
    既然操作系统不轮询,那就只能是外设告诉操作系统"你来读数据吧"
    所以外设准备好数据之后,就向中断控制器发送中断信号

  • ②中断控制器会对接收到的中断信号进行处理分析,就能知道是哪个外设发过来的,对应的中断号是什么[每个外设都有自己对应的中断号]
    然后中断控制器就会告诉CPU有中断信号来了

  • ③操作系统为了能够处理所有的中断号(外设)在编写操作系统的源代码的时候,就已经有一张中断向量表[我们把它看做函数指针数组就行]
    中断向量表就是操作系统的一部分,所以开机的时候,就和操作系统一起初始化并加载到内存了
    中断向量表的下标,就是中断号
    中断向量表中的函数实现,要吗是操作系统内置的,要吗是从外设对应的驱动程序中拿到的

  • ④CPU知道有中断信号之后,就可以去中断控制器中获取到对应的中断号了

    • CPU拿到中断号之后,就可以调用内核代码:去中断向量表中调用对应函数
    • 但是大概率CPU知道有中断信号来了的时候,CPU上面还在调度着一个进程
      CPU要执行中断处理流程,就得把这个进程代码先剥离下来,把操作系统的源代码放上去(因为中断方法写在操作系统的源代码里)即从用户态→内核态
      为了保证执行完函数后,可以接着调这个进程,就得把这个进程对应的上下文数据保存在内核栈中,这个过程称为CPU上下文保护
  • ⑤CPU上下文保护,执行了对应的中断方法(读取外设的数据,把内存数据写到外设等等)之后
    恢复现场,从内核栈中,读取之前剥离下去的进程的上下文数据,继续执行进程的代码


这样操作系统和外设就可以并行执行,各忙各的
①操作系统想读取外设的数据,就给它发指令,然后接着忙自己的
外设接到指令,就自己准备数据,准备好了就发送中断信号,操作系统知道之后才来读取
②操作系统要向外设写数据,就直接给外设发指令,然后就不管了
因为外设准备好接收数据的时候,会发送中断信号



时钟中断(是谁推动操作系统工作?)

进程可以在操作系统的指挥下调度运行
那操作系统自己被谁指挥呢?被谁推动执行呢?

一般的外设都需要用户有操作了,才会准备数据触发硬件中断
那么有不需要用户,自己就会每隔一段时间就发送信号中断的设备吗?
有的兄弟,有的!

CPU中有一个时钟源,它就会每隔一段时间[纳秒级别]发送一次时钟(硬件)中断
所以CPU每隔一段时间时间就会接收到硬件中断,拿到对应的中断号n查询中断向量表,执行中断方法

中断向量表中的时钟中断的中断号n对应下标的方法,就是操作系统去事件源中获取要执行的各种任务(进程调度,文件管理等)

所以操作系统是如何运行的呢?
一般情况下:
时钟源通过定期发送时钟中断,推动操作系统进行任务工作!!!
比较特殊时:
外设通过硬件中断,推动操作系统执行外设处理任务

所以操作系统就是基于中断和中断向量表进行工作的


时钟源为什么在CPU里面?
因为如果和其他硬件一样,在外面的话
①时钟源就得先把中断信号给中断控制器,才能给CPU,太慢了
②时钟源发送时钟中断的频率太高,如果发给中断控制器,中断控制器里面就全是时钟中断,会占用其他硬件中断的空间

其实所谓的CPU主频,就是一秒钟内CPU里面的时钟源给CPU发送的时钟中断的次数
所以这就是为什么CPU主频越高,效率越高的原因
因为主频越高,每秒的时钟中断次数就越多,推动操作系统执行的任务就越多
这也意味着CPU处理任何硬件中断的效率更高,CPU的响应速度更快


综上:
操作系统要做的所有工作,都在中断向量表中存着
所以
操作系统的源代码就是:

  • ①把所有操作系统工作时需要的初始化工作做完

  • ②操作系统就只需要躺平(死循环),阻塞等待中断信号到来
    注意:
    是阻塞等待,所以没有中断信号来的时候,操作系统在进程等待队列里呆着,不会占用CPU资源

  • ③中断信号来辣,CPU再执行操作系统代码,就可以通过对应硬件中断的中断号找到对应的中断方法,再调度中断方法
    CPU执行操作系统的源代码的时候,操作系统就运行起来了!!!

例:
下面是模拟操作系统工作的代码
在这里插入图片描述

  • ①方法集合就是中断向量表
  • ②alarm闹钟就是时钟源
  • ③信号就是时钟中断
  • ④信号编号就是中断号

运行结果
在这里插入图片描述

只要有类似的代码,我们就可以基于信号,让我们的进程模拟操作系统的行为

只需要把上面的基于信号改成基于硬件中断,就是操作系统的运行原理



什么是时间片?

时间片的本质就是进程PCB中的一个计数器

因为时钟源发送时钟中断的时间间隔是固定的
所以
只需给每个进程的时间片,都设置成时钟源发送时钟中断的间隔的整数倍

每次接收到时钟中断的时候,CPU先保护进程上下文之后,执行中断处理流程之前,会先执行一下内核中的进程调度的函数
进程调度的函数就是:

  • ①判断正在CPU上的进程的时间片是否为0

  • ②不为0,则对时间片(计数器)-=时钟源发送时钟中断的间隔

  • ③为0就直接执行进程切换的代码

这样
就一定可以在第n次时钟中断到来时,时间片(计数器)减到0,即可完成一次进程调度,然后执行进程切换



软中断

软中断与硬件中断对应
顾名思义,就是软件发送中断信号

CPU接收到软件发送的中断信号的处理方法也一样是执行中断处理历程

  • ①先进行上下文保护,保存当前正在运行的进程上下文数据

  • ②把操作系统的代码切换上来,执行操作系统的写的代码
    即:通过中断号执行对应的中断方法,并执行中断方法

  • ③恢复现场,继续执行普通进程的代码

软中断的分类

  • ①陷阱:比如int 0X80syscall xxx等这些不是进程/CPU出现了错误而产生的软中断
    而是单纯地想让CPU执行操作系统的代码(一般是执行系统调用)

  • ②异常:比如除0,野指针等因为CPU/进程等出现错误而触发的软中断

以操作系统支持系统调用为例,理解软中断

为了让操作系统支持系统调用,CPU设计了对应的汇编指令
只要进程把这些汇编指令写进源代码里CPU执行到这些汇编指令的时候,CPU也会获得固定的一个中断号n,然后就相当于接收到了硬件中断一样,执行中断处理历程

所以:
软中断调用系统调用的过程

  • ①CPU执行进程的代码,执行到系统调用时,就会被翻译成执行对应的汇编指令(syscall xxx或者是int0X80)CPU就会获得对应的一个中断号n系统调用号
    CPU就会开始执行操作系统的代码

  • ②中断向量表中下标为对应的中断号n的函数指针,早就已经被初始化成了系统调用的入口函数的地址

  • ③操作系统有非常多的系统调用,操作系统的源代码中,把它们的地址都放进了一个被称为系统调用表的函数指针数组中
    系统调用入口函数做的事情就是:使用数组下标系统调用号,让操作系统找到对应的函数的起始地址,给CPU

  • ④操作系统把进程的状态由用户态设置成内核态!由进程自己执行内核代码!

  • ⑤CPU开始执行操作系统的代码,代码翻译如下:

    • 1.拿着中断号n在中断向量表中找到系统调用入口函数
    • 2.把系统调用号传给系统调用入口函数
    • 3.系统调用入口函数再根据系统调用号,在系统调用表中找到对应的系统调用的实现
    • 4.最后执行系统调用的代码
      在这里插入图片描述

用户(进程)是怎么把系统调用号传给操作系统的?
用户(进程)是怎么把系统调用号传给操作系统的?怎么把系统调用的参数传给操作系统的?操作系统又是怎么把系统调用的返回值传给进程的?

答案:使用寄存器互相传递[因为寄存器是所有进程共享的]
①字节数小的,可以直接用寄存器互相传递
②字节数大的,就用寄存器传递虚拟地址
(注意:操作系统可以直接访问进程的页表和进程地址空间,因为操作系统是进程的管理者,所以可以直接用虚拟地址访问进程地址空间的数据


综上:
因为所以系统调用的实现代码,都在操作系统的源代码中有,所以进程这里就没必要有代码实现了,即占空间,又用不了

所以我们的进程调用任何系统调用都只需要:

  • ①把对应的系统调用号和传给系统调用的参数写进特定寄存器中
  • ②直接使用汇编指令int 0X80或者syscallxxx触发软中断(触发了软中断之后,CPU会自动执行中断处理历程)
    只是根据系统调用的不同,系统调用号,参数不一样而已

所以我们进程里包的系统调用的头文件里面放的都是
上述的过程的汇编代码的C语言封装版
因为用户态调用系统调用要做的工作就这些,剩下的都是内核态该做的事了

例:
如下是C语言对系统调用vfork的封装:
在这里插入图片描述

所以C标准库不仅封装了自己的函数,还封装了所有平台的系统调用

因为所有语言都要使用系统调用
所以所有语言底层都是使用C语言实现的
C++也要兼容C语言



同理,操作系统处理进程异常错误

进程异常错误(除0,野指针,缺页中断等)出现时,都会触发软中断,不同的错误有不同的中断号,让CPU执行中断处理历程,让操作系统处理对应的错误



进程地址空间第3讲(用户态和内核态)

用户区和内核区

进程地址空间其实分为两个部分(以32位平台为例

  • ①用户区(0-3GB):映射各种用户的代码和数据
    用户访问用户区映射的代码和数据直接通过虚拟地址就可以访问

    • 用户区有自己的用户页表,每个进程都有一张自己的用户页表
  • ②内核区(3-4GB):映射整个操作系统
    用户要访问内核区的代码和数据,只能通过系统调用访问

    • 内核区有自己的内核页表,所有进程共用同一张内核页表
      这也是所有进程能共用同一个操作系统的原因之一

内核区虽然映射了整个操作系统,但是我们用户对于内核区只关心系统调用!!!
因为用户(进程)只能通过系统调用访问操作系统的资源,这是操作系统为了保护自己而做出的规定!

操作系统的资源有哪些?
进程PCB,文件的struct file等对所有软硬件先描述再组织的结构体变量和管理它们的数据结构
只要写在操作系统的源代码里面的,都是操作的资源
如果把操作系统看做一个用C语言写的进程,那么进程PCB,文件的struct file等就是操作系统进程内部定义的结构体
组织进程PCB的数据结构,也是操作系统内部定义的数据结构

用户(进程)不需要在意系统调用的地址
因为执行系统调用的时候,CPU上跑的是操作系统的代码(拿着中断号查中断向量表,再拿系统调用号查系统调用表),系统调用的地址应该操作系统自己关心

用户(进程)只需要知道,要调用哪一个系统调用就行
因为连系统调用号都已经被C标准库封装好了,链接C标准库的时候,就知道系统调用号了

所以进程调用任何形式的函数(自定义的,库函数,系统调用)都可以在自己的进程地址空间中完成调用



用户(进程)可以直接使用虚拟地址访问内核区的代码和数据吗?[用户态和内核态]

不行!!!

因为是CPU直接进行了限制
CPU中有一个CS寄存器,其中有一个两个比特位大小的标志位(称为CPL)

  • CPL为0,表示CPU处于内核态
  • CPL为3,表示CPU处于用户态

所以就算钻空子真的拿到了地址,通过了编译,进行访问
以用户态的身份,去访问操作系统的代码和数据CPU就直接报错了

进程只有通过中断的方式,切换到操作系统的代码,CPU中的CS寄存器中的标志位才会被改为0,才是内核态,才能正常执行


所以所谓用户态和内核态,其实是CPU表示自己当前执行的代码的权限级别
所以:

  • ①处于用户态,就是CPU执行代码时,只能访问进程地址空间中的用户区的代码和数据

  • ②处于内核态,就是CPU执行代码时,只能访问进程地址空间中的内核区的代码和数据

用户态→内核态的方法就是:中断(软中断和硬件/时钟中断都行)



内核态和用户态的互相切换是进程切换吗?

不是!!!只是模式切换
进程地址空间,PCB之类的都没换,这叫什么进程切换?

内核态和用户态的互相切换:本质是权限+代码的切换!!!
即:用户态→内核态
就是把权限变成了操作系统级别,然后执行的代码变成了操作系统的代码

所以:
我们平时说执行系统调用是操作系统在执行,其实是不准确的
其实是进程自己使用操作系统的权限执行操作系统(内核)的代码


而且这也不意味着执行系统调用时,进程就变成操作系统了:
进程的身份还是没变,只是权限提升了
而且:
给于进程的内核态权限仅限于完成当前系统调用所需操作(如读写文件、创建进程)
(操作系统通过“最小化暴露”内核接口(如系统调用表)维护自身完整性)

类似于普通用户sudo执行命令,普通用户只是暂时有了root权限,不是暂时变成了root用户


当然执行内核代码时,进程对应的CPU中部分寄存器也是会被覆盖的
所以:
用户态→内核态之前,会把执行内核代码时可能被覆盖的CPU寄存器(至少EIP寄存器一定会被覆盖,因为下一行代码都要执行操作系统的代码了)中的值存储在内核中的内核栈里面
之后内核态→用户态的时候,再读取内核栈,才能正确地恢复到用户态,才能继续执行之后的用户的代码



为什么CPU执行用户的代码时要切换成用户态呢?

上面说了进程(用户)不能直接访问操作系统的代码和数据,一是因为权限不够,二是为了保护操作系统

但是为什么CPU不能以内核态直接执行用户的代码呢?明明权限比用户态大啊?
原因就是:会权限放大,为了安全不能干
用户的代码都是用户自己自定义的,如果其中有非法操作(比如:杀死一些优先级比自己高的进程)这些非法操作以普通用户的权限根本无法执行成功

但是如果以内核权限来执行就可能可以执行成功,造成不好的后果



为什么要把操作系统映射到进程地址空间?

最主要的原因就是让所有进程可以看到同一个操作系统

  • ①CPU执行进程代码时,使用的是虚拟地址
    如果操作系统也把代码和数据映射到进程地址空间,那它的代码和数据也有了虚拟地址了
    进程执行内核代码,就是在自己的进程地址空间中执行的
    CPU中断之后,切换执行操作系统的代码的时候,处理流程就和用户代码一样了

  • ②如果不把操作系统的代码和数据映射到进程地址空间,那也一定需要一张表,用来表示操作系统中的变量和物理地址的映射

    • 因为操作系统本质也是进程,和调度进程类似,CPU调度操作系统也需要这样的表
      那只需要在这张表中加上对应的虚拟地址的映射,不就是内核页表吗?

整个物理内存中只需要存储一张内核页表即可
因为只需要让操作系统映射到所有进程的进程地址的位置都相同,那么映射得出的虚拟地址就全部相同



操作系统是如何扫描内核文件缓冲区判断要不要刷新到外设的呢?

不光是这个,操作系统如何知道内存够不够用,内存碎片在哪?怎么清理?
等等上述的各种各样的不是由用户指派进程检查,而是操作系统自己要维护的各种软硬件条件(信息)

操作系统是如何维护的呢?
其实从操作系统开机之后,就算用户不启动任何进程,操作系统也会自己创建一些进程,这些进程被称为操作系统的内核固定历程
操作系统就让这些自己创建的进程,每隔一段时间去检查一下各种软硬件条件,进而维护软硬件条件
这样操作系统自己专门处理中断就行了


网站公告

今日签到

点亮在社区的每一天
去签到