操作系统 (一): 引论

本文为《现代操作系统》的读书笔记

什么是操作系统

  • 操作系统的任务是为用户程序提供一个更好、更简单、更清晰的计算机模型,并管理处理器、主存、磁盘等设备

内核态、用户态

  • 多数计算机有两种运行模式:内核态用户态 (通常,在 PSW 中有一个二进制位控制这两种模式)
    • 软件中最基础的部分是操作系统,它运行在内核态。在这个模式中,操作系统具有对所有硬件的完全访问权,可以执行机器能够运行的任何指令
      • 操作系统运行在裸机之上,为所有其他软件提供基础的运行环境
    • 软件的其余部分运行在用户态。在用户态下,只使用了机器指令中的一个子集。一般而言, 在用户态中有关 I/O 和内存保护的所有指令是禁止的。当然, 将 PSW 中的模式位设置成内核态也是禁止的
      • 为了从操作系统中获得服务, 用户程序必须使用 系统调用(system call)陷入内核并调用操作系统。TRAP 指令把用户态切换成内核态,并启用操作系统。当有关工作完成之后,在系统调用后面的指令把控制权返回给用户程序
      • 用户接口程序:基于文本的通常称为 shell, 而基于图标的则称为图形用户界面 (Graphical User Interface, GUI), 它们被用户用来与操作系统交互
        在这里插入图片描述

补充:驱动程序

  • 设备驱动程序直接与设备控制器 (I/O 接口) 对话来控制相应的 I/O 设备
  • 绝大多数驱动程序仍然需要在内核态运行。因此为了能够使用设备驱动程序,必须把设备驱动程序装入操作系统中,这样它可在核心态运行. 要将设备驱动程序装入操作系统, 有三个途径
    • (1) 将内核与设备驱动程序重新链接, 然后重启动系统。许多UNIX系统以这种方式工作
    • (2) 在一个操作系统文件中设置一个入口,并通知该文件需要一个设备驱动程序, 然后重启动系统。在系统启动时, 操作系统去找寻所需的设备驱动程序井装载之。Windows就是以这种方式工作
    • (3) 操作系统能够在运行时接受新的设备驱动程序并且立即将其安装好, 无须重启动系统。这种方式采用得较少, 但是正在变得普及起来。热插拔设备, 诸如USB和IEEE 1394设备都需要动态可装载设备驱动程序
  • 在实现输入和输出时,用户程序发出一个系统调用, 内核将其翻译成一个对应设备驱动程序的过程调用。然后设备驱动程序采用 轮询/中断/DMA 等方式完成 I/O 数据传送

补充:中断
在这里插入图片描述

不同角度看操作系统

作为扩展机器的操作系统

  • 在机器语言一级上,多数计算机的体系结构(指令集、存储组织、I/O 和总线结构) 是很原始的, 而且编程是很困难的。操作系统的一个主要任务是隐藏硬件,呈现给程序良好、清晰、优雅、一致的抽象 (自顶向下的观点)
    • 以 SATA (Serial ATA) 硬盘为例,它的接口十分复杂,没有任何理智的程序员想要在硬件层面上和硬盘打交道。相反, 他们使用 硬盘驱动 (disk driver) 软件来和硬件交互。这类软件提供了读写硬盘块的接口, 而不用深入细节。操作系统包含很多用于控制输入/输出设备的驱动。但就算是在这个层面, 对于大多数应用来说还是太底层了。因此, 所有的操作系统都提供使用硬盘的又一层抽象: 文件。使用该抽象, 程序能创建、读写文件, 而不用处理硬件实际工作中那些恼人的细节

作为资源管理者的操作系统

  • 操作系统用来管理一个复杂系统的各个部分 (自底向上的观点)。从这个角度看,操作系统的任务是在相互竞争的程序之间有序地控制对资源的分配 (四大类资源:处理器存储器外设信息 (程序和数据等))
  • 资源管理包括用以下两种不同方式实现多路复用(共享) 资源
    • 时间复用. 例如,若在系统中只有一个CPU, 而多个程序需要在该CPU上运行,操作系统则首先把该CPU分配给某个程序,在它运行了足够长的时间之后,另一个程序得到CPU, 然后是下一个,如此进行下去
    • 空间复用: 每个客户都得到资源的一部分,从而取代了客户排队。例如,通常在若干运行程序之间分割内存,这样每一个运行程序都可同时入驻内存(例如,为了轮流使用CPU) 。假设有足够的内存可以存放多个程序,那么在内存中同时存放若干个程序的效率,比把所有内存都分给一个程序的效率要高得多

Intel Pentium 4 引入了被称为 多线程 (multithreading) 或 超线程 (hyperthreading) 的特性。近似地说, 多线程允许 CPU 保持两个不同的线程状态,然后在纳秒级的时间尺度内来回切换。(线程是一种轻量级进程, 即一个运行中的程序)

