1. 程式人生 > >linux下內存

linux下內存

任務 驅動程序 頻繁 article 延遲函數 固定 主存 res 一個

MMU由一個或一組芯片組成。其功能是把邏輯地址映射為物理地址,進行地址轉換(MMU是CPU的一部分)

機器指令仍然用邏輯地址指定一個操作數的地址或一條指令的地址

每個邏輯地址都由一個段選擇符(16位)和段內的相對偏移量(32位)組成。

段寄存器的唯一目的是存放段選擇符。

MMU包括兩個部件:分段部件和分頁部件,分段機制將邏輯地址轉換為線性地址,分頁機制把線性地址轉換為物理地址。

在RAM芯片上的讀或寫必須串行地運行,因此一種內存仲裁器的硬件電路插在總線和每一個RAM芯片之間。

在整個系統中全局描寫敘述符(GDT)僅僅有一張,能夠被存放在內存的不論什麽位置。,但CPU必須知道GDT的入口。GDT不僅存放了段描寫敘述符,還有其他描寫敘述符,都是64bit長,它是全局可見的。對不論什麽一個任務都是這樣。

GDT的第一項總是設為0,這就確保空段選擇符的邏輯地址會被覺得是無效的,因此引起一個處理器異常。?????

分段能夠給每一個進程分配不同的線性地址空間。而分頁能夠把同一線性地址空間映射到不同的物理地址空間。

執行在用戶態的全部進程都使用一對同樣的段來對指令和數據尋址,這兩個段就是所謂的用戶代碼段和用戶數據段。

全部段都是從0x00000000 開始。能夠得出一個重要的結論,就是在linux邏輯地址與線性地址是一致的,即邏輯地址的偏移量子段與對應的線性地址的值總是一致的。

當指向指令或者數據結構的指針進行保存時,內核根本不須要為其設置邏輯地址的段選擇符,由於CS寄存器就含有當前的段選擇符。

每一個處理器有一個任務狀態段(tss)。對應的線性地址空間都是內核數據段對應的線性地址空間的小子集。

GDT中僅僅有少數項可能依賴CPU正在運行的進程(LDT和TLS段描寫敘述符)。

分頁單元的一個關鍵任務是所請求的訪問類型與線性地址的訪問權限相比較,假設這次內存訪問是無效的,就產生缺頁異常。

線性地址被分成以固定長度為單位的組,稱為頁。頁內部連續的線性地址被映射到連續的物理地址中。

分頁單元把全部的RAM分成固定長度的頁框。

把線性地址映射到物理地址的數據結構稱為頁表。

頁表存放在主存中。並在啟動分頁單元之前必須由內核對頁表進行適當的初始化。

二級模式通過僅僅為進程實際使用的那些虛擬內存區域請求頁表來降低內存使用量。

擴展分頁用於把大段連續的線性地址轉換成對應的物理地址。在這些情況下,內核能夠不用中間頁表進行地址轉換,從而節省內存並保留TLB項。

與段的3種存取權限不同(讀寫運行)不同的是,頁的存取權限僅僅有兩種(讀寫)。

因為用戶進程線性地址空間的須要,內核不能直接對1GB以上的RAM進行尋址。

僅僅有內核可以改動進程的頁表。所以在用戶態下執行的進程不能使用物理地址。

使用的級別數量取決於CPU類型。

硬件快速緩存基於著名的局部性原理。該原理既使用於程序結構也適用於數據結構。

快速緩存單元插在分頁單元和主內存之間。

它包括一個硬件快速緩存內存和一個快速緩存控制器。快速緩存內存中存放內存中真正的行。快速緩存控制器存放一個標項數組。

系統的執行速度通常是被CPU從內存中取得指令和數據速率限制的。

當命中一個快速緩存時,快速緩存控制器進行不同的操作,詳細取決於存取類型。

cache訪問:1 寫回操作(write back) 2 寫穿操作(write through)。

回寫方式僅僅更新快速緩存行,不改變RAM的內容,提供了更快的功效。

僅僅有當CPU運行一條要求刷新快速緩存表項的指令時,或者當一個FLUSH硬件信號產生時(通常在快速緩存不命中之後)。快速控制器才把快速緩存行寫回道RAM中。

多處理器系統的每個處理器都有一個單獨的硬件快速緩存。因此須要額外的硬件電路用於保持快速緩存內容的同步。

TLB(translation lookaside buffer)的快速緩存用於加快線性地址的轉換。當線性地址被第一次使用時,通過慢速訪問RAM中的頁表計算出對應的物理地址。同一時候物理地址被存放在一個TLB表項中,以便以後對同一個線性地址的引用快速的得到轉換。

