Linux 獲取虛擬地址對應的物理地址【轉】


轉自:https://zhou-yuxin.github.io/articles/2017/Linux%20%E8%8E%B7%E5%8F%96%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E5%AF%B9%E5%BA%94%E7%9A%84%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80/index.html

現代操作系統中,進程是運行在虛擬地址空間上的。比如在32位機器上,進程可以認為自己有4GB的內存空間可以使用。但是真實被使用到的虛擬地址肯定得有真實的存儲介質相對應。如果不考慮swap的話,每一個虛擬地址都對應一個物理地址。現代的硬件一般是以分頁的方式管理內存的(x86的分段與分頁相結合的所謂段頁式也只是為了兼容罷了)。一個虛擬頁映射到一個物理頁。以常見的4KB的頁大小為例,如果一個虛擬地址0xfe0000對應物理地址的0x40000,那么接下來的虛擬地址0xfe0000+i就對應物理地址的0x40000+i,i=0,1,2,...,4095。但是虛擬地址的0xfe1000可能對應着一個相去甚遠的物理地址0x1c0000呢。也就是說,在虛擬地址中連續的地址,在物理上可能是碎片似的分散在內存條的各個地方,但是在一個頁內,地址是連續地一一對應的。

鑒於此,要把一個虛擬地址轉換成物理地址,其實就是要知道該虛擬地址所在的虛擬頁對應的物理頁。知道了物理頁,再加上頁內偏移量即可。以4KB的頁大小為例,一個32位長的虛擬地址,其高20位就稱為虛擬頁號,低12位就是頁內偏移。Linux為每一個進程都維護了一個頁表,放在內存中。頁表的每一項就是一個虛擬頁號對應的物理頁號。所以如果能夠訪問到頁表,那么就能夠把虛擬地址轉換成物理地址。然而,只有在內核態才有權限訪問頁表,用戶態是無權訪問的。另外,不同的硬件結構下,頁表的定位方式是不同的,而且可能很復雜,涉及多個寄存器。

一開始我沒有找到能在用戶態查詢頁表的方式。就當我快絕望的時候,萬能的GitHub又幫我我一把。我在某個項目中發現了其訪問/proc/<pid>/pagemap這個虛擬文件。后來我去查了這個虛擬文件,得到如下信息。

Documentation/vm/pagemap.txt

每一個頁對應一個64位,也就是8字節的字段。比如虛擬地址0xfe0020,其高20位為0xfe0,也就是其虛擬頁號為0xfe0。那么該虛擬頁的信息處於/proc/self/pagemap這個文件中偏移量為0xfe0*8=32512的地方。從此處讀取一個8字節的數據,先檢查最高位'page present',如果是1,那么說明該頁處於物理內存中,那么該8字節的第0-54位就是物理頁號。假設物理頁號是0x40,那么實際的物理地址就是(0x40<<12)+0x20=0x40020。

介紹完原理,那么封裝成一個函數就簡單多了。我這里就封裝一個最容易理解但是效率最低的實現方式:

#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>

size_t virtual_to_physical(size_t addr)
{
    int fd = open("/proc/self/pagemap", O_RDONLY);
    if(fd < 0)
    {
        printf("open '/proc/self/pagemap' failed!\n");
        return 0;
    }
    size_t pagesize = getpagesize();
    size_t offset = (addr / pagesize) * sizeof(uint64_t);
    if(lseek(fd, offset, SEEK_SET) < 0)
    {
        printf("lseek() failed!\n");
        close(fd);
        return 0;
    }
    uint64_t info;
    if(read(fd, &info, sizeof(uint64_t)) != sizeof(uint64_t))
    {
        printf("read() failed!\n");
        close(fd);
        return 0;
    }
    if((info & (((uint64_t)1) << 63)) == 0)
    {
        printf("page is not present!\n");
        close(fd);
        return 0;
    }
    size_t frame = info & ((((uint64_t)1) << 55) - 1);
    size_t phy = frame * pagesize + addr % pagesize;
    close(fd);
    return phy;
}

最后需要說明的是,只有root權限的進程能夠訪問/proc/<pid>/pagemap這個文件,否則讀出來的都是0。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM