摘自:http://blog.csdn.net/kickxxx/article/details/9322701
當(dāng)buddy系統(tǒng)還有大量的連續(xù)物理內(nèi)存時(shí),我們可以通過(guò)__pages_alloc成功分配很大的一塊連續(xù)物理內(nèi)存空間,隨著系統(tǒng)運(yùn)行時(shí)間加長(zhǎng),buddy系統(tǒng)內(nèi)很難中找到一塊大的連續(xù)物理內(nèi)存空間,因此__pages_alloc可能會(huì)失敗,即便通過(guò)kswapd進(jìn)行頁(yè)面的回收和交換,buddy仍然不可避免的碎片化
首先我們要明確的是,連續(xù)物理內(nèi)存的分配并不是必要的。對(duì)于大部分DMA操作,我們的確需要連續(xù)的物理內(nèi)存;但是對(duì)于某些分配內(nèi)存情況:比如,模塊加載,設(shè)備和聲音驅(qū)動(dòng)程序中,可以在內(nèi)核源碼中關(guān)鍵字vmalloc查找,對(duì)vmalloc的使用有個(gè)感性認(rèn)識(shí)。
vmalloc把buddy系統(tǒng)內(nèi)的不連續(xù)物理內(nèi)存,映射到內(nèi)核中一段連續(xù)的地址空間內(nèi),因此對(duì)于那些無(wú)法直接映射的高端物理內(nèi)存Highmem來(lái)說(shuō),vmalloc是主要用途之一(另外一個(gè)用途是應(yīng)用程序的地址映射,之前我一直搞不清它和vmalloc的關(guān)系。實(shí)際二者沒(méi)關(guān)系,只是看起來(lái)很像)。因此vmalloc理應(yīng)優(yōu)先使用廉價(jià)的Highmem內(nèi)存,而把寶貴的低端內(nèi)存,留給其他的內(nèi)核操作。事實(shí)上也是如此,vmalloc實(shí)現(xiàn)函數(shù)的分配標(biāo)志,指明了從Highmem分配
- void *vmalloc(unsigned long size)
- {
- return __vmalloc(size, GFP_KERNEL | <strong>__GFP_HIGHMEM</strong>, PAGE_KERNEL);
- }
對(duì)于vmalloc來(lái)說(shuō)是需要預(yù)留一定的地址空間的,我個(gè)人覺得地址空間也算是一種資源,尤其對(duì)于IA32體系結(jié)構(gòu)和大部分32bit體系結(jié)構(gòu),整個(gè)內(nèi)核地址空間只有1G bytes(3:1 split)。而DMA和Normal內(nèi)存zone 又需要占用數(shù)百M(fèi)的地址空間,參見下面這個(gè)經(jīng)典的kernel地址空間劃分圖
Persistent mappings和Fixmaps地址空間都比較小,這里我們忽略它們,這樣只剩下直接地址映射和VMALLOC區(qū),這個(gè)劃分應(yīng)該是平衡兩個(gè)需求的結(jié)果
1. 盡量增加DMA和Normal區(qū)大小,也就是直接映射地址空間大小,當(dāng)前主流平臺(tái)的內(nèi)存,基本上都超過(guò)了512MB,很多都是標(biāo)配1GB內(nèi)存,因此注定有一部分內(nèi)存無(wú)法進(jìn)行線性映射。
2. 保留一定數(shù)量的VMALLOC大小,這個(gè)值是應(yīng)用平臺(tái)特定的,如果應(yīng)用平臺(tái)某個(gè)驅(qū)動(dòng)需要用vmalloc分配很大的地址空間,那么最好通過(guò)在kernel參數(shù)中指定vmalloc大小的方法,預(yù)留較多的vmalloc地址空間。
3. 并不是Highmem沒(méi)有或者越少越好,這個(gè)是我的個(gè)人理解,理由如下:高端內(nèi)存就像個(gè)垃圾桶和緩沖區(qū),防止來(lái)自用戶空間或者vmalloc的映射破壞Normal zone和DMA zone的連續(xù)性,使得它們碎片化。當(dāng)這個(gè)垃圾桶較大時(shí),那么污染Normal 和DMA的機(jī)會(huì)自然就小了。
下面的圖是VMALLOC地址空間內(nèi)部劃分情況
在直接地址映射和VMALLOC區(qū)之間有一個(gè)8MiB的隔離帶,隔離帶是做什么的呢? 隔離帶是用來(lái)針對(duì)內(nèi)核故障的保護(hù)措施,當(dāng)訪問(wèn)虛擬地址越界時(shí),則會(huì)產(chǎn)生一個(gè)page fault異常,也就是說(shuō)這個(gè)內(nèi)核地址空間沒(méi)有對(duì)應(yīng)相應(yīng)的物理地址,這在內(nèi)核地址空間是不允許的。如果不存在隔離帶,那么越界訪問(wèn)不知不覺的跨越直接映射和VMALLOC區(qū),內(nèi)核卻沒(méi)注意到這個(gè)錯(cuò)誤。
在VMALLOC內(nèi)部,會(huì)劃分為多個(gè)vmalloc_area,每個(gè)vmalloc_area直間有一個(gè)4KB的地址空隙,通過(guò)這個(gè)小的隔離,可以防止不同映射區(qū)直接的越界訪問(wèn)。
數(shù)據(jù)結(jié)構(gòu)
在進(jìn)入vmalloc代碼實(shí)現(xiàn)之前,我們先了解相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
- struct vm_struct {
- /* keep next,addr,size together to speedup lookups */
- struct vm_struct *next;
- void *addr;
- unsigned long size;
- unsigned long flags;
- struct page **pages;
- unsigned int nr_pages;
- unsigned long phys_addr;
- };
內(nèi)核在管理虛擬內(nèi)存地址空間時(shí),必須通過(guò)數(shù)據(jù)結(jié)構(gòu)來(lái)跟蹤哪些子區(qū)域被使用,哪些是空閑的。所有的這些數(shù)據(jù)連接到一個(gè)鏈表中
@next:所有的vm_struct通過(guò)next 組成一個(gè)單鏈表,表頭為全局變量vmlist
@addr:定義了這個(gè)虛擬地址空間子區(qū)域的起始地址
@size:定義了這個(gè)虛擬地址空間子區(qū)域的大小
@flags:存儲(chǔ)了與該內(nèi)存區(qū)關(guān)聯(lián)的標(biāo)志
@pages是一個(gè)指針,指向page指針的數(shù)組,每個(gè)數(shù)組成員都表示一個(gè)映射到這個(gè)地址空間的物理頁(yè)面的實(shí)例。
@nr_pages:page指針數(shù)據(jù)的長(zhǎng)度
@phys_addr:僅當(dāng)用ioremap映射了由物理地址描述的物理內(nèi)存區(qū)域才有效。
注意 vm_struct和vm_area_struct是完全不同的,雖然二者都是做虛擬地址空間映射的:
1. 前者是內(nèi)核虛擬地址空間映射,而后者則是應(yīng)用進(jìn)程虛擬地址空間映射。
2. 前者不會(huì)產(chǎn)生page fault,而后者一般不會(huì)提前分配頁(yè)面,只有當(dāng)訪問(wèn)的時(shí)候,產(chǎn)生page fault來(lái)分配頁(yè)面。
vmalloc 映射示例
下圖給除了vmalloc映射的一個(gè)實(shí)例,這個(gè)vmalloc區(qū)映射了三個(gè)物理內(nèi)存頁(yè)面
從VMALLOC_START+100開始,大小為3*PAGE_SIZE的內(nèi)核地址空間,被映射到物理頁(yè)面725, 1023和7311
vmalloc 代碼實(shí)現(xiàn)
因?yàn)榇蟛糠煮w系結(jié)構(gòu)都支持mmu,這里我們只考慮有mmu的情況。實(shí)際上沒(méi)有mmu支持時(shí),vmalloc就無(wú)法實(shí)現(xiàn)非連續(xù)物理地址到連續(xù)內(nèi)核地址空間的映射,vmalloc退化為kmalloc實(shí)現(xiàn)
- 506 void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot)
- 507 {
- 508 return __vmalloc_node(size, gfp_mask, prot, -1);
- 509 }
- 510 EXPORT_SYMBOL(__vmalloc);
-
-
- 512 /**
- 513 * vmalloc - allocate virtually contiguous memory
- 514 * @size: allocation size
- 515 * Allocate enough pages to cover @size from the page level
- 516 * allocator and map them into contiguous kernel virtual space.
- 517 *
- 518 * For tight control over page level allocator and protection flags
- 519 * use __vmalloc() instead.
- 520 */
- 521 void *vmalloc(unsigned long size)
- 522 {
- 523 return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
- 524 }
- 525 EXPORT_SYMBOL(vmalloc);
非常清楚,vmalloc優(yōu)先使用HIGHMEM內(nèi)存。返回值為內(nèi)核虛擬地址空間地址,這個(gè)地址以及@size決定的分配空間,一定在VMALLOC范圍之內(nèi)。
__vmalloc也僅僅是__vmalloc_node包裝函數(shù)
- 479 /**
- 480 * __vmalloc_node - allocate virtually contiguous memory
- 481 * @size: allocation size
- 482 * @gfp_mask: flags for the page level allocator
- 483 * @prot: protection mask for the allocated pages
- 484 * @node: node to use for allocation or -1
- 485 *
- 486 * Allocate enough pages to cover @size from the page level
- 487 * allocator with @gfp_mask flags. Map them into contiguous
- 488 * kernel virtual space, using a pagetable protection of @prot.
- 489 */
- 490 static void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot,
- 491 int node)
- 492 {
- 493 struct vm_struct *area;
- 494
- 495 size = PAGE_ALIGN(size);
- 496 if (!size || (size >> PAGE_SHIFT) > num_physpages)
- 497 return NULL;
- 498
- 499 area = get_vm_area_node(size, VM_ALLOC, node, gfp_mask);
- 500 if (!area)
- 501 return NULL;
- 502
- 503 return __vmalloc_area_node(area, gfp_mask, prot, node);
- 504 }
495 把請(qǐng)求的@size按照頁(yè)面對(duì)齊,說(shuō)明分配是按照4K對(duì)齊的
非常自然的,分配過(guò)程分為兩個(gè)步驟:1 分配地址空間,2 進(jìn)行映射
499 從VMALLOC地址空間申請(qǐng)一塊合適的地址空間
503 有了地址空間后,就需要對(duì)地址空間進(jìn)行頁(yè)面映射,也就是說(shuō)分配頁(yè)面物理頁(yè)面
分配地址空間
- 263 struct vm_struct *get_vm_area_node(unsigned long size, unsigned long flags,
- 264 int node, gfp_t gfp_mask)
- 265 {
- 266 return __get_vm_area_node(size, flags, VMALLOC_START, VMALLOC_END, node,
- 267 gfp_mask);
- 268 }
在VMALLOC_START和VMALLOC_END指定的范圍內(nèi)查找
- 169 static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,
- 170 unsigned long start, unsigned long end,
- 171 int node, gfp_t gfp_mask)
- 172 {
- 173 struct vm_struct **p, *tmp, *area;
- 174 unsigned long align = 1;
- 175 unsigned long addr;
- 176
- 177 BUG_ON(in_interrupt());
- 178 if (flags & VM_IOREMAP) {
- 179 int bit = fls(size);
- 180
- 181 if (bit > IOREMAP_MAX_ORDER)
- 182 bit = IOREMAP_MAX_ORDER;
- 183 else if (bit < PAGE_SHIFT)
- 184 bit = PAGE_SHIFT;
- 185
- 186 align = 1ul << bit;
- 187 }
- 188 addr = ALIGN(start, align);
- 189 size = PAGE_ALIGN(size);
- 190 if (unlikely(!size))
- 191 return NULL;
- 192
- 193 area = kmalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
- 194
- 195 if (unlikely(!area))
- 196 return NULL;
- 197
- 198 /*
- 199 * We always allocate a guard page.
- 200 */
- 201 size += PAGE_SIZE;
- 202
- 203 write_lock(&vmlist_lock);
- 204 for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {
- 205 if ((unsigned long)tmp->addr < addr) {
- 206 if((unsigned long)tmp->addr + tmp->size >= addr)
- 207 addr = ALIGN(tmp->size +
- 208 (unsigned long)tmp->addr, align);
- 209 continue;
- 210 }
- 211 if ((size + addr) < addr)
- 212 goto out;
- 213 if (size + addr <= (unsigned long)tmp->addr)
- 214 goto found;
- 215 addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
- 216 if (addr > end - size)
- 217 goto out;
- 218 }
- 219
- 220 found:
- 221 area->next = *p;
- 222 *p = area;
- 223
- 224 area->flags = flags;
- 225 area->addr = (void *)addr;
- 226 area->size = size;
- 227 area->pages = NULL;
- 228 area->nr_pages = 0;
- 229 area->phys_addr = 0;
- 230 write_unlock(&vmlist_lock);
- 231
- 232 return area;
- 233
- 234 out:
- 235 write_unlock(&vmlist_lock);
- 236 kfree(area);
- 237 if (printk_ratelimit())
- 238 printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc=<size> to increase size.\n");
- 239 return NULL;
- 240 }
@start是進(jìn)行掃描的首地址,@end是掃描的終止地址。在start和end指定的地址空間內(nèi)分配。
193 首先分配一個(gè)vm_struct 結(jié)構(gòu),因?yàn)檫@個(gè)機(jī)構(gòu)很小,自然使用kmalloc進(jìn)行分配了,至于在哪個(gè)node分配,不用care
198~201 每個(gè)vm_struct之間都有4KB的隔離區(qū),所以這里多分配4KB
204 ~ 218 循環(huán)遍歷已經(jīng)創(chuàng)建的vm_struct區(qū),找到能夠創(chuàng)建地址空間的位置
205 ~ 209 如果start大于當(dāng)前vm_struct的起始位置,那么我們嘗試下一個(gè)。同時(shí)判斷start是否落在這個(gè)vm_struct內(nèi),如果是還要修改start
213 ~ 214 如果size + addr小于當(dāng)前的vm_struct,說(shuō)明匹配了一個(gè)可用位置,直接跳到found標(biāo)號(hào)
221 ~ 222 把這個(gè)vm_struct增加到vmlist中去
從這個(gè)函數(shù)我們可以看出來(lái),vm_struct的分配并不會(huì)考慮最優(yōu)匹配,而是在碰到一個(gè)夠用空間后直接返回。
分配物理頁(yè)面 并映射
__vmalloc_area_node
- 426 void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
- 427 pgprot_t prot, int node)
- 428 {
- 429 struct page **pages;
- 430 unsigned int nr_pages, array_size, i;
- 431
- 432 nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
- 433 array_size = (nr_pages * sizeof(struct page *));
- 434
- 435 area->nr_pages = nr_pages;
- 436 /* Please note that the recursion is strictly bounded. */
- 437 if (array_size > PAGE_SIZE) {
- 438 pages = __vmalloc_node(array_size, gfp_mask | __GFP_ZERO,
- 439 PAGE_KERNEL, node);
- 440 area->flags |= VM_VPAGES;
- 441 } else {
- 442 pages = kmalloc_node(array_size,
- 443 (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO,
- 444 node);
- 445 }
- 446 area->pages = pages;
- 447 if (!area->pages) {
- 448 remove_vm_area(area->addr);
- 449 kfree(area);
- 450 return NULL;
- 451 }
- 452
- 453 for (i = 0; i < area->nr_pages; i++) {
- 454 if (node < 0)
- 455 area->pages[i] = alloc_page(gfp_mask);
- 456 else
- 457 area->pages[i] = alloc_pages_node(node, gfp_mask, 0);
- 458 if (unlikely(!area->pages[i])) {
- 459 /* Successfully allocated i pages, free them in __vunmap() */
- 460 area->nr_pages = i;
- 461 goto fail;
- 462 }
- 463 }
- 464
- 465 if (map_vm_area(area, prot, &pages))
- 466 goto fail;
- 467 return area->addr;
- 468
- 469 fail:
- 470 vfree(area->addr);
- 471 return NULL;
- 472 }
432 根據(jù)area的size計(jì)算需要的物理頁(yè)面數(shù)目,減去1個(gè)PAGE_SIZE是因?yàn)檫@個(gè)vmalloc區(qū)包含一個(gè)4KB的隔離區(qū)
433 ~ 445 為area->pages數(shù)組分配內(nèi)存,理論上一個(gè)頁(yè)面只能保證1000個(gè)page指針,所以area->pages也使用vmalloc分配就很正常了
453 ~ 463調(diào)用alloc_pages_node一個(gè)一個(gè)的分配page,所以vmalloc的分配速度自然沒(méi)有使用alloc_pages的kmalloc高,但是vmalloc的成功率就很高了。
465 目前為止,還有一件事沒(méi)完成,那就是物理地址對(duì)邏輯地址的映射,map_vm_area就是做這事的
線性地址到物理地址映射
- 148 int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)
- 149 {
- 150 pgd_t *pgd;
- 151 unsigned long next;
- 152 unsigned long addr = (unsigned long) area->addr;
- 153 unsigned long end = addr + area->size - PAGE_SIZE;
- 154 int err;
- 155
- 156 BUG_ON(addr >= end);
- 157 pgd = pgd_offset_k(addr);
- 158 do {
- 159 next = pgd_addr_end(addr, end);
- 160 err = vmap_pud_range(pgd, addr, next, prot, pages);
- 161 if (err)
- 162 break;
- 163 } while (pgd++, addr = next, addr != end);
- 164 flush_cache_vmap((unsigned long) area->addr, end);
- 165 return err;
- 166 }
- 167 EXPORT_SYMBOL_GPL(map_vm_area);
153 別忘了,減去隔離區(qū)的一個(gè)PAGE_SIZE大小
158 ~ 163 對(duì)addr和end范圍內(nèi)的頁(yè)表進(jìn)行映射,包括pgd pud pmd和pte。
164 這是一個(gè)體系結(jié)構(gòu)相關(guān)的函數(shù),有些體系結(jié)構(gòu)無(wú)法察覺到頁(yè)表的變化,因此在修改頁(yè)表后,需要程序主動(dòng)的去刷新以下;有些CPU有感知變化的能力,會(huì)自動(dòng)的刷新高速緩存,IA32就是如此。
GOOD,分析完了,vmalloc的代碼還是簡(jiǎn)明清晰的,這次閱讀解決了幾個(gè)以前迷惑的問(wèn)題
1. vmalloc區(qū)域是不是和應(yīng)用空間內(nèi)存映射一樣,通過(guò)page fault來(lái)裝載頁(yè)面的。
答案:不是,vmalloc映射建立好后,邏輯地址,物理頁(yè)面全部分配好,而且頁(yè)表也已經(jīng)更新好,和用戶空間映射完全一樣。這是合理的,因?yàn)槿绻裼脩艨臻g映射那樣,訪問(wèn)地址產(chǎn)生page fault,會(huì)使得vmalloc獲得的內(nèi)存使用上受到極大的限制,比如不能在禁止調(diào)度的地方訪問(wèn)vmalloc分配的地址。