操作系统的功能

存储器管理功能

  • 内存分配: 记录整个内存,按照某种策略实施分配,或回收释放的内存空间
  • 地址映射: 硬件支持下解决地址映射,即逻辑到物理地址转换
  • 内存保护: 保证各程序空间不受“进犯”
  • 内存扩充: 通过虚拟存储器技术虚拟成比实际内存大的多的空间来满足实际运行的需要

处理机管理功能

  • 作业和进程调度
  • 进程通信: 由于多个程序(进程)彼此间会发生相互制约关系,需要设置进程同步机制。进程之间往往需要交换信息,为此系统要提供通信机制

设备管理功能

  • 缓冲区管理: 管理各类 I/O 设备的数据缓冲区,解决 CPU 和外设速度不匹配的矛盾
  • 设备分配: 根据I/O请求和相应分配策略分配外部设备以及通道、控制器等
  • 设备驱动: 实现用户提出的I/O操作请求,完成数据的输入输出。这个过程是系统建立和维持的
  • 设备无关性: 应用程序独立于实际的物理设备,由操作系统将逻辑设备映射到物理设备

文件管理功能

  • 文件存储空间的管理:记录空闲空间、为新文件分配必要的外存空间,回收释放的文件空间,提高外存的利用率等
  • 目录管理: 目录文件的组织、及实现用户对文件的“按名存取”、目录的快速查询和文件共享等
  • 文件的读写管理和存取控制: 根据用户请求,读取或写入外存。并防止未授权用户的存取或破坏,对各文件(包括目录文件)进行存取控制

操作系统的历史

批处理系统

  • 批处理系统:加载在计算机上的一个系统软件。在它的控制下,计算机能够自动、成批地处理一个或多个用户的作业

联机批处理系统

  • 作业的输入/输出由 CPU 来处理。但在作业输入和结果输出时,主机的高速 CPU 仍处于空闲状态,等待慢速的输入/输出设备完成工作,主机处于“忙等”状态

脱机批处理系统

  • 增加一台不与主机直接相连而专门用于与输入/输出设备打交道的卫星机,输入/输出地脱离主机控制,充分发挥主机的告诉计算能力
  • 不足的是,每次主机内存进存放一道作业,每当它运行期间发出 I/O 请求后,高速 CPU 便处于等待低速 I/O 完成状态,致使 CPU 空闲

多道程序系统

  • 将内存分几个部分, 每一部分存放不同的作业。即允许多个程序同时进入内存并运行,它们共享系统的各种软、硬件资源。当一个作业等待 I/O 操作完成时, 另一个作业可以使用 CPU。如果内存中可以同时存放足够多的作业, 则 CPU 利用率可以接近 100%
  • 为了避免多道程序互相干扰(包括操作系统), 需要有某种保护机制。虽然这种机制必然是硬件形式的, 但是由操作系统掌控

多道批处理系统

简称为 “批处理系统”

  • 多道:系统可同时容纳多个作业。这些作业放在外存中,组成一个后备队列,系统按一定的调度原则每次从后备队列中选取一个或多个作业进入内存运行,运行作业结束、退出运行、后备作业进入运行均有系统自动实现,从而在系统中形成一个自动转接的、连续的作业流
  • 成批:在系统运行过程中,不允许用户与其作业交互。即,作业一旦进入系统,用户就不能直接干预其运行

分时系统

  • 分时系统:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。若某个作业在分配给它的时间片内不能完成其计算,则该作业暂停中断,把处理机让给另一作业使用,等待下一轮时再继续其运行
  • 由于作业运行轮转很快,给每个用户的印象是好像独占了一台计算机,不至于让用户等待每一个命令的处理时间过长

实时系统

  • 实时系统能及时响应随机发生的外部事件,并在严格时间范围内完成对该事件的处理

通用操作系统

  • 操作系统的三种基本类型:多道批处理系统、分时系统、实时系统
  • 通用操作系统:具有多种类型操作特征的操作系统 (Windows, Linux…)

操作系统概念