在多處理器系統中。每一個CPU都有自己的TLB。

Linux的進程處理非常大程度上依賴於分頁。

在初始化階段,內核必須建立一個物理地址映射來指定哪些物理地址範圍對內核可用而哪些不可用(或者由於他們映射硬件設備I/O的共享內存,或者由於對應的頁框含有BIOS數據)。

宏PAGE_OFFSET的值是區分用戶空間和內核空間的範圍大小,也是進程在線性地址中偏移量。PAGE_SHIFT是指頁大小。

****

內核維持著一組自己使用的頁表。駐留在所謂的主內核全局文件夾中,系統初始化後。該組頁表還從未被不論什麽進程貨不論什麽內核線程直接使用。

在內核剛剛被裝入內存後,CPU仍然執行於實模式。所以分頁功能沒有被啟用。

暫時頁的全局文件夾放在swapper_pg_dir變量中。

由內核頁表提供的終於映射必須把從PAGE_OFFSET開始線性地址轉化為從0開始的物理地址。

主內核全局文件夾仍然保存在swapper_pg_dir變量中。由paging_init()函數初始化。

內核線性地址第四個GB的初始部分映射系統的物理內存至少有128MB的線性地址總是留作他用,由於內核使用這些線性地址實現非連續內存分配和固定映射的線性地址。

間接飲用一個指針變量比間接引用一個馬上常量地址要多一次內存訪問。


RAM的某些部分永久的分配給內核。並用來存放內核代碼及靜態內核數據結構。

RAM的其余部分稱為動態內存,這不僅是進程所須要的寶貴資源。也是內核本身所要的寶貴資源。

內核必須記錄每一個頁框當前的狀態。

在一下情況下頁框是不空暇的。包括用戶態進程的數據,某個軟件快速緩存的數據,動態分配的內核數據結構。設備驅動程序緩沖的數據,內核模塊的代碼等。

頁框的狀態信息保存在一個類型為page的頁描寫敘述符中,全部的頁描寫敘述符存放在mem_map數組中。

在NUMA模型中,給定CPU對不同內存單元的訪問時間可能不一樣,系統的物理內存被劃分為幾個節點。在一個單獨的節點內,隨意給定CPU訪問頁面所需的時間都是同樣的。每一個節點都有一個類型為gd_data_t的描寫敘述符。

每一個頁描寫敘述符都有到內存節點和到節點管理區(包括對應框)的鏈接。

當內核調用一個內存分配函數時,必須指明請求頁框所在的管理區。

原子請求(GFP_ATOMIC)從不被堵塞。假設沒有足夠的空間,則不過分配失敗而已。

保留內存的數量(KB)存放在min_free_kbytes變量中。它的初始值在內核初始化時設置。

參數gfp_mask是一組標誌,它指明了怎樣尋找空暇的頁框。

與直接映射的物理內存末端。高端內存的始端所相應的線性地址存放在high_memory變量中。

返回所分配頁框線性地址的頁分配器不適用於高端內存,即不適用ZPONE_HIGHMEM內存管理區內的頁框。

高端內存頁框分配僅僅能通過alloc_pages()函數和它的快捷函數alloc_page()。

這些函數不返回第一個被分配頁框的線性地址。由於假設該頁框屬於高端內存。那麽這種線性地址根本不存在。取而代之。這些函數返回第一個被分配頁框的頁描寫敘述符的線性地址。

全部頁描寫敘述符一旦被分配在低端內存中,他們在內核初始化階段就不會改變。

內核能夠採用三種不同的機制將頁框映射到高端內存,分別叫做永久內核映射,暫時內核映射及非連續內存分配。

建立永久內核映射可能堵塞當前的進程,這發生在空暇頁表項不存在時。因此,永久內核映射不能用於中斷處理程序和可延遲函數。

建立暫時內核映射絕不會要求堵塞當前進程,可是缺點是僅僅有非常少的暫時內核映射能夠同一時候建立起來。

永久內核映射使用內核頁表中i 個專門的頁表,其地址被存放在pkmap_page_table變量中,頁表中的表項數由LAST_PKMAP宏產生,該表映射的線性地址從PKMAP_BASE開始。

為了記錄該段內存頁框與永久內核映射包括的線性地址之間的聯系,內核使用了page_address_htable散列表。

page_address()函數返回頁框相應的線性地址,假設頁框在高端內存中而且沒有被映射。則返回NULL。

kmap()函數建立永久內核映射,假設頁框確實屬於高端內存,則調用kmap_high();

在高端內存的任一頁框都能夠通過一個“窗體”映射到內核地址空間。留給暫時內核映射的窗體數是非常少的。

