Skip to content

Latest commit

 

History

History
228 lines (177 loc) · 22 KB

42 操作系统运行机制、中断与异常、系统调用、体系结构.md

File metadata and controls

228 lines (177 loc) · 22 KB

#操作系统

[4] 操作系统的运行机制

1.程序是如何运行的

程序是静态的文本代码,为了使计算机程序得以运行,计算机需要加载代码,同时也要加载数据,然后由处理器执行指令。整个过程可以总结为编译、链接、装载、执行。
整个过程为:代码 -> 编译器翻译 -> 链接器链接 -> 机器指令(二进制文件)

在这个过程中,一条高级语言的代码翻译过来可能会对应多条机器指令,程序运行的过程其实就是CPU执行一条一条的机器指令的过程。
注意,“指令”就是处理器(CPU)能识别、执行的最基本命令。Linux、Windows、MacOS 的“小黑框”中使用的命令行(command)也称为“指令”,其实这是“交互式命令接口”,注意与本节的“指令(Instruction)”区别开。本节中的“指令”指二进制机器指令(0-1数字)。

2.内核程序和应用程序

我们普通程序员写的程序都是应用程序。但是,微软(Microsoft)、苹果(Apple)有一帮人负责实现操作系统,他们写的是内核程序。由很多内核程序,组成了操作系统内核,或简称内核(Kernel)。

  • 内核是操作系统最重要最核心的部分,也是最接近硬件的部分。因此,它可以完全控制系统中的所有内容。硬件和软件的每个操作都由内核进行管理。
  • 内核被保留并通常加载到单独的内存空间中,称为受保护的内核空间。它受到保护,不会被应用程序或操作系统中不太重要的部分访问。其他应用程序,如浏览器、文字处理器、音频和视频播放器,使用单独的内存空间,称为用户空间
  • 由于这两个独立的空间,用户数据和内核数据不会相互干扰,也不会造成任何不稳定和缓慢。

甚至可以说,一个操作系统只要有内核就够了,例如,Docker仅需Linux内核,Docker利用Linux核心中的资源分离机制,例如cgroups,以及Linux核心命名空间(namespaces),来建立独立的容器(containers)。

当然,操作系统的功能未必都在内核中,如图形化用户界面 GUI。

3.特权指令和非特权指令

在操作系统的分级保护域(hierarchical protection domains)中,应用程序只能使用“非特权指令”,如:加法指令、减法指令等。 操作系统内核作为 “管理者”,有时会让CPU执行一些“特权指令”,如:内存清零指令。这些指令影响重大,只允许“管理者”——即操作系统内核来使用。
在CPU设计和生产的时候就划分了特权指令和非特权指令,二者相互隔离和独立,因此CPU执行一条指令前就能判断出其类型。

4.CPU和内核态和用户态

CPU有两种状态,内核态(Kernel Mode)和用户态(User Mode):

  • 处于内核态时,说明此时正在运行的是内核程序,此时可以执行特权指令。
  • 处于用户态时,说明此时正在运行的是应用程序,此时只能执行非特权指令。

拓展: CPU 中有一个寄存器叫程序状态字寄存器(PSW,Program Status Word),其中有个二进制位,1表示“内核态”,0表示“用户态”。
别名:内核态 = 核心态 = 管态;用户态 = 目态

用户模式与内核模式
用户模式是一种受限模式,应用程序正在执行并启动该模式。 内核模式是计算机在访问硬件资源时进入的特权模式。
模式
用户模式被视为从属模式或受限模式。 内核模式是系统模式、主模式或特权模式。
地址空间
在用户模式下,进程获取自己的地址空间。 在内核模式下,进程获取单个地址空间。
中断
在用户模式下,如果发生中断,只有一个进程失败。 在内核模式下,如果发生中断,整个操作系统可能会失败。
限制
在用户模式下,访问内核程序存在限制。无法直接访问它们。 在内核模式下,可以访问用户程序和内核程序。

5.状态切换:变态

问题:如何实现CPU状态的切换

  1. 刚开机时,CPU 为“内核态”,操作系统内核程序先上CPU运行。
  2. 开机完成后,用户可以启动某个应用程序。
  3. 操作系统内核程序在合适的时候主动让出 CPU,让该应用程序上CPU运行。
  4. 应用程序运行在“用户态”。
  5. 此时,一位猥琐黑客在应用程序中植入了一条特权指令,企图破坏系统。操作系统内核在让出CPU之前,会用一条特权指令把 PSW 的标志位设置为“用户态”。
  6. CPU发现接下来要执行的这条指令是特权指令,但是自己又处于“用户态”。
  7. 这个非法事件会引发一个中断信号,CPU检测到中断信号后,会立即变为“内核态”,并停止运行当前的应用程序,转而运行处理中断信号的内核程序。
  8. “中断”使操作系统再次夺回CPU的控制权。
  9. 操作系统会对引发中断的事件进行处理,处理完了再把CPU使用权交给别的应用程序。

