
作者简介:
Loopers,码龄11年,喜欢研究内核基本原理
在32位机器上,总共有4G大小的虚拟地址空间,其中0-3G是给应用程序使用,3-4G是给内核使用。
在64位机器上,目前还不完全支持64位地址宽度,常见的地址长度有39(512GB)和48位(256TB),目前我使用的模拟器采用的是39位的地址宽度,这样的话用户空间和内核空间各占512GB的地址空间。
当一个应用程序在用户跑起来的时候,它内部是如何正常运行的,通过一个简单的例子详细说明下。
#include<stdio.h>#include<malloc.h>staticint global_data=1;staticint global_data1;int bss_data;int bss_data1;intmain(){int stack_data = 1;int stack_data1 = 2;int data[200*1024];staticint data_val=1;int* malloc_data=malloc(10);int* malloc_data1=(int*)malloc(300);int* malloc_data2=(int*)malloc(300*1024);// stack segmentprintf("stack segment!\n");printf("\t stack_data=0x%lx\n",&stack_data);printf("\t stack_data1=0x%lx\n",&stack_data1);// heap segmentprintf("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 segmentprintf("code segment!\n");printf("\t code_data=0x%lx\n",main);//data segmentprintf("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 segmentprintf("bss segment!\n");printf("\t bss_data=0x%lx\n",&bss_data);printf("\t bss_data1=0x%lx\n",&bss_data1);return0;}为了更好的实验,我们需要在ARM64的机器上运行上述的测试例子。然后打印各个段的地址。
root:/ # ./data/vmastack segment! stack_data=0x7fe8a41e24 stack_data1=0x7fe8a41e20heap segment! malloc_data=0x356db9d0 malloc_data1=0x356db9f0 malloc_data2=0x6ff3187010code segment! code_data=0x400620data segment! global_data=0x48b960 global_data1=0x48d380 data_val=0x48b964bss segment! bss_data=0x48e448 bss_data1=0x48e44c我们根据各个段打印的地址来用一张图描述下各个段的位置。目前描述的是ARM64架构,可能不同架构不是一样

我们将ARM64的用户空间放大,就可以清晰的看见各个段在整个用户空间的位置。
以上实验是针对ARM64架构的实验结果的。大家有兴趣的话可以研究下32位系统。我这里直接给出32系统的结果,当然了也是实验的结果,这是N年之前在32的ubuntu机器做的结果


对应的结果如下

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

大家可以去对对地址是否落在对应的区域。
内核通过vma来描述各个段,而各个vma会通过链表或者红黑树链接在一起,会将链表的头放在mm_struct结构中的。

这里不具体描述vma了,有兴趣的可以去查询相关的code去看。大概描述下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>staticint mpid=1;staticvoidprint_vma(struct task_struct *task){structmm_struct *mm;structvm_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);}staticintvma_start(){structtask_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); } }return0;}staticvoidvma_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/maps00400000-0047c000 r-xp 00000000103:236918 /data/vma0048b000-0048e000 rw-p 0007b000 103:236918 /data/vma0048e000-0048f000 rw-p 0000000000:00038382000-383a4000 rw-p 0000000000:000 [heap]78941af000-78941fb000 rw-p 0000000000:00078941fb000-78941fc000 r--p 0000000000:000 [vvar]78941fc000-78941fd000 r-xp 0000000000:000 [vdso]7fc0ed3000-7fc0f9d000 rw-p 0000000000:000 [stack]再看看我们的驱动程序的打印信息
[ 2432.979096] Got the process id =4766[ 2432.979495] vma[4766][ 2432.979500] This mm_struct has 8 vma[ 2432.979504] vma number 1:[ 2432.979508] Start address 0x400000, End address 0x47c000[ 2432.979511] vma number 2:[ 2432.979515] Start address 0x48b000, End address 0x48e000[ 2432.979518] vma number 3:[ 2432.979522] Start address 0x48e000, End address 0x48f000[ 2432.979525] vma number 4:[ 2432.979529] Start address 0x38382000, End address 0x383a4000[ 2432.979532] vma number 5:[ 2432.979536] Start address 0x78941af000, End address 0x78941fb000[ 2432.979539] vma number 6:[ 2432.979543] Start address 0x78941fb000, End address 0x78941fc000[ 2432.979547] vma number 7:[ 2432.979551] Start address 0x78941fc000, End address 0x78941fd000[ 2432.979554] vma number 8:[ 2432.979558] Start address 0x7fc0ed3000, End address 0x7fc0f9d000[ 2432.979564] Code segment start=0x400000, end=0x47b76f Data Segment start=0x48b770, end=0x48d348 Stack segment start=0x7fc0f9ba00通过这个例子我们就清晰的了解到各个vma是用来描述各个段的,各个段的信息通过vm_area_struct结构有详细的描述。而且各个vma都是通过双链表链接在一起的。链表的主要作用是方便删除增加;另外一种红黑树组织方式是为了查找方便的。
小哥搜集了一些嵌入式学习资料,公众号内回复【1024】即可找到下载链接!
推荐好文点击蓝色字体即可跳转
☞专辑|Linux应用程序编程大全 ☞ 专辑|学点网络知识 ☞ 专辑|手撕C语言 ☞ 专辑|手撕C++语言
☞ 专辑|经验分享 ☞ 专辑|从单片机到Linux ☞ 专辑|电能控制技术 ☞ 专辑|嵌入式必备数学知识 ☞ MCU进阶专辑
☞ 经验分享