Linux进程的虚拟地址空间Word格式.docx
《Linux进程的虚拟地址空间Word格式.docx》由会员分享,可在线阅读,更多相关《Linux进程的虚拟地址空间Word格式.docx(5页珍藏版)》请在冰豆网上搜索。
vmalloc函数申请的物理地址空间并不连续。
vmalloc函数通过重新建立虚拟地址空间和物理地址空间之间的映射,即新建页表项,将离散的物理地址空间映射到连续的虚拟地址空间。
因此,使用该函数的开销比较大。
下面的程序简单的演示了这三个函数的使用方法。
从结果中可以看出,这些函数申请的地址都在3GB(0xBFFFFFFF)以上。
完整代码在如下。
staticint__initmenroyshow_init(void) { printk("
mmshowmoduleis
working\n"
);
pagemem=__get_free_page(GFP_KERNEL);
if(!
pagemem) goto
gfp_fail;
printk(KERN_INFO"
pagemem=0x%lx\n"
pagemem);
kmallocmem=
kmalloc(100*sizeof(char),GFP_KERNEL);
kmallocmem) gotokmalloc_fail;
kmallocmem=0x%p\n"
kmallocmem);
vmallocmem=
vmalloc(1000000*sizeof(char));
vmallocmem) gotovmalloc_fail;
vmallocmem=0x%p\n"
vmallocmem);
return0;
gfp_fail:
free_page(pagemem);
kmalloc_fail:
kfree(kmallocmem);
vmalloc_fail:
vfree(vmallocmem);
return-1;
} //运行结果:
#pagemem=0xf3211000 #
kmallocmem=0xd581e700 #vmallocmem=0xf9251000 每个进程够拥有属于自己的3GB的虚拟空间(用户空间),那么这个3GB的空间是如何划分的?
通常,除了我们熟悉的代码段和数据段,用户空间还包括堆栈段和堆。
我们可以通过下面的演示程序来了解这些区域到底负责存储程序的那些内容。
int
bss_var;
intdata_var0=1;
intmain(intargc,char**argv) { printf("
The
userspace'
saddressdivisionofaprocessasfollow:
\n"
printf("
Data
segment:
addressof\"
main\"
function:
%p\n\n"
main);
printf("
Datasegment:
addressof
data_var:
%p\n"
&
amp;
data_var0);
staticintdata_var1=4;
newendof
data_var1);
BSS:
bss_var:
bss_var);
char*str=(char*)malloc(sizeof(char)*10);
initialheapend:
str);
char*buf=(char
*)malloc(sizeof(char)*10);
newheapend:
buf);
intstack_var0
=2;
Stacksegment:
initialendof
stack:
stack_var0);
intstack_var1=3;
stack_var1);
Theuserspace'
s
addressdivisionofaprocessasfollow:
Datasegment:
addressof"
main"
function:
0x8048454 Datasegment:
addressofdata_var:
0x804a01c newendof
0x804a020 BSS:
addressofbss_var:
0x804a02c initialheap
end:
0x8f77008 newheapend:
0x8f77018 Stacksegment:
initialendof
0xbfe0a3b4 newendofstack:
0xbfe0a3b0
可以看到,代码段存放程序的代码;
数据段存放全局变量和static类型的局部变量。
此外,未初始化的全局变量虽然也存在于数据段,但是这些未初始化的变量都集中在靠近数据段上边界的区域,这个区域称为BSS段。
以上这些空间是进程所必须拥有的,它们在进程运行之前就分配好了。
程序中的局部变量一般被分配在堆栈段,其位于用户空间最顶部。
与固定的代码段和数据段不同的是,堆栈段存储数据是从高低值往低地址延伸的。
因此,在数据段到堆栈段之间,形成了一片空洞,这片空洞用于存储malloc函数所动态分配的空间,这片空洞区域被称为堆。
通过下面这个图可以更进一步的了解到进程用户空间的划分情况。
以上是关于进程用户空间划分的大致分析,上述理论在内核代码中如何体现?
它将涉及到mm_struct结构和vm_area_struct结构。
每一个进程都拥有3GB大小的用户空间,而连续用户空间又按照存储内容的不同被划分成若干个区域。
在内核中,主要通过mm_struct结构体和vm_area_struct结构体对进程用户空间进行描述。
前者是对进程的用户空间进行整体的描述;
而后者则是对用户空间中的某个区域进行描述。
显然,每一个进程对应的有一个mm_struct结构和多个vm_area_struct结构。
1.mm_struct结构 最新版本中的mm_struct结构字段比较多,接下来只对部分字段做以说明。
mmap:
vm_area_struct结构体类型的指针。
指向进程用户空间中各区域所组成的双链表。
链表方式可以高效的遍历所有元素;
mm_rb:
rb_root结构体类型。
同样描述内存区域块,只不过采用红黑树来表示。
用红黑树可以快速索引到指定的元素;
mm_users:
atomic_t类型。
用来记录正在使用该地址空间的进程数目。
比如,当前有3个进程正在共享该地址空间,那么其值为3;
mm_count:
记录mm_struct结构体被引用的次数。
如果当前该地址空间只被两个进程所共享,那么该值为1,mm_users为2;
当这两个进程都退出时,该值为0,mm_users也为0。
另外,内核线程并不需要访问用户的内存空间,也并不需要创建页表。
内核线程一般会直接使用前一个进程的mm_struct结构。
因此该字段的计数还包括内核线程对这个结构的引用。
map_count:
int类型。
内存区域的个数;
pgd:
pgd_t类型,该结构体类型内部封装的是unsigned
long类型的数据。
pgd表示的是页目录基址。
当调度程序调度一个进程运行时,就将这个线性地址转化为物理地址,并写入CR3控制寄存器中;
start_code,
end_code,start_data,end_data:
unsignedlong类型。
进程代码段和数据段的起始地址和终止地址;
start_brk,
brk,start_stack:
unsigned
long类型。
分别为堆的起始地址和终止地址,堆栈的起始地址。
上文说过,进程的堆栈段是根据需求向下(朝低地址方向)延伸的,因此这里并没有堆栈段的终止地址;
arg_start,
arg_end,env_start,env_end:
命令行参数所在内存的起始地址和终止地址,环境变量所在内存的起始地址和终止地址;
2.vm_area_struct结构 上面我们已经知道,该结构体描述的是进程用户空间中的一个虚拟内存区间(Virtual
Memory
Area,VMA)。
vm_mm:
mm_struct结构体类型指针。
指向该区域所属的用户空间对应的mm_struct结构体。
vm_start,vm_end:
该虚存区域的起始地址和终止地址。
vm_next,vm_prev:
vm_area_struct结构体类型指针。
构成VMA双联表。
vm_flags:
该虚存区的标志。
vm_page_prot:
pgprot_t结构体类型,内部封装了unsigned
访问控制权限。
vm_ops:
vm_operations_struct结构体类型。
该虚存区域的操作函数接口,这些函数可以对虚存区中的页进行操作。
3.数据结构的关系 了解了上述结构体的关键字段,它们与进程之间的逻辑关系便是我们接下来要关心的重点。
我们知道,一个进程在内核中使用task_struct结构对其进行描述。
task_struct结构中有一个mm字段,它所