进程

  • 进程本质上是正在执行的一个程序. 进程基本上是容纳运行一个程序所需要所有信息的容器
    • 与每个进程相关的是地址空间(address space) , 这是从某个最小值的存储位置(通常是零) 到某个最大值的存储位置的列表。在这个地址空间中, 进程可以进行读写。该地址空间中存放有可执行程序、程序的数据以及程序的堆栈
    • 与每个进程相关的还有资源集, 通常包括寄存器(含有程序计数器和堆栈指针)、打开文件的清单、突出的报警、有关进程清单, 以及运行该程序所需要的所有其他信息

  • 对进程建立一种直观感觉的最便利方式是分析一个多道程序设计系统. 假设现在同时有多个活动进程,操作系统周期性地挂起一个进程然后启动运行另一个进程 (伪并行), 这可能是由于在过去的一两秒钟内, 第一个进程已使用完分配给它的时间片。一个进程暂时被挂起后, 在随后的某个时刻里, 该进程再次启动时的状态必须与先前暂停时完全相同, 这就意味着在挂起时该进程的所有信息都要保存下来。在许多操作系统中, 与一个进程有关的所有信息, 除了该进程的地址空间的内容以外,均存放在操作系统的一张表中, 称为进程表(process table) , 进程表是数组(或链表) 结构, 当前存在的每个进程都要占用其中一项
  • 所以, 一个(挂起的) 进程包括: 进程的地址空间, 以及对应的进程表项(其中包括寄存器以及稍后重启动该进程所需要的许多其他信息)

  • 合作完成某些作业的相关进程经常需要彼此通信以便同步它们的行为。这种通信称为进程间通信 (interprocess communication)

  • 有时, 需要向一个正在运行的进程传送信息, 而该进程并没有等待接收信息。例如, 一个进程通过网络向另一台机器上的进程发送消息进行通信。为了保证一条消息或消息的应答不会丢失, 发送者要求.它所在的操作系统在指定的若干秒后给一个通知, 这样如果对方尚未收到确认消息就可以进行重发。在设定该定时器后, 程序可以继续做其他工作。在限定的秒数流逝之后, 操作系统向该进程发送一个警告信号(alarm signal)。此信号引起该进程暂时挂起, 无论该进程正在做什么, 系统将其寄存器的值保存到堆栈, 并开始运行一个特别的信号处理过程, 比如重新发送可能丢失的消息。这些信号是软件模拟的硬件中断,除了定时器到期之外,该信号可以由各种原因产生。许多由硬件检测出来的陷阱, 如执行了非法指令或使用了无效地址等, 也被转换成该信号并交给这个进程
  • 系统管理器授权每个进程使用一个给定的 UID (User IDentification)。子进程拥有与父进程一样的 UID。用户可以是某个组的成员, 每个组也有一个 GID (Group IDentification)

地址空间

  • 通常, 每个进程有一些可以使用的地址集合, 典型值从 0 开始直到某个最大值
    • 在最简单的情形下, 一个进程可拥有的最大地址空间小于主存。在这种方式下, 进程可以用满其地址空间, 而且内存中也有足够的空间容纳该进程
    • 但是, 在许多 32 位或 64 位地址的计算机中, 分别有 2 32 2^{32} 232 2 64 2^{64} 264 字节的地址空间。如果一个进程有比计算机拥有的主存还大的地址空间, 而且该进程希望使用全部的内存, 那就可以使用虚拟内存技术, 操作系统把部分地址空间装入主存, 部分留在磁盘上, 并且在需要时来回交换它们。在本质上, 操作系统创建了一个地址空间的抽象, 作为进程可以引用地址的集合。该地址空间与机器的物理内存解耦, 可能大于也可能小于该物理空间。对地址空间物理空间的管理组成了操作系统功能的一个重要部分

  • 在 UNIX 中的进程将其存储空间划分为三段:正文段(如程序代码)、数据段(如变量) 以及堆栈段
    在这里插入图片描述

文件

  • 实际上, 支持操作系统的另一个关键概念是文件系统, 用以隐藏磁盘和其他 I/O 设备的细节特性, 给提供程序员一个良好、清晰的独立于设备的抽象文件模型

  • 创建文件、删除文件、读文件和写文件等都需要系统调用
    • 在读写文件之前, 首先要打开文件, 检查其访问权限。若权限许可, 系统将返回一个小整数, 称作文件描述符(file descriptor) , 供后续操作使用。若禁止访问, 系统则返回一个错误码
  • 为了提供保存文件的地方, 大多数操作系统支持目录(directory) 的概念. 目录项可以是文件或者目录, 这样就产生了层次结构一文件系统
    • 目录层结构中的每一个文件都可以通过从目录的顶部即根目录(root directory) 开始的路径名 (path name) 来确定
    • 每个进程有一个工作目录(working directory), 对于没有以斜线开头给出绝对地址的路径, 将在这个工作目录下寻找. 进程可以通过使用系统调用指定新的工作目录, 从而变更其工作目录
      在这里插入图片描述

管道 (pipe)

  • 管道是一种虚文件, 它可连接两个进程。如果进程 A A A B B B 希望通过管道对话,它们必须提前设置该管道。当进程 A A A 想对进程 B B B 发送数据时,它把数据写到管道上, 仿佛管道就是输出文件一样。进程 B B B 可以通过读该管道而得到数据,仿佛该管道就是一个输入文件一样
  • 这样, 在 UNIX 中两个进程之间的通信就非常类似于普通文件的读写了。更为强大的是,若进程想发现它所写入的输出文件不是真正的文件而是管道, 则需要使用特殊的系统调用
    在这里插入图片描述

