4G虚拟地址空间布局图:
由于每个进程都不能直接访问内核空间,而是通过系统调用间接进入内核,因此,所有的进程都共享内核空间。而每个进程都拥有各自的用户空间,各个进程之间不能相互访问彼此的用户空间。因此,对每一个具体的进程而言,都拥有4GB的虚拟地址空间。一个程序在经过编译、连接之后形成的地址空间是一个虚拟的地址空间,只有当程序运行的时候才会分配具体的物理空间。由此我们可以得知,程序的虚拟地址相对来时候是固定的,而物理地址则随着每一次程序的运行而有所不同。对于内核空间而有,它与物理内存之间存在一个简单的线性关系,即存在3GB的偏移量。在Linux内核中,这个偏移量叫做PAGE_OFFSET。如果内核的某个物理地址为x,那么对应的内核虚拟地址就为x+PAGE_OFFSET。对于用户空间而言,它与物理内存之间的映射远不止这么简单。与内核空间和物理空间的线性映射不同的是,分页机制将虚拟用户空间和物理地址空间分成大小相同的页,然后再通过页表将虚拟页和物理页块映射起来。
内核使用mm_struct来描述一个进程的虚拟地址空间:
struct mm_struct{
struct vm_area_struct* mmap; //指向虚拟区域(VMA)链表
rb_root_t mm_rb; //指向red_black树
struct vm_area_struct* mmap_cache; //指向最近找到的虚拟区间
pgd_t* pgd; //指向进程的页目录
atomic_t mm_users; //用户空间中的有多少用户
atomic_t mm_count; //对“struct mm_struct"有多少引用
int map_count; //虚拟区间的个数
struct list_head mmlist; //所有活动(active)mm的链表
unsigned long start_code,end_code,start_data,end_data;
unsigned long start_brk,brk,start_stack;
unsigned long arg_start,arg_end,env_start,env_end;
};
struct vm_area_struct:用来描述一个虚拟内存区域(VMA)。内核将每个内存区域作为一个单独的内存对象管理,每个内存对象管理,每个内存区域都有一致的属性,比如权限等。所以我们程序的代码段、数据段和bss段在内核里都分别有一个struct vm_area_struct结构体来描述:
struct vm_area_struct{
struct mm_struct* vm_mm; //虚拟内存区域所在的地址空间
unsigned long vm_start; //在虚拟内存中的起始地址
unsigned long vm_end; //在虚拟内存中的结束地址
struct vm_area_struct* vm_next;
pgprot_t vm_page_prot; //对这个虚拟内存区域的存取权限
unsigned long vm_flags; //虚拟内存区域的标志
rb_node_t vm_rb; //对这个虚拟内存区域进行操作的函数
struct vm_operations_struct* vm_ops;
struct file* vm_file;
};