[5] 中断和异常

CPU 上会运行两种程序,一种是操作系统内核程序,一种是应用程序,在合适的情况下,操作系统内核会把CPU的使用权主动让给应用程序(进程管理)。“中断”是让操作系统内核夺回CPU使用权的唯一途径。

1.中断和忙等待

中断(Interrupt),指处理器接收到来自硬件或软件的信号,提示发生了某个事件,应予以注意,这种情况就称为中断。
中断是让操作系统内核夺回CPU使用权的唯一途径,如果没有中断机制,那么一旦应用程序上CPU运行,CPU就会一直运行这个应用程序。这就与操作系统的并发属性产生冲突。
中断的设计之初,是用以提高计算机工作效率、增强计算机功能。最初引入硬件中断,只是出于性能上的考量,如果计算机系统没有中断,则处理器与外部设备通信时,它必须在向该设备发出指令后进行忙等待(Busy waiting),反复轮询该设备是否完成了动作并返回结果。这就造成了大量处理器周期被浪费。

初看这句话,会觉得难以理解,为什么中断还反而提高了效率?我在做事的时候,最讨厌别人打断我了,这会严重降低我的效率。
对比现实世界,这个中断,其实很好理解。比如我是一个厨子,我现在要做一顿饭,需要烧水、刷锅、切菜、炒菜、煮饭这几个步骤,但是我相信没有人会傻到,把这几件事情一件一件地干完。这样非常浪费时间,我可以把水烧了,就开始去煮饭,等锅在煮的时候,自己去切菜,等水烧开了,然后先把手头的事情放下,那么把水装到水壶里,再去刷锅,然后等这些做完了,在回头去切菜。
与中断对应的是忙等待。这里的忙等待,是指一种进程执行状态。进程执行到一段循环程序的时候,由于循环判断条件不能满足而导致处理器反复循环,处于繁忙状态,该进程虽然繁忙但无法前进。
忙等待,在现实世界里,就是一直不停的等着别人做,然后等别人做完了,自己再做。就很像那种身边讨厌的人,自己的活没做完,但是总是催着别人做。这样的效率其实并不高,反倒是在得知别人还没做完的时候,先做自己的事情,然后等自己做的差不多了,然后跑过来对齐下进度。

回到中断一开始引入的目的,再引入硬件中断以后,当处理器发出设备请求后就可以立即返回以处理其他任务,而当设备完成动作后,发送中断信号给处理器,后者就可以再回过头获取处理结果。这样,在设备进行处理的周期内,处理器可以执行其他一些有意义的工作,而只付出一些很小的切换所引发的时间代价。
后来,用于CPU外部与内部紧急事件的处理、机器故障的处理、时间控制等多个方面,并产生通过软件方式进入中断处理(软中断)的概念。回顾整个中断,其实是和操作系统的并发性一脉相承,由于操作系统的并发性让多个程序并发执行,所以在需要一种切换机制,这就是中断。

2.中断过程

中断的过程发生如下:

当发生异常或中断时,执行会从用户模式转换到处理异常或中断的内核模式。具体来说,必须采取以下步骤来处理异常或中断: 进入内核,必须首先将当前正在执行的进程的上下文(所有 CPU 寄存器的值)保存到内存。 内核现在已准备好处理异常/中断。

  1. 确定异常/中断的原因。
  2. 处理异常/中断。 处理完异常/中断后,内核将执行以下操作步骤:
  3. 选择要还原和恢复的进程。
  4. 恢复所选进程的上下文。
  5. 恢复所选进程的执行。 因此,中断的本质,就是发生中断就意味着需要操作系统介入,开展管理工作。按照分类,中断分为外中断和内中断。

3.内中断和外中断

内中断指,与当前执行的指令有关,中断信号来源于CPU内部。外中断指,与当前执行的指令无关,中断信号来源于CPU外部。一般而言,狭义的中断特指外中断,而内中断很多时候也叫做异常和例外。所以在上图中,由用户态切换成内核态的原因可能由外部事件触发(中断)和内部程序执行异常(内中断)导致。

