《现代操作系统》

《现代操作系统》第 2 章进程与线程 2 1 进程 2 1 1 进程模型在进程模型中 计算机上所有可运行的软件 通常也包括操作系统 被组织成若干顺序进程 sequentialpr 简称进程 process

大家好,欢迎来到IT知识分享网。

第2章 进程与线程

2.1 进程

2.1.1 进程模型

这里的关键思想是: 一个进程是某种类型的一个活动, 它有程序、 输入、 输出以及状态。 单个处理器可以被若干进程共享, 它使用某种调度算法决定何时停止一个进程的工作, 并转而为另一个进程提供服务。

2.1.2 创建进程
2.1.3 进程的终止
2.1.4 进程的层次结构

某些系统中, 当进程创建了另一个进程后, 父进程和子进程就以某种形式继续保持关联。 子进程自身可以创建更多的进程, 组成一个进程的层次结构。 请注意, 这与植物和动物的有性繁殖不同, 进程只有一个父进程(但是可以有零个、 一个、 两个或多个子进程)

2.1.5 进程的状态

在这里插入图片描述

2.1.6 进程的实现
2.1.7 多道程序设计模型

采用多道程序设计可以提高CPU的利用率。 严格地说, 如果进程用于计算的平均时间是进程在内存中停留时间的20%, 且内存中同时有5个进程, 则CPU将一直满负载运行。 然而, 这个模型在现实中过于乐观,因为它假设这5个进程不会同时等待I/O。

2.2 线程

在传统操作系统中,每个进程有一个地址空间和一个控制线程。事实上,这几乎就是进程的定义。不过,经常存在在同一个地址空间中准并行运行多个控制线程的情形,这些线程就像(差不多)分离的进程(共享地址空间除外)。

2.2.1 线程的使用

为什么人们需要在一个进程中再有一类进程?

  1. 并行实体共享同一个地址空间和所有可用数据的能力。
  2. 第二个关于需要多线程的理由是, 由于线程比进程更轻量级, 所以它们比进程更容易(即更快) 创建,也更容易撤销。
  3. 性能方面。如果存在着大量的计算和大量的I/O处理, 拥有多个线程允许这些活动彼此重叠进行, 从而会加快应用程序执行的速度。
  4. 在多CPU系统中, 多线程是有益的, 在这样的系统中, 真正的并行有了实现的可能。
2.2.2 经典的线程模型

在这里插入图片描述
在多线程的情况下, 进程通常会从当前的单个线程开始。 这个线程有能力通过调用一个库函数(如thread_create) 创建新的线程。

当一个线程完成工作后, 可以通过调用一个库过程(如thread_exit) 退出。 该线程接着消失, 不再可调度。 在某些线程系统中, 通过调用一个过程, 例如thread_join, 一个线程可以等待一个(特定) 线程退出。

一个常见的线程调用是thread_yield, 它允许线程自动放弃CPU从而让另一个线程运行。

2.2.3 POSIX线程
2.2.4 在用户空间中实现线程

有两种主要的方法实现线程包: 在用户空间中和在内核中。 这两种方法互有利弊, 不过混合实现方式也是可能的。 我们现在介绍这些方法, 并分析它们的优点和缺点。

2.2.5 在内核中实现线程

在内核中实现线程此时不再需要运行时系统了。 另外, 每个进程中也没有线程表。 相反, 在内核中有用来记录系统中所有线程的线程表。 当某个线程希望创建一个新线程或撤销一个已有线程时, 它进行一个系统调用, 这个系统调用通过对线程表的更新完成线程创建或撤销工作。

2.2.6 混合实现
2.2.7 调度程序激活机制

该机制工作的基本思路是, 当内核了解到一个线程被阻塞之后(例如, 由于执行了一个阻塞系统调用或者产生了一个页面故障) , 内核通知该进程的运行时系统, 并且在堆栈中以参数形式传递有问题的线程编号和所发生事件的一个描述。 内核通过在一个已知的起始地址启动运行时系统, 从而发出了通知, 这是对UNIX中信号的一种粗略模拟。 这个机制称为上行调用(upcall) 。

2.2.8 弹出式线程

弹出式线程的关键好处是, 由于这种线程相当新, 没有历史——没有必须存储的寄存器、 堆栈诸如此类的内容, 每个线程从全新开始, 每一个线程彼此之间都完全一样。 这样, 就有可能快速创建这类线程。 对该新线程指定所要处理的消息。 使用弹出式线程的结果是, 消息到达与处理开始之间的时间非常短。

2.2.9 使单线程代码多线程化

一个线程的代码就像进程一样, 通常包含多个过程, 会有局部变量、 全局变量和过程参数。 局部变量和参数不会引起任何问题, 但是有一个问题是, 对线程而言是全局变量, 并不是对整个程序也是全局的。 有许多变量之所以是全局的, 是因为线程中的许多过程都使用它们(如同它们也可能使用任何全局变量一样) , 但是其他线程在逻辑上和这些变量无关。

1.一种解决方案是为每个线程赋予其私有的全局变量。 在这个方案中, 每个线程有自己的errno以及其他全局变量的私有副本, 这样就避免了冲突。

2.还有另一种方案, 可以引入新的库过程, 以便创建、 设置和读取这些线程范围的全局变量。

3.另一种解决方案是, 为每个过程提供一个包装器, 该包装器设置一个二进制位从而标志某个库处于使用中。 在先前的调用还没有完成之前, 任何试图使用该库的其他线程都会被阻塞。 尽管这个方式可以工作, 但是它会极大地降低系统潜在的并行性。

2.3 进程间通信

2.3.1 竞争条件

在一些操作系统中, 协作的进程可能共享一些彼此都能读写的公用存储区。 这个公用存储区可能在内存中(可能是在内核数据结构中) , 也可能是一个共享文件。

两个或多个进程读写某些共享数据, 而最后的结果取决于进程运行的精确时序, 称为竞争条件(race condition) 。 调试包含有竞争条件的程序是一件很头痛的事。 大多数的测试运行结果都很好, 但在极少数情况下会发生一些无法解释的奇怪现象。

2.3.2 临界区

怎样避免竞争条件? 实际上凡涉及共享内存、 共享文件以及共享任何资源的情况都会引发与前面类似的错误, 要避免这种错误, 关键是要找出某种途径来阻止多个进程同时读写共享的数据。 换言之, 我们需要的是互斥(mutual exclusion) , 即以某种手段确保当一个进程在使用一个共享变量或文件时, 其他进程不能做同样的操作。

