小編給大家分享一下Linux內(nèi)核設備驅(qū)動之內(nèi)存管理的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)是一家集網(wǎng)站建設,大興安嶺企業(yè)網(wǎng)站建設,大興安嶺品牌網(wǎng)站建設,網(wǎng)站定制,大興安嶺網(wǎng)站建設報價,網(wǎng)絡營銷,網(wǎng)絡優(yōu)化,大興安嶺網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
/********************** * linux的內(nèi)存管理 **********************/
到目前為止,內(nèi)存管理是unix內(nèi)核中最復雜的活動。我們簡單介紹一下內(nèi)存管理,并通過實例說明如何在內(nèi)核態(tài)獲得內(nèi)存。
(1)各種地址
對于x86處理器,需要區(qū)分以下三種地址:
*邏輯地址(logical address)
只有x86支持。每個邏輯地址都由一個段(segment)和一個偏移量(offset)組成,偏移量指明了從段的開始到實際地址之間的距離。
邏輯地址共48位,段選擇符16位,偏移量32位。linux對邏輯地址的支持很有限
*線性地址(linear address)
也稱為虛擬地址(virtual address)。
32位無符號整數(shù),從0x0000,0000到0xffff,ffff,共4GB的地址范圍。無論是應用程序還是驅(qū)動程序,我們在程序中使用的地址都是虛擬地址。
*物理地址(physical address)
32位無符號整數(shù),與從CPU的地址引腳發(fā)送到存儲器總線上的電信號相對應。用于存儲器尋址。
找一個程序,如scanf.c,運行兩個,然后執(zhí)行下面指令觀察:
$>pmap $(pid) $>cat /proc/$(pid)/maps
(2)物理內(nèi)存和虛擬內(nèi)存
a.物理內(nèi)存
就是系統(tǒng)中實際存在的RAM,比如我們常說的一條256兆RAM。x86處理器和物理內(nèi)存之間是通過實際的物理線路連接的。
另外,x86處理器還通過主板連接了很多的外設,這些外設也通過實際的物理線路和處理器相連。
對于處理器來說,多數(shù)的外設和RAM的訪問方式是一致的,都是由程序發(fā)出物理地址訪問實際的物理器件。
外設和RAM共享一個4G大小的物理內(nèi)存空間。
b.虛擬內(nèi)存
是在物理內(nèi)存之上為每個進程構架的一種邏輯內(nèi)存,處于應用程序的內(nèi)存請求與硬件內(nèi)存管理單元(Memory Management Unit, MMU) 之間.MMU將應用程序使用的虛擬內(nèi)存根據(jù)預先定義好的頁表轉化為物理地址,然后通過物理地址對實際的外設或RAM進行訪問。
虛擬內(nèi)存有很多用途和優(yōu)點:
*若干個進程可以并發(fā)地執(zhí)行
*應用程序所需內(nèi)存大于物理內(nèi)存時也可以運行
*程序只有部分代碼裝入內(nèi)存時進程可以執(zhí)行它
*允許每個進程訪問可用物理內(nèi)存的一個子集
*進程可以共享庫函數(shù)或程序的一個單獨內(nèi)存映像
*程序是可重定位的,也就是說,可以把程序放在物理內(nèi)存的任何地方
*編程者可以編寫與機器無關的代碼,不必關心物理內(nèi)存的組織結構
(3)RAM的使用
linux將實際的物理RAM劃分為兩部分使用,其中若干兆字節(jié)專門用于存放內(nèi)核映像(也就是內(nèi)核代碼和內(nèi)核靜態(tài)數(shù)據(jù)結構),RAM的其余部分通常由虛擬內(nèi)存系統(tǒng)來處理,并用在以下3種可能的方面:
*滿足內(nèi)核對緩存,描述符和其他動態(tài)內(nèi)核數(shù)據(jù)結構的請求
*滿足進程對一般內(nèi)存區(qū)的請求及對文件內(nèi)存映射的請求
*借助于高速緩存從磁盤及其他緩沖設備獲得較好的性能
虛擬內(nèi)存必須解決的一個主要問題是內(nèi)存碎片,因為通常內(nèi)核使用連續(xù)的物理內(nèi)存,所以碎片過多可能導致請求失敗。
/********************** * 在內(nèi)核中獲取內(nèi)存 **********************/
和在用戶空間中一樣,在內(nèi)核中也可以動態(tài)分配和釋放內(nèi)存,但受到的限制要比用戶空間多一些。
(1)內(nèi)核中的內(nèi)存管理
內(nèi)核把物理頁作為內(nèi)存管理的基本單位。這主要是因為內(nèi)存管理單元(MMU)是以頁為單位進行虛擬地址和物理地址轉換的,從虛擬內(nèi)存的角度來看,頁就是最小單位。大多數(shù)32位體系結構支持4KB的頁。
a.頁
內(nèi)核用struct page表示系統(tǒng)中的每個物理頁。
包括
struct page{ page_flags_t flags; atomic_t _count; atomic_t _mapcount; unsigned long private; struct address_space *mapping; pgoff_t index; struct list_head lru; void *virtual; };
flags用于存放頁的狀態(tài),定義在
page結構與物理頁相關,并非與虛擬頁相關。結構的目的再于描述物理內(nèi)存本身,而不是其中的數(shù)據(jù)。
內(nèi)核根據(jù)page結構來管理系統(tǒng)中所有的頁,內(nèi)核通過page可以知道一個頁是否空閑(也就是頁有沒有被分配)。
如果頁已經(jīng)被分配,內(nèi)核還需要知道誰擁有這個頁。
擁有者可能是用戶空間進程,動態(tài)分配的內(nèi)核數(shù)據(jù),靜態(tài)內(nèi)核代碼,或頁高速緩存等。
系統(tǒng)中的每個物理頁都要分配這樣一個結構。如果結構體40字節(jié)大小,則128MB物理內(nèi)存(4K的頁)需要分配1MB多用于page結構。
b.區(qū)
由于硬件的限制,內(nèi)核不能對所有的頁一視同仁。內(nèi)核使用區(qū)(zone)對具有相似特性的頁進行分組。這些特性包括:
*一些硬件只能用某些特定的內(nèi)存地址來執(zhí)行DMA
*一些體系結構其內(nèi)存的物理尋址范圍遠大于虛擬尋址范圍,這樣,就有一些內(nèi)存不能永久地映射到內(nèi)核空間
針對這些限制,linux采用了三種區(qū)(
ZONE_DMA:這個區(qū)包含的頁能執(zhí)行DMA操作
ZONE_NORMAL:這個區(qū)包含的都是能正常映射的頁
ZONE_HIGHMEM:這個區(qū)包含高端內(nèi)存(大于896M),其中的頁不能永久地映射到內(nèi)核的地址空間
對于x86,這3個區(qū)對于的物理內(nèi)存分別是:
ZONE_DMA: <16MB
ZONE_NORMAL: 16~896MB
ZONE_HIGHMEM: >896MB
見
系統(tǒng)中只有3個這樣的區(qū)結構。
(2)頁分配
內(nèi)核是使用頁進行內(nèi)存管理的,因此,我們在內(nèi)核中也可以要求系統(tǒng)以頁為單位給我們分配內(nèi)存。當然,以頁為單位分配可能造成內(nèi)存浪費,因此,只有在我們確定需要整頁內(nèi)存時才調(diào)用他們。
a.分配
#include1. struct page * alloc_pages( unsigned int gfp_mask, unsigned int order); //分配2的order次方個連續(xù)的物理頁。 2. void *page_address( struct page *page); //返回一個指針,指向給定物理頁當前的虛擬地址 3. unsigned long __get_free_pages( unsigned int gfp_mask, unsigned int order); //相當于上兩個函數(shù)結合 4. struct page * alloc_page( unsigned int gfp_mask); 5. unsigned long __get_free_page( unsigned int gfp_mask); 6. unsigned long get_zeroed_page( unsigned int gfp_mask); //只分配一頁
b.gfp_mask標志
這個標志決定了內(nèi)核在分配內(nèi)存時的行為,以及從哪里分配內(nèi)存。
#include#define GFP_ATOMIC //原子分配,不會休眠,可用于中斷處理。 #define GFP_KERNEL //首選,內(nèi)核可能會睡眠,用在進程上下文中
c.釋放頁
void __free_pages(struct page *page, unsigned int order); void free_pages(unsigned long addr, unsigned int order); void free_page(unsigned long addr);
注意!只能釋放屬于你的頁。錯誤的參數(shù)可能導致內(nèi)核崩潰。
(3)通過kmalloc獲取內(nèi)存
kmalloc和malloc很象,是內(nèi)核中最常用的內(nèi)存分配函數(shù)。
kmalloc不會對分配的內(nèi)存區(qū)域清0,分配的區(qū)域在物理內(nèi)存中是連續(xù)的。
a.分配
#includevoid *kmalloc(size_t size, int flags)
size是要求分配的內(nèi)存的大小
kmalloc的參數(shù)flags可以控制kmalloc分配時的行為。和alloc_page時使用的標志是一致的。注意,kmalloc不能分配高端內(nèi)存
b.釋放
#includevoid kfree(const void *ptr);
如果要釋放的內(nèi)存已經(jīng)被釋放了,或者釋放屬于內(nèi)核其他部分的內(nèi)存,則會產(chǎn)生嚴重的后果。調(diào)用kfree(NULL)是安全的。
要注意!內(nèi)核只能分配一些預定義的,固定大小的字節(jié)數(shù)組。kmalloc能處理的最小內(nèi)存塊是32或64。由于kmalloc分配的內(nèi)存在物理上連續(xù),所以有分配上限,通常不要超過128KB。
(4)通過vmalloc獲得內(nèi)存
vmalloc()分配的內(nèi)存虛擬地址是連續(xù)的,但物理地址不需要連續(xù)。這也是malloc()的分配方式。vmalloc分配非連續(xù)的內(nèi)存塊,再修改頁表,把內(nèi)存映射到邏輯空間連續(xù)的區(qū)域內(nèi)。
大多數(shù)情況下,只有硬件設備需要得到物理地址連續(xù)的內(nèi)存,內(nèi)核可以使用通過vmalloc獲得的內(nèi)存。但內(nèi)核中多采用kmalloc,這主要是考慮性能,因為vmalloc會引起較大的TLB抖動,除非映射大塊內(nèi)存時采用vmalloc。例如模塊動態(tài)加載時,就是加載到通過vmalloc分配的內(nèi)存。
vmalloc在
void* vmalloc(unsigned long size); void vfree(void *addr);
vmalloc會引起睡眠
(5)通過slab機制獲得內(nèi)存
分配和釋放數(shù)據(jù)結構是內(nèi)核最普遍的操作之一。
一種常用的方法是構建一個空閑鏈表,其中包含有可供使用的,已經(jīng)分配好的數(shù)據(jù)結構塊。
每次要分配數(shù)據(jù)結構就不用再申請內(nèi)存,而是直接從這個空閑鏈表中分配數(shù)據(jù)塊,釋放結構時將內(nèi)存還回這個鏈表。
這實際上是一種對象高速緩存(緩存對象).
linux針對這種要求提供了一個slab分配器來完成這一工作。
slab分配器要在幾個基本原則之間尋求平衡:
*頻繁使用的數(shù)據(jù)結構會頻繁分配和釋放,需要緩存
*頻繁分配和回收必然導致內(nèi)存碎片,為避免這一現(xiàn)象,空閑鏈表中的緩存會連續(xù)存放,從而避免碎片
*分配器可以根據(jù)對象大小,頁大小和總的高速緩存大小來進行優(yōu)化
kmalloc就建立在slab之上。
a.創(chuàng)建一個新的高速緩存
#includestruct kmem_cache *kmem_cache_create( const char *name, size_t size, size_t align, unsigned long flags, void(*ctor)(...));
name: 高速緩存的名字。出現(xiàn)在/proc/slabinfo
size: 緩存中每個元素的大小
align: 緩存中第一個對象的偏移,常用0
flags:分配標志。常用SLAB_HWCACHE_ALIGH,表明按cache行對齊,見slab.h
b.銷毀高速緩存
#includevoid kmem_cache_destroy(struct kmem_cache *cachep);
必須在緩存中的所有對象都被釋放后才能調(diào)用。
c.從高速緩存中獲得對象
void *kmem_cache_alloc( struct kmem_cache *cachep, int flags); flags: GFP_KERNEL
d.將對象釋放回高速緩存
void kmem_cache_free( struct kmem_cache *cachep, void *objp);
可參見kernel/fork.c
(6)高端內(nèi)存的映射
在高端內(nèi)存中的頁不能永久地映射到內(nèi)核地址空間,因此,通過alloc_pages()函數(shù)以__GFP_HIGHMEM標志獲得的頁不可能有虛擬地址。需要通過函數(shù)為其動態(tài)分配。
a.映射
要映射一個給定的page結構到內(nèi)核地址空間,可以使用:
void *kmap(struct page *page);
函數(shù)可以睡眠
b.解除映射
void kunmap(struct page* page);
以上是“Linux內(nèi)核設備驅(qū)動之內(nèi)存管理的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道!