8.3. get_free_page 和其友

2018-02-24 15:49 更新

8.3.?get_free_page 和其友

如果一個(gè)模塊需要分配大塊的內(nèi)存, 它常常最好是使用一個(gè)面向頁的技術(shù). 請(qǐng)求整個(gè)頁也有其他的優(yōu)點(diǎn), 這個(gè)在 15 章介紹.

為分配頁, 下列函數(shù)可用:

get_zeroed_page(unsigned int flags);
返回一個(gè)指向新頁的指針并且用零填充了該頁.

__get_free_page(unsigned int flags);
類似于 get_zeroed_page, 但是沒有清零該頁.

__get_free_pages(unsigned int flags, unsigned int order);
分配并返回一個(gè)指向一個(gè)內(nèi)存區(qū)第一個(gè)字節(jié)的指針, 內(nèi)存區(qū)可能是幾個(gè)(物理上連續(xù))頁長但是沒有清零.

flags 參數(shù)同 kmalloc 的用法相同; 常常使用 GFP_KERNEL 或者 GFP_ATOMIC, 可能帶有 __GFP_DMA 標(biāo)志( 給可能用在 ISA DMA 操作的內(nèi)存 ) 或者 __GFP_HIGHMEM 當(dāng)可能使用高端內(nèi)存時(shí). [29]order 是你在請(qǐng)求的或釋放的頁數(shù)的以 2 為底的對(duì)數(shù)(即, log2N). 例如, 如果你要一個(gè)頁 order 為 0, 如果你請(qǐng)求 8 頁就是 3. 如果 order 太大(沒有那個(gè)大小的連續(xù)區(qū)可用), 頁分配失敗. get_order 函數(shù), 它使用一個(gè)整數(shù)參數(shù), 可以用來從一個(gè) size 中提取 order(它必須是 2 的冪)給主機(jī)平臺(tái). order 允許的最大值是 10 或者 11 (對(duì)應(yīng)于 1024 或者 2048 頁), 依賴于體系. 但是, 一個(gè) order-10 的分配在除了一個(gè)剛剛啟動(dòng)的有很多內(nèi)存的系統(tǒng)中成功的機(jī)會(huì)是小的.

如果你好奇, /proc/buddyinfo 告訴你系統(tǒng)中每個(gè)內(nèi)存區(qū)中的每個(gè) order 有多少塊可用.

當(dāng)一個(gè)程序用完這些頁, 它可以使用下列函數(shù)之一來釋放它們. 第一個(gè)函數(shù)是一個(gè)落回第二個(gè)函數(shù)的宏:


void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);

如果你試圖釋放和你分配的頁數(shù)不同的頁數(shù), 內(nèi)存圖變亂, 系統(tǒng)在后面時(shí)間中有麻煩.

值得強(qiáng)調(diào)一下, __get_free_pages 和其他的函數(shù)可以在任何時(shí)候調(diào)用, 遵循我們看到的 kmalloc 的相同規(guī)則. 這些函數(shù)不能在某些情況下分配內(nèi)存, 特別當(dāng)使用 GFP_ATOMIC 時(shí). 因此, 調(diào)用這些分配函數(shù)的程序必須準(zhǔn)備處理分配失敗.

盡管 kmalloc( GFP_KERNEL )有時(shí)失敗當(dāng)沒有可用內(nèi)存時(shí), 內(nèi)核盡力滿足分配請(qǐng)求. 因此, 容易通過分配太多的內(nèi)存降低系統(tǒng)的響應(yīng). 例如, 你可以通過塞入一個(gè) scull 設(shè)備大量數(shù)據(jù)使計(jì)算機(jī)關(guān)機(jī); 系統(tǒng)開始爬行當(dāng)它試圖換出盡可能多的內(nèi)存來滿足 kmalloc 的請(qǐng)求. 因?yàn)槊總€(gè)資源在被增長的設(shè)備所吞食, 計(jì)算機(jī)很快就被說無法用; 在這點(diǎn)上, 你甚至不能啟動(dòng)一個(gè)新進(jìn)程來試圖處理這個(gè)問題. 我們?cè)?scull 不解釋這個(gè)問題, 因?yàn)樗皇且粋€(gè)例子模塊并且不是一個(gè)真正的放入多用戶系統(tǒng)的工具. 作為一個(gè)程序員, 你必須小心, 因?yàn)橐粋€(gè)模塊是特權(quán)代碼并且可能在系統(tǒng)中開啟新的安全漏洞(最可能是一個(gè)服務(wù)拒絕漏洞好像剛剛描述過的.)

8.3.1.?一個(gè)使用整頁的 scull: scullp

為了真實(shí)地測(cè)試頁分配, 我們已隨其他例子代碼發(fā)布了 scullp 模塊. 它是一個(gè)簡化的 scull, 就像前面介紹過的 scullc.

scullp 分配的內(nèi)存量子是整頁或者頁集合: scullp_order 變量缺省是 0, 但是可以在編譯或加載時(shí)改變.

下列代碼行顯示了它如何分配內(nèi)存:


/* Here's the allocation of a single quantum */
if (!dptr->data[s_pos])
{
        dptr->data[s_pos] =
                (void *)__get_free_pages(GFP_KERNEL, dptr->order);
        if (!dptr->data[s_pos])
                goto nomem;
        memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
}

scullp 中釋放內(nèi)存的代碼看來如此:


/* This code frees a whole quantum-set */
for (i = 0; i < qset; i++)
        if (dptr->data[i])
                free_pages((unsigned long)(dptr->data[i]), dptr->order);

在用戶級(jí)別, 被感覺到的區(qū)別主要是一個(gè)速度提高和更好的內(nèi)存使用, 因?yàn)闆]有內(nèi)部的內(nèi)存碎片. 我們運(yùn)行一些測(cè)試從 scull0 拷貝 4 MB 到 scull1, 并且接著從 scullp0 到 scullp1; 結(jié)果顯示了在內(nèi)核空間處理器使用率有輕微上升.

性能的提高不是激動(dòng)人心的, 因?yàn)?kmalloc 被設(shè)計(jì)為快的. 頁級(jí)別分配的主要優(yōu)勢(shì)實(shí)際上不是速度, 而是更有效的內(nèi)存使用. 按頁分配不浪費(fèi)內(nèi)存, 而使用 kmalloc 由于分配的粒度會(huì)浪費(fèi)無法預(yù)測(cè)數(shù)量的內(nèi)存.

但是 __get_free_page 函數(shù)的最大優(yōu)勢(shì)是獲得的頁完全是你的, 并且你可以, 理論上, 可以通過適當(dāng)?shù)脑O(shè)置頁表來組合這些頁為一個(gè)線性的區(qū)域. 例如, 你可以允許一個(gè)用戶進(jìn)程 mmap 作為單個(gè)不聯(lián)系的頁而獲得的內(nèi)存區(qū). 我們?cè)?15 章討論這種操作, 那里我們展示 scullp 如何提供內(nèi)存映射, 一些 scull 無法提供的東西.

8.3.2.?alloc_pages 接口

為完整起見, 我們介紹另一個(gè)內(nèi)存分配的接口, 盡管我們不會(huì)準(zhǔn)備使用它直到 15 章. 現(xiàn)在, 能夠說 struct page 是一個(gè)描述一個(gè)內(nèi)存頁的內(nèi)部內(nèi)核結(jié)構(gòu). 如同我們將見到的, 在內(nèi)核中有許多地方有必要使用頁結(jié)構(gòu); 它們是特別有用的, 在任何你可能處理高端內(nèi)存的情況下, 高端內(nèi)存在內(nèi)核空間中沒有一個(gè)常量地址.

Linux 頁分配器的真正核心是一個(gè)稱為 alloc_pages_node 的函數(shù):


struct page *alloc_pages_node(int nid, unsigned int flags,
 unsigned int order);

這個(gè)函數(shù)頁有 2 個(gè)變體(是簡單的宏); 它們是你最可能用到的版本:


struct page *alloc_pages(unsigned int flags, unsigned int order);
struct page *alloc_page(unsigned int flags);

核心函數(shù), alloc_pagesnode, 使用 3 個(gè)參數(shù), nid 是要分配內(nèi)存的 NUMA 節(jié)點(diǎn) ID[30], flags 是通常的 GFP 分配標(biāo)志, 以及 order 是分配的大小. 返回值是一個(gè)指向描述分配的內(nèi)存的第一個(gè)(可能許多)頁結(jié)構(gòu)的指針, 或者, 如常, NULL 在失敗時(shí).