shell

  • 操作系统是进行系统调用的代码。编辑器、编译器、汇编程序、链接程序、效用程序以及命令解释器等, 尽管非常重要,也非常有用,但是它们确实不是操作系统的组成部分
  • 本小节将大致介绍一下 UNIX 的命令解释器,称为 shell。尽管 shell 本身不是操作系统的一部分, 但它体现了许多操作系统的特性, 并很好地说明了系统调用的具体用法。shell 同时也是终端用户与操作系统之间的接口

  • 用户登录时,同时启动了一个 shell。它以终端作为标准输入和标准输出
  • 假如用户键入 dateshell 创建一个子进程,并运行 date 程序作为子进程
richard@richard-ubuntu:~$ date
2021年 03月 18日 星期四 14:07:57 CST
  • 用户可以重定向标准输出和输入: 下面的命令调用 sort 程序, 从 file1 中取得输入,输出送到 file2
richard@richard-ubuntu:~$ sort <file 1 >file2
  • 可以将一个程序的输出通过管道作为另一程序的输入
richard@richard-ubuntu:~$ cat file1 file2 file3 | sort > /dev/lp

内核

  • 通常将 OS 中一些与硬件紧密相关的模块(如:中断处理程序;各种常用设备的驱动程序)以及运行频率较高的模块(时钟管理、进程调度以及许多模块公用的一些基本操作)都安排在紧靠硬件的软件层次中,并使它们常驻内存,以提高OS 的运行效率,并对它们加以特殊的保护。这部分就是 OS 的内核

系统调用

  • 如果一个进程正在用户态运行一个用户程序,并且需要一个系统服务,比如从一个文件读数据,那么它就必须执行一个陷阱系统调用指令,将控制转移到操作系统。操作系统接着通过参数检查找出所需要的调用进程。然后,它执行系统调用,并把控制返回给在系统调用后面跟随着的指令。在某种意义上,进行系统调用就像进行一个特殊的过程调用,但是只有系统调用可以进入内核, 而过程调用则不能

  • 为了使系统调用机制更清晰,我们简要地考察 UNIX 提供的 read 系统调用. 它有三个参数:第一个参数指定文件,第二个指向缓冲区,第三个说明要读出的字节数
  • 几乎与所有的系统调用一样,它的调用由C程序完成,方法是调用一个与该系统调用名称相同的库过程: read
count = read(fd, buffer, nbytes);	// read 系统调用
  • 系统调用(以及库过程) 在 count 中返回实际读出的字节数。这个值通常和 nbytes 相同,但也可能更小 (读到文件尾)
  • 如果系统调用不能执行,不论是因为无效的参数还是磁盘错误,count 都会被置为 -1, 而在全局变量 errno 中放入错误号
  • 具体执行过程如下:
    • (1~4) 先将三个参数压入栈 (1~3),然后调用 read 库过程 (4),这个指令是用来调用所有过程的正常过程调用指令
    • (5~6) 在可能是由汇编语言写成的库过程中,一般把系统调用的编号放在操作系统所期望的地方,如寄存器中 (5)。然后执行一个 TRAP 指令将用户态切换到内核态,并在内核中的一个固定地址开始执行(6)。TRAP 指令实际上与过程调用指令非常类似,它们后面都跟随一个来自远处位置的指令,以及供以后使用的一个保存在栈中的返回地址. 然而,TRAP 指令与过程指令存在两个方面的差别。首先,它的副作用是,切换到内核态。而过程调用指令井不改变模式。其次,不像给定过程所在的相对或绝对地址那样,TRAP指令不能跳转到任意地址上。根据机器的体系结构,或者跳转到一个单固定地址上,或者指令中有一8位长的字段,它给定了内存中一张表格的索引,这张表格中含有跳转地址
    • (7~10) 跟随在 TRAP 指令后的内核代码开始检查系统调用编号,然后分派给正确的系统调用处理器,这通常是通过一张由系统调用编号所引用的、指向系统调用处理器的指针表来完成(7)。此时,系统调用处理器运行(8)。一旦系统调用处理器完成其工作,控制可能会在跟随 TRAP 指令后面的指令中返回给用户空间库过程(9)。这个过程接着以通常的过程调用返回的方式,返回到用户程序(10)
    • (11) 为了完成整个工作,用户程序还必须清除堆栈,如同它在进行任何过程调用之后一样(11)。假设堆栈向下增长,编译后的代码准确地增加堆栈指针值,以便清除调用 read 之前压入的参数。在这之后,原来的程序就可以随意执行了
      在这里插入图片描述

  • 下面几小节中, 我们将考察一些常用的 POSIX (可移植操作系统接口 Portable Operating System Interface of UNIX) 系统调用, 或者用更专业的说法, 考察进行这些系统调用的库过程
    在这里插入图片描述
  • 将 POSIX 过程映射到系统调用井不是一对一的。POSIX 标准定义了构造系统所必须提供的一套过程,但是井没有规定它们是系统调用、库调用还是其他的形式
    • 如果不通过系统调用就可以执行一个过程(即无须陷入内核),那么从性能方面考虑,它通常会在用户空间中完成
    • 不过,多数 POSIX 过程确实进行系统调用,通常是一个过程直接映射到一个系统调用上。在一些情形下,特别是所需要的过程仅仅是某个调用的变体时,一个系统调用会对应若干个库调用

用于进程管理的系统调用

在这里插入图片描述


fork

  • UNIX 中fork唯一可以在 POSIX 中创建进程的途径, 它创建一个原有进程的精确副本。在调用了 fork 后, 父进程和子进程拥有相同的内存映像、同样的环境字符串和同样的打开文件 (但它们的地址空间是不同的,不可写的内存区是共享的)
    • 通常, 子进程接着执行 execve 或一个类似的系统调用, 以修改其内存映像并运行一个新的程序
  • fork 调用返回一个值,在子进程中该值为零,并且在父进程中等于子进程的进程标识符 (Process IDentifier, PID)。使用返回的 PID, 就可以在两个进程中看出哪一个是父进程,哪一个是子进程

waitpid

  • 多数情形下,在 fork 之后,子进程需要执行与父进程不同的代码
    • 这里考虑 shell 的情形。它从终端读取命令,创建一个子进程,等待该子进程执行命令,在该子进程终止时,读入下一条命令。为了等待子进程结束,父进程执行 waitpid 系统调用,它只是等待,直至子进程终止
  • waitpid 可以等待一个特定的子进程,或者通过将第一个参数设为 -1 的方式,等待任何一个老的子进程。在 waitpid 完成之后,将把第二个参数 statloc 所指向的地址设置为子进程的退出状态(正常或异常终止以及退出值)。有各种可使用的选项,它们由第三个参数确定。例如,如果没有已经退出的子进程则立即返回

execve

  • 现在考虑 shell 如何使用 fork。在键入一条命令后,shell 调用 fork 创建一个新的进程。这个子进程必须执行用户的命令。通过使用 execve 系统调用可以实现这一点,这个系统调用会引起其整个核心映像被一个文件所替代该文件由第一个参数 name 给定。(实际上,该系统调用自身是 exec 系统调用, 但是若干个不同的库过程使用不同的参数和稍有差别的名称调用该系统调用。在这里,我们把它们都视为系统调用)
    在这里插入图片描述
  • 在最一般情形下,execve 有三个参数:将要执行的文件名称,一个指向变量数组的指针,以及一个指向环境数组的指针. 各种库例程,包括 execlexecvexecle 以及 execve, 允许略掉参数或以各种不同的方式给定。在本书中, 我们在所有涉及的地方使用 exec 描述系统调用
    • 例如, cp file1 file2 命令将 file1 复制到 file2。在 shell 创建进程之后,该子进程定位和执行文件 cp, 并将源文件名和目标文件名传递给它. cp 主程序(以及多数其他 C 程序的主程序)都有声明 main(argc, argv, envp)
      • 其中 argc 是该命令行内有关参数数目的计数器,包括程序名称。例如,上面的例子中,argc 为 3
      • argv 是一个指向数组的指针。该数组的元素 i 是指向该命令行第 i 个字符串的指针。在本例中,argv[0] 指向字符串"cp" , argv[1] 指向字符串 “file1”, argv[2] 指向字符串 “file2
      • envp 是一个指向环境的指针,该环境是一个数组,含有 name = value 的赋值形式,用以将诸如终端类型以及根目录等信息传送给程序。还有供程序调用的库过程,用来取得环境变量。在图 1- 19中,没有环境参数传递给子进程,所以 execve 的第三个参数为 0

exit

  • 这是在进程完成执行后应执行的系统调用。这个系统调用有一个参数一一退出状态 (0至255), 该参数通过 waitpid 系统调用中的 statloc 返回父进程

用于文件管理的系统调用

在这里插入图片描述

open, close

  • 要读写一个文件,先要使用 open 打开该文件。这个系统调用通过绝对路径名或指向工作目录的相对路径名指定要打开文件的名称,而代码 O_RDONLYO_WRONLYO_RDWR 的含义分别是只读、只写或两者都可以。为了创建一个新文件,使用 O_CREAT 参数。然后可使用返回的文件描述符进行读写操作
  • 接着,可以用 close 关闭文件

lseek

  • 与每个文件相关的是一个指向文件当前位置的指针。在顺序读(写) 时,该指针通常指向要读出(写入)的下一个字节。lseek 调用可以改变该位置指针的值,这样后续的 readwrite 调用就可以在文件的任何地方开始。lseek 有三个参数:第一个是文件的描述符,第二个是文件位置,第三个说明该文件位置是相对于文件起始位置、当前位置还是文件的结尾。在修改了指针之后,lseek 所返回的值是文件中的绝对位置

