通过小实验让你彻底理解VMA

通过小实验让你彻底理解VMA在 32 位机器上 总共有 4G 大小的虚拟地址空间 其中 0 3G 是给应用程序使用 3 4G 是给内核使用 在 64 位机器上 目前还不完全支持 64 位地址宽度 常见的地址长度有 39 和 48 位 目前我使用的模拟器采用的是 39 位的地址宽度 这样的话用户空间和内

大家好,欢迎来到IT知识分享网。


作者简介:

Loopers,码龄11年,喜欢研究内核基本原理

在32位机器上,总共有4G大小的虚拟地址空间,其中0-3G是给应用程序使用,3-4G是给内核使用。

在64位机器上,目前还不完全支持64位地址宽度,常见的地址长度有39(512GB)和48位(256TB),目前我使用的模拟器采用的是39位的地址宽度,这样的话用户空间和内核空间各占512GB的地址空间。

当一个应用程序在用户跑起来的时候,它内部是如何正常运行的,通过一个简单的例子详细说明下。

#include <stdio.h>
#include <malloc.h>

static int global_data=1;
static int global_data1;
int bss_data;
int bss_data1;

int main()
{
int stack_data = 1;
int stack_data1 = 2;
int data[200*1024];

static int data_val=1;

int* malloc_data=malloc(10);
int* malloc_data1=(int*)malloc(300);
int* malloc_data2=(int*)malloc(300*1024);

// stack segment
printf("stack segment!\n");
printf("\t stack_data=0x%lx\n",&stack_data);
printf("\t stack_data1=0x%lx\n",&stack_data1);

// heap segment
printf("heap segment!\n");
printf("\t malloc_data=0x%lx\n",malloc_data);
printf("\t malloc_data1=0x%lx\n",malloc_data1);
printf("\t malloc_data2=0x%lx\n",malloc_data2);

//code segment
printf("code segment!\n");
printf("\t code_data=0x%lx\n",main);

//data segment
printf("data segment!\n");
printf("\t global_data=0x%lx\n",&global_data);
printf("\t global_data1=0x%lx\n",&global_data1);
printf("\t data_val=0x%lx\n",&data_val);

//bss segment
printf("bss segment!\n");
printf("\t bss_data=0x%lx\n",&bss_data);
printf("\t bss_data1=0x%lx\n",&bss_data1);

return 0;
}

为了更好的实验,我们需要在ARM64的机器上运行上述的测试例子。然后打印各个段的地址。

root:/ # ./data/vma
stack segment!
stack_data=0x7fe8a41e24
stack_data1=0x7fe8a41e20
heap segment!
malloc_data=0x356db9d0
malloc_data1=0x356db9f0
malloc_data2=0x6ff
code segment!
code_data=0x
data segment!
global_data=0x48b960
global_data1=0x48d380
data_val=0x48b964
bss segment!
bss_data=0x48e448
bss_data1=0x48e44c

我们根据各个段打印的地址来用一张图描述下各个段的位置。目前描述的是ARM64架构,可能不同架构不是一样

通过小实验让你彻底理解VMA

我们将ARM64的用户空间放大,就可以清晰的看见各个段在整个用户空间的位置。

  • 代码段是用户虚拟地址空间的最低位置,代码段就是我们code所在的位置
  • 在代码段的位置上面就是数据段,数据段就是全局初始化的变量。
  • 数据段的位置就是BSS段,BSS段就是未初始化的全局变量。
  • Stack段就是函数调用的局部变量,或者函数中定义的局部数组。可以看到栈是从高地址往下增长的。
  • Heap段就是对应的malloc申请的区域,从实验结果上来看heap段正好位于用户空间中间部分,而且是从下往上增长的。
  • Mmap区域,就是我们使用mmap映射那段区域。当使用malloc申请的大于128K,则会使用mmap区域的。

以上实验是针对ARM64架构的实验结果的。大家有兴趣的话可以研究下32位系统。我这里直接给出32系统的结果,当然了也是实验的结果,这是N年之前在32的ubuntu机器做的结果

通过小实验让你彻底理解VMA
通过小实验让你彻底理解VMA

对应的结果如下

通过小实验让你彻底理解VMA

可以看到和ARM64表现是一样的。

VMA(Virtual Memory Area)

上述说的各个段最终还需要映射到具体的物理内存的,而在内核中使用VMA来描述各个段的。我们可以通过cat /proc/pid/maps命令来对应下上面的实验结果

通过小实验让你彻底理解VMA

大家可以去对对地址是否落在对应的区域。

