预计阅读本页时间:-
13.3.6 静态与动态结构
操作系统设计人员经常被迫在静态与动态数据结构之间进行选择。静态结构总是简单易懂,更加容易编程并且用起来更快;动态结构则更加灵活。一个显而易见的例子是进程表。早期的系统只是分配一个固定的数组,存放每个进程结构。如果进程表由256项组成,那么在任意时刻只能存在256个进程。试图创建第257个进程将会失败,因为缺乏表空间。类似的考虑对于打开的文件表(每个用户的和系统范围的)以及许多其他内核表格也是有效的。
一个替代的策略是将进程表建立为一个小型表的链表,最初只有一个表。如果该表被填满,可以从全局存储池中分配另一个表并且将其链接到前一个表。这样,在全部内核内存被耗尽之前,进程表不可能被填满。
另一方面,搜索表格的代码会变得更加复杂。例如,在图13-5中给出了搜索一个静态进程表以查找给定PID,pid的代码。该代码简单有效。对于小型表的链表,做同样的搜索则需要更多的工作。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

当存在大量的内存或者当表的利用可以猜测得相当准确时,静态表是最佳的。例如,在一个单用户系统中,用户不太可能立刻启动64个以上的进程,并且如果试图启动第65个进程失败了,也并不是一个彻底的灾难。
还有另一种选择是使用一个固定大小的表,但是如果该表填满了,就分配一个新的固定大小的表,比方说大小是原来的两倍。然后将当前的表项复制到新表中并且把旧表返回空闲存储池。这样,表总是连续的而不是链接的。此处的缺点是需要某些存储管理,并且现在表的地址是变量而不是常量。
对于内核栈也存在类似的问题。当一个线程切换到内核模式,或者当一个内核模式线程运行时,它在内核空间中需要一个栈。对于用户线程,栈可以初始化成从虚拟地址空间的顶部向下生长,所以大小不需要预先设定。对于内核线程,大小必须预先设定,因为栈占据了某些内核虚拟地址空间并且可能存在许多栈。问题是:每个栈应该得到多少空间?此处的权衡与进程表是类似的。
另一个静态-动态权衡是进程调度。在某些系统中,特别是在实时系统中,调度可以预先静态地完成。例如,航空公司在班机启航前几周就知道它的飞机什么时候要出发。类似地,多媒体系统预先知道何时调度音频、视频和其他进程。对于通用的应用,这些考虑是不成立的,并且调度必须是动态的。
还有一个静态-动态问题是内核结构。如果内核作为单一的二进制程序建立并且装载到内存中运行,情况是比较简单的。然而,这一设计的结果是添加一个新的I/O设备就需要将内核与新的设备驱动程序重新链接。UNIX的早期版本就是以这种方式工作的,在小型计算机环境中它相当令人满意,那时添加新的I/O设备是十分罕见的事情。如今,大多数操作系统允许将代码动态地添加到内核之中,随之而来的则是所有额外的复杂性。