1. 程式人生 > >Linux 從虛擬地址到實體地址

Linux 從虛擬地址到實體地址

我們都知道,動態共享庫裡面的函式的共享的,這也是動態庫的優勢所在,就是節省記憶體。C 編譯出來的可執行檔案幾乎都會用到libc的庫,假如沒有這個共享的技術,每個可執行檔案都要佔一份libc庫的記憶體,這將是極大的記憶體浪費。 可是一直沒搞明白,怎麼樣才能證明共享庫裡面函式的地址在實體記憶體層面是同一份。其實,這個問題的本質是程式裡面的邏輯地址和實體記憶體地址之間是怎樣對映的,說的再赤裸裸一點,就是我給你個邏輯地址,請你在實體記憶體中找到對應的地址,或者我給你個實體地址,請你把這個實體記憶體裡面存的東西告訴我。

    最近兩天,發現了一篇很牛的博文,這個博文徹底解決了邏輯地址 線性地址 實體地址的記憶體對映問題,作者的功力特別身後,他十分kind的提供了一篇29頁的
pdf文件,此文章一出,就徹底終結這個問題了。那我為什麼還要寫這篇博文呢。作者以2.6.18核心為例,提供了兩個核心模組和兩個應用層的程式,我在自己的Ubuntu 12.04上花了時間完整的驗證了文件裡面PAE(Physical Address Extension)模式的地址對映,發現程式碼裡面存在一些相容性的問題,導致編譯不過,主要是核心版本不同和gcc帶來的一些小問題。所以我花了4個多小時才把這個實驗完整的做下來。如果想通過做實驗來加深理解的筒子可以參考我修改後的程式。我無意抄襲,還是那句話,光榮屬於前輩。

    下面的圖來自Intel的手冊64-ia-32-architectures-software-developer-vol-3a-part-1-manual ,很好的解釋的邏輯地址到實體地址的對映。所謂邏輯地址,就是我們C 語言中取地址符後,看到的地址。


    採用原文的函式
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     unsigned long tmp;
  5.     tmp = 0x12345678;
  6.     printf("tmp address:0x%08lX\n", &tmp);
  7.     return 0;
  8. }
  1. tmp address:0xBF86D16C
輸出的地址為0xBF86D16C,這個就是官方手冊上說的邏輯地址。首先需要將邏輯地址轉化成線性地址。然後將線性地址轉化成實體地址。將邏輯地址轉化成線性地址,就是江湖傳說的分段機制,也就是上圖下面的segmentation。

    1 段式對映
    臨時變數tmp的邏輯地址0xBF86D16C就是偏移量,因為tmp位於棧中,IA-32提供了SS(Stack Segment)暫存器。


  1. //arch/x86/kernel/process_32.c
  2. //-------------------------------------------
  3. void
  4. start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
  5. {
  6.     set_user_gs(regs, 0); 
  7.     regs->fs = 0;
  8.     regs->ds = __USER_DS;
  9.     regs->es = __USER_DS;
  10.     regs->ss = __USER_DS;
  11.     regs->cs = __USER_CS;
  12.     regs->ip = new_ip;
  13.     regs->sp = new_sp;
  14.     /* 
  15.      * Free the old FP and other extended state
  16.      */
  17.     free_thread_xstate(current);
  18. }
實際上有6個段暫存器,但是DS,ES ,SS的值是一樣的,FS和GS都是0,這樣其實6個段暫存器本質是兩個:CS和DS。每個程序的6個暫存器是一樣的,不同的是EIP和ESP。從上面的程式碼中也可以看到。

  1. arch/x86/include/asm/segment.h
  2. ------------------------------------------
  3. #define GDT_ENTRY_DEFAULT_USER_CS    14
  4. #define GDT_ENTRY_DEFAULT_USER_DS    15
  5. #define GDT_ENTRY_KERNEL_BASE        (12)
  6. #define GDT_ENTRY_KERNEL_CS        (GDT_ENTRY_KERNEL_BASE+0)
  7. #define GDT_ENTRY_KERNEL_DS        (GDT_ENTRY_KERNEL_BASE+1)
  8. #define __KERNEL_CS    (GDT_ENTRY_KERNEL_CS*8)
  9. #define __KERNEL_DS    (GDT_ENTRY_KERNEL_DS*8)
  10. #define __USER_DS    (GDT_ENTRY_DEFAULT_USER_DS*8+3)
  11. #define __USER_CS    (GDT_ENTRY_DEFAULT_USER_CS*8+3)
