加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專(zhuān)業(yè)用戶(hù)
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

通過(guò)小實(shí)驗(yàn)讓你徹底理解VMA(虛擬內(nèi)存空間)

2022/10/04
2250
閱讀需 20 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

作者簡(jiǎn)介:Loopers,碼齡11年,喜歡研究?jī)?nèi)核基本原理

在32位機(jī)器上,總共有4G大小的虛擬地址空間,其中0-3G是給應(yīng)用程序使用,3-4G是給內(nèi)核使用。

在64位機(jī)器上,目前還不完全支持64位地址寬度,常見(jiàn)的地址長(zhǎng)度有39(512GB)和48位(256TB),目前我使用的模擬器采用的是39位的地址寬度,這樣的話用戶(hù)空間和內(nèi)核空間各占512GB的地址空間。

當(dāng)一個(gè)應(yīng)用程序在用戶(hù)跑起來(lái)的時(shí)候,它內(nèi)部是如何正常運(yùn)行的,通過(guò)一個(gè)簡(jiǎn)單的例子詳細(xì)說(shuō)明下。

#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%lxn",&stack_data);
    printf("t stack_data1=0x%lxn",&stack_data1);
 
    // heap segment
    printf("heap segment!n");
    printf("t malloc_data=0x%lxn",malloc_data);
    printf("t malloc_data1=0x%lxn",malloc_data1);
    printf("t malloc_data2=0x%lxn",malloc_data2);
 
    //code segment
    printf("code segment!n");
    printf("t code_data=0x%lxn",main);
 
    //data segment
    printf("data segment!n");
    printf("t global_data=0x%lxn",&global_data);
    printf("t global_data1=0x%lxn",&global_data1);
    printf("t data_val=0x%lxn",&data_val);
 
    //bss segment
    printf("bss segment!n");
    printf("t bss_data=0x%lxn",&bss_data);
    printf("t bss_data1=0x%lxn",&bss_data1);
 
    return 0;
}

為了更好的實(shí)驗(yàn),我們需要在ARM64的機(jī)器上運(yùn)行上述的測(cè)試?yán)印H缓蟠蛴「鱾€(gè)段的地址。

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

我們根據(jù)各個(gè)段打印的地址來(lái)用一張圖描述下各個(gè)段的位置。目前描述的是ARM64架構(gòu),可能不同架構(gòu)不是一樣

我們將ARM64的用戶(hù)空間放大,就可以清晰的看見(jiàn)各個(gè)段在整個(gè)用戶(hù)空間的位置。

代碼段是用戶(hù)虛擬地址空間的最低位置,代碼段就是我們code所在的位置

在代碼段的位置上面就是數(shù)據(jù)段,數(shù)據(jù)段就是全局初始化的變量。

數(shù)據(jù)段的位置就是BSS段,BSS段就是未初始化的全局變量。

Stack段就是函數(shù)調(diào)用的局部變量,或者函數(shù)中定義的局部數(shù)組。可以看到棧是從高地址往下增長(zhǎng)的。

Heap段就是對(duì)應(yīng)的malloc申請(qǐng)的區(qū)域,從實(shí)驗(yàn)結(jié)果上來(lái)看heap段正好位于用戶(hù)空間中間部分,而且是從下往上增長(zhǎng)的。

Mmap區(qū)域,就是我們使用mmap映射那段區(qū)域。當(dāng)使用malloc申請(qǐng)的大于128K,則會(huì)使用mmap區(qū)域的。

以上實(shí)驗(yàn)是針對(duì)ARM64架構(gòu)的實(shí)驗(yàn)結(jié)果的。大家有興趣的話可以研究下32位系統(tǒng)。我這里直接給出32系統(tǒng)的結(jié)果,當(dāng)然了也是實(shí)驗(yàn)的結(jié)果,這是N年之前在32的ubuntu機(jī)器做的結(jié)果

 

對(duì)應(yīng)的結(jié)果如下

 

可以看到和ARM64表現(xiàn)是一樣的。

VMA(Virtual Memory Area)

上述說(shuō)的各個(gè)段最終還需要映射到具體的物理內(nèi)存的,而在內(nèi)核中使用VMA來(lái)描述各個(gè)段的。我們可以通過(guò)cat /proc/pid/maps命令來(lái)對(duì)應(yīng)下上面的實(shí)驗(yàn)結(jié)果

 

大家可以去對(duì)對(duì)地址是否落在對(duì)應(yīng)的區(qū)域。

內(nèi)核通過(guò)vma來(lái)描述各個(gè)段,而各個(gè)vma會(huì)通過(guò)鏈表或者紅黑樹(shù)鏈接在一起,會(huì)將鏈表的頭放在mm_struct結(jié)構(gòu)中的。

 

這里不具體描述vma了,有興趣的可以去查詢(xún)相關(guān)的code去看。大概描述下vma的定義

 

這里我們只需要掌握用戶(hù)空間的各個(gè)段的布局,心中知道代碼段,數(shù)據(jù)段,stack,heap段各個(gè)的位置。以及各個(gè)段在內(nèi)核中通過(guò)vma去描述,而各個(gè)vma是通過(guò)鏈表或者紅黑樹(shù)鏈接一起的。鏈表頭會(huì)掛載mm_struct的mmap中,紅黑樹(shù)的的頭掛在mm_struct的mmap_rb上。

鏈表是為了插入方便,而紅黑樹(shù)是為了查找方便。

了解了VMA的組織數(shù)據(jù)后,用一個(gè)例子來(lái)通過(guò)驅(qū)動(dòng)模塊來(lái)獲取VMA各個(gè)段的信息

#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 vman", 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%lxn", vma->vm_start, vma->vm_end);
        }
 
        printk("Code segment start=0x%lx, end=0x%lxn"
                "Data Segment start=0x%lx, end=0x%lxn"
                "Stack segment start=0x%lxn",
                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 =%dn", 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, int0);

我們通過(guò)獲取應(yīng)用程序的pid,然后通過(guò)模塊參數(shù)傳遞到驅(qū)動(dòng)模塊中,匹配到相同的pid,則將此進(jìn)程的名字(comm字段),PID(pid)字段打印出來(lái)。同時(shí)獲取當(dāng)前進(jìn)程有多少個(gè)vma,打印各個(gè)vma的開(kāi)始地址和結(jié)束地址。

通過(guò)maps命令獲取進(jìn)程的各個(gè)vma信息

root:/data # cat /proc/4766/maps
00400000-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
38382000-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]

再看看我們的驅(qū)動(dòng)程序的打印信息

2432.979096] Got the process id =47662432.979495] vma[4766]
[ 2432.979500] This mm_struct has 8 vma
[ 2432.979504] vma number 1:
[ 2432.979508] Start address 0x400000, End address 0x47c0002432.979511] vma number 2:
[ 2432.979515] Start address 0x48b000, End address 0x48e0002432.979518] vma number 3:
[ 2432.979522] Start address 0x48e000, End address 0x48f0002432.979525] vma number 4:
[ 2432.979529] Start address 0x38382000, End address 0x383a40002432.979532] vma number 5:
[ 2432.979536] Start address 0x78941af000, End address 0x78941fb0002432.979539] vma number 6:
[ 2432.979543] Start address 0x78941fb000, End address 0x78941fc0002432.979547] vma number 7:
[ 2432.979551] Start address 0x78941fc000, End address 0x78941fd0002432.979554] vma number 8:
[ 2432.979558] Start address 0x7fc0ed3000, End address 0x7fc0f9d0002432.979564] Code segment start=0x400000, end=0x47b76f 
               Data Segment start=0x48b770, end=0x48d348
               Stack segment start=0x7fc0f9ba00

通過(guò)這個(gè)例子我們就清晰的了解到各個(gè)vma是用來(lái)描述各個(gè)段的,各個(gè)段的信息通過(guò)vm_area_struct結(jié)構(gòu)有詳細(xì)的描述。而且各個(gè)vma都是通過(guò)雙鏈表鏈接在一起的。鏈表的主要作用是方便刪除增加;另外一種紅黑樹(shù)組織方式是為了查找方便的。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

針對(duì)嵌入式人工智能,物聯(lián)網(wǎng)等專(zhuān)業(yè)技術(shù)分享和交流平臺(tái),內(nèi)容涉及arm,linux,android等各方面。