自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux內(nèi)存管理--高端內(nèi)存映射與非連續(xù)內(nèi)存分配

系統(tǒng) Linux
返回頁框線性地址的頁分配函數(shù)對于高端內(nèi)存是無效的,因為高端內(nèi)存不會自動的映射到某個線性地址。內(nèi)核可以采用三種方式來使用高端物理內(nèi)存:永久內(nèi)核映射,臨時內(nèi)核映射和非連續(xù)內(nèi)存分配。

對于32位的機器來說,高于896的物理內(nèi)存在內(nèi)核中屬于高端內(nèi)存,并沒有對內(nèi)存做一一的映射,系統(tǒng)保留了128M的線性地址空間來臨時映射這些高于896M的高端物理內(nèi)存,該線性地址為3G+768m~4G。返回頁框線性地址的頁分配函數(shù)對于高端內(nèi)存是無效的,因為高端內(nèi)存不會自動的映射到某個線性地址。例如__get_free_pages(GFP_HIGH_MEM,0)函數(shù)分配高端內(nèi)存頁框時,返回的是NULL;內(nèi)核可以采用三種方式來使用高端物理內(nèi)存:***內(nèi)核映射,臨時內(nèi)核映射和非連續(xù)內(nèi)存分配。建立***內(nèi)核映射可能會阻塞當前進程的執(zhí)行,這發(fā)生在沒有高端內(nèi)存沒有空閑的頁表項來做映射的情況下,因此在中斷等不能阻塞的代碼中不要使用***內(nèi)核映射。臨時內(nèi)核映射不會發(fā)生阻塞的情況,但必須保證沒有其他的內(nèi)核路徑在使用同樣的臨時內(nèi)核映射。


一、***內(nèi)存映射

***內(nèi)核映射使用的是內(nèi)核主頁表中的一個專門的頁表,其地址存放在pkmap_page_table中,頁表的頁表項由宏LAST_PKMAP產(chǎn)生,頁表中包含512或者1024項。

該頁表映射的線性地址從PKMAP_BASE開始,pkmap_count數(shù)組包含了LAST_PKMAP個計數(shù)器,pkmap_page_table頁表中的每項都有對應(yīng)一個計數(shù)值:

計數(shù)器為0:對應(yīng)的頁表項是空閑可用的。

計數(shù)器為1:對應(yīng)的頁表項沒有映射任何高端內(nèi)存,但是它不能夠使用,因為自從***一次使用以來,其相應(yīng)的TLB尚未被刷新。

計數(shù)器為n:有多個內(nèi)核成分使用該頁表項所對應(yīng)的頁框。