內核必須確保同一窗體永遠不會被兩個不同的控制路徑同一時候使用。

因此,km_type結構中的那個符號僅僅能由一種內核成分使用。並以該成分命名。最後一個符號KM_TYPE_NR本身並不表示一個線性地址,但由每一個CPU用來產生不同的可用窗體數。

為了建立暫時內核映射,內核調用kmap_atomic()函數。

Linux採用著名的夥伴系統算法來解決外碎片問題,把全部的空暇頁框分組為11塊鏈表,每一個塊鏈表分別包括大小1,2,4,8,16,32,64,128,256。512,1024個連續的頁框。對1024個頁框的最大請求相應著4MB大小的連續RAM塊。

LRU鏈表是統稱:細分為活動鏈表,非活動鏈表;鏈表中存放的是進程用戶態地址空間或者頁快速緩存的全部頁。前者是近期被訪問過的頁。後者是一段時間內未曾被訪問過的頁。

每一個夥伴系統使用的主要數據結構:mem_map數組。free_area數組(該元素的free_list數組的第k個元素標識全部大小為2^k的空暇塊,該鏈表包括每一個空暇頁框塊的起始頁框的頁描寫敘述符,指向鏈表中相鄰元素的指針存放在頁描寫敘述符的lru字段中)。最後,一個2^k的空暇頁塊的第一個頁描寫敘述符的private字段存放了塊的order,也就是數字k。

__rmqueue();函數用來在管理區中找到一個空暇塊。

該函數須要兩個參數,管理區描寫敘述符的地址和order。

__rmqueue()函數如果調用者已經禁止了本地中斷並獲得了保護夥伴系統數據結構的zone->lock自旋鎖。

__free_pcppages_bulk()函數依照夥伴系統的策略釋放頁框。

為了提升系統性能,每一個內存管理區定義了一個“每CPU”頁框快速緩存。全部“每CPU”快速緩存包括一些預先分配的頁框,他們被用於滿足本地CPU發出的單一內存請求。

實現每CPU頁框快速緩存的主要數據結構是存放在(zone)內存管理區描寫敘述符pageset字段中的一個per_cpu_pageset數組數據結構。

buffered_rmqueue()函數在指定的內存管理區中分配頁框,它使用每CPU頁框快速緩存來處理單一頁框請求。

為了釋放單個頁框到每CPU頁框快速緩存。內核使用free_hot_page()和free_cold_page()函數。

管理區分配器是內核頁框分配器的前端,該構件必須分配必須分配一個包括足夠多空暇頁框的內存區。

夥伴系統算法採用頁框作為基本內存區,這適合於對大塊內存。

內核函數傾向於重復請求同一類型的內存區。

slab分配器吧對象分組放進快速緩存,每一個快速緩存都是同類線性對象的一種“儲備”。

包括快速緩存的主內存區被劃分為多個slab,每一個slab由一個多多個連續的頁框組成,這些頁框中既包括已分配的對象,也包括空暇的對象。

每一個快速緩存都是由kmem_cache類型的數據結構來描寫敘述。

快速緩存被分為兩種類型。普通和專用,普通快速緩存僅僅由slab分配器用於自己的目的(kmem_cache)。而專用快速緩存由內核的其余部分使用。

在系統初始化期間調用kmem_cache_init()和kmem_sizes_init()來建立普通快速緩存。

專用快速緩存是由kmem_cache_create()函數創建的。

為了避免浪費內存空間,內核必須在撤銷快速緩存本身之前就撤銷其全部的slab。kmem_cache_shrink()函數通過重復調用slab_destroy()撤銷快速緩存中全部的slab.

全部普通和專用快速緩存的名字都能夠在執行期間通過讀取/proc/slabinfo文件得到。

當slab分配器創建新的slab時。它依賴頁框分配器來獲得一組連續的空暇頁框,為了達到此目的,它調用kmem_getpages()函數。

在相反的操作中,通過調用kmem_freepages()函數能夠釋放分配給slab的頁框。

一個新創建的快速緩存沒有包括不論什麽slab,因此也沒有空暇的對象。

僅僅由當條件都為真(1.已發出一個分配對象的請求 2.快速緩存不包括不論什麽空暇對象)才給快速緩存分配slab。

slab分配器通過調用cache_grow()函數給快速緩存分配一個新的slab,而這個函數調用kmem_getpages()從分區頁表分配器獲得一組頁框來存放一個單獨的slab,然後又調用alloc_slabmgmt()獲得一個新的slab描寫敘述符。

僅僅有當頁框空暇時夥伴系統的函數才會使用lru字段。而僅僅要涉及夥伴系統。slab分配器函數所處理的頁框就不空暇並將PG_slab標誌置位。