其中,内中断(Exception)的三种常见类型:

  1. 陷入(trap):由陷入指令引发,是应用程序故意引发的,比如,read, fork, execve...
    • 例如,某应用程序想请求操作系统内核的服务,此时会执行一条特殊的指令——陷入(trap)指令,该指令会引发一个内部中断信号。
    • 系统调用(system call),就是通过陷入(trap)指令完成,由外部调用系统接口,完成用户态转化成内核态的状态。
  2. 故障(fault):由错误条件引起的,可以被错误处理程序纠正并返回正常程序。内核程序修复故障后会把 CPU使用权还给应用程序,让它继续执行下去。
    • 例如,缺页故障(Page fault),当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。
  3. 终止(abort):由致命错误引起,内核程序无法修复该错误,因此一般不再将CPU使用权还给引发终止的应用程序,而是直接终止该应用程序
    • 如:整数除0、非法使用特权指令。CPU发现当前状态非内核态,该行为是非法状态,引发中断信号(Signal),强行转化成内核态,然后运行处理中断信号的内核程序。

联想到做饭的例子,其实也可以类比现实世界。比如我正在切菜,突然自己想起自己有些特别重要的事情急着做,比如有人马上来家做客,这个时候需要发下微信,问下别人到哪了,这个时候切菜的过程就中断了。这就类似于陷入,通过个人主观地中断去做另一件事。
或者,当我切菜的时候,突然发现自己少洗了一道菜,比如我要做西红柿炖牛腩,突然发现西红柿没有,这个时候我就要去超市临时买点西红柿,等买回来,在继续切菜。这是一种故障,是由于错误的条件引起(忘记备菜了),但是我可以很快去楼下买完回来继续做,不影响后面出菜。
最差的情况就是我切菜的时候,切到手了,鲜血直流,我得马上找布包扎上药膏,然后跟家人说,今天这个饭目前我是做不了, 把活丢给了别人。这种就是致命错误导致的终止,而且无法修复,只能终止这个过程。

外中断的两种常见类型:

  1. 时钟中断,由时钟部件发来的中断信号;
    • 时钟部件每隔一个时间片(如 50ms)会给CPU发送一个时钟中断信号。
    • CPU接受中断信号,转成内核态。
    • CPU处理时钟中断的内核程序,操作系统内核决定接下来让另一个应用程序上CPU运行。
  2. I/O 中断请求:
    • 当输入输出任务完成时,向CPU发送中断信号。
    • 处理I/O中断的内核程序。

4.中断基本原理

不同的中断信号,需要用不同的中断处理程序来处理。当CPU检测到中断信号后,会根据中断信号的类型去查询中断向量表(interrupt vector table,IVT),以此来找到相应的中断处理程序在内存中的存放位置。这里的中断向量表,中断向量表 是一种数据结构,它将中断处理程序列表与中断向量表中的中断请求类型列表相关联。中断向量表的每个条目(称为中断向量)都是中断处理程序(interrupt handler,也称为ISR)的地址。

大多数处理器都有一个中断向量表,例如,常见的中断向量表的前几位有:

中断编号 IVT 地址 中断名称
0 00-03 CPU 除以零, CPU divide by zero
1 04-07 单步调试,Debug single step
2 08-0B 不可屏蔽中断, Non Maskable Interrupt (NMI input on processor)
3 0C-0F 调试断点, Debug breakpoints
... ... ...

显然,中断处理程序一定是内核程序,需要运行在“内核态”。

[6] 系统调用

操作系统作为用户和计算机硬件之间的接口,需要向上提供一些简单易用的服务。主要包括命令接口程序接口。其中,程序接口由一组系统调用组成。

“系统调用”是操作系统提供给应用程序(程序员/编程人员)使用的接口,可以理解为一种可供应用程序调用的特殊函数,应用程序可以通过系统调用来请求获得操作系统内核的服务

1.系统调用与库函数的关系

程序 系统调用关系
普通应用程序 可直接进行系统调用,也可使用库函数。
编程语言 向上提供库函数。有时会将系统调用封装成库函数,以隐藏系统调用的一些细节,使程序员编程更加方便。
操作系统 向上提供系统调用,使得上层程序能请求

并不是不是所有的库函数都会系统调用

  • 不涉及系统调用的库函数,例如“取绝对值”的函数,abs()函数。
  • 涉及系统调用的库函数,例如“创建一个新文件”的函数,fopen()函数。

2.为什么系统调用是必须的?

【问题】由于计算机内运行多个程序,如何保证各个程序有条不紊地并发运行?
由操作系统内核对共享资源进行统一的管理,并向上提供“系统调用” ,用户进程想要使用共享资源,只能通过系统调用向操作系统内核发出请求,换而言之,系统调用是用户唯一能使用的操作系统对外暴露的接口,内核按照系统调用,会对各个请求进行协调处理

3.什么功能要用到系统调用?

