分类: BSD
2007-05-14 12:56:56
每一个进程都有自己的地址空间。这个地址空间最初被分为三个逻辑段:文本段、数据段和堆栈段。文本段是只读的,包含程序的机器指令。数据段和堆栈段都是可读写的。数据段部分已初始化和未初始化的数据。堆栈段保存程序运行时的堆栈。随着程序的执行,内核会自动扩展堆栈段。进程可以使用系统调用来扩展和收缩它的数据段。然而,只有当文本段的内容被来自文件系统的数据覆盖或者当发生调试时,才能改变文本段的大小。最开始,子进程的段内容是从父进程复制的。
不必为了京城的运行,而使进程地址空间的全部内容常驻内存。进程执行过程中,如果用到地址空间的某部分,而这部分不再主存中,系统会将这部分信息的页放入内存。当系统资源不足时,系统采取两级方法来保持可用资源。如果有部分适当数量的资源可被利用,系统会让进程让出部分最近未被使用的内存。如果资源将严重不足,系统会将一个进程的全部内容交换到二级缓存。系统采取的请求页和交换技术是有效的对进程透明的。然而,进程可以告诉系统将要执行的操作需要的内存。
BSD内存管理设计
大地址空间的支持、映像文件和共享内存是4.2BSD的需求。一个指定的接口(mmap())运行不相关的进程可以一个共享的文件映像到他们的地址空间。如果多个进程映射同一个文件到他们的地址空间,一个进程对它内存空间中这个文件的改变将会反射到其他进程的地址空间中,也会反射到映像文件中。最终,4.2BSD没有将mmap接口纳入其中,因为会对其他部分(如网络)产生压力。
在4.3BSD的开发期间,mmap()接口有了更深的发展。超过40家公司和研究机构参与其中。这个接口的第一个UNIX实现是被Sun Microsystems完成的。
在4.3BSD中避免了接口执行带来的时间压力。尽管可以纳入4.3BSD虚拟内存系统中,但最终开发者决定不放在4.3中,这个虚拟内存系统实现已经近10年了。此外,原来的虚拟内存系统的设计是基于一个假设——电脑内存是小而昂贵的,而本地磁盘是快速、大容量切便宜的。因此,这个虚拟内存系统的设计思路是以增加而外的磁盘访问来节省内存。另外4.3BSD的实现是依赖VAX硬内存管理,不便移植到其他电脑架构上。最终,这个虚拟内存系统没有设计成与现在日益通用和重要的多处理器紧密相关。
尝试通过修改原有实现来对其进行改善看来注定是失败的。另一方面,全新的设计充分发挥大内存的优势,节省了磁盘传输,并且有潜力运行在多处理器上。因此,在4.4BSD上,虚拟内存系统被完全替换。4.4BSD的虚拟内存系统基于Mach2.0的虚拟内存系统,并从Mach2.5和Mach3.0升级。
FreeBSD的虚拟内存系统是一个对4.4BSD的虚拟内存系统做了大范围调整的版本。它有效的支持共享,一个清除的划分机器相关与机器不相关的特性,也支持多处理器。进程可以在它地址空间的任何部分映射映像文件。它们可以通过共享同一个文件的映像来共享它们地址空间的一部分。一个进程对文件的改变在其他进程的地址空间可见,同时也会写回到原文件。进程也可以请求文件的私有映像,从而避免它们对文件的任何改变会在其他映射这个文件的进程的地址空间可见或者写回到原文件。
另一个和虚拟内存系统相关的问题是当发生read或write系统调用时信息传递到内核的方法。对于这些系统调用,内核从进程的地址空间拷贝数据到内核的一个缓存中。做这种拷贝有几个原因。
大量数据的读写操作使用拷贝是比较耗时的。另外一种可选方案是重映射进程的内存到内核中。使用内存映射的最大动机是需要访问大文件或者在进程间传递大量数据。mmap()接口为这些任务提供方法而不使用拷贝操作。
系统调用mmap()不支持运行在不同机器上的进程之间映射内存。这些进程通过网络使用socket连接进行通信。因此,通过网络传输文件内容是避免拷贝的另外一种值得采取的通用操作。历史上,传输一个文件的做法是将文件读到进程缓冲区,在把缓冲区写到socket。这个方法需要拷贝两次数据:第一次从内核拷贝到引用程序缓冲,再从应用程序缓冲拷贝到内核来使用socket发送。FreeBSD有一个系统调用sendfile,从一个文件发送数据到socket而不做任何拷贝。
内核中的内存管理
内核常常为一个简单的系统调用短暂的运行来分配内存。在用户进程中,这种短时间使用的内存会在运行时堆栈上分配。因为内核有一个有些的堆栈,所有即使在堆栈上分配一个中等大小的内存块都是不可行的。因此这些内存必须通过更动态的机制来分配。例如,当系统必须传递一个路径名时,它必须分配一个1Kbyte的缓存来保持这个名字。其他内存块很可能比一个简单的系统调用更持久,所以不能被分配到堆栈上,即使堆栈是空的。一个例子就是协议控制块,它要贯穿于整个网络连接的全程。
随着内核中加入更多服务,动态内存分配的需求也随之增长。通用内存分配器减小了在内核中编写代码的复杂度。因此,FreeBSD内核中有一个通用内存分配器,可以用于系统的任何部分。它有一个类似于C库函数malloc()和free()的接口,为应用程序提供内存分配。象C函数一样,这个分配子程序需要获得一个参数,这个参数指明需要的内存大小。请求内存的范围不做硬性规定。然而,被分配的物理内存没有分页。这个free子程序需要获得一个指针,这个指针指向将要被释放的内存位置。但是,不需要指明这块内存的大小。