内核通过vma来描述各个段,而各个vma会通过链表或者红黑树链接在一起,会将链表的头放在mm_struct结构中的。

通过小实验让你彻底理解VMA

这里不具体描述vma了,有兴趣的可以去查询相关的code去看。大概描述下vma的定义

通过小实验让你彻底理解VMA
通过小实验让你彻底理解VMA

这里我们只需要掌握用户空间的各个段的布局,心中知道代码段,数据段,stack,heap段各个的位置。以及各个段在内核中通过vma去描述,而各个vma是通过链表或者红黑树链接一起的。链表头会挂载mm_struct的mmap中,红黑树的的头挂在mm_struct的mmap_rb上。

链表是为了插入方便,而红黑树是为了查找方便。

了解了VMA的组织数据后,用一个例子来通过驱动模块来获取VMA各个段的信息

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/mm.h>

static int mpid=1;

static void print_vma(struct task_struct *task)
{
struct mm_struct *mm;
struct vm_area_struct *vma;
int count=0;

mm = task->mm;
printk("This mm_struct has %d vma\n", mm->map_count);

for(vma = mm->mmap; vma; vma=vma->vm_next){
printk("vma number %d: \n", ++count);
printk("Start address 0x%lx, End address 0x%lx\n", vma->vm_start, vma->vm_end);
}

printk("Code segment start=0x%lx, end=0x%lx\n"
"Data Segment start=0x%lx, end=0x%lx\n"
"Stack segment start=0x%lx\n",
mm->start_code, mm->end_code, mm->start_data, mm->end_data, mm->start_stack);
}

static int vma_start()
{
struct task_struct *task;
printk("Got the process id =%d\n", mpid);

for_each_process(task) {
if(task->pid == mpid){
printk("%s[%d]\n", task->comm, task->pid);
print_vma(task);
}
}
return 0;
}

static void vma_exit()
{
printk("print segment info module exit!\n");
}

module_init(vma_start);
module_exit(vma_exit);
module_param(mpid, int, 0);

我们通过获取应用程序的pid,然后通过模块参数传递到驱动模块中,匹配到相同的pid,则将此进程的名字(comm字段),PID(pid)字段打印出来。同时获取当前进程有多少个vma,打印各个vma的开始地址和结束地址。

通过maps命令获取进程的各个vma信息

root:/data # cat /proc/4766/maps
00-0047c000 r-xp 00000000 103:23 6918 /data/vma
0048b000-0048e000 rw-p 0007b000 103:23 6918 /data/vma
0048e000-0048f000 rw-p 00000000 00:00 0
-383a4000 rw-p 00000000 00:00 0 [heap]
78941af000-78941fb000 rw-p 00000000 00:00 0
78941fb000-78941fc000 r--p 00000000 00:00 0 [vvar]
78941fc000-78941fd000 r-xp 00000000 00:00 0 [vdso]
7fc0ed3000-7fc0f9d000 rw-p 00000000 00:00 0 [stack]

再看看我们的驱动程序的打印信息

[ 2432.] Got the process id =4766
[ 2432.] vma[4766]
[ 2432.] This mm_struct has 8 vma
[ 2432.] vma number 1:
[ 2432.] Start address 0x, End address 0x47c000
[ 2432.] vma number 2:
[ 2432.] Start address 0x48b000, End address 0x48e000
[ 2432.] vma number 3:
[ 2432.] Start address 0x48e000, End address 0x48f000
[ 2432.] vma number 4:
[ 2432.] Start address 0x, End address 0x383a4000
[ 2432.] vma number 5:
[ 2432.] Start address 0x78941af000, End address 0x78941fb000
[ 2432.] vma number 6:
[ 2432.] Start address 0x78941fb000, End address 0x78941fc000
[ 2432.] vma number 7:
[ 2432.] Start address 0x78941fc000, End address 0x78941fd000
[ 2432.] vma number 8:
[ 2432.] Start address 0x7fc0ed3000, End address 0x7fc0f9d000
[ 2432.] Code segment start=0x, end=0x47b76f
Data Segment start=0x48b770, end=0x48d348
Stack segment start=0x7fc0f9ba00

通过这个例子我们就清晰的了解到各个vma是用来描述各个段的,各个段的信息通过vm_area_struct结构有详细的描述。而且各个vma都是通过双链表链接在一起的。链表的主要作用是方便删除增加;另外一种红黑树组织方式是为了查找方便的。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/145863.html

(0)
上一篇 2025-04-18 19:10
下一篇 2025-04-18 19:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信