__USER_CS(14*8 +3 = 115)的值展開二進位制的結果為:

  1. 0000000001110 011
    __USER_DS(15*8 + 3 =123)的值展開二進位制的結果為:

  1. 0000000001111 011
上面的兩組數字就是段選擇符,段選擇符有16位,其中含義如下圖:




 TI表示我要選擇的段描述符是存在GDT中還是LDT中。GDT和LDT可以簡單理解成兩個表,每個表裡面都存放這一組地址。

我們的CS和DS對應的TI位都是0,換句話說,我們要著的段描述符在GDT中。實際上,我們的Linux程式裡用的段描述符總是選擇GDT,幾乎沒有選擇LDT的。毛德操老爺子說,只有像wine這種程序才會用到LDT這樣的東西。

    RPL表示特權等級,0表示最高許可權,3表示無特權。之所以在


  1. #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS*8+3)
有個+3,就是表示,我的段無特權,同時我的段描述符存在GDT這張表裡面。前面的13位表示是GDT表的index,或者說是第幾項。 

    接下來就是去GDT這張表,去找到我們要的段描述符。等等,我們一直很爽的叫著GDT,知道我們的DS段描述符是在index =15的位置,可是從來沒有人告訴我們GDT這張表放在哪裡。

    GDTR橫空出世了,GDT的地址就存放在GDTR這個暫存器裡面。問題是怎麼讀出啦GDTR暫存器的值?

    前面提到的博文作者寫了一個核心模組,來提取GDTR,CR0 CR3  等的值,主幹程式碼在下面:


  1. static int my_get_info( char *buf, char **start, off_t off, int count )
  2. {
  3.     int len = 0;
  4.     struct mm_struct *mm;
  5.     mm = current->active_mm;
  6.     cr0 = read_cr0();
  7.     cr3 = read_cr3();
  8.     cr4 = read_cr4();
  9.     //asm(" sgdt gdtr");
  10.     asm("sgdt %0":"=m"(gdtr));
  11.     len += sprintf( buf+len, "cr4=%08X ", cr4 );
  12.     len += sprintf( buf+len, "PSE=%X ", (cr4>>4)&);
  13.     len += sprintf( buf+len, "PAE=%X ", (cr4>>5)&);
  14.     len += sprintf( buf+len, "\n" );
  15.     len += sprintf( buf+len, "cr3=%08X cr0=%08X\n",cr3,cr0);
  16.     len += sprintf( buf+len, "pgd:0x%08X\n",(unsigned int)mm->pgd);
  17.     len += sprintf( buf+len, "gdtr address:%lX, limit:%X\n", gdtr.address,gdtr.limit);
  18. // len += sprintf( buf+len, "cpu_gdt_table address:0x%08lX\n", cpu_gdt_table);
  19.     return len;
  20. }
  asm那句程式碼在我的gcc下編譯不過,我修改了下。感興趣的同學可以考慮下為啥編譯不過。
    
    總之我們有辦法取GDTR暫存器的值,從而找到了GDT這張表,然後從這張表裡面著第16項(index=15),我們就能找到我們的DS段描述符。


  1. [email protected]:~/code/c/self/mm_addr# ./mem_map 
  2. %ebp:0xBF86D178
  3. tmp address:0xBF86D16C
  4. cr4=000006F0 PSE=1 PAE=
  5. cr3=06E3C000 cr0=8005003B
  6. pgd:0xC6E3C000
  7. gdtr address:F7BB9000, limit:FF
  國外大牛提供了一個叫做dram的核心模組,還有一個fileview的tool,這個tool+模組相互配合,能夠讀到實體地址裡面對應的內容。這個核心模組是大殺器啊,我解決共享庫迷惑就全靠在這個核心模組上了。作者是低於2.6.32的核心,我們是高於2.6.32的核心,所以稍加修改,就能用在我的Ubuntu上了。

    可以算出GDT的地址為F7BB9000 - c0000000,然後用作者提供的工具fileview去看下記憶體內容
  1. -----------------------------------------------------------
  2. gdtr : f7bb9000 - c0000000 = 37bb9000
  3.  0000037BB9000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  4.  0000037BB9010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  5.  0000037BB9020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  6.  0000037BB9030 FF FF 00 B9 61 F3 DF B7 00 00 00 00 00 00 00 00 ....a...........
  7.  0000037BB9040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  8.  0000037BB9050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
  9.  0000037BB9060 FF FF 00 00 00 9B CF 00 FF FF 00 00 00 93 CF 00 ................
  10.  0000037BB9070 FF FF 00 00 00 FB CF 00 FF FF 00 00 00 F3 CF 00 ................
  11.  0000037BB9080 6B 20 C0 EA BB 8B 00 F7 00 00 00 00 00 00 00 00 k ..............
  12.  0000037BB9090 FF FF 00 00 00 9A 40 00 FF FF 00 00 00 9A 00 00 ......@.........
  13.  0000037BB90A0 FF FF 00 00 00 92 00 00 00 00 00 00 00 92 00 00 ................
  14.  0000037BB90B0 00 00 00 00 00 92 00 00 FF FF 00 00 00 9A 40 00 ..............@.
  15.  0000037BB90C0 FF FF 00 00 00 9A 00 00 FF FF 00 00 00 92 40 00 ..............@.
  16. 相關推薦

    Linux 虛擬地址實體地址

    我們都知道,動態共享庫裡面的函式的共享的,這也是動態庫的優勢所在,就是節省記憶體。C 編譯出來的可執行檔案幾乎都會用到libc的庫,假如沒有這個共享的技術,每個可執行檔案都要佔一份libc庫的記憶體,這將是極大的記憶體浪費。 可是一直沒搞明白,怎麼樣才能證明共享庫裡面函式

    linux記憶體管理---虛擬地址 邏輯地址 線性地址 實體地址的區別(一)

    分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

    linux系統虛擬地址 實體地址 匯流排地址

    在linux核心書籍中,介紹記憶體管理的部分,頻繁出現三個概念,實體地址、虛擬地址、匯流排地址 他們區別如下: 實體地址:MMU看到的記憶體的地址 虛擬地址:cpu,程式設計師操作的地址 匯流排地址:裝置看到的地址 struct pci_device_id結構體用於定義該驅動程式支援的

    Linux下邏輯地址-線性地址-實體地址圖解

    轉載:http://blog.csdn.net/wxzking/article/details/5905214 一、邏輯地址轉線性地址 機器語言指令中出現的記憶體地址,都是邏輯地址,需要轉換成線性地址,再經過MMU(CPU中的記憶體管理單元)轉換成實體地址才能夠被訪問

    Linux使用者空間訪問實體地址

    因為專案需要,需要在Linux userspace 讀寫訪問實際實體地址。 一)使用者空間可以直接通過開啟 /dev/mem 裝置檔案,然後mmap() 影射進行訪問 static int read_type() { void * map_base; FILE *f

    分段 分頁 虛擬記憶體空間 邏輯地址 實體地址

    一、虛擬記憶體空間   虛擬記憶體空間是系統的一種技術,當程式被載入記憶體時,運用虛擬記憶體空間技術讓程式誤認為自己目前獨佔電腦記憶體,能夠佔用電腦所有的記憶體,訪問所有記憶體地址。 以32位作業系統為例:   32位系統程式的指標為32位(4位元組),2

    Linux核心的虛擬地址、邏輯地址、線性地址實體地址的區別

    概述 分頁機制在段機制之後進行,以完成線性—實體地址的轉換過程。段機制把邏輯地址轉換為線性址頁機制進一步把該線性地址再轉換為實體地址    幾種地址的解釋 分析linux記憶體管理機制,離不了上述幾個概念,在介紹上述幾個概念之前,先從《深入理解linux核心》這

    Linux記憶體管理-虛擬地址、邏輯地址、線性地址實體地址的區別

    概述 分頁機制在段機制之後進行,以完成線性—實體地址的轉換過程。段機制把邏輯地址轉換為線性址頁機制進一步把該線性地址再轉換為實體地址    幾種地址的解釋 分析linux記憶體管理機制,離不了上述幾個概念,在介紹上述幾個概念之前,先從《深入理解linux核心》這

    LINUX 邏輯地址、線性地址虛擬地址實體地址

    1、概念解釋 實體地址:   用於記憶體晶片級的單元定址,與地址匯流排相對應。這個概念應該是這幾個概念中最好理解的一個,但是值得一提的是,雖然可以直接把實體地址理解成插在機器上那根記憶體本身,把記憶體看成一個從0位元組一直到最大空量逐位元組的編號的大陣列,然

    嵌入式 Linux驅動虛擬地址實體地址的對映

    7)高階記憶體概念的由來:如上所述,Linux將4GB的線性地址空間劃分成兩部分,從0x00000000到0xBFFFFFFF共3GB空間作為使用者空間由使用者程序獨佔,這部分線性地址空間並沒有固定對映到實體記憶體空間上;從0xC0000000到0xFFFFFFFF的第4GB線性地址空間作為核心空間,在嵌入式

    Linux 對處理器實體地址/虛擬地址和ioremap函式的個人理解

    寫在前面 在Linux驅動學習過程中,遇到了ioremap這個函式,引數是phy_addr,返回值是虛擬地址。 linux啟動以後,由於mmu的存在,想要控制暫存器,要找到暫存器實體地址的虛擬地址對映(聽著比較拗口)。 前幾天遇到了一個問題,手上的板子是

    linux記憶體管理---虛擬地址、邏輯地址、線性地址實體地址的區別(一)

         分析linux記憶體管理機制,離不了上述幾個概念,在介紹上述幾個概念之前,先從《深入理解linux核心》這本書中摘抄幾段關於上述名詞的解釋: 一、《深入理解linux核心》的解釋 邏輯地址(Logical Address) 

    linux記憶體管理---實體地址、線性地址虛擬地址、邏輯地址之間的轉換

    CPU的頁式記憶體管理單元,負責把一個線性地址,最終翻譯為一個實體地址。從管理和效率的角度出發,線性地址被分為以固定長度為單位的組,稱為頁(page),例如一個32位的機器,線性地址最大可為4G,可以用4KB為一個頁來劃分,這頁,整個線性地址就被劃分為一個tatol_page[2^20]的大陣列,共有2的20

    Linux 虛擬地址實體地址的對映關係分析

    Ordeder原創文章,原文連結: http://blog.csdn.net/ordeder/article/details/41630945 原始碼版本 2.4.0 1. 虛擬空間 0-3G 使用者空間  0x00000000  ~ 0xbfffffff 3-4G 核心

    淺析Linux 64位系統虛擬地址實體地址的對映及驗證方法

    # 虛擬記憶體 先簡單介紹一下作業系統中為什麼會有虛擬地址和實體地址的區別。因為Linux中有程序的概念,那麼每個程序都有自己的獨立的地址空間。 現在的作業系統都是64bit的,也就是說如果在使用者態的程序中建立一個64位的指標,那麼在這個程序中,這個指標能夠指向的範圍是0~0xFFFFFFFFFFFFFF

    Linux驅動虛擬地址和物理地址的映射

    沒有 映射 跟著 申請 不能 物理地址 技術 存在 ngs 一般情況下,Linux系統中,進程的4GB內存空間被劃分成為兩個部分------用戶空間和內核空間,大小分別為0~3G,3~4G。 用戶進程通常情況下,只能訪問用戶空間的虛擬地址,不能訪問到內核空間。 每個

    Linux 學習之路 --------ip地址虛擬網路

    // ifconfig 檢視IP地址 網路資訊   我的IP  39.161.136.25 ①     為網絡卡臨時配置IP地址 ifconfig eth0 39.161.136.5 (netmask /255.255.255.0&n

    ARM 32 實體地址轉換虛擬地址

    第一章  虛擬記憶體分佈及常用巨集定義 1.1記憶體分佈   ARMlinux下虛擬記憶體分佈在核心文件有介紹,與X86是有些不同。 部分地址分段在9850K專案上發現未曾使用,故灰色處理。 Kernel/documentation/arm/memory.tx

    【迅為iTop4412學習筆記】10.瞭解實體地址虛擬地址

    宣告 以下都是我剛開始看驅動視訊的個人強行解讀,如果有誤請指出,共同進步。 本節目標 瞭解實體地址和虛擬地址,MMU是關鍵 在之前我們對linux驅動已經有了一個大概印象,而編寫的程式碼也都是學習性質的編寫,在本節之後就開始逐漸深入。 首先

    微控制器中的虛擬地址實體地址

    對於核心實體記憶體對映區的虛擬記憶體,使用virt_to_phys()可以實現核心虛擬地址轉化為實體地址,phys_to_virt()可以實現實體地址轉化為核心虛擬地址。 #define __virt_to_phys(x)