日期:2014-05-16 浏览次数:20673 次
内核处理管理本身的内存外,还必须管理用户空间进程的内存。我们称这个内存为进程地址空间,也就是系统中每个用户空间进程所看到的内存。linux操作系统采用虚拟内存技术,因此,系统中的所有进程之间虚拟方式共享内存。对一个进程而言,它好像都可以访问整个系统的所有物理内存。即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。
每个进程都有一个32位或64位的平坦地址空间,空间的具体大小取决于体系结构。术语“平坦”指的是地址空间范围是一个独立的连续区间(比如,地址从0扩展到4294967295的32位地址空间)。一些操作系统提供了段地址空间,这种地址空间并非是一个独立的线性区域,而是被分段的,但现代采用虚拟内存的操作系统通常都是使用平坦地址空间而不是分段式的内存模式。一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,实际上也彼此互不相干。
进程只能访问有效内存区域内的内存地址。内存区域可以包含各种内存对象:代码段、数据段、bss段、栈、堆。
内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示,定义在文件<linux/shed.h>。在上一篇文章中介绍了这个结构体。
1)分配内存描述符
在进程的进程描述符(task_struct结构体就表示进程描述符)中,mm域存放着该进程使用的内存描述符,所以current->mm便指向当前进程的内存描述符。fork()函数利用copy_mm()函数复制父进程的内存描述符,也就是current->mm域给其子进程,而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep_slab缓存中分配得到的。通常,每个进程都有唯一的mm_struct结构体,既唯一的进程地址空间。
2)撤销内存描述符
当进程退出时,内核会调用定义在kernel/exit.c中的exit_mm()函数,该函数执行一些常规的撤销工作,同时更新一些统计量。其中,该函数会调用mmput()函数减少内存描述符中的mm_users用户计数,如果用户计数为0,调用mmdrop()函数,减少mm_count使用计数。如果使用计数也等于0,说明该内存描述符不再有任何使用者了,那么调用free_mm()宏通过kmem_cache_free()函数将mm_struct结构体归还给mm_cachep_slab缓存中。
3)mm_struct与内核线程
内核线程没有进程地址空间,也没有相关的内存描述符。所以内核线程对应的进程描述符中mm域为空。因为内核线程并不需要访问任何用户空间的内存而且因为内核线程在用户空间中没有任何页,所以实际上它们并不需要有自己的内存描述符和页表。尽管如此,即使访问内核内存,内核线程也还是需要使用一些数据的,比如页表。
当一个进程被调度时,该进程的mm域指向的地址空间被装载到内存,进程描述符中的active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,所以mm域为NULL。当一个内核线程被调度时,内核发现它的mm域为NULL,就会保留前一个进程的地址空间,随后内核更新内核线程对应的进程描述符中的active_mm域,时期指向前一个进程的内存描述符。
内存区域由vm_area_struct结构体描述,定义在文件<linux/mm_types.h>中。内存区域在linux内核中也经常称作虚拟内存区域(virtual memory Areas,VMAs)。
vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性,比如访问权限等,另外,相应的操作也都一致。在上一篇文章中有结构体的定义。
每个内存描述符都对应于进程地址空间中的唯一区间。vm_start域指向区间的首地址,vm_end是内存区间的结束地址,vm_mm域指向和VMA相关的mm_struct结构体,每个VMA对其相关的mm_struct结构体来说都是唯一的,所以即使两个独立的进程将同一个文件映射到各自的地址空间,他们分别都会有一个vm_area_struct结构体来标志自己的内存区域;反过来,如果两个线程共享一个地址空间,那么他们也同时共享其中的所有vm_area_struct结构体。
1)VMA操作
vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。vm_area_struct作为通用对象代表了任何类型的内存区域,而操作表描述针对特定的对象实例的特定方法。