新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > linux下內(nèi)存管理學(xué)習(xí)心得(二)

linux下內(nèi)存管理學(xué)習(xí)心得(二)

作者: 時間:2016-11-23 來源:網(wǎng)絡(luò) 收藏

二、頁面使用API

1、獲取頁:

struct page* alloc_pages(gfp_t gfp_mask,unsigned int order);

該函數(shù)分配2^order個連續(xù)的物理頁,并返回一個指針指向第一個頁的page結(jié)構(gòu)體。

如果想得到該頁的邏輯地址可以用void* page_address(struct page* page);

或者直接申請頁返回第一個頁的邏輯地址:

unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order);

釋放頁:

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

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

void free_page(unsigned long addr);

注意:釋放頁只能釋放你自己的頁。以上都用來申請頁為大小的內(nèi)存。如果申請以字節(jié)為大小的話需要用下面的

kmalloc()

原型:void* kmalloc(size_t size,gfp_t flags);這個分配時物理上時連續(xù)的。注意:我們知道分配內(nèi)存的基本單元是頁這里為什么是字節(jié)呢,這個需要下面的內(nèi)容介紹slab。

對應(yīng)的是kfree();

原型:void kfree(const void *ptr);

vmalloc()函數(shù):

與kmalloc類似,但是vmalloc分配的虛擬地址是連續(xù)的,而物理地址則是無序的。由malloc返回的頁在進程的虛擬地址空間內(nèi)是連續(xù)的,但這并不能保證他們在物理RAM中也是連續(xù)的。kmalloc確保頁在物理地址上時連續(xù)的虛擬地址當然也是連續(xù)的。一般硬件設(shè)備必須要是物理上時連續(xù)的。從性能上講一般來說都是使用kmalloc因為vmalloc的話獲得的頁必須一個一個的映射(因為物理上不是連續(xù)的),這需要專門建立頁表項。

下圖是內(nèi)核中內(nèi)存分配圖:

三、分配字節(jié)與分配頁

1、kmalloc() 分配連續(xù)的物理地址,用于小內(nèi)存分配。
2、__get_free_page() 分配連續(xù)的物理地址,用于整頁分配。