应用程序通过系统调用请求操作系统的服务,而系统中的各种共享资源都由操作系统内核统一掌管,因此凡是与共享资源有关的操作(如存储分配、I/O操作、文件管理等),都必须通过系统调用的方式向操作系统内核提出服务请求,由操作系统内核代为完成。这样可以保证系统的稳定性和安全性,防止用户进行非法操作。 常规的系统调用的类型有以下几类方式:

  • 进程控制:如创建进程、终止进程、等待事件、唤醒事件分配和释放内存。fork,wait,exit,exec函数。
  • 文件管理:如创建文件、删除文件、打开、关闭、读、写。create ,open,read,write,close,link,unlink,lseek,chmod,rename函数。
  • 设备管理:如请求设备、释放设备、读、写。swapon、swapoff函数。
  • 信息维护:如读取时间或日期、设置时间或日期。time函数。
  • 进程通信:如创建、删除通信连接,发送、接收消息。socket函数。

4.系统调用过程

系统调用过程主要分以下几个步骤:

  1. 应用程序,传递系统调用参数 ,传递陷入(trap)指令;
  2. CPU执行陷入指令,引发内中断(用户态);
  3. CPU转入相应中断处理程序,即系统调用入口程序;
  4. CPU根据寄存器中的参数判断用户究竟需要哪种系统调用服务;
  5. 执行相应的内请求核程序处理系统调用(内核态);
  6. 返回应用程序。

注意:

  1. 陷入指令是在用户态执行的,执行陷入指令之后立即引发一个内中断,使CPU进入核心态
  2. 发出系统调用请求是在用户态,而对系统调用的相应处理在核心态下进行;
  3. 别名:陷入指令 = trap 指令 = 访管指令。

[7] 操作系统的体系结构

1.内核与非内核

内核是操作系统最基本、最核心的部分。实现操作系统内核功能的那些程序就是内核程序。计算机操作系统的内核部分主要包含以下:

  1. 时钟管理(Digital Clock Manage):实现计时功能,
  2. 中断处理(Interrupt Processing):负责实现中断机制,
  3. 原语(primitive):是一种特殊的程序,具有原子性。也就是说,这段程序的运行必须一气呵成,不可被“中断”。运行时间较短,调用频繁。例如设备驱动、CPU切换等。
  4. 对系统资源管理:例如进程、存储器、设备、文件。

前三项主要是与硬件关联较紧密的模块。最后一项更多的是对数据结构的操作,不会直接涉及硬件。

非内核部分,例如图形界面接口。Ubuntu、CentOS 的开发团队,其主要工作是实现非内核功能,而内核都是用了Linux内核。

2.大内核和微内核

操作系统内核需要运行在内核态,操作系统的非内核功能运行在用户态。现在,应用程序想要请求操作系统的服务,这个服务的处理同时涉及到进程管理、存储管理、设备管理。在这一过程中,状态切换需要时间成本,因此频繁地变态会降低系统性能。根据内核的设计理念区别,操作系统的内核可以分为微内核和宏内核。

宏内核,简单理解其实就是把上面所有的功能都整合在一起。我们可以把进程管理、管理内存、管理硬盘、管理各种I/O设备……这些功能看作一个个模块。在宏内核中,这些模块都是集成在一起的,运行在内核进程中,只有处于内核态下才能运行。而微内核则和宏内核结构相反,它提倡内核中的功能模块尽可能的少。内核只提供最核心的功能,比如任务调度,中断处理等等。其他实际的模块功能如进程管理、存储器管理、文件管理……这些则被移出内核,变成一个个服务进程,和用户进程同等级,只是它们是一种特殊的用户进程。
典型的大内核/宏内核/单内核操作系统:Linux、UNIX,从Linux的内核架构可以看出,Linux的内核是将诸多功能融合进内核中,例如用户、系统、文件、网络等等。

典型的微内核操作系统: Windows NT、Mac OS X。Window 的内核设计是混合型内核,可以看到内核中有一个 MicroKernel 模块,这个就是最小版本的内核,当今 Windows 7、Windows 10 使用的内核叫 Windows NT,NT 全称叫 New Technology。而整个内核实现是一个完整的程序,含有非常多模块。

可以用企业的管理,形象比喻操作系统的体系结构。内核就是企业的管理层,负责一些重要的工作,只有管理层才能执行特权指令,普通员工只能执行非特权指令。用户态、核心态之间的切换相当于普通员工和管理层之间的工作交接。

  • 宏内核,类比于企业初创时体量不大,管理层的人会负责大部分的事情。优点是效率高,核心管理与基层员工直接进行信息交流;缺点是组织结构混乱,难以维护。
  • 微内核,类比于随着企业体量越来越大,管理层只负责最核心的一些工作。优点是组织结构清晰,方便维护;缺点是效率低,因为企业的命令传达需要经过多次传递。从核心到高管到中层管理再到基层。

参考链接: