我们都知道每个进程都拥有4G的线性地址空间, 3G的用户空间,1G的内核空间。但是是否存在这个4G的线性空间?如果存在,那么2个进程就拥有了8G的内存空间,与事实的不符的是只有4G的物理内存空间。则这个4G线地址空间并不是真实的存在,是对主存的抽象概念,是一种虚拟的存储器,是我们使用数据结构构造虚拟空间。虚拟存储器为每个进程提供了一致的地址空间,简化了存储器的管理。也为每个进程的线性地址空间提供了保护,不被其他的进程破坏(通过权限和地址映射)。
现代操作系统使用页存储管理机制来使这个虚拟存储器进行工作的,但是如何构造每个进程的虚拟存储器?
在linux中,使用vm_area_struct结构体来构造这个4G的线性地址空间,程序在装载的时候,会尽量把相同权限属性的段分配到同一空间,进程还有多个段信息,每个段信息将由一个vm_area_struct来索引。(机器环境:fedora:3.11.10-100.fc18.x86_64,kernel:linux-3.16.3)
4G线性地址空间
struct vm_area_struct {
unsigned long vm_start;
unsigned long vm_end;
/*vm_start段的首地址,vm_end 段的尾地址
*/
struct vm_area_struct *vm_next, *vm_prev;
struct rb_node vm_rb;
/*vm_next, vm_prev是vm_area_struct的链表结构,而vm_rb 是vm_area_struct的树形结构(红黑树),链表用于遍历全部节点的时候,
*红黑树适用于地址空间中定位特定内存区域的时候,同时使用这两种数据结构,是为了在不同的操作中都能获得高性能
*/
unsigned long rb_subtree_gap;
/*Largest free memory gap in bytes to the left of this VMA.
*Either between this VMA and vma->vm_prev, or between one
*of the VMAs below us in the VMA rbtree and its ->vm_prev.
*This helps get_unmapped_area find a free area of the right size.
*(我的理解就是没有映射的区域(两个VMA之间的距离,但是使用内核模块打印这个值,与VMA的距离是不符的))
*/
struct mm_struct *vm_mm;
/*与此VMA相关的mm_struct
*/
pgprot_t vm_page_prot;
/*进程访问控制权限
*/
unsigned long vm_flags;
/*VMA的标志,例如:页面可读写可执行,是否共享.....
*/
union {
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} linear;
struct list_head nonlinear;
}shared;
/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree, or
* linkage of vma in the address_space->i_mmap_nonlinear list.*跟高速缓存有关
*/struct list_head anon_vma_chain;
struct anon_vma *anon_vma;
/*anon_vma_chaon是annon_vma构成的链表,anon_vma是匿名VMA对象,例如堆就是匿名映射
*/
const struct vm_operations_struct *vm_ops;
/*指定内存区域相关的操作函数表,包括open,close,fault.....
*/
unsigned long vm_pgoff;
struct file *vm_file;
void *vm_private_data;
/*vm_pgoff是在vm_file 文件中的偏移。vm_file 是被映射的文件,vm_private_data是私有数据
*/
#ifndef CONFIG_MMU
struct vm_region *vm_region;
/*The vm_region function returns information on a region within the specified task's address space,but in there ,the specified address is nommu mapping region*/
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /*VMA使用NUMA架构*/
#endif
}
怎么查看虚拟存储器,可是使用/proc/pid/maps或者pmap pid来查看进程的虚拟地址空间,当然我们也可以实现自己的kernel module来查看虚拟地址空间:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
MODULE_LICENSE("GPL");static int __init module(void)
{struct pid *current_pid;
struct task_struct *current_task;struct mm_struct *mymm;
struct vm_area_struct *pos = NULL;current_pid = find_vpid(xxx);
current_task = pid_task(current_pid, PIDTYPE_PID);mymm = current_task->mm;
for(pos = mymm->mmap; pos; pos = pos->vm_next) {printk("0x%hx-0x%hx\t", pos->vm_start, pos->vm_end);
if(pos->vm_flags & VM_READ) {
printk("r");
} else {
printk("-");
}
if(pos->vm_flags & VM_WRITE) {
printk("w");
} else {
printk("-");
}
if(pos->vm_flags & VM_EXEC) {
printk("x");
} else {
printk("-");
}
printk("\n");
}
return 0;
}
static void __exit rmodule(void)
{printk(KERN_ALERT"Goodbye,world\n");
}
module_init(module);
module_exit(rmodule);
将上面的程序保存为vm_area_struct.c,然后使用下面的Makefile文件进行编译:
obj-m := vm_area_struct.o
CURRENT_PATH := ${shell pwd}
CURRENT_KERNEL_PATH := ${shell uname -r}
LINUX_KERNEL_PATH := /usr/src/kernels/$(CURRENT_KERNEL_PATH)all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
生成vm_area_struct.ko文件,用root权限执行 insmod vm_area_struct.ko, 然后执行lsmod 来查看kernel模块是否加载进去,然后查看/var/log/message文件查看结果,最后使用rmmod vm_area_struct卸载内核模块。
在message文件输出与maps文件对比,出现了http://stackoverflow.com/questions/26277134/about-the-proc-xx-map-and-the-vm-area-struct
在/proc/maps中不会出现栈保护页,而使用内核模块会出现这个4K的页,故会出现上述的问题。可在kernel中的./fs/proc/task_mmu.c中查看。
参考资料:
1:深入理解计算机系统 第9章,主要还是虚拟存储器的如何工作的和如何使用管理虚拟存储器;
2:linux内核设计与实现,主要是数据结构的介绍。