stat

  • UNIX 为每个文件保存了该文件的类型(普通文件、特殊文件、目录等) 、大小、最后修改时间以及其他信息。程序可以通过 stat 系统调用查看这些信息
  • 第一个参数指定了要被检查的文件;第二个参数是一个指针,该指针指向存放这些信息的结构。对于一个打开的文件而言,fstat 调用完成同样的工作

用于目录管理的系统调用

在这里插入图片描述

link

  • 它的作用是允许同一个文件以两个或多个名称出现
    • 它的典型应用是, 在同一个开发团队中允许若干个成员共享一个共同的文件, 他们每个人都在自己的目录中有该文件, 但可能采用的是不同的名称
  • 在 UNIX 中,每个文件都有唯一的编号,即 i-编号, 用以标识文件。该 i-编号是对 i-节点表格的一个引用, 它们一一对应, 说明该文件的拥有者、磁盘块的位置等。目录就是一个包含了 (i-编号, 文件ASCII名称)对集合的文件
    在这里插入图片描述

mount

  • mount 系统调用允许将两个文件系统合并成为一个
    • 通常的情形是, 在硬盘某个分区中的根文件系统含有常用命令的二进制(可执行) 版和其他常用的文件;用户文件在另一个分区。并且, 用户可插入包含需要读入的文件的U盘。通过执行 mount 系统调用, 可以将一个USB文件系统添加到根文件系统中
    • 第一个参数是 USB 驱动器 0 的块特殊文件名称, 第二个参数是要被安装在树中的位置,第三个参数说明将要安装的文件系统是可读写的还是只读的
    • mount 调用之后,驱动器0上的文件可以使用从根目录开始的路径或工作目录路径, 而不用考虑文件在哪个驱动器上
      在这里插入图片描述
mount("/dev/sdb0", "/mnt", 0);
  • mount 调用使得把可移动介质都集中到一个文件层次中成为可能,而不用考虑文件在哪个驱动器上

各种系统调用

在这里插入图片描述
kill

  • kill 系统调用供用户或用户进程发送信号用。若一个进程准备好捕捉一个特定的信号,那么, 在信号到来时, 运行一个信号处理程序。如果该进程没有准备好,那么信号的到来会杀掉该进程(此调用名称的由来)

Windows Win32 API

  • Windows 和 UNIX 的主要差别在于编程方式。UNIX 程序包括做各种处理的代码以及完成特定服务的系统调用。相反,Windows 程序通常是事件驱动程序。其中主程序等待某些事件发生,然后调用一个过程处理该事件
    • 典型的事件包括被敲击的键、移动的鼠标、被按下的鼠标或插入的U盘。调用事件处理程序处理事件,刷新屏幕, 并更新内部程序状态

  • 在 Windows 中也有系统调用,但不同于 UNIX,库调用和实际的系统调用几乎是不对应的。微软定义了一套过程,称为Win32应用编程接口 (Application Program Interface, API), 程序员用这套过程获得操作系统的服务

由于最新几版 Windows 中有许多过去没有的新调用,所以究竟 Win32 是由什么构成的,这个问题的答案仍然是含混不清的。在本小节中,Win32表示所有 Windows 版本都支持的接口。Win32 提供各 Windows 版本的兼容性

  • Win32 API 调用的数量是非常大的,有数千个。此外,尽管其中许多确实涉及系统调用,但有一大批 Win32 API 完全是在用户空间进行的。结果,在Windows中,不可能了解哪一个是系统调用,哪一个只是用户空间中的库调用。事实上,某个版本中的一个系统调用,会在另一个不同版本的用户空间中执行,或者相反
  • 当我们在本书中讨论 Windows 的系统调用时, 将使用 Win32 过程,这是因为微软保证:随着时间流逝, Win32过程将保持稳定。但是读者有必要记住, 它们并不全都是系统调用
    在这里插入图片描述

操作系统结构

单体系统

  • 在大多数常见的组织中, 整个操作系统在内核态以单一程序的方式运行. 整个操作系统以过程集合的方式编写,先编译所有单个的过程,再将它们链接成一个大型可执行二进制程序
    • 使用这种技术,系统中每个过程可以自由调用其他过程, 只要后者提供了前者所需要的一些有用的计算工作。调用任何一个你所需要的过程或许会非常高效, 但上千个可以不受限制地彼此调用的过程常常导致系统笨拙且难于理解。并且,任何一个过程的崩溃都会连累整个系统

  • 对于这类操作系统的基本结构, 有着如下结构上的建议:
    • 需要一个主程序, 用来处理服务过程请求
    • 需要一套服务过程, 用来执行系统调用
    • 需要一套实用过程, 用来辅助服务过程
  • 在该模型中, 每一个系统调用都通过一个服务过程为其工作并运行之。要有一组实用程序来完成一些服务过程所需要用到的功能, 如从用户程序取数据等
    在这里插入图片描述

  • 除了在计算机初启时所装载的核心操作系统外, 许多操作系统支持可装载的扩展, 诸如 I/O 设备驱动和文件系统。这些部件可以按照需要载入
    • 在 UNIX 中它们被叫作共享库(shared Iibrary)
    • 在 Windows 中则被称为动态链接库(Dynamic Link Library, DLL)。它们的扩展类型为 .dll