在某些时候进程可能需要访问共享内存或共享文件, 或执行另外一些会导致竞争的操作。 我们把对共享内存进行访问的程序片段称作临界区域(critical region) 或临界区(critical section) 。 如果我们能够适当地安排, 使得两个进程不可能同时处于临界区中, 就能够避免竞争条件。

在这里插入图片描述

2.3.3 忙等待的互斥

只有在有理由认为等待时间是非常短的情形下, 才使用忙等待。 用于忙等待的锁, 称为自旋锁(spin lock) 。

2.3.4 睡眠与唤醒

Peterson解法和TSL或XCHG解法都是正确的, 但它们都有忙等待的缺点。 这些解法在本质上是这样的:当一个进程想进入临界区时, 先检查是否允许进入, 若不允许, 则该进程将原地等待, 直到允许为止。

这种方法不仅浪费了CPU时间, 而且还可能引起预想不到的结果。 考虑一台计算机有两个进程, H优先级较高, L优先级较低。 调度规则规定, 只要H处于就绪态它就可以运行。 在某一时刻, L处于临界区中, 此时H变到就绪态, 准备运行(例如, 一条I/O操作结束) 。 现在H开始忙等待, 但由于当H就绪时L不会被调度, 也就无法离开临界区, 所以H将永远忙等待下去。 这种情况有时被称作优先级反转问题(priority inversion problem) 。

生产者-消费者问题

2.3.5 信号量
2.3.6 互斥量

如果不需要信号量的计数能力, 有时可以使用信号量的一个简化版本, 称为互斥量(mutex) 。 互斥量仅仅适用于管理共享资源或一小段代码。 由于互斥量在实现时既容易又有效, 这使得互斥量在实现用户空间线程包时非常有用。

互斥量是一个可以处于两态之一的变量: 解锁和加锁。 这样, 只需要一个二进制位表示它, 不过实际上, 常常使用一个整型量, 0表示解锁, 而其他所有的值则表示加锁。 互斥量使用两个过程。 当一个线程(或进程) 需要访问临界区时, 它调用mutex_lock。 如果该互斥量当前是解锁的(即临界区可用) , 此调用成功, 调用线程可以自由进入该临界区。

2.3.7 管程

与管程和信号量有关的另一个问题是, 这些机制都是设计用来解决访问公共内存的一个或多个CPU上的互斥问题的。 通过将信号量放在共享内存中并用TSL或XCHG指令来保护它们, 可以避免竞争。 如果一个分布式系统具有多个CPU, 并且每个CPU拥有自己的私有内存, 它们通过一个局域网相连, 那么这些原语将失效。

2.3.8 消息传递

上面提到的其他的方法就是消息传递(message passing) 。 这种进程间通信的方法使用两条原语send和receive, 它们像信号量而不像管程, 是系统调用而不是语言成分。 因此, 可以很容易地将它们加入到库例程中去。

2.3.9 屏障

2.4 调度

当计算机系统是多道程序设计系统时, 通常就会有多个进程或线程同时竞争CPU。 只要有两个或更多的进程处于就绪状态, 这种情形就会发生。 如果只有一个CPU可用, 那么就必须选择下一个要运行的进程。 在操作系统中, 完成选择工作的这一部分称为调度程序(scheduler), 该程序使用的算法称为调度算法(scheduling algorithm) 。

2.4.1 调度介绍
2.4.2 批处理系统中的调度
2.4.3 交互式系统中的调度
2.4.4 实时系统中的调度
2.4.5 策略和机制

一个进程有许多子进程并在其控制下运行。 主进程完全可能掌握哪一个子进程最重要(或最紧迫) 而哪一个最不重要。 但是, 以上讨论的调度算法中没有一个算法从用户进程接收有关的调度决策信息, 这就导致了调度程序很少能够做出最优的选择。

2.4.6 线程调度

当若干进程都有多个线程时, 就存在两个层次的并行: 进程和线程。 在这样的系统中调度处理有本质差别, 这取决于所支持的是用户级线程还是内核级线程(或两者都支持) 。

用户级线程和内核级线程之间的差别在于性能。 用户级线程的线程切换需要少量的机器指令, 而内核级线程需要完整的上下文切换, 修改内存映像, 使高速缓存失效, 这导致了若干数量级的延迟。 另一方面, 在使用内核级线程时, 一旦线程阻塞在I/O上就不需要像在用户级线程中那样将整个进程挂起。

另一个重要因素是用户级线程可以使用专为应用程序定制的线程调度程序。

2.5 经典的IPC问题

2.5.1 哲学家就餐问题
2.5.2 读者-写者问题

例如, 设想一个飞机订票系统, 其中有许多竞争的进程试图读写其中的数据。 多个进程同时读数据库是可以接受的, 但如果一个进程正在更新(写) 数据库, 则所有的其他进程都不能访问该数据库, 即使读操作也不行。 这里的问题是如何对读者和写者进行编程?

在一个读者到达, 且一个写者在等待时, 读者在写者之后被挂起, 而不是立即允许进入。

第3章 存储管理

3.1 无存储器抽象

MOV REGISTER1,1000 

计算机会将位置为1000的物理内存中的内容移到REGISTER1中。 因此, 那时呈现给编程人员的存储器模型就是简单的物理内存: 从0到某个上限的地址集合, 每一个地址对应一个可容纳一定数目二进制位的存储单元, 通常是8个。

3.2 一种存储器抽象: 地址空间

在系统中没有对物理内存的抽象的情况下, 很难做到上述情景, 因此, 我们需要其他办法。

3.2.1 地址空间的概念

地址空间是一个进程可用于寻址内存的一套地址集合。 每个进程都有一个自己的地址空间, 并且这个地址空间独立于其他进程的地址空间(除了在一些特殊情况下进程需要共享它们的地址空间外) 。

基址寄存器与界限寄存器

当一个进程运行时, 程序的起始物理地址装载到基址寄存器中, 程序的长度装载到界限寄存器中。 使用基址寄存器和界限寄存器是给每个进程提供私有地址空间的非常容易的方法, 因为每个内存地址在送到内存之前, 都会自动先加上基址寄存器的内容。

3.2.2 交换技术
3.2.3 空闲内存管理

在动态分配内存时, 操作系统必须对其进行管理。 一般而言, 有两种方式跟踪内存使用情况: 位图和空闲链表。

3.3 虚拟内存

3.4 页面置换算法

3.4.10 页面置换算法小结

在这里插入图片描述

3.5 分页系统中的设计问题

3.5.1 局部分配策略与全局分配策略

第4章 文件系统

