操作系统中一些基本概念

启动

在计算机上电之后,在硬件的帮助下,PC 会指向 BIOS 程序,CPU 运行 BIOS 程序,进行硬件初始化、自检、系统启动自举程序等。

  • BIOS: 全称是Basic Input Output System,即基本输入输出系统。事实上BIOS 的全称应该是ROM -BIOS,即只读存储器基本输入输出系统。具体地,BIOS 是一组固化到计算机内主板上一个ROM 芯片上的程序,它包含计算机最重要的基本输入输出的程序、系统设置信息、开机上电自检程序和系统启动自举程序。BIOS 的主要功能就是为计算机提供最底层的、最直接的硬件设置和控制。计算机在启动时可以执行BIOS 程序,是因为 CPU 的硬件设计时,将 PC 设置为一个固定的地址,这个地址就指向 BIOS 程序(之后体系结构研讨课会做这件事情)。

现在开始关注系统启动,此时 CPU 正在运行 BIOS 程序,那么如何开始运行操作系统呢,由于存储BIOS 程序的ROM 空间很小,空间不足于存储操作系统的代码,因此一般我们会将操作系统的程序放在磁盘或者其他存储设备。

因此现在的任务是需要将整个操作系统从磁盘中读取到内存中执行,在当时的 OS 实验课中分两批加载操作系统。BIOS 会将第一批(操作系统引导程序)加载到内存中,操作系统引导程序负责剩下的操作系统程序的加载。

BIOS 会通过中断操作将引导程序加载到内存,此时,操作系统的代码第一次出现在内存中,然后 CPU 通过跳转指令,从 BIOS 程序跳到引导程序,CPU 终于开始运行操作系统自己的代码。引导程序的作用是将剩下的操作系统程序加载到内存中,然后跳到操作系统程序入口开始执行。之后,整个操作系统就开始运行起来了。

  • 解释一下上面这段话的过程,CPU 上电后跳转到一个位置,这个位置可为一处可执行代码,代码目标为将存储设备上的引导程序的内容拷贝到一个固定的位置,然后跳转到引导程序部分即可,引导程序再去加载剩余的操作系统,然后跳转至操作系统。

  • 继续解释上面的问题,有人可能会说,为啥不直接把操作系统存进去,还要搞个引导程序?这个算是当时实验的一种方案,并不是规定死的,可以理解为有一个引导程序比较方便调整操作系统的读入,或者说如果想在把操作系统存进来之后先不执行,搞点其他操作,这样会方便一些,如果直接放进来可能就会比较麻烦。

调度

为了描述和控制进程的运行,操作系统需要为每个进程定义一个数据结构去描述一个进程,这就是我们所说的进程控制块 PCB。它是进程重要的组成部分,它记录了操作系统用于描述进程的当前状态和控制进程的全部信息,比如:进程号、进程状态、发生任务切换时保存的现场(通用寄存器的值)、栈地址空间等信息。操作系统就是根据进程的 PCB 来感知进程的存在,并依此对进程进行管理和控制,PCB 是进程存在的唯一标识。

  • 进程与线程的区别是什么? 在现代操作系统中,进程本质上是资源单位,而线程是真正的执行单位。系统中的资源是按照进程来管理的,比如每个进程有独立的地址空间、文件描述符表以及所属于这个进程的线程(可以把线程理解为处理器资源)等。而线程只负责执行。

拥有了 PCB 之后,我们就可以去管理进程从而去实现进程的切换了。当进程发生切换的时候,操作系统就会将当前正在运行进程的现场(寄存器的值)保存到 PCB 中,然后从其他进程的 PCB 中选择一个,对这个 PCB 里的保存的现场进行恢复,从而实现跳到下一个进程的操作。这个选择下一个将要运行的进程的切换过程也就是我们平常所说的调度。

  • 简单的来理解任务切换,我们实际上只需要一个全局的 PCB 指针,它指向哪个 PCB,说明那个 PCB 对应的任务为正在运行的任务。在进行保存和恢复的时候只对这个指针对应 PCB 进行保存和恢复。

  • 调度过程的触发方式大致可分为两种:第一种是在不具备中断处理能力时,通过进程自己使用调度方法去“主动”的交出控制权的非抢占式调度;第二种是在具备了中断处理能力后,通过周期性触发时钟中断去触发调度方法,从而使得进程“被迫”交出控制权的抢占式调度。

  • 调度细节不难,几种方案理论课都讲的很清楚,自己看就行。

用户态和内核态

以 Linux 为例,对于用户态进程来说,出于程序设计方便和内存安全的角度等原因,为每个用户态进程引入了独立的虚拟地址空间,其被映射到用户空间。用户进程平时运行在用户态,有自己的虚拟地址空间,但是可以通过中断、系统
调用等内陷到内核态。内核进程,没有独立的地址空间,所有内核线程的地址空间都是一样的,内核进程运行在内核空间,本身就是内核的一部分或者说是内核的分身。之前所说的,都是直接调用了内核的代码,但其实作为用户进程的任务是不允许直接访问内核代码段的,需要通过内核给出的访问接口去访问内核代码段,调用内核的功能。这个访问接口我们通常称之为系统调用。

例外处理

当发生异常时,处理器执行地址会将发生异常的地址放入特定的寄存器,然后自动跳转到一个固定的例外处理入口(体系结构研讨课会做)在发生例外时,跳转到例外处理入口是由硬件做的,但是例外的处理需要软件(操作系统)完成。

  • 触发例外的情况有很多,系统调用是一种例外,中断也是一种例外。中断又可以分为时钟中断、设备中断等,而设备中断又可以分为键盘中断、串口中断等,不同的例外硬件会有不同的反馈,软件部分只需要根据某些值进行判断处理即可。

  • 注意处理例外的时候需要关中断,防止发生其他问题产生冲突(如时钟中断),处理完后再开中断。

用户态和内核态的切换————以系统调用为例

系统调用也是例外的一种,只不过这种中断是用户主动触发的,我们触发系统调用中断的方式是在使用 syscall 汇编指令,当触发系统调用中断时和处理其他例外一样,处理器会自动跳入例外处理入口,保存用户态现场,然后进入到内核的系统调用处理代码段,当调用完内核代码后返回用户态现场。

  • 其实说到这可能也就明白了,所谓的用户态内核态切换,就是上下文现场切换的时候换的是哪里的上下文(用户还是内核)。

  • 完全区分用户态和内核态:当我们的操作系统可以进行虚存管理之后,我们就可以进一步划分用户态空间和内核态空间:让用户进程运行在用户态空间,内核运行在内核空间内。这样用户进程就不能直接访问内核的函数,只能通过中断和系统调用陷入内核。即在编译用户进程时需要指定用户进程的虚拟地址,当真正开始运行用户进程时再把用户进程搬移到编译用户进程时指定的虚拟地址,并给用户进程分配用户栈,至于硬件部分,可以通过给某些特定的寄存器赋值来表明状态。