进程地址空间包含了某个进程可寻址的虚拟内存以及在此虚拟内存中进程可使用的地址。每个进程被分配了一个平坦的32或64位地址空间。不同的进程在各自的某个相同的内存地址处可以存储不同的数据。另外,进程之间也可以共享地址空间,这样的进程被称为线程。
虽然一个进程可以寻址多达4G的内存,但它并没有权利访问所有的地址。地址空间中有趣的部分是内存地址区间,如08048000-0804c000,进程对处于这个区间中的地址有访问权限。这些合法的区间称为内存区。进程可以通过内核动态地向它的进程空间增加或删除内存区。
进程只能访问处于合法内存区的地址。这些内存区域有相关的权限,如可读,可写和可执行。任何非法地址或非法访问都将导致“Segmmentation Fault”错误。内存区可包含如下一些信息:
l 可执行文件代码的内存映射,称为代码段。
l 可执行文件的初始化了的全局变量,称为数据段。
l 零页的内存映射,包含未初始化的全局变量,称为bss段。
l 用于进程用户空间栈的零页内存映射。
l 每个共享库附加的代码、数据以及bss段,如C库和动态链接器,被装载到进程的地址空间。
l 任何内存映射文件。
l 任何共享内存段。
l 任何匿名的内存映射,如与malloc()相关的内存映射。
这些内存区域并不重叠。
内存描述符
内核用被称为内存描述符的数据结构来表示一个进程的地址空间。该结构包含了所有与进程地址空间相关的信息。内存描述符用struct mm_struct来表示。数据结构如下所示:
struct mm_struct {
struct vm_area_struct *mmap; /* list of memory areas */
struct rb_root mm_rb; /* red-black tree of VMAs */
struct vm_area_struct *mmap_cache; /* last used memory area */
unsigned long free_area_cache; /* 1st address space hole */
pgd_t *pgd; /* page global directory */
atomic_tmm_users;/*addressspaceusers*/
atomic_tmm_count;/*primaryusagecounter*/
int map_count; /* number of memory areas */
struct rw_semaphore mmap_sem; /* memory area semaphore */
spinlock_t page_table_lock; /* page table lock */
struct list_head mmlist; /* list of all mm_structs */
unsigned long start_code; /* start address of code */
unsigned long end_code; /* final address of code */
unsigned long start_data; /* start address of data */
unsigned long end_data; /* final address of data */
unsigned long start_brk; /* start address of heap */
unsigned long brk; /* final address of heap */
unsigned long start_stack; /* start address of stack */
unsigned long arg_start; /* start of arguments */
unsigned long arg_end; /* end of arguments */
unsigned long env_start; /* start of environment */
unsigned long env_end; /* end of environment */
unsigned long rss; /* pages allocated */
unsigned long total_vm; /* total number of pages */
unsigned long locked_vm; /* number of locked pages */
unsigned long saved_auxv[AT_VECTOR_SIZE]; /* saved auxv */
cpumask_t cpu_vm_mask; /* lazy TLB switch mask */
mm_context_t context; /* arch-specific data */
unsigned long flags; /* status flags */
int core_waiters; /* thread core dump waiters */
struct core_state *core_state; /* core dump support */
spinlock_t ioctx_lock; /* AIO I/O list lock */
struct hlist_head ioctx_list; /* AIO I/O list */
};
mm_users表示使用该地址空间的进程数。mm_count是 mm_struct的主引用计数。如果有9个线程共享一个地址空间,则mm_users=9,而mm_count=1。当mm_users变为0后,则mm_count变为0.mmap和mm_rb都是用于组织进程空间中的内存区,前者使用的链表结构,后者使用的是红黑树。前者主要用于遍历,后者主要用于查找。所有的mm_struct都是通过mmlist链接到一个双重链表中。
分配一个内存描述符
与某个任务相关联的内存描述符存储在任务进程描述符的mm域。current->mm代表当前进程的内存描述符。copy_mm()函数复制父进程的内存描述符。mm_struct是从mm_cachep slab缓存中通过allocate_mm()分配的。通常,每个进程都有一外唯一的mm_struct,从而拥有唯一的进程地址空间。
销毁一个内存描述符
当与某个特定的地址空间相关联的进程退出后,会调用exit_mm()函数。
虚拟内存区域
数据结构vm_area_struct代表虚拟内存区域。vm_area_struct描述了处于某个地址空间中的一个连接区间中的单个内存区域。内核将每个内存区域视为一个唯一的内存对象。
虚拟内存操作
vm_area_struct中的域vm_ops指向了与给定的内存区域相关联的操作函数表。这个操作函数表由vm_operations_struct表示,定义如下:
struct vm_operations_struct {
void (*open) (struct vm_area_struct *);
void (*close) (struct vm_area_struct *);
int (*fault) (struct vm_area_struct *, struct vm_fault *);
int (*page_mkwrite) (struct vm_area_struct *vma, struct vm_fault *vmf);
int (*access) (struct vm_area_struct *, unsigned long ,
void *, int, int);
};
其中,open在某个内存区域添加到某个地址空间时被调用。close在某个内存区域从某个地址空间中删除进调用。fault在一个物理页不存在时调用。page_mkwrite在将一个只读的页改为可写的时候调用。access在函数get_user_pages()调用失败时被函数access_process_vm()调用。