文件是进程创建的信息逻辑单元。 一个磁盘一般含有几千甚至几百万个文件, 每个文件是独立于其他文件的。 文件不仅仅被用来对磁盘建模, 以替代对随机存储器( RAM) 的建模, 事实上, 如果能把每个文件看成一种地址空间, 那么读者就离理解文件的本质不远了。

4.1 文件

4.1.1 文件命名
4.1.2 文件结构
4.1.3 文件类型

很多操作系统支持多种文件类型。 如UNIX和Windows中都有普通文件和目录, UNIX还有字符特殊文件(character special file) 和块特殊文件(block special file) 。

4.1.4 文件存取

早期操作系统只有一种文件存取方式: 顺序存取(sequential access) 。 进程在这些系统中可从头顺序读取文件的全部字节或记录, 但不能跳过某一些内容, 也不能不按顺序读取。 顺序存取文件是可以返回到起点的, 需要时可多次读取该文件。 在存储介质是磁带而不是磁盘时, 顺序存取文件是很方便的。

当用磁盘来存储文件时, 我们可以不按顺序地读取文件中的字节或记录, 或者按照关键字而不是位置来存取记录。 这种能够以任何次序读取其中字节或记录的文件称作随机存取文件(random access file) 。 许多应用程序需要这种类型的文件。

4.1.5 文件属性

文件都有文件名和数据。 另外, 所有的操作系统还会保存其他与文件相关的信息, 如文件创建的日期和时间、 文件大小等。 这些附加信息称为文件属性(attribute) , 有些人称之为元数据(metadata) 。

4.1.6 文件操作

4.2 目录

文件系统通常提供目录或文件夹用于记录文件, 在很多系统中目录本身也是文件。 本节讨论目录、 目录的组成、 目录的特性和可以对目录进行的操作。

4.2.1 一级目录系统

目录系统的最简单形式是在一个目录中包含所有的文件。 这有时称为根目录, 但是由于只有一个目录,所以其名称并不重要。

4.2.2 层次目录系统

所需要的是层次结构(即, 一个目录树) 。 通过这种方式, 可以用很多目录把文件以自然的方式分组。

4.2.3 路径名
4.2.4 目录操作

4.3 文件系统的实现

4.3.1 文件系统布局

在这里插入图片描述

4.3.2 文件的实现

连续分配方案也同样有相当明显的不足之处: 随着时间的推移, 磁盘会变得零碎。

与连续分配方案不同, 这一方法可以充分利用每个磁盘块。 不会因为磁盘碎片(除了最后一块中的内部碎片) 而浪费存储空间。

4.3.3 目录的实现
4.3.4 共享文件

共享文件是方便的, 但也带来一些问题。 如果目录中包含磁盘地址, 则当连接文件时, 必须把C目录中的磁盘地址复制到B目录中。 如果B或C随后又往该文件中添加内容, 则新的数据块将只列入进行添加工作的用户的目录中。 其他的用户对此改变是不知道的。 所以违背了共享的目的。

有两种方法可以解决这一问题。 在第一种解决方案中, 磁盘块不列入目录, 而是列入一个与文件本身关联的小型数据结构中。 目录将指向这个小型数据结构。 这是UNIX系统中所采用的方法(小型数据结构即是i节点) 。

在第二种解决方案中, 通过让系统建立一个类型为LINK的新文件, 并把该文件放在B的目录下, 使得B与C的一个文件存在连接。 新的文件中只包含了它所连接的文件的路径名。 当B读该连接文件时, 操作系统查看到要读的文件是LINK类型, 则找到该文件所连接的文件的名字, 并且去读那个文件。 与传统(硬) 连接相对比起来, 这一方法称为符号连接(symbolic linking) 。

4.3.5 日志结构文件系统
4.3.6 日志文件系统

虽然基于日志结构的文件系统是一个很吸引人的想法, 但是由于它们和现有的文件系统不相匹配, 所以还没有被广泛应用。 尽管如此, 它们内在的一个思想, 即面对出错的鲁棒性, 却可以被其他文件系统所借鉴。 这里的基本想法是保存一个用于记录系统下一步将要做什么的日志。 这样当系统在完成们即将完成的任务前崩溃时, 重新启动后, 可以通过查看日志, 获取崩溃前计划完成的任务, 并完成它们。 这样的文件系统被称为日志文件系统, 并已经被实际应用。

4.3.7 虚拟文件系统

4.4 文件系统管理和优化

4.4.1 磁盘空间管理
  1. 块大小
    分配单位很小意味着每个文件由很多块组成, 每读一块都有寻道和旋转延迟时间, 所以, 读
    取由很多小块组成的文件会非常慢。

  2. 记录空闲块
    第一种
    方法是采用磁盘块链表, 每个块中包含尽可能多的空闲磁盘块号。
    第二种
    另一种空闲磁盘空间管理的方法是采用位图。n个块的磁盘需要n位位图。



  3. 磁盘配额
    为了防止人们贪心而占有太多的磁盘空间, 多用户操作系统常常提供一种强制性磁盘配额机制。 其思想是系统管理员分给每个用户拥有文件和块的最大数量, 操作系统确保每个用户不超过分给他们的配额。
4.4.2 文件系统备份
  • 物理转储
    物理转储的主要优点是简单、 极为快速(基本上是以磁盘的速度运行) 。 主要缺点是, 既不能跳过选定的目录, 也无法增量转储, 还不能满足恢复个人文件的请求。 正因如此, 绝大多数配置都使用逻辑转储。
  • 逻辑转储
    逻辑转储从一个或几个指定的目录开始, 递归地转储其自给定基准日期(例如, 最近一次增量转储或全面系统转储的日期) 后有所更改的全部文件和目录。
4.4.3 文件系统的一致性

影响文件系统可靠性的另一个问题是文件系统的一致性。 很多文件系统读取磁盘块, 进行修改后, 再写回磁盘。 如果在修改过的磁盘块全部写回之前系统崩溃, 则文件系统有可能处于不一致状态。

4.4.4 文件系统性能
  1. 高速缓存
    常用的减少磁盘访问次数技术是块高速缓存(block cache) 或者缓冲区高速缓存(buffer cache) 。
    常用的算法是: 检查全部的读请求, 查看在高速缓存中是否有所需要的块。 如果存在, 可执行读操作而无须访问磁盘。 如果该块不在高速缓存中, 首先要把它读到高速缓存, 再复制到所需地方。

  2. 块提前读
    第二个明显提高文件系统性能的技术是: 在需要用到块之前, 试图提前将其写入高速缓存, 从而提高命中率。
  3. 减少磁盘臂运动
    另一种重要技术是把有可能顺序存取的块放在一起, 当然最好是在同一个柱面上, 从而减少磁盘臂的移动次数。
