六。記憶體管理機制--MMU
阿新 • • 發佈:2019-02-16
1、#define abc(n) do{xxx;yyy;zzz;}while(0)
#define abc(n) {xxx;yyy;zzz;}
加上do while 和不加可能執行結果可能一樣,但是核心一般都加
是怕在執行函式的時候出現錯誤
int a ;
if(a > 0)
abc(100);//若用下面的巨集定義,加上分號編譯出錯,是因為在後面加了分號,多加的分號就分割了if / else
else
abc(200);
return 0;
################# 記憶體管理 #################
1、概念:
MPU 記憶體保護單元< 有作業系統的一般不用,用 MMU >
會寫一個頁表類似的東西,但不是頁表,記錄下面記憶體空間的只讀、同步等屬性
每一段記憶體空間的許可權:ro sync ex rw nx ...
示意將記憶體分為:[0, 0x1000)[0x1000, 0x2000)[0x2000, 0x3000)[0x3000, ...)
MMU 記憶體管理單元 MMU = MPU + (查詢頁表)
arm9、arm11、armA系列內有 MMU;一般面向應用的電子帶有。但是並不是說有作業系統就一定有 MMU
如果做工控,控制器一般有 MPU 或沒有,eg:cortex-M 和 cortex-R 系列微控制器用的就是 MPU;只有面向想飛電子的高階晶片才用 MMU,安裝有作業系統的控制器,要實現多工,多工會用到虛擬地址,所以需要 MMU。但並使有作業系統就一定有 MMU
2、linux核心管理機制
bootargs = "mem = 128M" 當初寫這個原因就是告訴核心記憶體有多大,核心就可以計算出用分配多少頁表
核心用的是小頁對映 page = 4K
每個頁對應一個結構體 struct page ---> 描述一頁的實體記憶體
virtual 如果分配過存虛擬地址,沒有的就是NULL
page 結構體在記憶體中以連續、有序陣列存放
struct zone //標記的都是實體地址;標準的linux/unix X86 上這樣分配
DMA [0 - 16M] //分這16M是歷史問題,以前的DMA只有24位,最大訪問16M,所以留下這塊給DMA用
NORMAL[16M - 896M]
HIGH [896M - ...]
嵌入式
2.6.28 DMA[0x50000000, 0x58000000] 此核心只有這一個區
3.4.24 NORMAL[0x50000000, 0x58000000] 此核心只有這一個區
只要用到分配記憶體,不管三七二十一,先包含 <linux/slab.h> 這個標頭檔案
3、MMU:<筆記>
MMU
—————————————————
| | ADDR | TLB | |
| ARM core |====== | 快取 | | 32位,實體地址
| | |———--| |=============
|————— | | | ADDR
| CP15 | 查頁表 | |
| | | | |
| ---> c2(TTBR) | | |
| | | | |
| | | | |
———|—————————————
|-----------------------------> 頁表基地址
a>.從arm出來的地址總線上,肯定是實體地址;虛擬地址一定不會出現在地址總線上,
用到虛擬地址的時候一定是 MMU 開啟的情況,MMU拿到虛擬地址後取查頁表,找到對應的實體地址,將其放到總線上;如果關閉 MMU,從ARM core中出來的地址就直接上匯流排了。前期在學裸板的時候,MMU 是關閉的,我們直接訪問的就是實體地址。
b>.MMU
1.MMU 內有一段快取,存放的是用過的頁表的,
2.頁表的每一個條目(32位,armA8以前的都是,armA9、armA15等為了使用大記憶體擴寬了)都在一定範圍內對應一個虛擬地址和實體地址的對應關係
MMU 是通過實體地址訪問頁表的位置的(即找到基地址)
CP15 可用於開關 MMU,其中的 C2 暫存器記憶體放著頁表的基地之
訪問虛擬地址的時候, MMU 根據 C2裡的基地址找到頁表的位置,再根據基地址找到相應的條目,找到虛擬地址和實體地址的對應關係
c>.arm1176 手冊的 464 頁的圖就是一個頁表的放大圖,此圖的上面是低地址,下面是高地址
這些條目就最後兩位的不同,有 4 中情況:
<1>.00 --- 非法的;此類條目是不能用的,每個程序都會有 4G 的虛擬記憶體,但不是全部的都會被對映,沒對映的記憶體就是這一類,主要訪問這類記憶體就會出現段錯誤,
<2>.01 --- 頁對映;對應有二級頁表;通過一級頁表的31 ~ 20 位找到找到最後兩位是 01 的條目,通過一級頁表的 31 ~ 10 位找到二級頁表的基地址,再根據 19 ~ 12 位作為二級頁表的偏移,找到二級頁表中對應的相應的條目,二級頁表的條目根據最後兩位也有 4 種情況:
(1)00 --- 非法的。與一級頁表的相同
(2)01 --- 64K 大頁對映。通過二級頁表的31 ~ 16 位作為基地址,知道某個大頁的基地址,然後 15 ~ 0 位做偏移找到相對應的條目,得到實體地址
(3)1x --- 4K 小頁對映。通過二級頁表的31 ~ 12 位作為基地址,知道某個大頁的基地址,然後 11 ~ 0 位做偏移找到相對應的條目,得到實體地址
<3>.10 --- (bit(18) = 0)普通段對映;普通段對映,1M實體記憶體; 使用虛擬地址的前12位,做偏移。若找到段對映條目,把頁表的31 ~ 20 位拿出,以段對齊,後20 位補 0,找到段,再將虛擬地址的後20位在段內做偏移,找到一個字,就是對應的實體地址;
<4>.10 --- (bit(18) = 1)超級段對映,16M實體記憶體;超級段對映方法相同與上面相同
d>.例子:根據對映關係舉個例子,普通段對映eg:虛擬地址:0x12345678,實體地址:0x56000000 ~ 0x57000000
通過 123 做偏移找到的條目中前 12 位放 560, 對映到實體地址前12位560,後面以 0 補齊,就找到段的起始位置;
後20位做段內偏移,找到對應的字
e>.例子:二級頁表;例如虛擬地址是0x12345000 找到0x56001000;
一級頁表從51000000開始
二級頁表從50000000開始
則在二級頁表的第0x45個位置 尾部寫10,然後把0x56001放到前31到10位上,二級頁表完成
把一級頁表的第123位,尾部寫01,寫上二級頁表起始地址0x50000000的前20位寫進去,一級頁表完成
4、
虛擬記憶體 實體記憶體
4G ———————— ————————————
間接/動態 對映區 HIGH
—————————
直接/靜態 對映區 ———————————— 896M
3G ———————— 0xc00000000 NORMAL
———————————— 16M
DMA
0 ————————
———————————— 0M
核心一啟動,只會對映直接區,直接對映區是對映到DMA 和 NORMAL 記憶體處,即低896M記憶體了;這種對映叫做平坦對映
核心剛啟動的時候 HIGH 是不能訪問的
直接對映區會根據你的實體記憶體的大小變化,最大是 896M;剩下的都是間接對映區
HIGH記憶體一般通過 ioremap 對映到間接對映區;因為間接對映區地址有限,對映完一定要通過 iounmap 釋放
5、buddy 記憶體管理子系統:
buddy上掛載了55條連結串列,分成11組(0 ~ 10),每組5條,每條連結串列上掛載的是5種不同的記憶體,5種類型的頁:
MIGRATE_UNMOVABLE 0 -- 不可移動的頁;啟動規定好的,系統工作相關的
MIGRATE_RECLAIMABLE 1 -- 啟動規定好的;系統工作相關的
MIGRATE_MOVABLE 2 -- 可移動的頁;對映檔案的都是可移動的,當核心想得到連續的記憶體,需要移動檔案所佔的實體記憶體,核心自動更改頁表位置和對映關係,對程式不會產生影響
MIGRATE_PCPTYPES 3 -- 沒用
MIGRATE_RESERVE 3 -- 沒用
MIGRATE_ISOLATE 4 -- 沒用
MIGRATE_TYPES 5 -- 沒用
buddy子系統是基於MMU的,是管理記憶體子系統最底層的一層,每一組所掛的連結串列的節點大小等於 2 的組號次冪 乘以 4K(頁的大小),即 2^(組號) * 4K;
由上可知, buddy 分配連續的最大物理空間是 4M,可以通過修改結構體 struct zone 中的 MAX_ARDER 的值增加組個數
buddy子系統掛載的永遠都是空閒的記憶體;申請記憶體時,按照大小是層0組開始一層層的向上的,不夠向上層借;釋放內層時一樣,若是連續記憶體,大小達到上層要求,就會掛載到上層,一層層的往上
優點:能最大限度的保證有連續的記憶體
缺點:分配方法粗糙,操作範圍最小為 4K,會造成記憶體浪費
buddy子系統常用函式:
alloc_pages() 申請連續的頁,實體地址連續;
釋放用 __free_pages()
alloc_page() 申請一頁;
釋放用 __free_page()
__get_free_pages() 與 alloc_pages() 函式沒什麼區別;
釋放用 free_pages()
__get_free_page() 與 alloc_page() 函式沒什麼區別;
釋放用 free_page()
注:不建議在 buddy 中拿記憶體
6、slab 子系統
基於 buddy 子系統的一層子系統
slab 從buddy子系統中申請的記憶體分成兩類,通用記憶體、專用記憶體:
通用記憶體:
可以訪問到位元組
申請記憶體函式:
< 一下函式帶 z 只是會把申請的記憶體刷成 0 >
kmalloc() / kzalloc():所申請的記憶體虛擬地址和實體地址都是連續的,一般最大 4M
釋放用 kfree()
vmalloc() / vzalloc():申請大記憶體,虛擬地址一定連續,實體地址可能不連續
釋放用 vfree()
vmalloc / vzalloc 需要自己的標頭檔案 <linux/vmalloc.h>
申請優先從高階記憶體(HIGH)分配記憶體,高階記憶體沒有時再取低端記憶體
專用記憶體:
可用於程序、網路、...
用於程序:會提前建立好程序結構體 task_struct ,用就拿,不用送回
用於網路:會提前建立號快取 sk_buff,用就拿,不用送回
建立快取記憶體:kmem_cache_create()
建立一次會有 n 個,大於 n 時,核心會自動再分配 n 個(不同的核心 n 的大小不定)
拿從快取記憶體:kmem_cache_alloc()
放回快取記憶體:kmem_cache_free()
銷燬快取記憶體:kmem_cache_destory()
優先把快取記憶體放到硬體cache中
每一個快取記憶體對應於一個結構體 struct kmem_cache,自己建立頁需要先建立這麼一個結構體,可以通過 kmem_cache 的個數來判斷快取記憶體的個數
kmem_cache_creat("name", size,align,flags, ctor)
name:你的快取記憶體的名字
size:每個元素的大小
align:0;無用是就填 0
flags:對其方式;因為快取記憶體會被快取到硬體cache中,所以以硬體cache行的方式對其最快
ctor:函式指標;會產生 n 個結構體,這個函式會被呼叫n次,這個函式可自己寫,可以用於初始化結構體內某些有規律的成員
7.dma 一致性記憶體
注意與 SCU(一致性硬體)的區分
一致性硬體:
SCU可以保證一級cash的一致性
cache分為資料cache和指令cache,
ddr把東西放到cache,arm再從cache拿
音效卡放音樂的時候,cpu需要把資料寫道cache,然後cache資料拿到ddr,dma再從ddr中取資料放到pcmdata,再音效卡播放
要求實時性很強,arm 中的資料一到 cache 中,就立刻需要同步到 DDR(記憶體)中,DMA會不斷從記憶體中讀取資料,這就叫一致性記憶體
分配一致性記憶體:dma_alloc_coherent() / dam_zalloc_coherent() < z 清零 >
包含在標頭檔案<linux/dma-mapping.h>
釋放一致性記憶體:dma_free_coherent()
DMA 使用的一般都是實體地址,%99.9的可能是這樣的;只有很少很少的一部分,會使用虛擬地址,DMA內部就會帶有一個硬體,能讀取頁表
v = dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
引數1:NULL先不需關心
引數2:申請大小
引數3:引用回一個實體地址,這個實體地址可以在配置dma通道的時候用
引數4:GFP_KERNEL
這種方式是放在動態記憶體區(間接對映區)這裡就相當於是映射了一個裝置dma,還有ioremap也是
8、修改頁表:
外設的地址也叫裝置記憶體,一般不會對映到直接對映區
通過函式 ioremap 將實體地址對映到間接對映區,用完就要用 iounmap 函式釋放
包含標頭檔案 <asm/io.h>
ioremap(0x7f008000, 0x1000)
分配地址要以頁對齊,因為底層實現一次是一個頁(4K)
9、綜述:
對映 修改頁表
——————————————————————————————————————————————
dma 一致性記憶體
——————————————————————————————————————————————
slab
1.物理連續 2.物理不連續 3.快取記憶體
——————————————————————————————————————————————
buddy
——————————————————————————————————————————————
MMU
——————————————————————————————————————————————
buddy子系統是核心分配記憶體的基礎,slab從buddy申請記憶體,然後把記憶體告訴通用快取或者專用快取(slab就是聯絡buddy和cache的橋樑),在這裡能呼叫kmalloc和zmalloc和建立快取記憶體,如果cache需要和記憶體同步,就要建立一致性記憶體dma_alloc系列函式,如果在知道實體地址但是不知道虛擬地址的情況下,就需要修改頁表來讓實體地址對映到間接對映區,這些所有的申請空間函式和都對映到動態對映區,其中dma和ioremap相當於添加了一個裝置,即把外部的dma和led等等裝置的實體地址對映到動態記憶體區(間接對映區)
10、函式解析:
kmalloc/kzalloc(20, GPL_KERNEL);
GPL_KERNEL:允許函式睡眠
GPL_ATOMIC:不允許函式睡眠,被打斷就返回出錯
vmalloc(20)
預設傳的就是 GPL_KERNEL,在不允許睡眠的情況下不能使用這個函式
11、不同層次申請、釋放記憶體方法:
程式碼參考 <path>/code/04mm/*
從buffy子系統裡拿
方法1:
建立頁表
alloc_pages可以申請若干個連續的頁
__free_pages()
alloc_page可以申請一個頁
__free_page()
方法2:
建立頁表
__get_free_pages() 申請若干頁
free_pages()
__get_free_page()申請一個頁
free_page()
從slab裡拿
方法3:
建立快取記憶體:
kmem_cache_create()建立快取記憶體
kmem_cache_alloc()申請快取記憶體空間(拿東西)
kmem_cache_free()釋放快取空間(放回去)
kmem_cache_destroy()銷燬快取記憶體
為dma申請
方法4:
建立一致性記憶體
dma_alloc_coherent()分配
dma_zalloc_coherent()//把記憶體裡所有東西都清0
dma_free_coherent()
為對映建立
方法5:
例如知道實體地址 0x72000000 然後用ioremap()做對映(對映到間接對映區)用iounmap()釋放
因為在程式碼裡只能訪問虛擬地址,所以需要把實體地址對映到間接對映區,然後為了不混淆,用的時候對映,不用的時候釋放