Zohar's blog

操作系统 - 引论

Computercomputer

本文叙述了操作系统的基本概念、目标、作用即设计方法等…

基本概念

  • {中央处理器}(Central Processing Unit):是计算机运算和控制核心,是信息处理、程序运行的最终执行单元。

    由于电子计算机用硬件电路的方式进行工作,电路以电流的开关作为信号,因此 CPU 所处理的信号是以 0 和 1 表示的二进制信号。

  • {指令}(Instruction):是控制 CPU 执行某种操作的命令,是二进制形式的操作码。

    如:数据传送指令、算术运算指令、位运算指令、程序流程控制指令、串操作指令、处理器控制指令。由于是直接控制 CPU 工作的二进制串,因此必须在 CPU 设计阶段予以电路支持,但以何种电路实现并没有任何要求,也就是指令与具体的硬件电路无关。因此不同厂商、不同型号的 CPU 所支持的指令可能不一样,如果支持相同的指令,其硬件电路也可能是不一样的。如对于 11111111 指令,两种 CPU 可能采用不同的电路设计,而对于 00000000 指令则可能是一款支持一款不支持。

  • {指令集}(Instruction Set):是指多个指令的集合。一揽子指令组成指令集。

    不同类型或者不同数量的指令组合成各种各样的指令集。经典的指令集如:Intel的 x86,EM64T,AMD 的 x86,x86-64,3D-Now! 等。一个已有的指令集可以看成是一套规范,它规定了支持它的计算机必须实现的指令,如果 CPU 支持所有该指令集内包含的指令,就表示支持该指令集。指令集仅仅规定了必须支持的指令,并没有规定实现方式,以何种硬件方式实现与指令集无关。

  • {处理器架构}(Processor architecture):是处理器的硬件架构,称为微架构,是一堆实现指令集所规定的操作运算的硬件电路。

    如上文所说,指令集与其实现电路无关,因此指令集只能决定微架构所实现的功能,并不能决定微架构的具体实现。

    因此,一个指令集可以通过不同的微架构实现,相同的微架构甚至可以用来实现不同的指令集。

  • {机器语言}(Machine Language):是计算机能够直接识别代码指令。机器语言代表了所有二进制指令,最初的程序员便是以机器语言进行编程的,致敬!

  • {程序}(Program):是一组计算机能识别和执行的指令。一连串指令组成程序。

    机器语言就是指令,程序就是使用机器语言编写而成的二进制代码。对于特定的硬件平台使用该平台的对应的机器语言编写的程序是可以直接在该上面运行的。保留在硬盘中的程序也称为可执行文件。

  • {标准库}(Standard Library):是标准程序库,是一系列常用程序的集合。

  • {链接器}(Linker):是一个将一个或多个目标代码库链接为一个可执行文件的程序。

    对于开发过的程序的二次开发,尤其是重新开发标准库中的程序,是对已有资源的严重浪费。因为程序员需要花费大量时间定义和开发已有的且从未改变过的代码。我们希望能够在开发某个程序的时候,对于需要但标准库中已有的功能进行复用,将各个模块连接成可执行文件,链接器就是用于完成这项工作。链接器并不总是连接需要的代码库并交付出完整的程序,因为有的代码库不存在与某些平台中,或者有的代码需要在运行的时候动态链接。

  • {汇编语言}(Assembly Language):是一种低级的计算机语言,又叫符号语言,是二进制操作码的符号表示方法。

    由于机器指令是一串二进制操作码,机器语言非常不利于人的阅读与编写,因此我们采用助记符的形式,将不同的指令用不同的符号表示起来,以方便对编程和阅读。由于相同作用的指令在不同指令集中的操作码是不一样的,因此同一个符号 add 可能在甲指令集中是 11111111,而在乙指令集中是 00000000,因此汇编符号对不同指令集的表示方法是不一样的,特定的指令集需要用特定版本的汇编语言才能予以表示,同一套汇编语言不可在不同指令集间移植。

  • {汇编器}(Assembler):是将汇编语言翻译为机器语言的程序。

    对于使用汇编语言编写的程序,在开发完成之后需要将这些符号转化为计算机指令,因此需要有一个翻译器能帮我们完成这项工作,这个程序就是汇编器。

  • {源代码}(Source Code):是由人直接编写,需要经过翻译的代码。

  • {目标代码}(Object Code):是源代码经过翻译器翻译出来的代码。

    目标代码可能是二进制指令,也可能不是,因为有时候在将代码翻译成一种格式之后,还需要对翻译出来的代码进行操作才能成为程序。比如对翻译出来的目标代码进行链接操作。因此链接器处理的目标代码可能是二进制代码也可能是其他代码。

  • {高级编程语言}(Hight-level Programming Language):是不依赖于机器硬件特性,更接近于数学语言或人类语言的编程语言。

    由于汇编语言与特定指令集架构绑定在一起,对于多个平台开发同样功能的程序需要多次开发,程序不具备移植性。因此需要设计一种编程语言,能够屏蔽不同平台的特性,让开发者只关注功能而无需关注各平台的差异,这种语言就是高级语言。对于高级语言,我们需要为不同硬件平台设计不同的编译器,在完成开发之后,通过特定平台的编译器将代码直接转化为该平台指令集所需的二进制代码。

    指令集规定了二进制指令的形式,汇编语言仅仅只是指令的表示,不具备平台无关性,因此无需也不可能先将代码编译为汇编再编译成二进制。

  • {编译器}(Compiler):是将一种语言翻译成另一种语言的机器。

    汇编器就是编译器的一种,但编译器通常意义上讲都是对于高级语言而言的。对于同一种高级语言,对于不同指令平台上会有不同的编译器,各平台编译器通过解析高级语言的语法生成适用于该平台的二进制代码。

  • {预处理器}(Preprocessor):是通过处理输入数据,产生能用来输入到其他程序的数据的程序。

    由于高级语言更接近于人类语言,因此代码之中的语法会更灵活和更复杂,在编译时需要进行更多的处理工作。一些高级语言的编译器会将部分处理操作独立拆分出来,交给另一个程序去进行,这是预处理器中的一种,用于代码预处理。代码的预处理工作并不是完全必要的,但对代码进行预处理,使代码变成更有利于编译器工作的状态可以加快编译的速度,或者是预处理与编译同时进行以加快编译速度。常见的预处理操作有注释删除、宏替换、条件编译、死代码消除等等。

    因此将源代码编译成程序的完整流程为:源代码 -> 预处理器 -> 目标代码 -> 链接器 -> 可执行程序。

  • {操作系统}(Operating System):是管理计算机硬件与软件资源的程序。

    操作系统是建立在程序之上的概念。操作系统所具有的最重要的两种功能:为用户应用程序提供抽象和管理计算机资源。注意所面向的对象是用户应用程序,操作系统的指令和抽象接口都是提供给用户程序,而非是正在使用操作系统的用户。对于系统设置的更改,操作系统也是通过中间程序间接的与用户进行交互,像 win 询问用户是否允许应用在管理员模式运行的情景,操作系统使用一道拦截鉴权程序与用户交互,并不会直接地和用户发生交互行为。

    程序是运行在 CPU 上的,而非运行在操作系统上的。操作系统所做的,是管理各程序的运行,包括提供运行中程序所需要的资源,分配程序所需要的内存等。而程序与操作系统无关这一个概念让很多人难以接受的点是:同一个硬件环境里,相同的程序代码无法在不同的操作系统中执行。程序指令的执行只与指令集架构有关是事实,相同程序无法在相同指令集架构上的不同操作系统上运行也是事实。原因同样是因为操作系统的职责是管理程序的运行,能够在特定操作系统中运行的程序必须能够支持操作系统的管理,因此可执行文件在编译时实际上增加一段{启动代码}(start-up code)。此代码相当于程序和操作系统之间的接口。不同操作系统的启动代码不同,所以即使指令集相同、二进制代码相同,不同操作系统也不能通用。

    一个非常好的例子就是,为了更好的支持 WSL,微软让自家 win 上的 VSCode 能够在 windows 上启动,同时也能在 wsl,即 linux 中启动。它就是通过解决启动代码这一障碍实现这一功能:它对于同一个 vscode 可执行文件,创建了两个不同的启动文件,一个是 cmd 文件使得能够在 windows 命令行中启动,另一个是 linux 下的可执行文件使得能够在 wsl 中启动:

    VSCode 的两个启动文件

  • {内核}(Kernel):内核,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。

    应用程序是基于操作系统之上运行的,笼统上讲也可以说是基于内核运行的,因为内核提供了所有基本必需的服务。操作系统中除内核外的其他功能并不是必须的,可以看作是用户程序,这些程序是为了更好的使用和管理系统而引入操作系统的。

  • 特权指令:具有特殊权限,只用于操作系统或其他系统软件,普通用户不能直接使用的命令。

    为了更好的与操作系统内核协作,同时保护系统内核和核心资源不受破坏,CPU 会将指令分级,级别最高的指令称为特权指令,级别较低的指令称为非特权指令。特权指令只有内核可以运行,普通应用程序无法运行。

  • CPU 指令运行模式:

    • 内核态:又称核心态、系统态、管态,是操作系统内核所运行的模式,运行在该模式的代码,同时可以无限制地对系统存储、外部设备进行访问。

    • 用户态:又称目态,不具备特权,无法使用特权指令。

  • {系统调用}(System Call):操作系统提供给应用程序的接口,使用系统调用可以进入内核执行特权指令。

  • {中断}(Interrupt Request):指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

    现代操作系统基本上是由中断驱动的,操作系统的运行的过程中,程序会随时对操作系统提出各种请求,这是软件中断的一种。操作系统会根据中断的类型和内容进行相应的处理,启用中断服务程序,处理完成之后再继续运行之前的工作。中断分为两种:

    • {硬件中断}(Hardware Interrupt):又称向量中断,是硬件中断源的识别标识。

      根据该标识可以在内存中设置向量中断与其对应的中断服务程序的入口地址的映射,在中断产生时就会直接进入中断服务程序。如果这个中断可以在中断屏蔽寄存器中关闭就属于可屏蔽中断,否则就是不可屏蔽中断。

    • {软件中断}(Software Interrupt):软件中断是由应用程序主动产生的中断。

      如果操作系统有对该中断提供支持,则操作系统能够予以响应,常见的如软件请求进入管理员模式。如果操作系统没有予以支持,则可能会导致软件异常等情况,但软件自身也可以对特定的中断提供支持,比如调试代码时的断点机制。

  • {陷阱}(Trap):或者叫陷阱门、自陷、陷入内核,是一条 CPU 指令,属于软件中断,用于将 CPU 从目态切换至管态。

    由于用户态无法执行特权指令,但应用程序很大概率会需要用到部分核心的功能,内核开放出一系列系统调用供用户使用内核功能。此时需要完成将 CPU 从用户程序交给内核的功能,陷阱常被用做此功能以实现系统调用。陷阱是中断的一种,因此当用户程序请求自陷时,系统会记住当前程序的执行状态再切换到管态实现特权功能,完成后再将结果返回给当前程序。