4.4.5 磁盘碎片整理

所有的空闲磁盘空间放在一个单独的、 与被安装的文件邻近的单元里。 但随着时间的流逝, 文件被不断地创建与删除, 于是磁盘会产生很多碎片, 文件与空穴到处都是。 结果是, 当创建一个新文件时, 它使用的块会散布在整个磁盘上, 造成性能的降低。

4.5 文件系统实例

4.5.1 CD-ROM文件系统
  1. ISO 9660文件系统
    最普遍的一种CD-ROM文件系统的标准是1988年被采纳的名为ISO 9660的国际标准。 实际上现在市场上的所有CD-ROM都支持这个标准, 有的则带有一些扩展(下面会对此进行讨论) 。 这个标准的一个目标就是使CD-ROM独立于机器所采用的字节顺序和使用的操作系统, 即在所有的机器上都是可读的。
  2. Rock Ridge扩展
  3. Joliet扩展
4.5.2 MS-DOS文件系统

MS-DOS按32位的数字存储文件的大小, 所以理论上文件大小能够大至4GB。

第5章 输入/输出

5.1 I/O硬件原理

5.1.1 I/O设备

字符设备
字符设备以字符为单位发送或接收一个字符流, 而不考虑任何块结构。 字符设备是不可寻址的, 也没有任何寻道操作。 打印机、 网络接口、 鼠标(用作指点设备) 、 老鼠(用作心理学实验室实验) , 以及大多数与磁盘不同的设备都可看作是字符设备。

5.1.2 设备控制器
5.1.3 内存映射I/O
5.1.4 直接存储器存取

无论一个CPU是否具有内存映射I/O,它都需要寻址设备控制器以便与它们交换数据。CPU可以从I/O控制器每次请求一个字节的数据,但是这样做浪费CPU的时间,所以经常用到一种称为直接存储器存取(Direct Memory Access,DMA)

在这里插入图片描述

5.1.5 重温中断

5.2 I/O软件原理

5.2.1 I/O软件的目标
  1. 设备独立性
    在设计I/O软件时一个关键的概念是设备独立性(device independence)。它的意思是应该能够编写出这样的程序:它可以访问任意I/O设备而无需事先指定设备。
  2. 错误处理
    一般来说,错误应该尽可能地在接近硬件的层面得到处理。
  3. 同步(synchronous)(即阻塞)和异步(asynchronous)(即中断驱动)传输
    大多数物理I/O是异步的——CPU启动传输后便转去做其他工作,直到中断发生。如果I/O操作是阻塞的,那么用户程序就更加容易编写——在read系统调用之后,程序将自动被挂起,直到缓冲区中的数据准备好。正是操作系统使实际上是中断驱动的操作变为在用户程序看来是阻塞式的操作。
  4. 缓冲(buffering)
    数据离开一个设备之后通常并不能直接存放到其最终的目的地。例如,从网络上进来一个数据包时,直到将该数据包存放在某个地方并对其进行检查,操作系统才知道要将其置于何处。
5.2.2 程序控制I/O

I/O的最简单形式是让CPU做全部工作,这一方法称为程序控制I/O(programmed I/O)。

借助于例子来说明程序控制I/O是最简单的。考虑一个用户进程,该进程想在打印机上打印8个字符的字符串“ABCDEFGH”。它首先要在用户空间的一个缓冲区中组装字符串,如图5-7a所示。

在这里插入图片描述

操作系统相继采取的操作总结在图5-8中。首先,数据被复制到内核空间。然后,操作系统进入一个密闭的循环,一次输出一个字符。在该图中,清楚地说明了程序控制I/O的最根本的方面,这就是输出一个字符之后,CPU要不断地查询设备以了解它是否就绪准备接收另一个字符。这一行为经常称为轮询(polling)或忙等待(busy waiting)。

在这里插入图片描述
程序控制I/O十分简单但是有缺点,即直到全部I/O完成之前要占用CPU的全部时间。

5.2.3 中断驱动I/O

允许CPU在等待打印机变为就绪的同时做某些其他事情的方式就是使用中断。

5.2.4 使用DMA的I/O

5.3 I/O软件层次

5.3.1 中断处理程序
5.3.2 设备驱动程序
5.3.3 与设备无关的I/O软件

虽然I/O软件中有一些是设备特定的,但是其他部分I/O软件是与设备无关的。

5.3.4 用户空间的I/O软件

尽管大部分I/O软件都在操作系统内部,但是仍然有一小部分在用户空间,包括与用户程序连接在一起的库,甚至完全运行于内核之外的程序。系统调用(包括I/O系统调用)通常由库过程实现。

5.4 盘

5.4.1 盘的硬件
  1. 磁盘
  2. RAID
    廉价磁盘冗余阵列
  3. .CD-ROM
  4. 可刻录CD
5.4.2 磁盘格式化
5.4.3 磁盘臂调度算法
5.4.4 错误处理
5.4.5 稳定存储器

5.5 时钟

时钟(clock)又称为定时器(timer),由于各种各样的原因决定了它对于任何多道程序设计系统的操作都是至关重要的。

5.5.1 时钟硬件
5.5.2 时钟软件
5.5.3 软定时器

5.6 用户界面:键盘、鼠标和监视器

5.6.1 输入软件
  1. 键盘软件
  2. 鼠标软件
5.6.2 输出软件
  1. 文本窗口
  2. X窗口系统
  3. 图形用户界面
  4. 位图
  5. 字体
5.7 瘦客户机
5.8 电源管理
5.8.1 硬件问题
5.8.2 操作系统问题
5.8.3 应用程序问题
5.9 有关输入/输出的研究
5.10 小结

习题

第6章 死锁

6.1 资源

资源就是随着时间的推移, 必须能获得、 使用以及释放的任何东西。

6.1.1 可抢占资源和不可抢占资源
6.1.2 资源获取

6.2 死锁概述

如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件, 那么, 该进程集合就是死锁的。

6.2.1 资源死锁的条件
6.2.2 死锁建模

6.3 鸵鸟算法

最简单的解决方法是鸵鸟算法: 把头埋到沙子里, 假装根本没有问题发生 。

6.4 死锁检测和死锁恢复

第二种技术是死锁检测和恢复。 在使用这种技术时, 系统并不试图阻止死锁的产生, 而是允许死锁发生, 当检测到死锁发生后, 采取措施进行恢复。 本节我们将考察检测死锁的几种方法以及恢复死锁的几种方法。