每一個對象都有類似kmem_bufctl_t的一個描寫敘述符。對象描寫敘述符存放在一個數組中。位於對應的slab描寫敘述符後。

對象描寫敘述符僅僅只是是一個無符號整數,僅僅由在對象空暇時才有意義。

它包括的是下一個空暇對象在slab中的下標。因此實現了slab內部空暇對象的一個簡單鏈表。

slab分配器所管理的對象能夠在內存中進行對齊,也就是存放他們的內存單元的起始物理地址是一個給定常量的倍數,一般是2的倍數。該常量也叫對齊因子。

slab分配器所同意的最大對齊因子是4096。即頁框大小。

同一硬件快速緩存行能夠映射RAM中非常多不同塊。

同樣大小的對象傾向於存放在快速緩存內同樣的偏移量處。

因此。不同的slab內具有同樣偏移量的對象終於非常可能映射在同一快速緩存行中。

快速緩存描寫敘述符的array字段是一組向array_cache數據結構的指針,系統中的每一個CPU相應於一個元素。每一個array_cache數據結構是空暇對象的本地快速緩存的一個描寫敘述符。

本地快速緩存描寫敘述符並不包括本地快速緩存本身的地址,本地快速緩存存放的是指向釋放對象的指針,而不是對象本身。

當創建一個新的slab快速緩存時。kmem_cache_create()函數決定本地快速緩存的大小,分配本地快速緩存。並將它們的指針存放在快速緩存的array字段。

通過調用kmem_cache_alloc()函數就能夠獲得新對象。

假設對存儲區的請求訪問不頻繁,就用一組普通快速緩存來處理,普通快速緩存中的對象具有幾何分布的大小。

調用kmalloc()函數就能夠得到這樣的類型的對象。

保留的內存池僅僅能用於滿足中斷處理程序或者內存臨界區發出的原子內存分配請求。而內存池是動態內存的設備,僅僅能被特定的內核成分(池的擁有者)使用。

一個內存池經常疊加在slab分配器之上,內存池能被用來分配不論什麽一種類型的動態內存,從整個頁框到使用kmalloc()分配的小內存區。

當內存元素是slab對象時,alloc和free對象一般由mempool_alloc_slab()和mempool_free_slab()函數實現。在這樣的情況下。mempool_t對象的pool_data字段存放了slab快速緩存描寫敘述符的地址。

mempool_create()函數創建一個新的內存池,它接受的參數為內存元素的個數min_nr,實現alloc和free方法的函數地址和賦給pol_data字段的隨意值。

為了從內存池分配一個元素,內核調用mempool_alloc()函數,將mmepool_t對象的地址和內存分配標誌傳遞給它。

把內存映射到一組連續的頁框是最好的選擇,這樣會充分利用快速緩存並獲得較低的平均訪問時間。

在物理內存映射的末尾與第一個內存區域之間插入一個大小為VMALLOC_OFFSET的安全區。目的是為了捕獲對內存的越界訪問。

技術分享

每一個非連續內存區都相應著一個類型為vm_struct的描寫敘述符。

get_vm_area()函數在線性地址VMALLOC_START和VMALLOC_END之間查找一個空暇區域,該函數使用兩個參數。被創建內存區的字節大小和指定空暇區類型。

map_vm_area()並不觸及當前進程的頁表。因此。當內核態的進程訪問非連續內存區域時,缺頁發生。由於該內存區所相應的進程頁表的表項為空。

然而,缺頁處理程序要檢查這個缺頁線程地址是否在主內核頁表中(init_mm.pgd頁全局目和它的子頁表)。一旦處理程序發現一個主內核頁表包括有這個線性地址的非空項。就把它的值復制到相應的進程頁表項中。並恢復進程的正常運行。

內核永遠也不會收回紮根於在內核頁全局文件夾中的頁上級文件夾,頁中間文件夾和頁表。


DMA忽略分頁單元而直接訪問地址總線,因此,所請求的緩沖區就必須位於連續的頁框中。

頻繁的改動頁表勢必導致平均訪問內存次數的添加,由於這會使CPU頻繁地刷新轉換後援緩沖器TLB的內容。


__get_free_page()或_alloc_page()從分區頁框分配器中獲得頁框,kmem_cache_alloc()或kmem_alloc()使用slab分配器為專用或通用對象分配塊,而vmallc()或vmalloc_32()獲得一塊非連續的內存區域。假設所請求的內存區得到滿足,這些函數都返回一個頁描寫敘述符地址或線性地址(即所分配動態內存的起始地址)。


linux下內存