源碼分析:

  1. void fastcall *kmap_high(struct page *page) 
  2. unsigned long vaddr; 
  3.  
  4.  
  5. spin_lock(&kmap_lock); 
  6. //page->virtual記錄了頁框?qū)?yīng)的線性地址 
  7. vaddr = (unsigned long)page_address(page); 
  8. //若頁框未被映射過,分配新的空閑頁表項 
  9. if (!vaddr) 
  10. vaddr = map_new_virtual(page); 
  11. //若是剛分配到了空閑頁表項的話,在map_new_virtual()中其count 
  12. //值被設(shè)置為了1,在這里再次++ 
  13. pkmap_count[PKMAP_NR(vaddr)]++; 
  14. BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2); 
  15. spin_unlock(&kmap_lock); 
  16. return (void*) vaddr; 
  17. static inline unsigned long map_new_virtual(struct page *page) 
  18. unsigned long vaddr; 
  19. int count; 
  20.  
  21.  
  22. start: 
  23. count = LAST_PKMAP; 
  24. //尋找一個空的頁表項 
  25. for (;;) { 
  26. //從上一次找到的空閑頁表項的位置開始尋找 
  27. last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK; 
  28. if (!last_pkmap_nr) { 
  29. flush_all_zero_pkmaps(); 
  30. count = LAST_PKMAP; 
  31. //找到一個未用的空閑頁表項 
  32. if (!pkmap_count[last_pkmap_nr]) 
  33. break;  /* Found a usable entry */ 
  34. //count變?yōu)?的話,意味著當前沒有空閑的頁表項 
  35. if (--count) 
  36. continue
  37. //沒有找到空閑的頁表項,將當前進程加入到等待隊列,進行調(diào)度,直到 
  38. //有空閑的頁表項或者該頁面被別人映射 
  39. DECLARE_WAITQUEUE(wait, current); 
  40.  
  41.  
  42. __set_current_state(TASK_UNINTERRUPTIBLE); 
  43. add_wait_queue(&pkmap_map_wait, &wait); 
  44. spin_unlock(&kmap_lock); 
  45. schedule(); 
  46. remove_wait_queue(&pkmap_map_wait, &wait); 
  47. spin_lock(&kmap_lock); 
  48. //有可能在該進程睡眠期間,有其它進程對該頁面做了內(nèi)存映射 
  49. if (page_address(page)) 
  50. return (unsigned long)page_address(page); 
  51.  
  52.  
  53. /* Re-start */ 
  54. goto start; 
  55. //得到對應(yīng)頁表項對應(yīng)的線性地址 
  56. vaddr = PKMAP_ADDR(last_pkmap_nr); 
  57. //設(shè)置對應(yīng)的頁表項 
  58. set_pte_at(&init_mm, vaddr, 
  59.   &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); 
  60. //設(shè)置***內(nèi)存映射數(shù)組的值 
  61. pkmap_count[last_pkmap_nr] = 1; 
  62. //將page->virtual的值設(shè)為vaddr,ok 
  63. set_page_address(page, (void *)vaddr); 
  64.  
  65.  
  66. return vaddr; 

二、臨時內(nèi)核映射

臨時內(nèi)核映射比較簡單,在內(nèi)核中,為每個cpu都保存了一組頁表項,每個頁表項由一個特定的內(nèi)核成分使用,需要注意的是,不同的內(nèi)核控制路徑不應(yīng)該同時使用一個頁表項,這樣的話,會使后一個內(nèi)核控制路徑將前一個內(nèi)核控制路徑設(shè)置頁表項給沖掉。

建立臨時內(nèi)核映射使用kmap_atomic()函數(shù)。

  1. void *__kmap_atomic(struct page *page, enum km_type type) 
  2. enum fixed_addresses idx; 
  3. unsigned long vaddr; 
  4.  
  5.  
  6. //禁止內(nèi)核搶占,以預(yù)防不同內(nèi)核控制路徑使用同一頁表項 
  7. inc_preempt_count(); 
  8. //非高端內(nèi)存,不用進行高端內(nèi)存映射 
  9. if (!PageHighMem(page)) 
  10. return page_address(page); 
  11. //得到使用的頁表項的下表索引 
  12. idx = type + KM_TYPE_NR*smp_processor_id(); 
  13. //得到相關(guān)頁表項的線性地址 
  14. vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); 
  15. //設(shè)置對應(yīng)的頁表項 
  16. set_pte(kmap_pte-idx, mk_pte(page, kmap_prot)); 
  17. local_flush_tlb_one((unsigned long)vaddr); 
  18.  
  19.  
  20. return (void*) vaddr; 

三、非連續(xù)內(nèi)存分配

下圖顯示了如何使用高于0xc0000000線性地址的線性地址空間:

  1. 內(nèi)存區(qū)的開始部分包含的是對前896MB的RAM進行映射的線性地址,直接映射的物理內(nèi)存的末尾的線性地址保存在high_memory變量中。
  2. 內(nèi)存區(qū)的結(jié)尾位置包含的是固定映射的線性地址。
  3. 從PKMAP_BASE開始,是用于高端內(nèi)存***映射的線性地址。
  4. 其余的線性地址用于非連續(xù)內(nèi)存區(qū),在物理內(nèi)存映射和***個內(nèi)存區(qū)間有一個8M的安全區(qū),用于捕捉對內(nèi)存的越界訪問,同樣道理,插入其它4KB大小的內(nèi)存區(qū)來隔離非連續(xù)內(nèi)存區(qū)。

非連續(xù)內(nèi)存區(qū)描述符數(shù)據(jù)結(jié)構(gòu):

  1. struct vm_struct { 
  2. void     *addr;//內(nèi)存區(qū)***個內(nèi)存單元的線性地址 
  3. unsigned long    size;//內(nèi)存區(qū)的大小加上4K,4K是用來檢查越界的內(nèi)存 
  4. unsigned long     flags;//非連續(xù)內(nèi)存的類型,VM_ALLOC表示使用vmalloc分配的內(nèi)存,VM_MAP表示使用vmap分配的內(nèi)存, 
  5.      //VM_IOREMAP表示用ioremap()分配的內(nèi)存 
  6. struct page  **pages;//非連續(xù)內(nèi)存的的物理頁數(shù)組 
  7. unsigned int     nr_pages;//非連續(xù)內(nèi)存的物理頁的個數(shù) 
  8. unsigned long    phys_addr; 
  9. struct vm_struct    *next;//用來將各個非連續(xù)內(nèi)存描述符串聯(lián)起來 
  10. }; 

1、分配非連續(xù)的內(nèi)存區(qū)

分配函數(shù)主要是vmalloc(),vmap(),vmalloc()會去調(diào)用__vmalloc_node()函數(shù):

  1. void *__vmalloc_node(unsigned long size, gfp_t gfp_mask, pgprot_t prot, 
  2. int node) 
  3. struct vm_struct *area; 
  4. //size要對其為4K的整數(shù)倍,因為非連續(xù)內(nèi)存區(qū)域是將各個物理頁進行映射 
  5. size = PAGE_ALIGN(size); 
  6. if (!size || (size >> PAGE_SHIFT) > num_physpages) 
  7. return NULL; 
  8. //找到一塊空閑的線性地址區(qū)域,用來映射該非連續(xù)內(nèi)存 
  9. area = get_vm_area_node(size, VM_ALLOC, node); 
  10. if (!area) 
  11. return NULL; 
  12.  
  13.  
  14. return __vmalloc_area_node(area, gfp_mask, prot, node); 
  15.  
  16.  
  17. void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask, 
  18. pgprot_t prot, int node) 
  19. struct page **pages; 
  20. unsigned int nr_pages, array_size, i; 
  21. //計算要映射的物理頁數(shù) 
  22. nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT; 
  23. //計算vm_struct中pages數(shù)組的數(shù)組元素個數(shù) 
  24. array_size = (nr_pages * sizeof(struct page *)); 
  25. //記錄下物理頁面的數(shù)目 
  26. area->nr_pages = nr_pages; 
  27. //為vm_struct中的pages數(shù)組分配內(nèi)存 
  28. if (array_size > PAGE_SIZE) { 
  29. pages = __vmalloc_node(array_size, gfp_mask, PAGE_KERNEL, node); 
  30. area->flags |= VM_VPAGES; 
  31. else 
  32. pages = kmalloc_node(array_size, (gfp_mask & ~__GFP_HIGHMEM), node); 
  33. area->pages = pages; 
  34. if (!area->pages) { 
  35. remove_vm_area(area->addr); 
  36. kfree(area); 
  37. return NULL; 
  38. memset(area->pages, 0, array_size); 
  39. //為非連續(xù)內(nèi)存進行頁面的分配,每次分配一個頁面,將其頁框指針記錄在pages數(shù)組中 
  40. for (i = 0; i < area->nr_pages; i++) { 
  41. if (node < 0) 
  42. area->pages[i] = alloc_page(gfp_mask); 
  43. else 
  44. area->pages[i] = alloc_pages_node(node, gfp_mask, 0); 
  45. if (unlikely(!area->pages[i])) { 
  46. /* Successfully allocated i pages, free them in __vunmap() */ 
  47. area->nr_pages = i; 
  48. goto fail; 
  49. //將各個物理頁框映射到分配好的空閑線性區(qū)里面去 
  50. if (map_vm_area(area, prot, &pages)) 
  51. goto fail; 
  52. return area->addr; 
  53.  
  54.  
  55. fail: 
  56. vfree(area->addr); 
  57. return NULL; 

__vmalloc_node()并不觸及當前進程的頁表,因此當內(nèi)核態(tài)進程訪問非連續(xù)內(nèi)存區(qū)時,會發(fā)生缺頁異常,因為對應(yīng)的進程的相應(yīng)地址對應(yīng)的頁表項為空。當缺頁異常發(fā)生時,異常處理程序會到內(nèi)核主頁表(init_mm.pgd頁全局目錄)中去查看是否有對應(yīng)的頁表項,有的話,就會修改當前進程的頁表項,并繼續(xù)進程的執(zhí)行。

2、釋放非連續(xù)的內(nèi)存區(qū)

  1. void vfree(void *addr) 
  2. BUG_ON(in_interrupt()); 
  3. __vunmap(addr, 1); 
  4. void __vunmap(void *addr, int deallocate_pages) 
  5. struct vm_struct *area; 
  6.  
  7.  
  8. if (!addr) 
  9. return
  10. //釋放的地址應(yīng)該是4k的整數(shù)倍 
  11. if ((PAGE_SIZE-1) & (unsigned long)addr) { 
  12. printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr); 
  13. WARN_ON(1); 
  14. return
  15. //移除對應(yīng)的vm_area數(shù)據(jù)描述符,解除對各個物理頁面的頁面映射項 
  16. area = remove_vm_area(addr); 
  17. if (unlikely(!area)) { 
  18. printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n"
  19. addr); 
  20. WARN_ON(1); 
  21. return
  22.  
  23.  
  24. debug_check_no_locks_freed(addr, area->size); 
  25. //需要向伙伴系統(tǒng)歸還非連續(xù)的物理頁 
  26. if (deallocate_pages) { 
  27. int i; 
  28. //將各個物理頁面歸還給伙伴系統(tǒng) 
  29. for (i = 0; i < area->nr_pages; i++) { 
  30. BUG_ON(!area->pages[i]); 
  31. __free_page(area->pages[i]); 
  32.  
  33.  
  34. if (area->flags & VM_VPAGES) 
  35. vfree(area->pages); 
  36. else 
  37. kfree(area->pages); 
  38.  
  39.  
  40. kfree(area); 
  41. return

與vmalloc()一樣,該函數(shù)修改的是主內(nèi)核頁全局目錄和它的頁表表項,內(nèi)核永遠不會回收頁全局,頁上級,頁中間目錄,也不會回收頁表,而進程的頁表會指向這些表項。這樣的話,假設(shè)一個內(nèi)核進程訪問已經(jīng)釋放的非連續(xù)內(nèi)存,最終就會訪問到已經(jīng)被清空的頁表表項,從而引發(fā)缺頁異常,這就是一個錯誤。

責任編輯:奔跑的冰淇淋 來源: ChinaUnix博客
相關(guān)推薦

2009-10-19 09:45:06

linux內(nèi)存內(nèi)存管理

2023-10-18 13:31:00

Linux內(nèi)存

2018-05-18 09:07:43

Linux內(nèi)核內(nèi)存

2013-10-12 11:15:09

Linux運維內(nèi)存管理

2013-10-11 17:32:18

Linux運維內(nèi)存管理

2011-12-20 10:43:21

Java

2009-06-03 15:52:34

堆內(nèi)存棧內(nèi)存Java內(nèi)存分配

2021-07-14 10:00:32

Python內(nèi)存測量

2024-11-07 09:37:46

2018-07-23 09:26:08

iOS內(nèi)存優(yōu)化

2009-06-16 11:11:07

Java內(nèi)存管理Java內(nèi)存泄漏

2021-10-15 08:51:09

Linux內(nèi)存 Kmalloc

2023-09-05 09:36:19

2021-04-27 13:56:49

內(nèi)存.映射地址

2022-03-07 10:54:34

內(nèi)存Linux

2022-08-08 08:31:00

Linux內(nèi)存管理

2013-03-28 09:55:37

Java對象

2017-05-18 16:30:29

Linux內(nèi)存管理

2021-02-28 13:22:54

Java內(nèi)存代碼

2025-04-15 06:00:00

點贊
收藏

51CTO技術(shù)棧公眾號