6.4.1 每种类型一个资源的死锁检测

这一算法是依次将每一个节点作为一棵树的根节点, 并进行深度优先搜索。 如果再次碰到已经遇到过的节点, 那么就算找到了一个环。 如果从任何给定的节点出发的弧都被穷举了, 那么就回溯到前面的节点。 如果回溯到根并且不能再深入下去, 那么从当前节点出发的子图中就不包含任何环。 如果所有的节点都是如此, 那么整个图就不存在环, 也就是说系统不存在死锁。

6.4.2 每种类型多个资源的死锁检测

如果有多种相同的资源存在, 就需要采用另一种方法来检测死锁。 现在我们提供一种基于矩阵的算法来检测从P1 到Pn 这n个进程中的死锁。 假设资源的类型数为m, E1 代表资源类型1, E2 代表资源类型2, Ei 代表资源类型i(1≤i≤m)。 E是现有资源向量(existing resource vector) , 代表每种已存在的资源总数。 比如, 如果资源类型1代表磁带机, 那么E1 =2就表示系统有两台磁带机。

在任意时刻, 某些资源已被分配所以不可用。 假设A是可用资源向量(available resource vector) , 那么Ai 表示当前可供使用的资源数(即没有被分配的资源) 。 如果仅有的两台磁带机都已经分配出去了, 那么A1 的值为0。

现在我们需要两个数组: C代表当前分配矩阵(current allocation matrix) , R代表请求矩阵(request matrix) 。 C的第i行代表Pi 当前所持有的每一种类型资源的资源数。 所以, Cij 代表进程i所持有的资源j的数量。 同理, Rij 代表Pi 所需要的资源j的数量。 这四种数据结构如图6-6所示。

在这里插入图片描述
这四种数据结构之间有一个重要的恒等式。 具体地说, 某种资源要么已分配要么可用。 这个结论意味着:
在这里插入图片描述

换言之, 如果我们将所有已分配的资源j的数量加起来再和所有可供使用的资源数相加, 结果就是该类资源的资源总数。

在这里插入图片描述
第1个不能被满足, 因为没有CDROM驱动器可供使用。
第2个也不能被满足, 由于没有打印机空闲。 幸运的是, 第3个可被满足, 所以进程3
运行并最终释放它所拥有的资源, 给出
A=(2 2 2 0)
接下来, 进程2也可运行并释放它所拥有的资源, 给出
A=(4 2 2 1)
现在剩下的进程都能够运行, 所以这个系统中不存在死锁。






6.4.3 从死锁中恢复
  1. 利用抢占恢复
    在某些情况下, 可能会临时将某个资源从它的当前所有者那里转移到另一个进程。 许多情况下, 尤其是对运行在大型主机上的批处理操作系统来说, 需要人工进行干预。
  2. 利用回滚恢复
    一旦检测到死锁, 就很容易发现需要哪些资源。 为了进行恢复, 要从一个较早的检查点上开始, 这样拥有所需要资源的进程会回滚到一个时间点, 在此时间点之前该进程获得了一些其他的资源。
  3. 通过杀死进程恢复
    最直接也是最简单的解决死锁的方法是杀死一个或若干个进程。 一种方法是杀掉环中的一个进程。 如果走运的话, 其他进程将可以继续。 如果这样做行不通的话, 就需要继续杀死别的进程直到打破死锁环。

6.5 死锁避免

6.5.1 资源轨迹图

避免死锁的主要算法是基于一个安全状态的概念。

6.5.2 安全状态和不安全状态

如果没有死锁发生, 并且即使所有进程突然请求对资源的最大需求, 也仍然存在某种调度次序能够使得每一个进程运行完毕, 则称该状态是安全的。

6.5.3 单个资源的银行家算法

银行家算法就是对每一个请求进行检查, 检查如果满足这一请求是否会达到安全状态。 若是, 那么就满足该请求; 若否, 那么就推迟对这一请求的满足。 为了看状态是否安全, 银行家看他是否有足够的资源满足某一个客户。 如果可以, 那么这笔投资认为是能够收回的, 并且接着检查最接近最大限额的一个客户, 以此类推。 如果所有投资最终都被收回, 那么该状态是安全的, 最初的请求可以批准。

6.5.4 多个资源的银行家算法

可以把银行家算法进行推广以处理多个资源。

6.6 死锁预防

6.6.1 破坏互斥条件

先考虑破坏互斥使用条件。 如果资源不被一个进程所独占, 那么死锁肯定不会产生。

6.6.2 破坏占有和等待条件
6.6.3 破坏不可抢占条件
6.6.4 破坏环路等待条件

在这里插入图片描述

6.7 其他问题

6.7.1 两阶段加锁

常用的方法是两阶段加锁(two-phase locking) 。 在第一阶段, 进程试图对所有所需的记录进行加锁,一次锁一个记录。 如果第一阶段加锁成功, 就开始第二阶段, 完成更新然后释放锁。 在第一阶段并没有做实际的工作。

6.7.2 通信死锁

源死锁是最普遍的一种类型, 但不是惟一的一种。 另一种死锁发生在通信系统中(比如说网络) , 即两个或两个以上进程利用发送信息来通信时。 一种普遍的情形是进程A向进程B发送请求信息, 然后阻塞直至B回复。 假设请求信息丢失, A将阻塞以等待回复, 而B会阻塞等待一个向其发送命令的请求, 因此发生死锁。

根据标准的定义, 在一系列进程中, 每个进程因为等待另外一个进程引发的事件而产生阻塞, 这就是一种死锁。 相比于更加常见的资源死锁, 我们把上面这种情况叫做通信死锁(communication deadlock) 。

另外一种技术通常可以用来中断通信死锁: 超时。

并非所有在通信系统或者网络发生的死锁都是通信死锁。 资源死锁也会发生, 如图6-15中的网络。

在这里插入图片描述

6.7.3 活锁

在某种情形下, 轮询(忙等待) 可用于进入临界区或存取资源。 采用这一策略的主要原因是, 相比所做的工作而言, 互斥的时间很短而挂起等待的时间开销很大。

6.7.4饥饿

在动态运行的系统中, 在任何时刻都可能请求资源。 这就需要一些策略来决定在什么时候谁获得什么资源。 虽然这个策略表面上很有道理, 但依然有可能使一些进程永远得不到服务, 虽然它们并不是死锁进程。

6.8.有关死锁的研究

死锁在操作系统发展的早期就作为一个课题被详细地研究过。 死锁的检测是一个经典的图论问题, 任何对数学有兴趣的研究生都可以在其上做3~4年的研究。