至于為什么說以上函數(shù)分配的是連續(xù)的物理地址和返回的到底是物理地址還是虛擬地址,下面的記錄會做出解釋。
kmalloc() 函數(shù)本身是基于 slab 實現(xiàn)的。slab 是為分配小內(nèi)存提供的一種高效機制。但 slab 這種分配機制又不是獨立的,它本身也是在頁分配器的基礎(chǔ)上來劃分更細粒度的內(nèi)存供調(diào)用者使用。也就是說系統(tǒng)先用頁分配器分配以頁為最小單位的連續(xù)物理地址,然后 kmalloc() 再在這上面根據(jù)調(diào)用者的需要進行切分。
關(guān)于以上論述,我們可以查看 kmalloc() 的實現(xiàn),kmalloc()函數(shù)的實現(xiàn)是在 __do_kmalloc() 中,可以看到在 __do_kmalloc()代碼里最終調(diào)用了 __cache_alloc() 來分配一個 slab,其實
kmem_cache_alloc() 等函數(shù)的實現(xiàn)也是調(diào)用了這個函數(shù)來分配新的 slab。我們按照 __cache_alloc()函數(shù)的調(diào)用路徑一直跟蹤下去會發(fā)現(xiàn)在 cache_grow() 函數(shù)中使用了 kmem_getpages()函數(shù)來分配一個物理頁面,kmem_getpages() 函數(shù)中調(diào)用的alloc_pages_node() 最終是使用 __alloc_pages() 來返回一個struct page 結(jié)構(gòu),而這個結(jié)構(gòu)正是系統(tǒng)用來描述物理頁面的。這樣也就證實了上面所說的,slab 是在物理頁面基礎(chǔ)上實現(xiàn)的。kmalloc() 分配的是物理地址。
__get_free_page() 是頁面分配器提供給調(diào)用者的最底層的內(nèi)存分配函數(shù)。它分配連續(xù)的物理內(nèi)存。__get_free_page() 函數(shù)本身是基于 buddy 實現(xiàn)的。在使用 buddy 實現(xiàn)的物理內(nèi)存管理中最小分配粒度是以頁為單位的。關(guān)于以上論述,我們可以查看__get_free_page()的實現(xiàn),可以看到__get_free_page()函數(shù)只是一個非常簡單的封狀,它的整個函數(shù)實現(xiàn)就是無條件的調(diào)用 __alloc_pages() 函數(shù)來分配物理內(nèi)存,上面記錄 kmalloc()實現(xiàn)時也提到過是在調(diào)用 __alloc_pages() 函數(shù)來分配物理頁面的前提下進行的 slab 管理。那么這個函數(shù)是如何分配到物理頁面又是在什么區(qū)域中進行分配的?回答這個問題只能看下相關(guān)的實現(xiàn)。可以看到在 __alloc_pages() 函數(shù)中,多次嘗試調(diào)用get_page_from_freelist() 函數(shù)從 zonelist 中取得相關(guān) zone,并從其中返回一個可用的 struct page 頁面(這里的有些調(diào)用分支是因為標志不同)。至此,可以知道一個物理頁面的分配是從 zonelist(一個 zone 的結(jié)構(gòu)數(shù)組)中的 zone 返回的。那么 zonelist/zone 是如何與物理頁面關(guān)聯(lián),又是如何初始化的呢?繼續(xù)來看 free_area_init_nodes() 函數(shù),此函數(shù)在系統(tǒng)初始化時由 zone_sizes_init() 函數(shù)間接調(diào)用的,zone_sizes_init()函數(shù)填充了三個區(qū)域:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。并把他們作為參數(shù)調(diào)用 free_area_init_nodes(),在這個函數(shù)中會分配一個 pglist_data 結(jié)構(gòu),此結(jié)構(gòu)中包含了 zonelist/zone結(jié)構(gòu)和一個 struct page 的物理頁結(jié)構(gòu),在函數(shù)最后用此結(jié)構(gòu)作為參數(shù)調(diào)用了 free_area_init_node() 函數(shù),在這個函數(shù)中首先使用 calculate_node_totalpages() 函數(shù)標記 pglist_data 相關(guān)區(qū)域,然后調(diào)用 alloc_node_mem_map() 函數(shù)初始化 pglist_data結(jié)構(gòu)中的 struct page 物理頁。最后使用 free_area_init_core()函數(shù)關(guān)聯(lián) pglist_data 與 zonelist?,F(xiàn)在通以上分析已經(jīng)明確了__get_free_page() 函數(shù)分配物理內(nèi)存的流程。但這里又引出了幾個新問題,那就是此函數(shù)分配的物理頁面是如何映射的?映射到了什么位置?到這里不得不去看下與 VMM 相關(guān)的引導(dǎo)代碼。
在看 VMM 相關(guān)的引導(dǎo)代碼前,先來看一下 virt_to_phys() 與phys_to_virt 這兩個函數(shù)。顧名思義,即是虛擬地址到物理地址和物理地址到虛擬地址的轉(zhuǎn)換。函數(shù)實現(xiàn)十分簡單,前者調(diào)用了__pa( address ) 轉(zhuǎn)換虛擬地址到物理地址,后者調(diào)用 __va(addrress ) 將物理地址轉(zhuǎn)換為虛擬地址。再看下 __pa __va 這兩個宏到底做了什么。
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
通過上面可以看到僅僅是把地址加上或減去 PAGE_OFFSET,而PAGE_OFFSET 在 x86 下定義為 0xC0000000。這里又引出了疑問,在 linux 下寫過 driver 的人都知道,在使用 kmalloc() 與
__get_free_page() 分配完物理地址后,如果想得到正確的物理地址需要使用 virt_to_phys() 進行轉(zhuǎn)換。那么為什么要有這一步呢?我們不分配的不就是物理地址么?怎么分配完成還需要轉(zhuǎn)換?如果返回的是虛擬地址,那么根據(jù)如上對 virt_to_phys() 的分析,為什么僅僅對 PAGE_OFFSET 操作就能實現(xiàn)地址轉(zhuǎn)換呢?虛擬地址與物理地址之間的轉(zhuǎn)換不需要查頁表么?代著以上諸多疑問來看 VMM 相關(guān)的引導(dǎo)代碼。
直接從 start_kernel() 內(nèi)核引導(dǎo)部分來查找 VMM 相關(guān)內(nèi)容??梢钥吹降谝粋€應(yīng)該關(guān)注的函數(shù)是 setup_arch(),在這個函數(shù)當中使用paging_init() 函數(shù)來初始化和映射硬件頁表(在初始化前已有 8M
內(nèi)存被映射,在這里不做記錄),而 paging_init() 則是調(diào)用的pagetable_init() 來完成內(nèi)核物理地址的映射以及相關(guān)內(nèi)存的初始化。在 pagetable_init() 函數(shù)中,首先是一些 PAE/PSE/PGE 相關(guān)判斷
與設(shè)置,然后使用 kernel_physical_mapping_init() 函數(shù)來實現(xiàn)內(nèi)核物理內(nèi)存的映射。在這個函數(shù)中可以很清楚的看到,pgd_idx 是以PAGE_OFFSET 為啟始地址進行映射的,也就是說循環(huán)初始化所有物理地址是以 PAGE_OFFSET 為起點的。繼續(xù)觀察我們可以看到在 PMD 被初始化后,所有地址計算均是以 PAGE_OFFSET 作為標記來遞增的。分析到這里已經(jīng)很明顯的可以看出,物理地址被映射到以 PAGE_OFFSET
開始的虛擬地址空間。這樣以上所有疑問就都有了答案。kmalloc() 與__get_free_page() 所分配的物理頁面被映射到了 PAGE_OFFSET 開始的虛擬地址,也就是說實際物理地址與虛擬地址有一組一一對應(yīng)的關(guān)系,
正是因為有了這種映射關(guān)系,對內(nèi)核以 PAGE_OFFSET 啟始的虛擬地址的分配也就是對物理地址的分配(當然這有一定的范圍,應(yīng)該在 PAGE_OFFSET與 VMALLOC_START 之間,后者為 vmalloc() 函數(shù)分配內(nèi)存的啟始地址)。這也就解釋了為什么 virt_to_phys() 與 phys_to_virt() 函數(shù)的實現(xiàn)僅僅是加/減 PAGE_OFFSET 即可在虛擬地址與物理地址之間轉(zhuǎn)換,正是因為了有了這種映射,且固定不變,所以才不用去查頁表進行轉(zhuǎn)換。這也同樣回答了開始的問題,即 kmalloc() / __get_free_page() 分配的是物理地址,而返回的則是虛擬地址(雖然這聽上去有些別扭)。正是因為有了這種映射關(guān)系,所以需要將它們的返回地址減去 PAGE_OFFSET 才可以得到真正的物理地址。(此處參考:http://linux.chinaunix.net/techdoc/develop/2007/04/17/955506.shtml)。


上一頁 1 2 下一頁

評論


技術(shù)專區(qū)

關(guān)閉