http://blog.chinaunix.net/uid-24517549-id-4117279.htmlhtml
1、內存管理概述node
(一)、虛擬內存實現結構算法
(1)內存映射模塊(mmap):負責把磁盤文件的邏輯地址映射到虛擬地址,以及把虛擬地址映射到物理地址。緩存
(2)交換模塊(swap):負責控制內存內容的換入和換出,它經過交換機制,使得在物理內存的頁面(RAM 頁)中保留有效的頁 ,即從主存中淘汰最近沒被訪問的頁,保存近來訪問過的頁。併發
(3)核心內存管理模塊(core):負責核心內存管理功能,即對頁的分配、回收、釋放及請頁處理等,這些功能將被別的內核子系統(如文件系統)使用。app
(4)結構特定的模塊:負責給各類硬件平臺提供通用接口,這個模塊經過執行命令來改變硬件MMU 的虛擬地址映射,並在發生頁錯誤時,提供了公用的方法來通知別的內核子系統。這個模塊是實現虛擬內存的物理基礎。函數
(二)、內核空間和用戶空間性能
Linux 簡化了分段機制,使得虛擬地址與線性地址老是一致,所以,Linux 的虛擬地址空間也爲0~4G 字節。Linux 內核將這4G 字節的空間分爲兩部分。將最高的1G 字節(從虛擬地址0xC0000000 到0xFFFFFFFF),供內核使用,稱爲「內核空間」。而將較低的3G 字節(從虛擬地址0x00000000 到0xBFFFFFFF),供各個進程使用,稱爲「用戶空間」。由於每一個進程能夠經過系統調用進入內核,所以,Linux 內核由系統內的全部進程共享。因而,從具體進程的角度來看,每一個進程能夠擁有4G 字節的虛擬空間。圖 6.3 給出了進程虛擬空間示意圖。ui
Linux 使用兩級保護機制:0 級供內核使用,3 級供用戶程序使用。從圖6.3 中能夠看出,每一個進程有各自的私有用戶空間(0~3G),這個空間對系統中的其餘進程是不可見的。最高的1G 字節虛擬內核空間則爲全部進程以及內核所共享。this
(三)、虛擬內存實現機制間的關係
首先內存管理程序經過映射機制把用戶程序的邏輯地址映射到物理地址,在用戶程序運行時若是發現程序中要用的虛地址沒有對應的物理內存時,就發出了請頁要求①;若是有空閒的內存可供分配,就請求分配內存②(因而用到了內存的分配和回收),並把正在使用的物理頁記錄在頁緩存中③(使用了緩存機制)。若是沒有足夠的內存可供分配,那麼就調用交換機制,騰出一部份內存④⑤。另外在地址映射中要經過TLB(翻譯後援存儲器)來尋找物理頁⑧;交換機制中也要用到交換緩存⑥,而且把物理頁內容交換到交換文件中後也要修改頁表來映射文件地址⑦。
2、內存分配與釋放
在Linux 中,CPU 不能按物理地址來訪問存儲空間,而必須使用虛擬地址;所以,對於內存頁面的管理,一般是先在虛存空間中分配一個虛存區間,而後才根據須要爲此區間分配相應的物理頁面並創建起映射,也就是說,虛存區間的分配在前,而物理頁面的分配在後。
(一)、夥伴算法(Buddy)
Linux 的夥伴算法把全部的空閒頁面分爲10 個塊組,每組中塊的大小是2 的冪次方個頁面,例如,第0 組中塊的大小都爲2^0(1 個頁面),第1 組中塊的大小都爲2^1(2 個頁面),第9 組中塊的大小都爲2^9(512 個頁面)。也就是說,每一組中塊的大小是相同的,且這一樣大小的塊造成一個鏈表。
咱們經過一個簡單的例子來講明該算法的工做原理。
假設要求分配的塊的大小爲128 個頁面(由多個頁面組成的塊咱們就叫作頁面塊)。該算法先在塊大小爲128 個頁面的鏈表中查找,看是否有這樣一個空閒塊。若是有,就直接分配;若是沒有,該算法會查找下一個更大的塊,具體地說,就是在塊大小256 個頁面的鏈表中查找一個空閒塊。若是存在這樣的空閒塊,內核就把這256 個頁面分爲兩等份,一份分配出去,另外一份插入到塊大小爲128 個頁面的鏈表中。若是在塊大小爲256 個頁面的鏈表中也沒有找到空閒頁塊,就繼續找更大的塊,即512 個頁面的塊。若是存在這樣的塊,內核就從512 個頁面的塊中分出128 個頁面知足請求,而後從384 個頁面中取出256 個頁面插入到塊大小爲256 個頁面的鏈表中。而後把剩餘的128 個頁面插入到塊大小爲128 個頁面的鏈表中。若是512 個頁面的鏈表中尚未空閒塊,該算法就放棄分配,併發出出錯信號。
以上過程的逆過程就是塊的釋放過程,這也是該算法名字的來由。知足如下條件的兩個塊稱爲夥伴:
(1)兩個塊的大小相同;
(2)兩個塊的物理地址連續。
夥伴算法把知足以上條件的兩個塊合併爲一個塊,該算法是迭代算法,若是合併後的塊還能夠跟相鄰的塊進行合併,那麼該算法就繼續合併。
(二)、Slab 分配機制
能夠根據對內存區的使用頻率來對它分類。對於預期頻繁使用的內存區,能夠建立一組特定大小的專用緩衝區進行處理,以免內碎片的產生。對於較少使用的內存區,能夠建立一組通用緩衝區(如Linux 2.0 中所使用的2 的冪次方)來處理,即便這種處理模式產生碎
片,也對整個系統的性能影響不大。
硬件高速緩存的使用,又爲儘可能減小對夥伴算法的調用提供了另外一個理由,由於對夥伴算法的每次調用都會「弄髒」硬件高速緩存,所以,這就增長了對內存的平均訪問次數。
Slab 分配模式把對象分組放進緩衝區(儘管英文中使用了Cache 這個詞,但實際上指的是內存中的區域,而不是指硬件高速緩存)。由於緩衝區的組織和管理與硬件高速緩存的命中率密切相關,所以,Slab 緩衝區並不是由各個對象直接構成,而是由一連串的「大塊(Slab)」構成,而每一個大塊中則包含了若干個同種類型的對象,這些對象或已被分配,或空閒,如圖6.10 所示。通常而言,對象分兩種,一種是大對象,一種是小對象。所謂小對象,是指在一個頁面中能夠容納下好幾個對象的那種。例如,一個inode 結構大約佔300 多個字節,所以,一個頁面中能夠容納8 個以上的inode 結構,所以,inode 結構就爲小對象。Linux 內核中把小於512 字節的對象叫作小對象。
實際上,緩衝區就是主存中的一片區域,把這片區域劃分爲多個塊,每塊就是一個Slab,每一個Slab 由一個或多個頁面組成,每一個Slab 中存放的就是對象。
3、地址映射機制
在進程的task_struct 結構中包含一個指向 mm_struct 結構的指針,mm_strcut 用來描述一個進程的虛擬地址空間。進程的 mm_struct 則包含裝入的可執行映像信息以及進程的頁目錄指針pgd。該結構還包含有指向 vm_area_struct 結構的幾個指針,每一個 vm_area_struct 表明進程的一個虛擬地址區間。vm_area_struct 結構含有指向vm_operations_struct 結構的一個指針,vm_operations_struct 描述了在這個區間的操做。vm_operations 結構中包含的是函數指針;其中,open、close 分別用於虛擬區間的打開、關閉,而nopage 用於當虛存頁面不在物理內存而引發的「缺頁異常」時所應該調用的函數,當 Linux 處理這一缺頁異常時(請頁機制),就能夠爲新的虛擬內存區分配實際的物理內存。圖6.15 給出了虛擬區間的操做集。
struct mm_struct { struct vm_area_struct *mmap; /* list of VMAs */ struct rb_root mm_rb; struct vm_area_struct *mmap_cache; /* last find_vma result */ ... unsigned long start_code, end_code, start_data, end_data; unsigned long start_brk, brk, start_stack; ... }; struct vm_area_struct { struct mm_struct *vm_mm; /* The address space we belong to. */ unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_end; /* The first byte after our end address within vm_mm. */ .... /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next; .... /* describe the permissable operation */ unsigned long vm_flags; /* operations on this area */ struct vm_operations_struct * vm_ops; struct file * vm_file; /* File we map to (can be NULL). */ } ; /* * These are the virtual MM functions - opening of an area, closing and * unmapping it (needed to keep files on disk up-to-date etc), pointer * to the functions called when a no-page or a wp-page exception occurs. */ struct vm_operations_struct { void (*open)( struct vm_area_struct *area); void (*close)( struct vm_area_struct *area); struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);
4、malloc 和 free 的實現
Normally, malloc() allocates memory from the heap, and adjusts the size of the heap as required, using sbrk(2). When
allocating blocks of memory larger than MMAP_THRESHOLD bytes, the glibc malloc() implementation allocates the memory as a
private anonymous mapping using mmap(2). MMAP_THRESHOLD is 128 kB by default, but is adjustable using mallopt(3). Allo‐
cations performed using mmap(2) are unaffected by the RLIMIT_DATA resource limit (see getrlimit(2)).
MAP_ANONYMOUS
The mapping is not backed by any file; its contents are initialized to zero. The fd and offset arguments are ignored;
however, some implementations require fd to be - 1 if MAP_ANONYMOUS ( or MAP_ANON) is specified, and portable applications
should ensure this. The use of MAP_ANONYMOUS in conjunction with MAP_SHARED is only supported on Linux since kernel 2.4.
(一)、使用brk()/ sbrk() 實現
圖中白色背景的框表示 malloc管理的空閒內存塊,深色背景的框不歸 malloc管,多是已經分配給用戶的內存塊,也可能不屬於當前進程, Break之上的地址不屬於當前進程,須要經過 brk系統調用向內核申請。每一個內存塊開頭都有一個頭節點,裏面有一個指針字段和一個長度字段,指針字段把全部空閒塊的頭節點串在一塊兒,組成一個環形鏈表,長度字段記錄着頭節點和後面的內存塊加起來一共有多長,以 8字節爲單位(也就是以頭節點的長度爲單位)。
1. 一開始堆空間由一個空閒塊組成,長度爲 7×8=56字節,除頭節點以外的長度爲 48字節。
2. 調用 malloc分配 8個字節,要在這個空閒塊的末尾截出 16個字節,其中新的頭節點佔了 8個字節,另外 8個字節返回給用戶使用,注意返回的指針 p1指向頭節點後面的內存塊。
3. 又調用 malloc分配 16個字節,又在空閒塊的末尾截出 24個字節,步驟和上一步相似。
4. 調用 free釋放 p1所指向的內存塊,內存塊(包括頭節點在內)歸還給了 malloc,如今 malloc管理着兩塊不連續的內存,用環形鏈表串起來。注意這時 p1成了野指針,指向不屬於用戶的內存, p1所指向的內存地址在 Break之下,是屬於當前進程的,因此訪問 p1時不會出現段錯誤,但在訪問 p1時這段內存可能已經被 malloc再次分配出去了,可能會讀到意外改寫數據。另外注意,此時若是經過 p2向右寫越界,有可能覆蓋右邊的頭節點,從而破壞 malloc管理的環形鏈表, malloc就沒法從一個空閒塊的指針字段找到下一個空閒塊了,找到哪去都不必定,全亂套了。
5. 調用 malloc分配 16個字節,如今雖然有兩個空閒塊,各有 8個字節可分配,可是這兩塊不連續, malloc只好經過 brk系統調用擡高 Break,得到新的內存空間。在 [K&R]的實現中,每次調用 sbrk函數時申請 1024×8=8192個字節,在 Linux系統上 sbrk函數也是經過 brk實現的,這裏爲了畫圖方便,咱們假設每次調用 sbrk申請 32個字節,創建一個新的空閒塊。
6. 新申請的空閒塊和前一個空閒塊連續,所以能夠合併成一個。在能合併時要儘可能合併,以避免空閒塊越割越小,沒法知足大的分配請求。
7. 在合併後的這個空閒塊末尾截出 24個字節,新的頭節點佔 8個字節,另外 16個字節返回給用戶。
8. 調用 free(p3)釋放這個內存塊,因爲它和前一個空閒塊連續,又從新合併成一個空閒塊。注意, Break只能擡高而不能下降,從內核申請到的內存之後都歸 malloc管了,即便調用 free也不會還給內核。
(二)、使用mmap() / munmap() 實現
在Linux下面,kernel 使用4096 byte來劃分頁面,而malloc的顆粒度更細,使用8 byte對齊,所以,分配出來的內存不必定是頁對齊的。而mmap 分配出來的內存地址是頁對齊的,因此munmap處理的內存地址必須頁對齊(Page Aligned)。