6.9 小结

死锁是任何操作系统的潜在问题。 在一组进程中, 每个进程都因等待由该组进程中的另一进程所占有的资源而导致阻塞, 死锁就发生了。 这种情况会使所有的进程都处于无限等待的状态。 一般来讲, 这是进程一直等待被其他进程占用的某些资源释放的事件。 死锁的另外一种可能的情况是一组通信进程都在等待一个消息, 而通信信道却是空的, 并且也没有采用超时机制。

通过跟踪哪一个状态是安全状态, 哪一个状态是不安全状态, 可以避免死锁。 安全状态就是这样一个状态: 存在一个事件序列, 保证所有的进程都能完成。 不安全状态就不存在这样的保证。 银行家算法可以通过拒绝可能引起不安全状态的请求来避免死锁。

也可以在设计系统时就不允许死锁发生, 从而在系统结构上预防死锁的发生。 例如, 只允许进程在任何时刻最多占有一个资源, 这就破坏了循环等待环路。 也可以将所有的资源编号, 规定进程按严格的升序请求资源, 这样也能预防死锁。

资源死锁并不是惟一的一种死锁。 尽管我们可以通过设置适当的超时机制来解决通信死锁, 但它依然是某些系统中潜在的问题。

活锁和死锁的问题有些相似, 那就是它也可以停止所有的转发进程, 但是二者在技术上不同, 由于活锁包含了一些实际上并没有锁住的进程, 因此可以通过先来先服务的分配策略来避免饥饿。

第8章 多处理机系统

8.1 多处理机

8.1.1 多处理机硬件

所有的多处理机都具有每个CPU可访问全部存储器的性质, 而有些多处理机仍有一些其他的特性, 即读出每个存储器字的速度是一样快的。 这些机器称为UMA(Uniform Memory Access, 统一存储器访问) 多处理机。

1.基于总线的UMA多处理机体系结构

最简单的多处理机是基于单总线的, 参见图8-2a。 两个或更多的CPU以及一个或多个存储器模块都使用同一个总线进行通信。 当一个CPU需要读一个存储器字(memory word) 时, 它首先检查总线忙否。 如果总线空闲, 该CPU把所需字的地址放到总线上, 发出若干控制信号, 然后等待存储器把所需的字放到总线上。

当某个CPU需要读写存储器时, 如果总线忙, CPU只是等待, 直到总线空闲。 这种设计存在问题。 在只有两三个CPU时, 对总线的争夺还可以管理; 若有32个或64个CPU时, 就不可忍受了。 这种系统完全受到总线带宽的限制, 多数CPU在大部分时间里是空闲的。

这一问题的解决方案是为每个CPU添加一个高速缓存(cache) , 如图8-2b所示。 这个高速缓存可以位于CPU芯片的内部、 CPU附近、 在处理器板上或所有这三种方式的组合。 由于许多读操作可以从本地高速缓存上得到满足, 总线流量就大大减少了, 这样系统就能够支持更多的CPU。 一般而言, 高速缓存不以单个字为基础, 而是以32字节或64字节块为基础。 当引用一个字时, 它所在的整个数据块(叫做一个cache行) 被取到使用它的CPU的高速缓存当中。

三类基于总线的多处理机: a)没有高速缓存; b)有高速缓存; c)有高速缓存与私有存储器
还有另一种可能性就是图8-2c中的设计, 在这种设计中每个CPU不止有一个高速缓存, 还有一个本地的私有存储器, 它通过一条专门的(私有) 总线访问。 为了优化使用这一配置, 编译器应该把所有程序的代码、 字符串、 常量以及其他只读数据、 栈和局部变量放进私有存储器中。 而共享存储器只用于可写的共享变量。 在多数情况下, 这种仔细的放置会极大地减少总线流量, 但是这样做需要编译器的积极配合。

2.使用交叉开关的UMA多处理机
即使有最好的高速缓存, 单个总线的使用还是把UMA多处理机的数量限制在16至32个CPU。 要超过这个数量, 需要不同类型的互连网络。 连接n个CPU到k个存储器的最简单的电路是交叉开关, 参见图8-3。
在这里插入图片描述

3.使用多级交换的UMA多处理机
Omega网络的接线模式常被称作全混洗(perfect shuffle) , 因为每一级信号的混合就像把一副牌分成两半, 然后再把牌一张张混合起来。

4.NUMA多处理机
单总线UMA多处理机通常不超过几十个CPU, 而交叉开关或交换网络多处理机需要许多(昂贵) 的硬件, 所以规模也不是那么大。 要想超过100个CPU还必须做些让步。
通常, 一种让步就是所有的存储器模块都具有相同的访问时间。 这种让步导致了前面所说的NUMA多处理机的出现。

这样设计的结果是多核芯片就相当于小的多处理机。 实际上, 多核芯片时常被称为片级多处理机(Chip-level MultiProcessors, CMP) 。

8.1.2 多处理机操作系统类型

1.每个CPU有自己的操作系统
组织一个多处理机操作系统的可能的最简单的方法是, 静态地把存储器划分成和CPU一样多的各个部分, 为每个CPU提供其私有存储器以及操作系统的各自私有副本。 实际上n个CPU以n个独立计算机的形式运行。
由于这些原因, 上述模型已很少使用, 尽管在早期的多处理机中它一度被采用, 那时的目标是把已有的操作系统尽可能快地移植到新的多处理机上。

2.主从多处理机
图8-8中给出的是第二种模型。 在这种模型中, 操作系统的一个副本及其数据表都在CPU 1上, 而不是在其他所有CPU上。 为了在该CPU 1上进行处理, 所有的系统调用都重定向到CPU 1上。 如果有剩余的CPU时间, 还可以在CPU 1上运行用户进程。 这种模型称为主从模型(master-slave) , 因为CPU 1是主CPU, 而其他的都是从属CPU。
在这里插入图片描述
这个模型的问题是, 如果有很多的CPU, 主CPU会变成一个瓶颈。 毕竟, 它要处理来自所有CPU的系统调用。


3.对称多处理机
我们的第三种模型, 即对称多处理机(Symmetric MultiProcessor, SMP) , 消除了上述的不对称性。 在存储器中有操作系统的一个副本, 但任何CPU都可以运行它。 在有系统调用时, 进行系统调用的CPU陷入内核并处理系统调用。

8.1.3 多处理机同步

