Chinaunix首页 | 论坛 | 博客
  • 博客访问: 406490
  • 博文数量: 61
  • 博客积分: 1991
  • 博客等级: 上尉
  • 技术积分: 492
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-08 12:28
文章分类

全部博文(61)

文章存档

2011年(5)

2010年(21)

2009年(3)

2008年(4)

2007年(28)

我的朋友

分类: 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系统调用时信息传递到内核的方法。对于这些系统调用,内核从进程的地址空间拷贝数据到内核的一个缓存中。做这种拷贝有几个原因。

  • 一般情况下,用户数据不是按页排列的,不是硬件页长度的倍数。
  • 如果把页从进程空间中取走,就不再能引用这个页。一些程序运行所用到的数据存储在缓存中,即使是这些数据已经被写进硬盘之后。
  • 如果一个进程被允许保留可的拷贝,这个页必须被作为copy_on_write。一个copy_on_write被设置成read_only,防止被写。如果进程尝试修改这个页,内核将得到一个写错误。此时,内核将创建这个页的一个拷贝来让进程修改。但是很不幸,典型的进程会直接试图写新的数据到它的输出缓冲,强制这个数据被拷贝。
  • 当被重映射到新的虚拟内存地址,大部分内存管理硬件会请求硬件地址转换缓存被选择性的清除。缓存清除通常比较慢。实际运行中对于小于4到8byte的数据,重映射比拷贝块要慢得多。

大量数据的读写操作使用拷贝是比较耗时的。另外一种可选方案是重映射进程的内存到内核中。使用内存映射的最大动机是需要访问大文件或者在进程间传递大量数据。mmap()接口为这些任务提供方法而不使用拷贝操作。

系统调用mmap()不支持运行在不同机器上的进程之间映射内存。这些进程通过网络使用socket连接进行通信。因此,通过网络传输文件内容是避免拷贝的另外一种值得采取的通用操作。历史上,传输一个文件的做法是将文件读到进程缓冲区,在把缓冲区写到socket。这个方法需要拷贝两次数据:第一次从内核拷贝到引用程序缓冲,再从应用程序缓冲拷贝到内核来使用socket发送。FreeBSD有一个系统调用sendfile,从一个文件发送数据到socket而不做任何拷贝。

内核中的内存管理

内核常常为一个简单的系统调用短暂的运行来分配内存。在用户进程中,这种短时间使用的内存会在运行时堆栈上分配。因为内核有一个有些的堆栈,所有即使在堆栈上分配一个中等大小的内存块都是不可行的。因此这些内存必须通过更动态的机制来分配。例如,当系统必须传递一个路径名时,它必须分配一个1Kbyte的缓存来保持这个名字。其他内存块很可能比一个简单的系统调用更持久,所以不能被分配到堆栈上,即使堆栈是空的。一个例子就是协议控制块,它要贯穿于整个网络连接的全程。

随着内核中加入更多服务,动态内存分配的需求也随之增长。通用内存分配器减小了在内核中编写代码的复杂度。因此,FreeBSD内核中有一个通用内存分配器,可以用于系统的任何部分。它有一个类似于C库函数malloc()和free()的接口,为应用程序提供内存分配。象C函数一样,这个分配子程序需要获得一个参数,这个参数指明需要的内存大小。请求内存的范围不做硬性规定。然而,被分配的物理内存没有分页。这个free子程序需要获得一个指针,这个指针指向将要被释放的内存位置。但是,不需要指明这块内存的大小。

一些大的持久的内存分配(比如贯穿与进程生存期间的进程跟踪信息的结构)不被通用内存分配器处理。内核为这种类型的内存分配提供一个区域分配器。内核为每个内存类型分配它自己的区域,所有它自己的内存分配都在这里执行。在一个区域分配的内存不能被其他区域使用,也不能被通用内存分配器操作。这个接口的语法与通用内存分配器类似。使用zalloc()子程序在一个区域中分配内存,使用zfree()释放内存。
阅读(1945) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~