與做機(jī)器人有關(guān)的網(wǎng)站軟件開發(fā)需要學(xué)什么
接前一篇文章:Linux內(nèi)核與驅(qū)動面試經(jīng)典“小”問題集錦(3)
問題5
問:Linux內(nèi)核中內(nèi)存分配都有哪些方式?它們之間的使用場景都是什么?
備注:這個問題是筆者近期參加蔚來面試時遇到的一個問題。這道題說是一道小題,其實(shí)應(yīng)該是一道大題,它考察的是候選者對于Linux內(nèi)存管理子系統(tǒng)中內(nèi)存分配這一塊的功力深淺。
答:
在Linux內(nèi)核空間中,申請內(nèi)存所涉及的函數(shù)主要包括kmalloc()、__get_free_pages()和vmalloc()等。其中,kmalloc()和__get_free_pages()(及其類似函數(shù))申請的內(nèi)存位于DMA和常規(guī)區(qū)域的映射區(qū),而且在物理上也是連續(xù)的,它們與真實(shí)的物理地址只有一個固定的偏移,因此存在較簡單的轉(zhuǎn)換關(guān)系;而vmalloc()在虛擬內(nèi)存空間給出一塊連續(xù)的內(nèi)存區(qū)。實(shí)質(zhì)上,這片連續(xù)的虛擬內(nèi)存在物理內(nèi)存中并不一定連續(xù),而vmalloc()申請的虛擬內(nèi)存和物理內(nèi)存之間也沒有簡單的換算關(guān)系。
1. kmalloc()
kmalloc函數(shù)在include/linux/slab.h中,代碼如下:
static __always_inline __alloc_size(1) void *kmalloc(size_t size, gfp_t flags)
{if (__builtin_constant_p(size) && size) {unsigned int index;if (size > KMALLOC_MAX_CACHE_SIZE)return kmalloc_large(size, flags);index = kmalloc_index(size);return kmalloc_trace(kmalloc_caches[kmalloc_type(flags, _RET_IP_)][index],flags, size);}return __kmalloc(size, flags);
}
kmalloc函數(shù)的第一個參數(shù)是要分配的塊的大小;第二個參數(shù)為分配標(biāo)志,用于控制kmalloc()的行為。
最常用的分配標(biāo)志是GFP_KERNEL,其含義是在內(nèi)核空間的進(jìn)程中申請內(nèi)存。kmalloc()的底層依賴于__get_free_pages()來實(shí)現(xiàn),分配標(biāo)志的前綴GFP正好是這個底層函數(shù)的縮寫。使用GFP_KERNEL標(biāo)志申請內(nèi)存時,若暫時不能滿足,則進(jìn)程會休眠等待頁,即會引起阻塞,因此不能在中斷上下文或持有自旋鎖的時候使用GFP_KERNEL申請內(nèi)存。
備注:這也是經(jīng)常會被問到的一道經(jīng)典面試題,即GFP_KERNEL能否用在中斷中?或者中斷中應(yīng)該使用哪些標(biāo)志?
由于在中斷處理函數(shù)、tasklet和內(nèi)核定時器等非進(jìn)程上下文中不能阻塞,所以此時驅(qū)動應(yīng)當(dāng)使用GFP_ATOMIC標(biāo)志來申請內(nèi)存。當(dāng)使用GFP_ATOMIC標(biāo)志申請內(nèi)存時,若不存在空閑頁,則不等待,直接返回。
其它的申請標(biāo)志還包括:
- GFP_USER:用來為用戶空間頁分配內(nèi)存,可能阻塞。
- GFP_HIGHUSER:類似于GFP_USER,但它從高端內(nèi)存分配。
- GFP_DMA:從DMA區(qū)域分配內(nèi)存,
- GFP_NOIO:不允許任何I/O初始化。
- GFP_NOFS:不允許任何文件系統(tǒng)調(diào)用。
- __GFP_HIGHMEM:指示分配的內(nèi)存可以位于高端內(nèi)存。
- __GFP_COLD:請求一個較長時間不訪問的頁。
- __GFP_NOWARN:當(dāng)一個分配無法滿足時,阻止內(nèi)核發(fā)出警告。
- __GFP_HIGH:高優(yōu)先級請求,允許獲得被內(nèi)核保留給緊急情況使用的最后的內(nèi)存頁。
- __GFP_REPEAT:分配失敗,則盡力重復(fù)嘗試。
- __GFP_NOFAIL:只許申請成功,不許失敗。不推薦使用此標(biāo)志。
- __GFP_NORETRY:若申請不到,則立即放棄。
使用kmalloc()申請的內(nèi)存應(yīng)該使用kfree()釋放,這個函數(shù)的用法和用戶空間的free()類似。
2. __get_free_pages()
__get_free_pages()系列函數(shù)/宏本質(zhì)上是Linux內(nèi)核最底層用于獲取空閑內(nèi)存的方法,因?yàn)榈讓拥腷uddy(伙伴)算法以2^n頁為單位管理空閑內(nèi)存,因此最底層的內(nèi)存申請總是以2^n頁為單位的。
__get_free_pages()系列函數(shù)/宏包括get_zeroed_page()、__get_free_page()和__get_free_pages()。
- get_zeroed_page()
該函數(shù)返回一個指向新頁的指針,并且將該頁清零。其在mm/page_alloc.c中,代碼如下:
unsigned long get_zeroed_page(gfp_t gfp_mask)
{return __get_free_page(gfp_mask | __GFP_ZERO);
}
EXPORT_SYMBOL(get_zeroed_page);
- __get_free_page();
該宏返回一個指向新頁的指針,但該頁不清零。其定義在include/linux/gfp.h中,如下:是:
#define __get_free_page(gfp_mask) \__get_free_pages((gfp_mask), 0)
它實(shí)際上就是調(diào)用了下邊的__get_free_pages()申請一頁。
- __get_free_pages()
__get_free_pages()也是在mm/page_alloc.c中,代碼如下:
/** Common helper functions. Never use with __GFP_HIGHMEM because the returned* address cannot represent highmem pages. Use alloc_pages and then kmap if* you need to access high mem.*/
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{struct page *page;page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order);if (!page)return 0;return (unsigned long) page_address(page);
}
EXPORT_SYMBOL(__get_free_pages);
該函數(shù)可分配多個頁,并返回所分配內(nèi)存的首地址。分配的頁數(shù)為2^order,分配的頁不清零。oeder允許的最大值是10(1024頁)或者11(2048頁),這取決于具體的硬件平臺。
__get_free_pages()和get_zeroed_page()在實(shí)現(xiàn)中調(diào)用了alloc_pages函數(shù),alloc_pages()既可以在內(nèi)核空間分配,也可以在用戶空間分配。該函數(shù)也在mm/page_alloc.c中,其原型如下:
struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid,nodemask_t *nodemask);
其參數(shù)含義與__get_free_pages()相似,但它返回分配的第一個頁的描述符而非首地址。
3. vmalloc
vmalloc()一般只為存在于軟件中(沒有對應(yīng)的硬件意義)的較大的順序緩沖區(qū)分配內(nèi)存。vmalloc()遠(yuǎn)大于__get_free_pages()的開銷。為了完成vmalloc(),新的頁表項(xiàng)需要被建立。因此,只是調(diào)用vmalloc()來分配少量的內(nèi)存(如1頁以內(nèi)的內(nèi)存)是不妥的。
vmalloc函數(shù)在mm/vmalloc.c中,代碼如下:
/*** vmalloc - allocate virtually contiguous memory* @size: allocation size** Allocate enough pages to cover @size from the page level* allocator and map them into contiguous kernel virtual space.** For tight control over page level allocator and protection flags* use __vmalloc() instead.** Return: pointer to the allocated memory or %NULL on error*/
void *vmalloc(unsigned long size)
{return __vmalloc_node(size, 1, GFP_KERNEL, NUMA_NO_NODE,__builtin_return_address(0));
}
EXPORT_SYMBOL(vmalloc);
vmalloc函數(shù)在申請內(nèi)存時,會進(jìn)行內(nèi)存的映射,改變頁表項(xiàng),而不像kmalloc()實(shí)際用的是開機(jī)過程中就映射好了的DMA和常規(guī)區(qū)域的頁表項(xiàng)。因此,vmalloc()的虛擬地址和物理地址不是一個簡單的線性映射。
vmalloc函數(shù)不能用在原子上下文中,因?yàn)槠鋬?nèi)部實(shí)現(xiàn)使用了標(biāo)志位GFP_KERNEL的kmalloc()。
這里多說一點(diǎn)。關(guān)于kmalloc與vmalloc的區(qū)別,參見筆者的這篇文章:中移(蘇州)軟件技術(shù)有限公司面試問題與解答(7)—— kmalloc與vmalloc的區(qū)別與聯(lián)系及使用場景。
以上是從具體的內(nèi)存分配函數(shù)的角度來說的。從更大的層面來講,Linux內(nèi)核物理內(nèi)存分配的一般方式包括:
(1)伙伴系統(tǒng)(Buddy System)
伙伴系統(tǒng)將物理內(nèi)存劃分為不同大小的塊,每個塊大小都是2的冪次。這些塊被組織成“伙伴”對,每對伙伴的大小是一樣的。
(2)slab分配器
slab分配器用于管理小塊內(nèi)存分配,如內(nèi)核數(shù)據(jù)結(jié)構(gòu)的分配。slab分配器將內(nèi)存劃分為不同的對象緩存,以提高內(nèi)存分配和釋放的效率。
(3)CMA(Contiguous Memory Allocator,連續(xù)內(nèi)存分配器)
對于需要連續(xù)大塊內(nèi)存的需求,Linux引入了CMA。它可以用于分配連續(xù)的物理內(nèi)存區(qū)域,如視頻緩沖等。
(4)頁分配器
Linux內(nèi)核將物理內(nèi)存劃分為固定大小的頁,通常是4KB。當(dāng)進(jìn)程需要內(nèi)存時,內(nèi)核會使用頁分配器來分配這些頁面。
(5)內(nèi)存回收
Linux內(nèi)核還會定期執(zhí)行內(nèi)存回收,以回收未使用的內(nèi)存。這包括清除不再使用的頁面,并將其返回到內(nèi)存池中。
可見,本題雖然看似是一道面試小題,但實(shí)際上其背后蘊(yùn)含的知識點(diǎn)是非常豐富的,也是非??简?yàn)功力的。
參考資料:
《Linux設(shè)備驅(qū)動開發(fā)詳解 —— 基于最新的Linux 4.0內(nèi)核》 宋寶華 編著,機(jī)械工業(yè)出版社