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

  1. 优点是所有内容都在一个地方,很容易找到。缺点是在一个蜂窝中顶层索引的一个坏磁盘块可能会对整个系统造成灾难。

  2. 硬件抽象层(HAL)是简单明了的。如果将鼠标、磁盘和所有其他设备驱动程序都包含在其中,将使其变得笨重,并破坏其作为一个隐藏计算机自身某些基本硬件差异(而不是 I/O 设备差异)的薄层的功能。

  3. 一个家谱数据库可以方便地使用标准的系统时间格式记录祖先的出生和死亡日期。实际上,任何历史数据库都可以使用这种格式。

  4. 如果句柄包含一个序列号,那么当关闭句柄后继续使用时,可以通过比较每个句柄中的序列号与句柄表中的序列号来轻松检测到这一点。在句柄和表项中都必须找到序列号的空间。每次重新使用句柄表项时,序列号就会增加,并且序列号被嵌入到句柄中。如果序列号是 N 位,则要避免检测,一个特定的句柄必须关闭和重新打开 2^N 次。因此,即使对于一个很小的 N ,也可以检测到许多程序中的句柄竞争条件。

  5. (a) 进程管理器使用对象管理器来创建线程。
    (b) 内存管理器使用安全管理器来判断是否可以映射文件。
    (c) 插拔管理器使用配置管理器来注册新设备。

  6. 信号是在某个进程的上下文中由一个新线程处理的,例如,当按下退出键或甚至当线程出现错误时。在线程的上下文中捕获信号并没有真正意义。这实际上是每个进程的活动。

  7. 在服务器上这样做可能更有意义。客户端机器的并发进程较少。只有在有多个进程共享它们时,共享库才有意义。否则,将库进行静态链接并接受重复是更高效的。静态链接的优点是只加载实际需要的过程。使用动态链接库可能导致内存中存在没有被任何人使用的过程。

  8. 这个问题是关于内核线程是否可以使用与其对应的用户线程相同的堆栈的。如果这样做,是有意义的,因为大多数系统调用都以空堆栈开始并在返回用户模式时再次将堆栈置空。这些堆栈分开的原因之一是因为肩负的开销与维护单独的堆栈所需的成本相比。开销是因为内核必须保护即将运行的用户堆栈的部分,并确保它足够大,否则必须让另一个线程请求将其扩大。这可能可以通过让内核总是在用户模式堆栈顶部使用一组固定页面来避免,但这就像有一个单独的堆栈一样。分开的另一个原因是内核喜欢知道数据来自哪里以便决定需要多少验证。如果将缓冲区分配在一个单独的内核堆栈上,缓冲区将具有内核地址,通常不需要进行检查。如果内核堆栈使用用户堆栈的一部分,那么内核将更难判断堆栈缓冲区是来自内核线程还是用户线程。此外,内核通常依赖于内核堆栈不能被页面替换。因此,内核要使用的用户堆栈页必须被锁定在内存中。另一方面,分配一个单独的堆栈并不是非常昂贵-远比解决所有这些问题要便宜得多。使线程昂贵的不是它们使用的堆栈,而是管理它们所需的所有元数据。

  9. TLB 中缓存的页表项的命中率对系统性能有很大影响。查找缺失项需要耗费大量资源。由于 TLB 只有有限数量的项,使用 2-MB 大页面可以映射更多虚拟地址到 TLB 中。大页面会浪费大量内存,因为在文件区域的最后一页会有未使用的空间。因此,它们只在大区域内使用时才有效。但即使如此,它们也会增加内存压力,因为大页面很有可能包含大量当前未访问的数据,如果使用 4-KB 页
    面则会被分页到磁盘上。

  10. 由于对象句柄中只有 32 位用于权限位,所以操作数有限制为 32。

  11. 不可能实现,因为信号量和互斥量是执行对象,而临界区不是。它们主要在用户空间中进行管理(但在需要阻塞时会有一个后备信号量)。对象管理器并不知道它们,而且它们也没有句柄,正如文本中所述。由于 WaitForMultipleObjects 是一个系统调用,系统无法对一些没有了解的事物进行布尔或操作。调用必须是一个系统调用,因为信号量和互斥量都是内核对象。简而言之,不可能有一个混合使用内核对象和用户对象的系统调用,只能选择其中之一。

  12. 这个变量可能是指向动态分配的数据结构的指针,其初始化和分配结构体的责任由第一个要使用它的线程承担。如果两个线程几乎同时尝试初始化变量,可能会分配两个不同的结构体,其中一个线程可能使用了错误的实例。InitOnceExecuteOnce 使用了额外的变量来记录每个独立初始化的状态,例如:uninitialized, initializing, initialized。实际进行初始化的线程设置额外的变量为 initializing,并在初始化完成后将其设置为 initialized,并调用 WakeByAddressAll。原子地设置和检查变量可以通过使用锁或类似 compare&swap 的硬件指令来执行。如果一个线程发现 foo 被设置为 initialized,则跳过初始化。如果它发现 foo 被设置为 initializing,则调用 WaitOnAddress 等待初始化完成。

  13. (a) 最后一个线程退出。
    (b) 一个线程执行 ExitProcess。
    (c) 拥有该进程句柄的另一个进程终止它。
    (现代应用程序) 操作系统决定终止它以便在交换文件中回收空间,或者因为应用程序正在被服务。

  14. 操作系统主要在内存不足或重新启动时终止现代应用程序。如果应用程序在前一种情况下尝试运行,可能没有足够的资源来成功保存其状态。而在后一种情况下,它们可能会无限期地延迟 Windows 的关闭,就像在桌面版 Windows 中经常发生的情况。尽管用户经常在应用程序之间切换,但这些切换的频率是以秒或分钟为单位的人类尺度。对于编写良好的应用程序来说,保存状态所需的几毫秒时间影响很小。此外,以这种方式编写应用程序会在应用程序崩溃时创建更好的用户体验,因为上次保存的状态可作为检查点使用。

  15. 由于 ID 立即被重用,一个程序如果通过 ID 识别出要操作的进程,可能会发现该进程已经终止,并且 ID 在它找到 ID 和使用 ID 之间已经被重用。UNIX 在所有其他(32,000)ID 被使用之前不会重用进程 ID。因此,虽然理论上可能发生同样的问题,但可能性非常低。Windows 通过按照 FIFO 顺序保持空闲列表来避免这个问题,因此 ID 通常长时间不被重用。另一种解决方案可能是添加序列号,就像建议使用普通句柄来解决类似问题的对象句柄一样。一般情况下,应用程序不应仅通过 ID 来标识特定进程,还应通过创建时间戳进行标识。要对进程执行操作,可以使用该 ID 获取一个句柄。一旦获取了句柄,可以验证创建时间戳。

  16. 最多几微秒。它立即抢占当前线程。只是要运行调度器代码进行线程切换需要多长时间的问题。

  17. 将优先级降低到基本优先级以下可以用作对使用过多 CPU 时间或其他资源的惩罚。

  18. AutoBoost 需要在仅当一个优先级较高的线程 B 正在等待被线程 A 持有的资源时,才提高线程 A 的优先级。为此,AutoBoost 需要跟踪系统中的所有资源以及每个资源所持有的线程。当 B 阻塞时,AutoBoost 会找到 A 并临时将其优先级提高到与 B 相同的优先级。

  19. 对于内核模式线程,它们的堆栈和大多数需要访问的数据结构在更改地址空间之后仍然可访问,因为它们在所有进程之间共享。如果用户模式线程要切换地址空间,它们将丢失对它们的堆栈和其他数据的访问。为用户模式找到一个解决方案,例如在跨进程调用期间更改堆栈,将允许线程创建进程作为隔离边界,并将 CPU 在它们之间传递,就像 CPU 在用户模式和内核模式之间传递一样。可以在未涉及调度器暂停调用方并唤醒服务器进程中(反之亦然)进行跨进程过程调用。

  20. 一种方法是提高重要进程的优先级。第二种方法是为重要进程提供更长的时间片。

  21. 尽管工作集没有被修剪,但进程仍然修改由文件支持的页面。这些文件将被定期推送到磁盘上。此外,应用程序会显式刷新页面到磁盘,以保存结果并保持文件的一致性。文件系统和注册表始终处于活动状态,并且它们试图确保如果系统崩溃,卷和注册表也不会丢失数据。

  22. 对于旋转盘,影响性能最重要的变量是执行 I/O 所需的寻道次数。通常不是 I/O 次数影响性能,而是当需要访问磁盘上的许多不同块时。减少工作集会导致 Windows 一次仅处理进程的少量内存页面。因为 Windows 在页面写出时分配页面文件中的空间,而不是在内存中分配页面,因此页面的写入通常可以写入磁盘的相同区域,从而减少写出页面所需的寻道次数。现代应用程序的前台进程在用户切换或关闭屏幕后很快停止运行。这意味着进程所需的工作集突然变为零。但这并不意味着进程不需要这些页面。只是在用户切换回应用程序之前不需要它们。到那时,很可能会再次需要大部分这些页面。这与通常的情况不同,通常情况下是因为应用程序在执行其他操作而进行页面修剪。在现代应用程序的情况下,是用户(因此是系统)在执行其他操作。换页不仅允许将特定应用程序的页面一起高效地移动到磁盘上,更重要的是它可以高效地将应用程序将要使用的所有页面一次性带回内存,而不使用需求分页。需求分页只会在需要时带入页面。最初,应用程序会运行一小段时间,产生页面错误,再运行一小段时间,再次产生页面错误,直到工作集被重新建立。每次单独的页面错误可能会导致一次寻道,如果任何中间的 I/O 活动使磁盘从交换区域移开。

  23. 在 x86 上,自映射是通过在页目录中获取两个条目,并且而不是让它们指向一个页表页,而是让它们指向包含它们的页目录来实现的。这些条目在进程的页目录中进行设置时首次初始化。由于始终使用相同的 PDE 条目,自映射的虚拟地址在每个进程中都是相同的。

  24. 要映射 4 GB 的地址空间需要 1M 个 PTE。如果每个 PTE 只有 4 个字节,则整个页表只有 4 MB。

  25. 是的。VAD 是内存管理器用来跟踪哪些地址正在使用和哪些是空闲的方式。为了防止后续的预留或提交尝试成功,预留区域需要一个 VAD。

  26. (1) 是关于何时以及如何修剪工作集的策略决策。(2) 和 (3) 是必要的。(4) 是关于如何积极地将脏页面写入磁盘的策略决策。(5) 和 (6) 是必要的。(7) 实际上不是一个策略问题,也不是必需的;系统永远不需要清零页面,但如果系统空闲,清零页面总是比执行空闲循环要好。

  27. 页面根本不会被移动。只有当页面不在任何工作集中时,才会将其放入列表之一。如果它仍然在一个工作集中,它不会进入任何空闲列表。

  28. 它不能放在已修改列表中,因为其中包含仍然映射中的页面,可能会发生缺页错误。未映射的页面不属于该类别。它当然不能直接进入空闲列表,因为这些页面随时可被放弃。脏页面不能随意放弃。因此,必须先将其写回磁盘,然后才能放入空闲列表。

  29. 通知事件意味着当事件被发出信号时,所有正在等待的线程都将变为可运行状态。这可能导致大量额外的上下文切换,因为线程尝试获取事件只会再次阻塞。在单处理器上,如果锁持有时间远远短于时间片,那么当其他线程获得运行机会时,前一个线程已经释放了锁。但是,对于持有时间较短的情况下,等待锁的线程数量的可能性也较低。在多处理器上,即使持有时间很短,也可能导致不必要的上下文切换,因为解除阻塞的线程可能会立即在不同的处理器上运行,但立即阻塞,因为只有一个线程能够成功获取锁。

  30. 托管第三方 DLL 的程序包括 Web 服务器、浏览器和文档编辑器等。托管的 DLL 可能质量不高,可能会破坏托管程序。通过创建托管处理的副本来运行 DLL,可以使原始进程免受错误的影响,或者(如果使用某种形式的进程沙盒)免受攻击。

  31. 这里有两个记录,字段如下。冒号前的值是头字段: 记录 1 = 0, 8: (3, 50), (1, 22), (3, 24), (2, 53) 记录 2 = 10, 10: (1, 60)

  32. 块 66 与现有运行块是连续的事实并没有帮助,因为块不按照逻辑文件顺序排列。换句话说,将块 66 用作新的块与使用块 90 没有什么区别。MFT 中的条目为:0, 8: (4, 20), (2, 64), (3, 80), (1, 66)

  33. 这只是个巧合。16 个块实际上压缩成了 8 个块。它完全有可能是 9 或 11 块。

  34. 除了用户 SID 外,其他信息都可以删除而不会影响安全策略的强度。

  35. 攻击者通常可以访问一个计算环境,并使用它来试图控制不同的受保护的计算环境。有很多例子:使用一个系统连接到没有足够防火墙的另一个系统上的服务;在浏览器中运行 Javascript 并控制浏览器;以普通用户身份运行并控制内核。
    从一个漏洞开始,例如某种方式允许攻击者修改内存中的任意位置,他们会如何利用它?他们如何找到要修改的内存位置?如果堆栈或堆中的虚函数表在每台机器上都位于相同的地址上,他们只需修改他们在那里找到的返回地址和函数指针,最终使程序跳转到他们想要的位置。他们希望程序去哪里?如果没有禁止执行保护,他们可以直接跳转到伪装成输入数据的指令中。如果没有禁止执行保护,他们必须选择已经可执行的指令并跳转到它们。如果他们能找到一系列有用的指令序列,通常以返回(即 gadgets)结束,他们可以在使用这种技术时执行相当有趣的程序。
    相关的一点是,在以上描述的两个点上,攻击者需要对他们攻击的地址空间中的数据和代码位置有一个良好的了解。地址空间布局随机化(ASLR)对地址空间进行了混淆,因此攻击者不太可能只是凭借猜测的可能性来进行攻击成功。
    但是,信息泄露可能意味着他们不必这样做。信息泄露是指系统不适当地提供可以被用于成功猜测特定系统或进程地址空间中堆栈或 DLL 等重要数据的信息的漏洞。
    信息泄露的信息源可以包括远程服务 API、托管在其他程序中的语言实现以及系统调用,包括设备驱动程序中 I/O 控制的实现。

  36. 不可以。当一个 DLL 加载时,它可以在进程内运行代码。这段代码可以访问进程的所有内存,并使用进程默认令牌中的所有句柄和凭据。模仿只是允许进程使用模仿的客户端的凭据获取新的句柄。允许任何不受信任的代码在进程内执行意味着该进程现在或将来可以被滥用其可访问的任何凭据。下次您使用任何配置为从不受信任站点下载代码的浏览器时,请考虑这一点。

  37. 调度程序总是会尝试将线程放置在其理想的处理器上,如果无法实现,则将其放置在同一节点上的处理器上。因此,即使线程当前正在不同节点的处理器上运行,在页错误结束时它可能会被调度回到首选节点。如果该节点上的所有处理器继续忙碌,它可能会在不同的节点上运行,并且会为刚刚出错的页面访问速度慢而支付代价。但是,它更有可能在早晚之间回到其首选节点,并从这个页面放置中受益。

  38. 文件系统上的元数据操作,如切换恢复文件,可能会产生不适当的时机,导致崩溃时没有恢复文件。有机会刷新内部数据缓冲区允许应用程序创建一致的状态,这样恢复时更容易。允许应用程序完成复杂操作将简化恢复时所需的逻辑量。
    通常,允许应用程序为卷的快照做准备允许它减少恢复后必须处理的临时状态的数量。崩溃恢复通常很难测试,因为很难评估所有可能的竞争条件的组合。从理论上讲,可以设计软件来处理各种恢复和故障场景,但这非常困难。一个面临这些挑战的软件的示例是 NTFS 文件系统本身。已经投入了大量的设计、分析和测试工作,以确保 NTFS 能够在崩溃后恢复而不需要运行 chkdsk(Windows 等价于 UNIX 的 fsck)。对于非常庞大的企业文件系统,进行崩溃后的 chkdsk 恢复可能需要几个小时或几天。

  39. 内存映射文件中的最后一页必须包含最后有效数据后的空数据。否则,如果磁盘的 I/O 驱动程序只覆盖了页面的一部分,并从磁盘中获取了数据,页面的其他部分中的先前数据将会被暴露。对于文件中的早期页面,这不是问题,因为系统会在向用户模式代码提供访问权限之前从文件中读取完整页面。当分配堆栈页面时,由于堆栈增长,这些页面必须包含全部零值,而不是由前一个用户留下的随机数据。

  40. 当单个服务器被打补丁时,系统管理员可以自由决定在方便的时间安排更新。如果需要重新启动根操作系统以安装更新,那么虚拟机系统不需要重新启动,但必须将其使用的所有内存保存到磁盘,并在根操作系统重新启动前进行恢复。这涉及到非常大量的 I/O,并且可能需要几分钟,尽管根操作系统的重启只需要几秒钟。在这几分钟内,虚拟机操作系统将无法使用。一种解决方案是通过在根操作系统重新启动时不修改或清除虚拟机系统使用的内存来避免进行 I/O 操作。根操作系统只需将内存保留在 RAM 中,并仅将重新启动后找到虚拟机内存所需的信息写入磁盘。

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