《现代操作系统》第十章习题答案

  1. 由于汇编语言是特定于每台机器的,将 UNIX 移植到一台新机器需要将整个代码重写为新机器的汇编语言。然而,一旦 UNIX 使用 C 语言编写完成,只需要重新编写操作系统的一小部分(例如用于 I/O 设备的设备驱动程序)。

  2. 系统调用接口与操作系统内核紧密耦合。标准化系统调用接口会对操作系统内核的设计施加严格限制(减少灵活性)。这也会使得 UNIX 的可移植性降低。

  3. 这允许 Linux 使用 gcc 编译器的特殊功能(如语言扩展),这些功能包括提供快捷方式和简化,以及为优化提供编译器提示。主要的缺点是,还有其他流行的、功能丰富的 C 编译器,如 LLVM,无法用于编译 Linux。如果在将来的某个时候,LLVM 或其他编译器在所有方面都变得比 gcc 更好,那么 Linux 将无法使用它。这可能成为一个严重的问题。

  4. 列出的文件有:bonefish, quacker, seahorse, 和 weasel

  5. 它打印文件 xyz 中包含字符串 "nd" 的行数。

  6. 管道的步骤如下:
    head –8 z | tail –1
    第一部分选择出文件 z 的前八行,并将它们传递给 tailtail 只会将最后一行显示在屏幕上。

  7. 它们是分开的,因此可以在不影响标准错误的情况下重定向标准输出。在管道中,标准输出可能会传递给另一个进程,但标准错误仍然会写入终端。

  8. 每个程序在自己的进程中运行,因此会启动六个新进程。

  9. 是的。子进程的内存是父进程的精确副本,包括栈。因此,如果环境变量存在于父进程的栈中,它们也会存在于子进程的栈中。

  10. 由于文本段是共享的,只需复制 36 KB。机器每微秒可以复制 80 字节,所以 36 KB 需要 0.46 毫秒。再加上 1 毫秒用于进入和退出内核,整个过程大约需要 1.46 毫秒。

  11. Linux 依赖写时复制。它给子进程提供了指向父进程地址空间的指针,但将父进程的页面标记为只读。当子进程试图写入父进程的地址空间时,会发生一个故障,并创建并分配父进程页面的副本到子进程的地址空间中。

  12. 负值允许该进程优先于所有普通进程。用户无法信任这种权力。只有超级用户才能使用它,并且只在关键情况下使用。

  13. 文本中表示 nice 值的范围为 -20 到 +19,因此默认的静态优先级必须为 120,而事实上也是如此。通过选择一个正的 nice 值并友好地请求将进程置于较低的优先级下。

  14. 是的。它不能再运行,所以它的内存越早回到空闲列表中,效果越好。

  15. 信号类似于硬件中断。一个例子是闹钟信号,它在未来的指定秒数的时间内向进程发送信号。另一个是浮点数异常信号,它表示除零或其他错误。还有许多其他信号存在。

  16. 恶意用户如果能够向任意的不相关进程发送信号,就会对系统造成严重破坏。没有任何东西能够阻止用户编写一个由循环组成的程序,该程序向所有 PID 从 1 到最大 PID 的进程发送信号。这些进程中的许多进程对于这个信号是毫无准备的,而且会被该信号杀死。如果你想杀死自己的进程,那是可以的,但是杀死邻居的进程是不可接受的。

  17. 在 Linux 或 Windows 中是不可能的,但 Pentium 硬件确实可以实现这一点。所需要的是利用硬件的分段功能,而这种功能在 Linux 或 Windows 中不被支持。操作系统可以放在一个或多个全局段中,通过受保护的过程调用来进行系统调用,而不是使用陷阱。OS/2 就是这样工作的。

  18. 通常,守护进程在后台运行,负责打印和发送电子邮件等任务。由于人们通常不会坐在椅子边等待它们完成,所以它们被赋予低优先级,利用交互式进程不需要的多余 CPU 时间。

  19. PID 必须是唯一的。计数器迟早会循环回到 0,然后向上增长,例如增长到 15。如果恰好进程 15 是几个月前启动的,但仍在运行,那么新进程将无法被分配到 PID 15。因此,在使用计数器选择了一个提议的 PID 后,必须搜索进程表,以查看该 PID 是否已被使用。

  20. 当进程退出时,父进程将获得其子进程的退出状态。需要 PID 来标识父进程,以便将退出状态传递到正确的进程。

  21. 在这种情况下,一个页面现在可以被这三个进程共享。一般来说,写时复制机制可能导致多个进程共享一个页面。为了处理这种情况,操作系统必须为每个共享页面保持一个引用计数。如果 p1 在一个被三个进程共享的页面上进行写操作,它会得到一个私有副本,而其他两个进程继续共享它们的副本。

  22. 如果所有的 sharing_flags 位都被设置,clone 调用会启动一个传统的线程。如果所有的位都被清除,该调用本质上是一个 fork。

  23. 每个调度决策都需要查找活动数组的位图,并搜索数组中第一个设置的位,这可以在常数时间内完成;从选定的队列中取出一个任务,同样是常数时间的操作;或者如果位图值为零,则交换活动和过期列表的值,同样也是常数时间的操作。

  24. 时钟中断,尤其是在高频率下,会占用相当多的 CPU 时间。它们的主要功能(除了用来跟踪时间,还可以用其他方式完成)是确定何时抢占长时间运行进程。然而,在正常情况下,一个进程每秒会进行数百次系统调用,因此内核可以在每次调用时检查正在运行的进程是否运行时间过长。为了处理完全 CPU 绑定的进程运行时间过长的情况,可以设置一个计时器,例如在 1 秒钟后触发,以防它没有进行任何系统调用。如果只有一个进程,不需要抢占,因此无需进行打断。

  25. 从块 0 加载的程序最多只能有 512 个字节长,因此它不能太复杂。加载操作系统需要理解文件系统布局,以便找到并加载操作系统。不同的操作系统具有非常不同的文件系统;只期望一个 512 字节的程序来解决所有这些问题是不够的。相反,块 0 的加载程序只需从磁盘分区上的固定位置获取另一个加载程序。这个程序可以更长且与系统相关,以便它能找到并加载操作系统。

  26. 如果使用了共享的文本,那么需要 100KB 的空间。每个进程需要 80KB 用于数据段和 10KB 用于栈,因此总共需要 370KB 的内存。如果没有使用共享文本,每个程序都需要 190KB,所以三个程序需要总共 570KB。

  27. 共享文件的进程,包括当前文件指针位置,可以共享一个打开的文件描述符,而不需要更新彼此的私有文件描述符表中的任何内容。与此同时,另一个进程可以通过一个单独的打开文件描述符访问同样的文件,获取一个不同的文件指针,并且按照自己的意愿在文件中移动。

  28. 文本段不会改变,因此它永远不需要分页出去。如果需要它的帧,则可以直接放弃它。页面总是可以从文件系统中检索到。数据段不能回到可执行文件,因为它很可能在带入之后发生了改变。将其分页回去会破坏可执行文件。栈段甚至在可执行文件中都不存在。

  29. 两个进程可以同时将同一个文件映射到它们的地址空间中。这为它们提供了共享物理内存的一种方式。共享内存的一半可以用作从 A 到 B 的缓冲区,另一半可以用作从 B 到 A 的缓冲区。为了通信,一个进程将一个消息写入到其共享内存的部分,然后向另一个进程发出一个信号,指示有一个等待它的消息。回复可以使用另一个缓冲区。

  30. 内存地址 65,536 是文件字节 0,所以内存地址 72,000 是字节 6464。

  31. 最初,文件的四个页面被映射:0,1,2 和 3。调用成功后,只剩下页面 2 和 3 仍然被映射,即字节 16,384 至 32,767。

  32. 这是可能的。例如,当栈增长超过底部页面时,会触发页错误,操作系统通常会将下一个最低的页面分配给它。然而,如果栈已经撞到了数据段,那么下一个页面无法分配给栈,因此进程必须被终止,因为它已经用完了虚拟地址空间。此外,即使虚拟内存中还有另一个可用的页面,磁盘的分页区域可能已满,无法为新页面分配支持存储器,这也将终止进程。

  33. 如果两个块不是兄弟块,则可以出现这种情况。考虑图 10-17(e)中的情况。有两个新的请求,每个请求 8 页。在这一点上,内存的底部 32 页由四个不同的用户拥有,每个用户有 8 页。现在用户 1 和 2 释放他们的页面,但是用户 0 和 3 保留他们的页面。这会产生一个使用了 8 页,8 页空闲,8 页空闲和 8 页使用的情况。我们有两个大小相等的相邻块,但不能合并,因为它们不是兄弟块。

  34. 对一个分区进行分页使得可以使用原始设备,而无需使用文件系统数据结构的开销。为了访问块 n,操作系统只需将其加到分区的起始块上,即可计算出其在磁盘上的位置。不需要经过所有本来需要的间接块。

  35. 通过工作目录的相对路径打开文件通常对编程人员或用户更方便,因为只需要更短的路径名。而且通常更简单,需要更少的磁盘访问。

  36. 结果如下:(a) 锁已被授予。(b) 锁已被授予。(c) C 被阻塞,因为字节 20 到 30 不可用。(d) A 被阻塞,因为字节 20 到 25 不可用。(e) B 被阻塞,因为字节 8 不可用于独占锁定。在这一点上,我们现在有一个死锁。这些进程中的任何一个都无法再次运行。

  37. 现在问题是,当锁变得可用时,哪个进程获得锁。最简单的解决方案是将其定义为未确定的。这是 POSIX 所做的,因为它是最容易实现的。另一个解决方案是要求按请求顺序授予锁。这种方法对实现来说更复杂,但可以防止饥饿。还有另一种可能性是让进程在请求锁时提供优先级,并使用这些优先级进行选择。

  38. 如果一个进程想要读取一些字节,它将请求共享锁;如果它想要更新一些字节,它将请求独占锁。如果有一系列请求共享锁的进程,请求独占锁的进程可能会被无限期地阻塞。换句话说,如果读者总是在写者之前,写者可能会遭受饥饿。

  39. 所有者可以读取、写入和执行它,其他人(包括所有者的组)只能读取和执行它,但不能写入它。

  40. 是的。任何能够读取和写入任意块的块设备都可以用于保存文件系统。即使无法定位到特定块,始终可以倒带磁带,然后向前计数到所请求的块。这样的文件系统不会是高性能的文件系统,但可以正常工作。作者实际上在使用 DECtapes 的 PDP-11 上做到了这一点,而且它可行。

  41. 不。文件仍然只有一个所有者。例如,如果只有所有者能够对文件进行写操作,那么其他方就无法进行写操作。将文件链接到你的目录中并不会突然赋予你任何在此之前没有的权限。它只是为访问文件创建了一个新的路径。

  42. 当使用 chdir 更改工作目录时,会获取新工作目录的 i 节点,并将其保留在内存中的 i 节点表中。根目录的 i 节点也在那里。在用户结构中,维护对这两个的指针。当需要解析路径名时,会检查第一个字符。如果是“/”,则使用指向根 i 节点的指针作为起始位置,否则使用指向工作目录的 i 节点的指针。

  43. 访问根目录的 i-node 不需要磁盘访问,因此我们有以下步骤:
    1. 读取 / 目录以查找 "usr"。
    2. 读取 i-node 以获取 usr 的 i-node。
    3. 读取 usr 目录以查找 "ast"。
    4. 读取 i-node 以获取 /usr/ast 的 i-node。
    5. 读取 usr/ast 目录以查找 "work"。
    6. 读取 i-node 以获取 usr/ast/work 的 i-node。
    7. 读取 usr/ast/work 目录以查找 "f"。
    8. 读取 i-node 以获取 usr/ast/work/f 的 i-node。
    9. 因此,总共需要八次磁盘访问来获取 i-node。

  44. i-node 包含 12 个地址。单间接块包含 256 个地址。双间接块引导到 65,536 个地址,三重间接块引导到 16,777,216 个地址,总共 16,843,018 个块。这限制了最大文件大小为 12 + 256 + 65,536 + 16,777,218 个块,约为 16GB。

  45. 当文件关闭时,内存中其 i-node 的计数器会递减。如果计数器大于零,则不能从表中删除 i-node,因为文件仍在某些进程中打开。只有当计数器归零时,才能删除 i-node。没有引用计数,系统将无法知道何时从表中删除 i-node。每次打开文件时单独复制 i-node 的做法行不通,因为在一个副本中所做的更改在其他副本中不可见。

  46. 通过维护每个 CPU 的运行队列,可以在本地进行调度决策,无需执行昂贵的同步机制来始终访问和更新共享运行队列。此外,如果在已经执行的 CPU 上调度线程,则有可能所有相关的内存页仍在缓存中。

  47. 一个缺点是安全性问题,因为可加载模块可能包含漏洞和攻击。另一个缺点是随着加载越来越多的模块,内核虚拟地址空间可能变得碎片化。

  48. 每 30 秒将修改后的文件内容强制写入磁盘,意味着崩溃造成的损失仅限于 30 秒的数据。如果没有运行 pdflush,一个进程可能会写入一个文件,然后退出,而文件的完整内容仍然在缓存中。实际上,用户可能会注销并带着仍在缓存中的文件回家。 一个小时后,系统可能会崩溃,丢失仍然只存在于缓存而不在磁盘上的文件。 第二天我们便会没有一个满意的用户。

  49. 它只需要将链接计数设置为 1,因为只有一个目录项引用该 i-node。

  50. 通常是 getpid,getuid,getgid,或类似的函数。它们只是从已知位置获取一个整数并返回。其他每个调用都会做更多的操作。

  51. 文件被简单地删除。这是删除文件的正常方式(实际上是唯一的方式)。

  52. 1.44MB 的软盘可以容纳 1440 个原始数据块。 ext2 文件系统的引导块、超级块、组描述符块、块位图和 i-node 位图每个都使用一个块的空间。如果创建 8192 个 128 字节的 i-node,这些 i-node 将占用另外 1024 个块,只剩下 411 个块未使用。至少需要一个块来存储根目录,留下 410 个块的文件数据空间。实际上,Linux 的 mkfs 程序足够智能,不会创建比可能会被使用的 i-node 更多的 i-node,因此效率并不会这么差。默认情况下,将创建占用 23 个块的 184 个 i-node。然而,由于 ext2 文件系统的开销,Linux 通常在软盘和其他小设备上使用 MINIX 1 文件系统。

  53. 通常情况下,有时候需要一个人能够执行通常禁止的操作。例如,一个用户启动了一个生成无限输出的作业。然后用户注销并去伦敦度假三周。迟早磁盘会填满,超级用户将不得不手动终止该进程并删除输出文件。还有其他类似的例子存在。

  54. 可能有人在教授更改权限时仍然打开了文件。教授应该删除该文件,然后将另一个副本放入公共目录中。此外,他应该使用更好的文件分发方法,比如网页,但这超出了本练习的范围。

  55. 例如,如果使用 fsuid 系统调用将超级用户权限授予另一个用户,该用户可以访问超级用户文件,但无法发送信号、终止进程或执行其他需要超级用户特权的操作。

  56. (a) 大多数文件在大小上被列为零字节,并包含大量信息。这是因为这些文件在磁盘上不存在。系统会根据需要从实际进程中检索信息。(b) 大多数时间和日期设置反映了当前的时间和日期。这是因为信息只是被检索,并且在许多情况下,它是不断更新的。(c) 大多数文件是只读的。这是因为它们提供的信息与进程相关,无法被用户更改。

  57. 这个活动需要保存用户当前的滚动位置和缩放级别,以便能够在恢复时显示相同部分的网页。它还需要保存用户在表单字段中输入的任何数据,以免丢失。它不需要保存它下载的用来显示网页的任何数据(HTML、图像、JavaScript 等);当活动被恢复时,这些数据可以重新获取和解析,并且通常这些数据仍然在浏览器的本地磁盘缓存中。

  58. 当您正在阅读和存储下载的数据时,您需要让设备保持运行状态,因此网络代码应在此期间持有一个唤醒锁(wake lock)。(额外积分:该应用程序还需要请求 "INTERNET" 权限,如图 10-63 所示。)

  59. 所有线程都需要在 fork 之后启动。新进程中只会有进行 fork 的线程在运行;如果您已经启动了其他线程,它们将不会在新进程中,并且会使进程处于一个不完全定义的状态。

  60. 您知道调用者以某种方式显式地获得了对您原始对象的引用。但是您无法确定调用者是否是您最初发送对象的同一个进程,该进程可能已经将对象传递给另一个进程。

  61. 随着需要更多的 RAM,内存耗尽杀手将按照从不必要到最必要的顺序开始终止进程。根据我们已经给出的操作顺序,我们可以确定正在运行具有以下内存耗尽级别的进程:
    1. 浏览器:FOREGROUND
    2. 邮件:SERVICE
    3. 启动器:HOME
    4. 相机:CACHED
    5. 因此,内存耗尽杀手将从底部开始工作,首先终止相机进程,然后是启动器,最后是邮件。

你可能感兴趣的:(《现代操作系统》习题答案,开发语言,学习,笔记,unix,linux,c语言)