任何实用的互斥信号量协议的核心都是一条特殊指令, 该指令允许检测一个存储器字并以一种不可见的操作设置。 我们来看看在图2-22中使用的指令TSL(Test and Set Lock) 是如何实现临界区的。 正如我们先前讨论的, 这条指令做的是, 读出一个存储器字并把它存储在一个寄存器中。 同时, 它对该存储器字写入一个1(或某些非零值) 。 当然, 这需要两个总线周期来完成存储器的读写。 在单处理机中, 只要该指令不被中途中断, TSL指令就始终照常工作。

自旋与切换
自旋直接浪费了CPU周期。 重复地测试锁并不是高效的工作。 不过, 切换也浪费了CPU周期, 因为必须保存当前线程的状态, 必须获得保护就绪链表的锁, 还必须选择一个线程, 必须装入其状态, 并且使其开始运行。

8.1.4 多处理机调度

1.分时
处理独立线程的最简单算法是, 为就绪线程维护一个系统级的数据结构, 它可能只是一个链表, 但更多的情况下可能是对应不同优先级一个链表集合, 如图8-12a所示。

在这里插入图片描述

不过这一方法有两个缺点, 一个是随着CPU数量增加所引起的对调度数据结构的潜在竞争, 二是当线程由于I/O阻塞时所引起上下文切换的开销(overhead)

2.空间共享
最简单的空间共享算法是这样工作的。 假设一组相关的线程是一次性创建的。 在其创建的时刻, 调度程序检查是否有同线程数量一样多的空闲CPU存在。 如果有, 每个线程获得各自专用的CPU(非多道程序处理) 并且都开始运行。 如果没有足够的CPU, 就没有线程开始运行, 直到有足够的CPU时为止。 每个线程保持其CPU直到它终止, 并且该CPU被送回可用CPU池中。 如果一个线程在I/O上阻塞, 它继续保持其CPU,而该CPU就空闲直到该线程被唤醒。 在下一批线程出现时, 应用同样的算法。

3.群调度(Gang Scheduling)
空间共享的一个明显优点是消除了多道程序设计, 从而消除了上下文切换的开销。 但是, 一个同样明显的缺点是当CPU被阻塞或根本无事可做时时间被浪费了, 只有等到其再次就绪。 于是, 人们寻找既可以调度时间又可以调度空间的算法, 特别是对于要创建多个线程而这些线程通常需要彼此通信的线程。

8.2 多计算机
8.2.1 多计算机硬件

1.互连技术
在每个节点上有一块网卡, 带有一根或两根从网卡上接出的电缆(或光纤) 。 这些电缆或者连到其他的节点上, 或者连到交换机上。
在这里插入图片描述

2.网络接口

8.2.2 低层通信软件

在多计算机系统中高性能通信的敌人是对包的过度复制。

8.2.3 用户层通信软件
send(dest,&mptr); 

而接收消息的调用可能是

receive(addr,&mptr); 

2.阻塞调用和非阻塞调用
在这里插入图片描述

8.2.4 远程过程调用
8.2.5 分布式共享存储器
8.2.6 多计算机调度

在一台多处理机中, 所有的进程都在同一个存储器中。 当某个CPU完成其当前任务后, 它选择一个进程并运行。 而在一台多计算机中, 情形就大不相同了。 每个节点有其自己的存储器和进程集合。

8.2.7 负载平衡

8.3 虚拟化

8.3.1 虚拟化的条件

简单地说, 每个有内核态和用户态的处理器都有一组只能在内核态执行的指令集合, 比如I/O指令、 改变MMU状态的指令等。 Popek和Goldberg(1974) 两人在他们的经典虚拟化工作中称这些指令为敏感指令(sensitive instruction) 。 还有一些指令如果在用户态下执行会引起陷入。 Popek和Goldberg称它们是特权指令(privileged instruction) 。 在他们的论文中首次论述指出, 当且仅当敏感指令是特权指令的子集时, 机器才是可虚拟化的。

8.3.2 Ⅰ型管理程序
8.3.3 Ⅱ型管理程序

II型管理程序也能正常工作的原因就已经很清楚了: 所有的敏感指令被仿真这些指令的过程调用所替代。 客户操作系统发射的敏感指令不会被真正的硬件执行。 它们转换成了对管理程序的调用, 而这些调用仿真了那些敏感指令。

8.3.4 准虚拟化
8.3.5 内存的虚拟化
8.3.6 I/0设备的虚拟化

当设备驱动程序试图进行I/O操作时, 它们会读写设备的硬件寄存器。 这些指令是敏感指令, 将会陷入到管理程序, 管理程序根据需要从硬件中读取或向硬件中写入所需的数据。

8.3.7 虚拟工具

使用虚拟机技术, 一个软件开发人员能够仔细地创建一个虚拟机, 装入所需的操作系统、 编译器、 函数库和应用程序代码, 组成一个整体来运行。 这个虚拟机映像可以被放到光盘(CD-ROM) 或网站上以供用户安装或下载。

8.3.8 多核处理机上的虚拟机
8.3.9 授权问题
8.4 分布式系统
8.4.1 网络硬件
8.4.2 网络服务和协议
8.4.3 基于文档的中间件
8.4.4 基于文件系统的中间件
8.4.5 基于对象的中间件
8.4.6 基于协作的中间件
8.4.7 网格
8.5 有关多处理机系统的研究

第9章 安全

9.1 环境安全

安全包含许多方面的内容, 其中比较主要的三个方面是威胁的实质、 入侵者的本性和数据的意外遗失。我们将分别加以研究。

9.1.1 威胁
9.1.2 入侵者
9.1.3 数据意外遗失
9.2 密码学原理

在算法中使用的加密参数叫做密钥(key) 。 如果P代表明文, KE 代表加密密钥, C代表密文, E代表加密算法(即, 函数) , 那么C=E(P,KE )。 这就是加密的定义。 其含义是把明文P和加密密钥KE 作为参数, 通过加密算法E就可以把明文变为密文。

9.2.1 私钥加密技术

许多类似的密钥系统都有这样一个特点, 那就是给定了加密密钥就能够较为容易地找到解密密钥, 反之亦然。 这样的系统采用了私钥加密技术或对称密钥加密技术。

9.2.2 公钥加密技术

由于对信息进行加密和解密的运算量是可控制的, 所以私钥加密体系十分有用。 但是它也有一个缺陷:发送者与接受者必须同时拥有密钥。

公钥加密技术(1976年由Diffie和Hellman提出) 。 这一体系的特点是加密密钥和解密密钥是不同的, 并且当给出了一个筛选过的加密密钥后不可能推出对应的解密密钥。 在这种特性下, 加密密钥可被公开而只有解密密钥处于秘密状态。