alloc_pages 簡化了情況, 通過在當(dāng)前 NUMA 節(jié)點(diǎn)分配內(nèi)存( 它使用 numa_node_id 的返回值作為 nid 參數(shù)調(diào)用 alloc_pages_node). 并且, 當(dāng)然, alloc_pages 省略了 order 參數(shù)并且分配一個(gè)單個(gè)頁.

為釋放這種方式分配的頁, 你應(yīng)當(dāng)使用下列一個(gè):


void __free_page(struct page *page);
void __free_pages(struct page *page, unsigned int order);
void free_hot_page(struct page *page);
void free_cold_page(struct page *page);

如果你對(duì)一個(gè)單個(gè)頁的內(nèi)容是否可能駐留在處理器緩存中有特殊的認(rèn)識(shí), 你應(yīng)當(dāng)使用 free_hot_page (對(duì)于緩存駐留的頁) 或者 free_cold_page 通知內(nèi)核. 這個(gè)信息幫助內(nèi)存分配器在系統(tǒng)中優(yōu)化它的內(nèi)存使用.

8.3.3.?vmalloc 和 其友

我們展示給你的下一個(gè)內(nèi)存分配函數(shù)是 vmlloc, 它在虛擬內(nèi)存空間分配一塊連續(xù)的內(nèi)存區(qū). 盡管這些頁在物理內(nèi)存中不連續(xù) (使用一個(gè)單獨(dú)的對(duì) alloc_page 的調(diào)用來獲得每個(gè)頁), 內(nèi)核看它們作為一個(gè)一個(gè)連續(xù)的地址范圍. vmalloc 返回 0 ( NULL 地址 ) 如果發(fā)生一個(gè)錯(cuò)誤, 否則, 它返回一個(gè)指向一個(gè)大小至少為 size 的連續(xù)內(nèi)存區(qū).

我們這里描述 vmalloc 因?yàn)樗且粋€(gè)基本的 Linux 內(nèi)存分配機(jī)制. 我們應(yīng)當(dāng)注意, 但是, vmalloc 的使用在大部分情況下不鼓勵(lì). 從 vmalloc 獲得的內(nèi)存用起來稍微低效些, 并且, 在某些體系上, 留給 vmalloc 的地址空間的數(shù)量相對(duì)小. 使用 vmalloc 的代碼如果被提交來包含到內(nèi)核中可能會(huì)受到冷遇. 如果可能, 你應(yīng)當(dāng)直接使用單個(gè)頁而不是試圖使用 vmalloc 來掩飾事情.

讓我們看看 vmalloc 如何工作的. 這個(gè)函數(shù)的原型和它相關(guān)的東西(ioremap, 嚴(yán)格地不是一個(gè)分配函數(shù), 在本節(jié)后面討論)是下列:


#include <linux/vmalloc.h> 
void *vmalloc(unsigned long size);
void vfree(void * addr);
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);

值得強(qiáng)調(diào)的是 kmalloc 和 _get_free_pages 返回的內(nèi)存地址也是虛擬地址. 它們的實(shí)際值在它用在尋址物理地址前仍然由 MMU (內(nèi)存管理單元, 常常是 CPU 的一部分)管理.[31] vmalloc 在它如何使用硬件上沒有不同, 不同是在內(nèi)核如何進(jìn)行分配任務(wù)上.

kmalloc 和 __get_free_pages 使用的(虛擬)地址范圍特有一個(gè)一對(duì)一映射到物理內(nèi)存, 可能移位一個(gè)常量 PAGE_OFFSET 值; 這些函數(shù)不需要給這個(gè)地址范圍修改頁表. vmalloc 和 ioremap 使用的地址范圍, 另一方面, 是完全地合成的, 并且每個(gè)分配建立(虛擬)內(nèi)存區(qū)域, 通過適當(dāng)?shù)卦O(shè)置頁表.

這個(gè)區(qū)別可以通過比較分配函數(shù)返回的指針來獲知. 在一些平臺(tái)(例如, x86), vmalloc 返回的地址只是遠(yuǎn)離 kmalloc 使用的地址. 在其他平臺(tái)上(例如, MIPS, IA-64, 以及 x86_64 ), 它們屬于一個(gè)完全不同的地址范圍. 對(duì) vmalloc 可用的地址在從 VMALLOC_START 到 VAMLLOC_END 的范圍中. 2 個(gè)符號(hào)都定義在 <asm/patable.h> 中.

