歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> linux內存管理概述(一)

linux內存管理概述(一)

日期:2017/2/25 10:38:11   编辑:Linux教程

  我們首先從進程的角度,來看linux中的內存空間。

  ***********************************************************************************

  Linux中的地址空間(一)

  有這麼一系列的問題,是否在困擾著你:用戶程序編譯連接形成的地址空間在什麼范圍內?內核編譯後地址空間在什麼范圍內?要對外設進行訪問,I/O的地址空間又是什麼樣的?

  先回答第一個問題。Linux最常見的可執行文件格式為elf(Executable and Linkable Format)。在elf格式的可執行代碼中,ld總是從0x8000000開始安排程序的“代碼段”,對每個程序都是這樣。至於程序執行時在物理內存中的實際地址,則由內核為其建立內存映射時臨時分配,具體地址取決於當時所分配的物理內存頁面。

  我們可以用Linux的實用程序objdump對你的程序進行反匯編,從而知曉其地址范圍。

  例如:假定我們有一個簡單的C程序Hello.c

  # include <stdio.h>

  greeting ( ) {

  printf(“Hello,world!\n”);

  }

  main() {

  greeting();

  }

  之所以把這樣簡單的程序寫成兩個函數,是為了說明指令的轉移過程。我們用gcc和ld對其進行編譯和連接,得到可執行代碼hello。然後,用Linux的實用程序objdump對其進行反匯編:

  $objdump –d hello

  得到的主要片段為:

  08048568 <greeting>:

  8048568: pushl %ebp

  8048569: movl %esp, %ebp

  804856b: pushl $0x809404

  8048570: call 8048474 <_init+0x84>

  8048575: addl $0x4, %esp

  8048578: leave

  8048579: ret

  804857a: movl %esi, %esi

  0804857c <main>:

  804857c: pushl %ebp

  804857d: movl %esp, %ebp

  804857f: call 8048568 <greeting>

  8048584: leave

  8048585: ret

  8048586: nop

  8048587: nop

  其中,像08048568這樣的地址,就是我們常說的虛地址(這個地址實實在在的存在,只不過因為物理地址的存在,顯得它是“虛”的罷了)。

  虛擬內存、內核空間和用戶空間

  Linux虛擬內存的大小為2^32(在32位的x86機器上),內核將這4G字節的空間分為兩部分。最高的1G字節(從虛地址0xC0000000到0xFFFFFFFF)供內核使用,稱為“內核空間”。而較低的3G字節(從虛地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為“用戶空間”。因為每個進程可以通過系統調用進入內核,因此,Linux內核空間由系統內的所有進程共享。於是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬地址空間(也叫虛擬內存)。

  每個進程有各自的私有用戶空間(0~3G),這個空間對系統中的其他進程是不可見的。最高的1GB內核空間則為所有進程以及內核所共享。另外,進程的“用戶空間”也叫“地址空間”,在後面的敘述中,我們對這兩個術語不再區分。

  用戶空間不是進程共享的,而是進程隔離的。每個進程最大都可以有3GB的用戶空間。一個進程對其中一個地址的訪問,與其它進程對於同一地址的訪問絕不沖突。比如,一個進程從其用戶空間的地址0x1234ABCD處可以讀出整數8,而另外一個進程從其用戶空間的地址0x1234ABCD處可以讀出整數20,這取決於進程自身的邏輯。

  任意一個時刻,在一個CPU上只有一個進程在運行。所以對於此CPU來講,在這一時刻,整個系統只存在一個4GB的虛擬地址空間,這個虛擬地址空間是面向此進程的。當進程發生切換的時候,虛擬地址空間也隨著切換。由此可以看出,每個進程都有自己的虛擬地址空間,只有此進程運行的時候,其虛擬地址空間才被運行它的CPU所知。在其它時刻,其虛擬地址空間對於CPU來說,是不可知的。所以盡管每個進程都可以有4 GB的虛擬地址空間,但在CPU眼中,只有一個虛擬地址空間存在。虛擬地址空間的變化,隨著進程切換而變化。

  從上面我們知道,一個程序編譯連接後形成的地址空間是一個虛擬地址空間,但是程序最終還是要運行在物理內存中。因此,應用程序所給出的任何虛地址最終必須被轉化為物理地址,所以,虛擬地址空間必須被映射到物理內存空間中,這個映射關系需要通過硬件體系結構所規定的數據結構來建立。這就是我們所說的段描述符表和頁表,Linux主要通過頁表來進行映射。

  於是,我們得出一個結論,如果給出的頁表不同,那麼CPU將某一虛擬地址空間中的地址轉化成的物理地址就會不同。所以我們為每一個進程都建立其頁表,將每個進程的虛擬地址空間根據自己的需要映射到物理地址空間上。既然某一時刻在某一CPU上只能有一個進程在運行,那麼當進程發生切換的時候,將頁表也更換為相應進程的頁表,這就可以實現每個進程都有自己的虛擬地址空間而互不影響。所以,在任意時刻,對於一個CPU來說,只需要有當前進程的頁表,就可以實現其虛擬地址到物理地址的轉化。

  Linux內核空間(二)

  內核空間到物理內存的映射

  內核空間對所有的進程都是共享的,其中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據,不管是內核程序還是用戶程序,它們被編譯和連接以後,所形成的指令和符號地址都是虛地址(參見2.5節中的例子),而不是物理內存中的物理地址。

  雖然內核空間占據了每個虛擬空間中的最高1GB字節,但映射到物理內存卻總是從最低地址(0x00000000)開始的,如圖4.2所示,之所以這麼規定,是為了在內核空間與物理內存之間建立簡單的線性映射關系。其中,3GB(0xC0000000)就是物理地址與虛擬地址之間的位移量,在Linux代碼中就叫做PAGE_OFFSET。

  我們來看一下在include/asm/i386/page.h頭文件中對內核空間中地址映射的說明及定義:

  #define __PAGE_OFFSET (0xC0000000)

  ……

  #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)

  #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)

  #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))

  對於內核空間而言,給定一個虛地址x,其物理地址為“x- PAGE_OFFSET”,給定一個物理地址x,其虛地址為“x+ PAGE_OFFSET”。

  這裡再次說明,宏__pa()僅僅把一個內核空間的虛地址映射到物理地址,而決不適用於用戶空間,用戶空間的地址映射要復雜得多,它通過分頁機制完成。

  內核空間為3GB~4GB,這1GB的空間分為如下幾部分。

  先說明符號的含義:

  PAGE_OFFSET: 0XC0000000,即3GB

  high_memory: 這個變量的字面含義是高端內存,到底什麼是高端內存,Linux內核規定,RAM的前896為所謂的低端內存,而896~1GB共128MB為高端內存。如果你的內存是512M,那麼high_memory是多少?是3GB+512M,也就是說,物理地址x<=896M,就有內核地址0xc0000000+x,否則,high_memory=0xc0000000+896M

  或者說high_memory實際值為0xc0000000+x,但最大不能超過0xc0000000+896M

  在源代碼中函數mem_init中,有這樣一行:

  high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);

  其中,max_low_pfn為物理內存的最大頁數。

  所以在圖中,PAGE_OFFSET到high_memory 之間就是所謂的物理內存映射。只有這一段之間物理地址與虛地址之間是簡單的線性關系。

  還要說明的是,要在這段內存分配內存,則調用kmalloc()函數。反過來說,通過kmalloc()分配的內存,其物理頁是連續的。

  void *kmalloc(size_t size, gfp_t flags);

  * The @flags argument may be one of:

  * %GFP_USER - Allocate memory on behalf of user. May sleep.

  * %GFP_KERNEL - Allocate normal kernel ram. May sleep.

  * %GFP_ATOMIC - Allocation will not sleep.

  VMALLOC_START:非連續區的的起始地址。

  VMALLOC_END:非連續區的的末尾地址

  在非連續區中,物理內存映射的末端與第一個VMalloc之間有一個8MB的安全區,目的是為了“捕獲”對內存的越界訪問。處於同樣的理由,插入其他4KB的安全區來隔離非連續區。

  非連續區的分配調用vmalloc()函數。

  void *vmalloc(unsigned long size);

  vmalloc()與 kmalloc()都是在內核代碼中用來分配內存的函數,但二者有何區別?

  從前面的介紹已經看出,這兩個函數所分配的內存都處於內核空間,即從3GB~4GB;但位置不同,kmalloc()分配的內存處於3GB~high_memory之間,這一段內核空間與物理內存的映射一一對應,而vmalloc()分配的內存在VMALLOC_START~4GB之間,這一段非連續內存區映射到物理內存也可能是非連續的。

  vmalloc()工作方式與kmalloc()類似, 其主要差別在於前者分配的物理地址無需連續,而後者確保頁在物理上是連續的(虛地址自然也是連續的)。

  盡管僅僅在某些情況下才需要物理上連續的內存塊,但是,很多內核代碼都調用kmalloc(),而不是用vmalloc()獲得內存。這主要是出於性能的考慮。vmalloc()函數為了把物理上不連續的頁面轉換為虛擬地址空間上連續的頁,必須專門建立頁表項。還有,通過vmalloc()獲得的頁必須一個一個的進行映射(因為它們物理上不是連續的),這就會導致比直接內存映射大得多的緩沖區刷新。因為這些原因,vmalloc()僅在絕對必要時才會使用——典型的就是為了獲得大塊內存時,例如,當模塊被動態插入到內核中時,就把模塊裝載到由vmalloc()分配的內存上。

Copyright © Linux教程網 All Rights Reserved