当我们使用公钥密码体系时, 每个人都拥有一对密钥(公钥和私钥) 并把其中的公钥公开。 公钥是加密密钥, 私钥是解密密钥。 通常密钥的运算是自动进行的, 有时候用户可以自选密码作为算法的种子。 在发送机密信息时, 用接收方的公钥将明文加密。 由于只有接收方拥有私钥, 所以也只有接收方可以解密信息。

9.2.3 单向函数

在接下来的许多场合里, 我们将看到有些函数f, 其特性是给定f和参数x, 很容易计算出y=f(x)。 但是给定f(x), 要找到相应的x却不可行。 这种函数采用了十分复杂的方法把数字打乱。 具体做法可以首先将y初始化为x。 然后可以有一个循环, 进行多次迭代, 只要在x中有1位就继续迭代, 随着每次迭代, y中的各位的排列以与迭代相关的方式进行, 每次迭代时添加不同的常数, 最终生成了彻底打乱位的数字排列。 这样的函数叫做加密散列函数。

9.2.4 数字签名
9.2.5 可信平台模块

有一种方法在工业上已经被采用, 该方法需要用到一种叫做可信平台模块(Trusted Platform Modules,TPM) 的芯片。 TPM是一种加密处理器(cryptoprocessor) , 使用内部的非易失性存储介质来保存密钥。

该芯片用硬件实现数据的加密/解密操作, 其效果与在内存中对明文块进行加密或对密文块进行解密的效果相同, TPM同时还可以验证数字签名。

9.3 保护机制

9.3.1 保护域

域(domain) 是一对(对象, 权限) 组合。 每一对组合指定一个对象和一些可在其上运行的操作子集。

9.3.2 访问控制列表

第一种方法包括一个关联于每个对象的(有序) 列表里, 列表里包含了所有可访问对象的域以及这些域如何访问这些对象的方法。 这一列表叫做访问控制表(Access Control List, ACL) 。

9.3.3 权能
9.3.4 可信系统
9.3.5 可信计算基

每一个可信系统的核心是最小的可信计算基(Trusted Computing Base, TCB) , 其中包含了实施的所有安全规则所必需的硬件和软件。

9.3.6 安全系统的形式化模型

第10章 实例研究1: Linux

10.2 Linux概述

10.2.1 Linux的设计目标

一直以来, UNIX都被设计成一种能够同时处理多进程和多用户的交互式系统。

10.2.2 到Linux的接口

在这里插入图片描述

10.2.3 shell

尽管Linux系统具有图形用户界面, 然而大多数程序员和高级用户都更愿意使用一个命令行界面, 称作shell。

10.2.4 Linux应用程序
10.2.5 内核结构

在这里插入图片描述
处在最顶层的是到内核的系统调用接口。 所有系统调用都来自这里, 其导致一个陷入, 并将系统从用户态转换到受保护的内核态, 继而将控制权交给上述的内核部件之一。

10.3 Linux中的进程

10.3.1 基本概念

在Linux系统中, 进程通过非常简单的方式创建。 系统调用fork将会创建一个与原始进程完全相同的进程副本。 调用fork函数的进程称为父进程, 新的进程称为子进程。 父进程和子进程都拥有自己的私有内存映像。 如果在调用fork函数之后, 父进程修改了属于它的一些变量, 这些变化对于子进程来说是不可见的, 反之亦然。

但是, 父进程和子进程可以共享已经打开的文件。

事实上, 父、 子进程的内存映像、 变量、 寄存器以及其他所有的东西都是相同的, 这就产生了一个问题: 该如何区别这两个进程?

10.3.2 Linux中进程管理相关的系统调用

在这里插入图片描述

10.3.3 Linux中进程与线程的实现

在这里插入图片描述

Linux中的线程
2000年的时候, Linux系统引入了一个新的、 强大的系统调用clone, 模糊了进程和线程的区别, 甚至使得两个概念的重要性被倒置。

pid=clone(function,stack_ptr,sharing_flags,arg); 

在这里插入图片描述

10.3.4 Linux中的调度

10.4 Linux中的内存管理

10.4.1 基本概念

每个Linux进程都有一个地址空间, 逻辑上有三段组成: 代码、 数据和堆栈段。

10.4.2 Linux中的内存管理系统调用

在这里插入图片描述

10.4.3 Linux中内存管理的实现

1.物理内存管理
在许多系统中由于异构硬件限制, 并不是所有的物理内存都能被相同地对待, 尤其是对于I/O和虚拟内存。 Linux区分三种内存区域(zone) :
1)ZONE_DMA: 可以用来DMA操作的页。
2)ZONE_NORMAL: 正常规则映射的页。
3)ZONE_HIGHMEM: 高内存地址的页, 并不永久性映射。
2.内存分配机制
Linux支持多种内存分配机制。 分配物理内存页框的主要机制是页面分配器, 它使用了著名的伙伴算法。





3.虚拟地址空间表示
虚拟地址空间被分割成同构连续页面对齐的区域。 也就是说, 每个区域由一系列连续的具有相同保护和分页属性的页面组成。

10.4.4 Linux中的分页

Linux分页背后的基本思想是简单的: 为了运行, 一个进程并不需要完全在内存中。 实际上所需要的是用户结构和页表。 如果这些被换进内存, 那么进程被认为是“在内存中”, 可以被调度运行了。 代码、 数据和栈段的页面是动态载入的, 仅仅是在它们被引用的时候。 如果用户结构和页表不在内存中, 直到交换器把它们载入内存进程才能运行。

页面置换算法
页面替换是这样工作的。 Linux试图保留一些空闲页面, 这样可以在需要的时候分配它们。 当然, 这个页面池必须不断地加以补充。 PFRA(页框回收算法) 算法展示了它是如何发生的。

10.5 Linux中的I/O系统

Linux和其他的UNIX系统一样, I/O系统都相当的简单明了。 基本上, 所有的I/O设备都被当作文件来处理, 并且通过与访问所有文件同样的read和write系统调用来访问。

10.5.1 基本概念
10.5.2 网络
10.5.3 Linux的输入/输出系统调用
10.5.4 输入/输出在Linux中的实现

在Linux中I/O是通过一系列的设备驱动来实现的, 每个设备类型对应一个设备驱动。 设备驱动的功能是对系统的其他部分隔离硬件的细节。

10.5.5 Linux中的模块

10.6 Linux文件系统

10.6.1 基本概念
10.6.2 Linux的文件系统调用

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121085.html

(0)
上一篇 2025-10-25 18:15
下一篇 2025-10-25 18:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信