vmalloc 分配的地址不能用于微處理器之外, 因?yàn)樗鼈冎辉谔幚砥鞯?MMU 之上才有意義. 當(dāng)一個(gè)驅(qū)動(dòng)需要一個(gè)真正的物理地址(例如一個(gè) DMA 地址, 被外設(shè)硬件用來驅(qū)動(dòng)系統(tǒng)的總線), 你無法輕易使用 vmalloc. 調(diào)用 vmalloc 的正確時(shí)機(jī)是當(dāng)你在為一個(gè)大的只存在于軟件中的順序緩沖分配內(nèi)存時(shí). 重要的是注意 vamlloc 比 __get_free_pages 有更多開銷, 因?yàn)樗仨毇@取內(nèi)存并且建立頁表. 因此, 調(diào)用 vmalloc 來分配僅僅一頁是無意義的.

在內(nèi)核中使用 vmalloc 的一個(gè)例子函數(shù)是 create_module 系統(tǒng)調(diào)用, 它使用 vmalloc 為在創(chuàng)建的模塊獲得空間. 模塊的代碼和數(shù)據(jù)之后被拷貝到分配的空間中, 使用 copy_from_user. 在這個(gè)方式中, 模塊看來是加載到連續(xù)的內(nèi)存. 你可以驗(yàn)證, 同過看 /proc/kallsyms, 模塊輸出的內(nèi)核符號(hào)位于一個(gè)不同于內(nèi)核自身輸出的符號(hào)的內(nèi)存范圍.

使用 vmalloc 分配的內(nèi)存由 vfree 釋放, 采用和 kfree 釋放由 kmalloc 分配的內(nèi)存的相同方式.

如同 vmalloc, ioremap 建立新頁表; 不同于 vmalloc, 但是, 它實(shí)際上不分配任何內(nèi)存. ioremap 的返回值是一個(gè)特殊的虛擬地址可用來存取特定的物理地址范圍; 獲得的虛擬地址應(yīng)當(dāng)最終通過調(diào)用 iounmap 來釋放.

ioremap 對(duì)于映射一個(gè) PCI 緩沖的(物理)地址到(虛擬)內(nèi)核空間是非常有用的. 例如, 它可用來存取一個(gè) PCI 視頻設(shè)備的幀緩沖; 這樣的緩沖常常被映射在高端物理地址, 在內(nèi)核啟動(dòng)時(shí)建立的頁表的地址范圍之外. PCI 問題在 12 章有詳細(xì)解釋.

由于可移植性, 值得注意的是你不應(yīng)當(dāng)直接存取由 ioremap 返回的地址好像是內(nèi)存指針.你應(yīng)當(dāng)一直使用 readb 和 其他的在第 9 章介紹的 I/O 函數(shù). 這個(gè)要求適用因?yàn)橐恍┢脚_(tái), 例如 Alpha, 無法直接映射 PCI 內(nèi)存區(qū)到處理器地址空間, 由于在 PCI 規(guī)格和 Alpha 處理器之間的在數(shù)據(jù)如何傳送方面的不同.

ioremap 和 vmalloc 是面向頁的(它通過修改頁表來工作); 結(jié)果, 重分配的或者分配的大小被調(diào)整到最近的頁邊界. ioremap 模擬一個(gè)非對(duì)齊的映射通過"向下調(diào)整"被重映射的地址以及通過返回第一個(gè)被重映射頁內(nèi)的偏移.

vmalloc 的一個(gè)小的缺點(diǎn)在于它無法在原子上下文中使用, 因?yàn)? 內(nèi)部地, 它使用 kmalloc(GFP_KERNEL) 來獲取頁表的存儲(chǔ), 并且因此可能睡眠. 這不應(yīng)當(dāng)是一個(gè)問題 -- 如果 __get_free_page 的使用對(duì)于一個(gè)中斷處理不足夠好, 軟件設(shè)計(jì)需要一些清理.

8.3.4.?一個(gè)使用虛擬地址的 scull : scullv

使用 vmalloc 的例子代碼在 scullv 模塊中提供. 如同 scullp, 這個(gè)模塊是一個(gè) scull 的簡化版本, 它使用一個(gè)不同的分配函數(shù)來為設(shè)備存儲(chǔ)數(shù)據(jù)獲得空間.