层次式系统

  • 层次式结构的操作系统:它的上层软件都是在下一层软件的基础之上构建的
    • 例如下图中系统共分为六层
      • 处理器分配第0层中进行, 当中断发生或定时器到期时, 由该层进行进程切换。在第0层之上,系统由一些连续的进程所组成, 编写这些进程时不用再考虑在单处理器上多进程运行的细节。也就是说, 在第0层中提供了基本的CPU多道程序设计功能
      • 内存管理第1层中进行, 它分配进程的主存空间,当内存用完时则在一个512K字的磁鼓上保留进程的一部分(页面)。在第1层上,进程不用考虑它是在磁鼓上还是在内存中运行。第1层软件保证一且需要访问某一页面,该页面必定已在内存中,并在页面不再需要时将其移出
      • 第2层处理进程与用户之间的通信。在这层的上部,可以认为每个进程都有自己的操作员控制台 (用户)
      • 第3层管理 I/O 设备和相关的信息流缓冲区。在第3层上,每个进程都与有良好特性的抽象 I/O 设备打交道,而不必考虑外部设备的物理细节
      • 第4层用户程序层。用户程序不用考虑进程、内存、控制台或I/O 设备管理等细节
      • 系统操作员进程位于第5层
        在这里插入图片描述

微内核

  • 在分层方式中,设计者要确定在哪里划分内核-用户的边界
    • 尽可能减少内核态中的功能可以避免内核中的错误会快速拖累系统
    • 相反,可以把用户进程设置为具有较小的权限, 这样,某个错误的后果就不会是致命的

  • 在微内核设计背后的思想是,为了实现高可靠性,将操作系统划分成小的、良好定义的模块,只有其中一个模块---- 微内核 运行在内核态,其余的模块由于功能相对弱些,则作为普通用户进程运行
    • 特别地,由于把每个设备驱动和文件系统分别作为普通用户进程,这些模块中的错误虽然会使这些模块崩溃,但是不会使得整个系统死机。相反,在单体系统中,由于所有的设备驱动都在内核中,一个有故障的音频驱动很容易引起对无效地址的引用, 从而造成恼人的系统立即停机
  • 通常的桌面操作系统并不使用微内核。然而,微内核在实时、工业、航空以及军事应用中特别流行,这些领域都是关键任务,需要有高度的可靠性
    • 这里对 MINIX 3 做简单的介绍,该操作系统把模块化的思想推到了极致,它将大部分操作系统分解成许多独立的用户态进程。MINIX 3 遵守 POSIX, 可在 www.minix3.org 站点获得免费的开放源代码
    • MINIX 3 的进程结构如下图所示,其中内核调用句柄用 Sys 标记。时钟设备驱动也在内核中,因为这个驱动与调度器交互密切。所有的其他设备驱动都作为单独的用户进程运行
    • 在内核的外部,系统的构造有三层进程,它们都在用户态运行。最底层中包含设备驱动器。由于它们在用户态运行,所以不能物理地访问 I/O 端口空间,也不能直接发出 I/O 命令。相反, 为了能够对 I/O 设备编程,驱动器构建了一个结构,指明哪个参数值写到哪个 I/O 端口,并生成一个内核调用,通知内核完成写操作。这个处理意味着内核可以检查驱动正在对 I/O 的读(或写)是否是得到授权使用的。这样,(与单体设计不同)一个有错误的音频驱动器就不能够偶发性地在硬盘上进行写操作
    • 在驱动器上面是另一用户态层,包含有服务器,它们完成操作系统的多数工作。由一个或多个文件服务器管理着文件系统,进程管理器创建、销毁和管理进程等。通过给服务器发送短消息请求 POSIX 系统调用的方式,用户程序获得操作系统的服务
      在这里插入图片描述

  • 一个与小内核相关联的思想是内核中的机制与策略分离的原则
    • 例如,一个比较简单的进程调度算法是,对每个进程赋予一个优先级,并让内核执行具有最高优先级的进程。这里,机制(在内核中)就是寻找最高优先级的进程井运行之。而策略(赋予进程优先级)可以由用户态中的进程完成。在这种方式中,机制和策略是分离的,从而使系统内核变得更小