基本功能

自举

操作系统可以将用户程序从磁盘加载到内存中启动,但操作系统本身就是一个大型的程序,在计算机启动后将操作系统如何加载到内存中的过程称为操作系统{自举}(bootstrapping)。

在最初的时候,人们通过将操作系统烧录入硬盘中的方式制作操作系统盘,即烧录成 ROM 的方式,一旦写入便无法更改。在电脑启动之后固定从该区域加载代码,即可完成操作系统的载入。这种方式不利于系统的修改、升级和拓展,而且操作系统体积庞大,烧录之后会浪费非常多资源。后来将这一过程优化为:将操作系统存储在磁盘中,将一小段引导代码烧录成 ROM,在加电自检后会直接启动该引导程序,再由该程序去指定的磁盘中载入操作系统,载入操作系统的部分代码之后,剩下工作交由操作系统来完成,这一功能如今由 BIOS 来执行。后来又出现了引导分区的技术,BIOS 启动时并非直接启动操作系统,而是启动磁盘中专有的引导分区中的引导程序,再由该程序自主寻找和启动其他磁盘分区中的操作系统。

存储管理

  • 内存分配

  • 地址映射

  • 内存保护

  • 内存扩充 - 虚拟存储技术

进程和处理机管理

  • 作业和进程调度

  • 进程控制

  • 进程通信

文件管理

  • 文件存储空间的管理

  • 文件操作的一般管理

  • 目录管理

  • 文件的读写管理和存取控制

设备管理

  • 缓冲区管理 - 三级缓冲区管理

  • 设备分配 - 用户的设备分配

  • 设备驱动 - 驱动外设工作

  • 设备无关性 - 用户程序与实际物理设备无关。

用户接口

  • 程序接口 - 系统调用接口

  • 命令行接口 - shell

  • 图形用户接口 - 图形界面

特征

特征:

  • 并发性:程序并发执行

  • 共享性:资源共享使用

  • 异步性:执行过程不定

  • 抽象性:资源高度抽象