這個(gè)模塊分配內(nèi)存一次 16 頁. 分配以大塊方式進(jìn)行來獲得比 scullp 更好的性能, 并且來展示一些使用其他分配技術(shù)要花很長時(shí)間的東西是可行的. 使用 __get_free_pages 來分配多于一頁是易于失敗的, 并且就算它成功了, 它可能是慢的. 如同我們前面見到的, vmalloc 在分配幾個(gè)頁時(shí)比其他函數(shù)更快, 但是當(dāng)獲取單個(gè)頁時(shí)有些慢, 因?yàn)轫摫斫⒌拈_銷. scullv 被設(shè)計(jì)象 scullp 一樣. order 指定每個(gè)分配的"級(jí)數(shù)"并且缺省為 4. scullv 和 scullp 之間的位于不同是在分配管理上. 這些代碼行使用 vmalloc 來獲得新內(nèi)存:


/* Allocate a quantum using virtual addresses */
if (!dptr->data[s_pos])
{
        dptr->data[s_pos] =
                (void *)vmalloc(PAGE_SIZE << dptr->order);
        if (!dptr->data[s_pos])
                goto nomem;
        memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
}

以及這些代碼行釋放內(nèi)存:


/* Release the quantum-set */
for (i = 0; i < qset; i++)
        if (dptr->data[i])
                vfree(dptr->data[i]);

如果你在使能調(diào)試的情況下編譯 2 個(gè)模塊, 你能看到它們的數(shù)據(jù)分配通過讀取它們?cè)?/proc 創(chuàng)建的文件. 這個(gè)快照從一套 x86_64 系統(tǒng)上獲得:


salma% cat /tmp/bigfile > /dev/scullp0; head -5 /proc/scullpmem
Device 0: qset 500, order 0, sz 1535135

item at 000001001847da58, qset at 000001001db4c000
0:1001db56000
1:1003d1c7000
salma% cat /tmp/bigfile > /dev/scullv0; head -5 /proc/scullvmem
Device 0: qset 500, order 4, sz 1535135
item at 000001001847da58, qset at 0000010013dea000
0:ffffff0001177000
1:ffffff0001188000

下面的輸出, 相反, 來自 x86 系統(tǒng):


rudo% cat /tmp/bigfile > /dev/scullp0; head -5 /proc/scullpmem
Device 0: qset 500, order 0, sz 1535135
item at ccf80e00, qset at cf7b9800
0:ccc58000
1:cccdd000
rudo% cat /tmp/bigfile > /dev/scullv0; head -5 /proc/scullvmem
Device 0: qset 500, order 4, sz 1535135
item at cfab4800, qset at cf8e4000
0:d087a000
1:d08d2000

這些值顯示了 2 個(gè)不同的行為. 在 x86_64, 物理地址和虛擬地址是完全映射到不同的地址范圍( 0x100 和 0xffffff00), 而在 x86 計(jì)算機(jī)上, vmalloc ;虛擬地址只在物理地址使用的映射之上.

[29] 盡管 alloc_pages (稍后描述)應(yīng)當(dāng)真正地用作分配高端內(nèi)存頁, 由于某些理由我們直到 15 章才真正涉及.

[30] NUMA (非統(tǒng)一內(nèi)存存取) 計(jì)算機(jī)是多處理器系統(tǒng), 這里內(nèi)存對(duì)于特定的處理器組("節(jié)點(diǎn)")是"局部的". 對(duì)局部內(nèi)存的存取比存取非局部內(nèi)存更快. 在這樣的系統(tǒng), 在當(dāng)前節(jié)點(diǎn)分配內(nèi)存是重要的. 驅(qū)動(dòng)作者通常不必?fù)?dān)心 NUMA 問題, 但是.

[31] 實(shí)際上, 一些體系結(jié)構(gòu)定義"虛擬"地址為保留給尋址物理內(nèi)存. 當(dāng)這個(gè)發(fā)生了, Linux 內(nèi)核利用這個(gè)特性, 并且 kernel 和 __get_free_pages 地址都位于這些地址范圍中的一個(gè). 這個(gè)區(qū)別對(duì)設(shè)備驅(qū)動(dòng)和其他的不直接包含到內(nèi)存管理內(nèi)核子系統(tǒng)中的代碼是透明的

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)