客户端–服务器模式

  • 客户端–服务器模式:将进程划分为两类:
    • 服务器进程,每个服务器提供某种服务
    • 客户端进程,使用这些服务
  • 客户端-服务器模式是一种可以应用在单机或者网络机器上的抽象。一般来说,客户端和服务器之间的通信是消息传递。为了获得一个服务,客户端进程构造一段消息,说明所需要的服务,并将其发给合适的服务器。该服务器完成工作,发送回应
    • 如果客户端和服务器运行在不同的计算机上,则它们通过局域网或广域网连接。由于客户端通过发送消息与服务器通信, 客户端并不需要知道这些消息。这就是网络中客户端-服务器的典型应用方式
      在这里插入图片描述

虚拟机

虚拟机的优点

  • 可以在同一台机器上实现虚拟化来运行所有的服务器,而不会由于一个服务器崩溃影响其他系统
  • 虚拟化为希望同时运行两个或多个操作系统(比如 Windows 和 Linux) 的最终用户服务

VM/370

  • 它是源于如下机敏的观察, 即分时系统应该提供这些功能:
    • (1) 多道程序
    • (2) 一个比裸机更方便的、有扩展界面的计算机
    • VM/370 存在的目的是将二者彻底地隔离开来
  • 这个系统的核心称为第一类虚拟机管理程序 (type 1 hypervisor), , 它在裸机上运行并且具备了多道程序功能。该系统向上层提供了若干台虚拟机
    • 这些虚拟机仅仅是裸机硬件的精确复制品。这个复制品包含了内核态/用户态、I/O 功能、中断及其他真实硬件所应该具有的全部内容
    • 由于每台虚拟机都与裸机相同, 所以每台虚拟机上都可以运行一台裸机所能够运行的任何类型的操作系统。不同的虚拟机可以运行不同的操作系统
      • 在早期的 VM/370 系统上, 有一些系统运行大型批处理或事务处理操作系统,而另一些虚拟机运行单用户、交互式系统供分时用户使用,这个系统称为会话监控系统 (Conversational Monitor System, CMS)。当一个 CMS 程序执行系统调用时, 该调用被陷入到其虚拟机的操作系统上, 而不是 VM/370 上,似乎它运行在实际的机器上,而不是在虚拟机上。CMS 然后发出普通的硬件 I/O 指令读出虚拟磁盘或其他需要执行的调用。这些 I/O 指令由 VM/370 陷入并完成
  • 通过对多道程序功能和提供扩展机器二者的完全分离, 每个部分都变得非常简单、非常灵活且容易维护
    在这里插入图片描述

  • 为了在一台计算机上运行虚拟机软件,其 CPU 必须被虚拟化。简言之,存在一个问题。当运行虚拟机(在用户态) 的操作系统执行某个特权指令时,比如修改 PSW 或进行 I/O 操作, 硬件实际上陷入到了虚拟机中,这样有关指令就可以在软件中模拟。在某些 CPU 上,试图在用户态执行特权指令时,会被忽略掉。这种特性使得在这类硬件中无法实现虚拟机

第二类虚拟机管理程序

  • 第一类和第二类虚拟机管理程序的真正区别在于,后者利用宿主操作系统 (host operating ystem) 并通过其文件系统创建进程、存储文件等。第一类虚拟机管理程序没有底层支持,所以必须自行实现所有功能
    • 当第二类虚拟机管理程序启动时,它从 CD-ROM 安装盘中读入供选择的客户操作系统 (guest operating system), 并安装在一个虚拟盘上,该盘实际上只是宿主操作系统的文件系统中的一个大文件
    • 由于没有可以存储文件的宿主操作系统,因此第一类虚拟机管理程序不能采用这种方式。它们必须在原始的硬盘分区上自行管理存储
      在这里插入图片描述

外核

  • 外核给每个用户整个资源的一个子集。这种被称为外核的程序在内核态运行,它的任务是为虚拟机分配资源,并检查使用这些资源的企图,以确保没有机器会使用他人的资源
    • 这样,某个虚拟机可能得到磁盘的 0 至 1023 盘块,而另一台虚拟机会得到 1024 至 2047 盘块,等等
    • 每个用户层的虚拟机可以运行自己的操作系统,但限制只能使用已经申请并且获得分配的那部分资源

  • 外核机制的优点是,它减少了映像层。在其他的设计中,每个虚拟机都认为它有自己的磁盘,其盘块号从0到最大编号,这样虚拟机监控程序必须维护一张表格以重映像磁盘地址(以及其他资源)。有了外核,这个重映像处理就不需要了。外核只需要记录已经分配给各个虚拟机的有关资源即可
  • 这个方法还有一个优点,它将多道程序(在外核内) 与用户操作系统代码(在用户空间内)加以分离,而且相应负载并不重,这是因为外核所做的只是保持多个虚拟机彼此不发生冲突

依靠 C 的世界

  • 操作系统通常包括很多部分的大型 C (有时是 C++) 程序

猜你喜欢

转载自blog.csdn.net/weixin_42437114/article/details/114241808