Linux高端内存映射(上)【转】

转自:http://blog.csdn.net/vanbreaker/article/details/7579941

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[-]

  1. 高端内存概述
  2. 永久内核映射

高端内存概述

在32位的系统上,内核占有从第3GB~第4GB的线性地址空间,共1GB大小,内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的128M线性地址空间作为访问高于896M的内存的一个窗口。引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于1G时,内核的1G线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于80x86开启PAE的情况下,允许的最大物理内存可达到64G,因此内核将自己的最后128M的线性地址空间腾出来,用以完成对高端内存的暂时性映射。而在64位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。下图描述了内核1GB线性地址空间是如何划分的

其中可以用来完成上述映射目的的区域为vmalloc area,Persistent kernel mappings区域和固定映射线性地址空间中的FIX_KMAP区域,这三个区域对应的映射机制分别为非连续内存分配,永久内核映射和临时内核映射。

永久内核映射

在内核初始化页表管理机制时,专门用pkmap_page_table这个变量保存了PKMAP_BASE对应的页表项的地址,由pkmap_page_table来维护永久内核映射区的页表项的映射,页表项总数为LAST_PKMAP个,具体可以看前面关于页表机制初始化的博文。这里的永久并不是指调用kmap()建立的映射关系会一直持续下去无法解除,而是指在调用kunmap()解除映射之间这种映射会一直存在,这是相对于临时内核映射机制而言的。

内核用一个pkmap_count数组来记录pkmap_page_table中每一个页表项的使用状态,其实就是为每个页表项分配一个计数器来记录相应的页表是否已经被用来映射。计数值分为以下三种情况:

计数值为0:对应的页表项没有映射高端内存,即为空闲可用的

计数值为1:   对应的页表项没有映射高端内存,但是不可用,因为上次映射后对应的TLB项还未被淸刷

计数值为n(n>1):对应的页表项已经映射了一个高端内存页框,并且有n-1个内核成分正在利用这种映射关系

下面结合代码进行具体的分析,先通过alloc_page(__GFP_HIGHMEM)分配到了一个属于高端内存区域的page结构,然后调用kmap(struct page*page)来建立与永久内核映射区的映射,需要注意一点的是,当永久内核映射区没有空闲的页表项可供映射时,请求映射的进程会被阻塞,因此永久内核映射请求不能发生在中断和可延迟函数中。

[cpp] view plain copy

  1. void *kmap(struct page *page)
  2. {
  3. might_sleep();
  4. if (!PageHighMem(page))/*页框属于低端内存*/
  5. return page_address(page);/*返回页框的虚拟地址*/
  6. return kmap_high(page);
  7. }

如果页框是属于高端内存的话,则调用kmap_high()来建立映射

[cpp] view plain copy

  1. void *kmap_high(struct page *page)
  2. {
  3. unsigned long vaddr;
  4. /*
  5. * For highmem pages, we can‘t trust "virtual" until
  6. * after we have the lock.
  7. */
  8. lock_kmap();/*获取自旋锁防止多处理器系统上的并发访问*/
  9. /*试图获取页面的虚拟地址,因为之前可能已经有进程为该页框建立了到永久内核映射区的映射*/
  10. vaddr = (unsigned long)page_address(page);
  11. /*虚拟地址不存在则调用map_new_virtual()为该页框分配一个虚拟地址,完成映射*/
  12. if (!vaddr)
  13. vaddr = map_new_virtual(page);
  14. pkmap_count[PKMAP_NR(vaddr)]++;/*相应的页表项的计数值加1*/
  15. BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
  16. unlock_kmap();
  17. return (void*) vaddr;
  18. }

如果该页框之前没被映射到永久内核映射区,则要通过map_new_virtual()函数在永久内核映射区对应的页表中找到一个空闲的表项来映射这个页框,简单的说就是为这个页框分配一个线性地址。

[cpp] view plain copy

  1. static inline unsigned long map_new_virtual(struct page *page)
  2. {
  3. unsigned long vaddr;
  4. int count;
  5. start:
  6. /*LAST_PKMAP为永久映射区可以映射的页框数,在禁用PAE的情况下为512,开启PAE的情况下为1024,
  7. 也就是说内核通过kmap,一次最多能映射2M/4M的高端内存*/
  8. count = LAST_PKMAP;
  9. /* Find an empty entry */
  10. for (;;) {
  11. /*last_pkmap_nr记录了上次遍历pkmap_count数组找到一个空闲页表项后的位置,首先从
  12. last_pkmap_nr出开始遍历,如果未能在pkmap_count中找到计数值为0的页表项,则last_pkmap_nr
  13. 和LAST_PKMAP_MASK相与后又回到0,进行第二轮遍历*/
  14. last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
  15. /*last_pkmap_nr变为了0,也就是说第一次遍历中未能找到计数值为0的页表项*/
  16. if (!last_pkmap_nr) {
  17. flush_all_zero_pkmaps();
  18. count = LAST_PKMAP;
  19. }
  20. if (!pkmap_count[last_pkmap_nr])/*找到一个计数值为0的页表项,即空闲可用的页表项*/
  21. break;  /* Found a usable entry */
  22. if (--count)
  23. continue;
  24. /*
  25. * Sleep for somebody else to unmap their entries
  26. */
  27. /*在pkmap_count数组中,找不到计数值为0或1的页表项,即所有页表项都被内核映射了,
  28. 则声明一个等待队列,并将当前要求映射高端内存的进程添加到等待队列中然后
  29. 阻塞该进程,等待其他的进程释放了KMAP区的某个页框的映射*/
  30. {
  31. DECLARE_WAITQUEUE(wait, current);
  32. __set_current_state(TASK_UNINTERRUPTIBLE);
  33. add_wait_queue(&pkmap_map_wait, &wait);
  34. unlock_kmap();
  35. schedule();
  36. remove_wait_queue(&pkmap_map_wait, &wait);
  37. lock_kmap();
  38. /* Somebody else might have mapped it while we slept */
  39. /*在睡眠的时候,可能有其他的进程映射了该页面,所以先试图获取页面的虚拟地址,成功的话直接返回*/                                 if(page_address(page))
  40. return (unsigned long)page_address(page);
  41. /* Re-start */
  42. goto start;/*唤醒后重新执行遍历操作*/
  43. }
  44. }
  45. /*寻找到了一个未被映射的页表项,获取该页表项对应的线性地址并赋给vaddr*/
  46. vaddr = PKMAP_ADDR(last_pkmap_nr);
  47. /*将pkmap_page_table中对应的pte设为申请映射的页框的pte,完成永久内核映射区中的页表项到物理页框的映射*/
  48. set_pte_at(&init_mm, vaddr,
  49. &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
  50. pkmap_count[last_pkmap_nr] = 1;
  51. /*设置页面的虚拟地址,将该页面添加到page_address_htable散列表中*/
  52. set_page_address(page, (void *)vaddr);
  53. return vaddr;
  54. }

当第一趟遍历pkmap_count数组找不到计数值为0的页表项时,就要调用flush_all_zero_pkmaps(),将计数值为1的页表项置为0,撤销已经不用了的映射,并且刷新TLB

[cpp] view plain copy

  1. <span style="font-size:12px;">static void flush_all_zero_pkmaps(void)
  2. {
  3. int i;
  4. int need_flush = 0;
  5. flush_cache_kmaps();
  6. for (i = 0; i < LAST_PKMAP; i++) {
  7. struct page *page;
  8. /*
  9. * zero means we don‘t have anything to do,
  10. * >1 means that it is still in use. Only
  11. * a count of 1 means that it is free but
  12. * needs to be unmapped
  13. */
  14. /*将计数值为1的页面的计数值设为0,*/
  15. if (pkmap_count[i] != 1)
  16. continue;
  17. pkmap_count[i] = 0;
  18. /* sanity check */
  19. BUG_ON(pte_none(pkmap_page_table[i]));
  20. /*
  21. * Don‘t need an atomic fetch-and-clear op here;
  22. * no-one has the page mapped, and cannot get at
  23. * its virtual address (and hence PTE) without first
  24. * getting the kmap_lock (which is held here).
  25. * So no dangers, even with speculative execution.
  26. */
  27. /*撤销之前的映射关系,并将page从page_address_htable散列表中删除*/
  28. page = pte_page(pkmap_page_table[i]);
  29. pte_clear(&init_mm, (unsigned long)page_address(page),
  30. &pkmap_page_table[i]);
  31. set_page_address(page, NULL);
  32. need_flush = 1;
  33. }
  34. if (need_flush)/*刷新TLB*/
  35. flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
  36. }</span>

在map_new_virtual()中,当找到一个空闲页表后,还要调用set_page_address()将page与该页表项对应的线性地址进行关联,这里并不是简单的page结构中的virtual给赋上相应的值,而是将该映射的page添加到page_address_htable散列表中,该散列表维护着所有被映射到永久内核映射区的页框,散列表中的每一项记录了页框的page结构地址和映射页框的线性地址。

[cpp] view plain copy

  1. <span style="font-size:12px;">void set_page_address(struct page *page, void *virtual)
  2. {
  3. unsigned long flags;
  4. struct page_address_slot *pas;
  5. struct page_address_map *pam;
  6. BUG_ON(!PageHighMem(page));
  7. pas = page_slot(page);
  8. if (virtual) {      /* Add */
  9. BUG_ON(list_empty(&page_address_pool));
  10. spin_lock_irqsave(&pool_lock, flags);
  11. /*从page_address_pool中得到一个空闲的page_address_map*/
  12. pam = list_entry(page_address_pool.next,
  13. struct page_address_map, list);
  14. list_del(&pam->list);/*将该节点从page_address_pool中删除*/
  15. spin_unlock_irqrestore(&pool_lock, flags);
  16. /*将页框的page结构地址和虚拟地址保存到page_address_map中,可以看到并没有直接设定page的虚拟地址*/
  17. pam->page = page;
  18. pam->virtual = virtual;
  19. spin_lock_irqsave(&pas->lock, flags);
  20. list_add_tail(&pam->list, &pas->lh);/*将pam添入散列表*/
  21. spin_unlock_irqrestore(&pas->lock, flags);
  22. } else {        /* Remove */ /*从散列表中删除一个节点,执行与上面相反的操作*/
  23. spin_lock_irqsave(&pas->lock, flags);
  24. list_for_each_entry(pam, &pas->lh, list) {
  25. if (pam->page == page) {
  26. list_del(&pam->list);
  27. spin_unlock_irqrestore(&pas->lock, flags);
  28. spin_lock_irqsave(&pool_lock, flags);
  29. list_add_tail(&pam->list, &page_address_pool);
  30. spin_unlock_irqrestore(&pool_lock, flags);
  31. goto done;
  32. }
  33. }
  34. spin_unlock_irqrestore(&pas->lock, flags);
  35. }
  36. done:
  37. return;
  38. }
  39. </span>

弄清楚了kmap()为页框建立永久内核映射,那么释放映射的话就容易理解了,当我们需要释放页框的映射时,调用kunmap()函数

[cpp] view plain copy

  1. <span style="font-size:12px;">void kunmap(struct page *page)
  2. {
  3. if (in_interrupt())
  4. BUG();
  5. if (!PageHighMem(page))/*页框处于低端内存则直接返回*/
  6. return;
  7. kunmap_high(page);
  8. }</span>

[cpp] view plain copy

  1. <span style="font-size:12px;">void kunmap_high(struct page *page)
  2. {
  3. unsigned long vaddr;
  4. unsigned long nr;
  5. unsigned long flags;
  6. int need_wakeup;
  7. lock_kmap_any(flags);
  8. vaddr = (unsigned long)page_address(page);
  9. BUG_ON(!vaddr);
  10. nr = PKMAP_NR(vaddr);
  11. /*
  12. * A count must never go down to zero
  13. * without a TLB flush!
  14. */
  15. need_wakeup = 0;
  16. switch (--pkmap_count[nr]) {/*该页面对应的页表项计数值减1*/
  17. case 0:
  18. BUG();
  19. case 1:
  20. /*
  21. * Avoid an unnecessary wake_up() function call.
  22. * The common case is pkmap_count[] == 1, but
  23. * no waiters.
  24. * The tasks queued in the wait-queue are guarded
  25. * by both the lock in the wait-queue-head and by
  26. * the kmap_lock.  As the kmap_lock is held here,
  27. * no need for the wait-queue-head‘s lock.  Simply
  28. * test if the queue is empty.
  29. */
  30. /*确定pkmap_map_wait等待队列是否为空*/
  31. need_wakeup = waitqueue_active(&pkmap_map_wait);
  32. }
  33. unlock_kmap_any(flags);
  34. /* do wake-up, if needed, race-free outside of the spin lock */
  35. if (need_wakeup)/*等待队列不为空则唤醒其中的进程*/
  36. wake_up(&pkmap_map_wait);
  37. }
  38. </span>
时间: 2024-10-08 09:48:12

Linux高端内存映射(上)【转】的相关文章

Linux高端内存映射

概述 在32位的系统上,内核占有从第3GB~第4GB的线性地址空间,共1GB大小,内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的128M线性地址空间作为访问高于896M的内存的一个窗口. 引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于1G时,内核的1G线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于80x86开启PAE的情况下,允许的最大物理内存可达到64G,因此内核将自己的最后128M的线性地址空间腾出来,用以完成对高端

【转载】linux内核笔记之高端内存映射

原文:linux内核笔记之高端内存映射 在32位的系统上,内核使用第3GB~第4GB的线性地址空间,共1GB大小.内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的128M线性地址空间作为访问高于896M的内存的一个窗口. 引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于1G时,内核的1G线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于80x86开启PAE的情况下,允许的最大物理内存可达到64G,因此内核将自己的最后128M的线

高端内存映射之kmap持久内核映射--Linux内存管理(二十)

1 高端内存与内核映射 尽管vmalloc函数族可用于从高端内存域向内核映射页帧(这些在内核空间中通常是无法直接看到的), 但这并不是这些函数的实际用途. 重要的是强调以下事实 : 内核提供了其他函数用于将ZONE_HIGHMEM页帧显式映射到内核空间, 这些函数与vmalloc机制无关. 因此, 这就造成了混乱. 而在高端内存的页不能永久地映射到内核地址空间. 因此, 通过alloc_pages()函数以__GFP_HIGHMEM标志获得的内存页就不可能有逻辑地址. 在x86_32体系结构总,

Linux高端内存

Linux高端内存是针对物理内存来说的,虚拟内存没有高端这个概念.Linux系统将虚拟内存分为两个部分,即用户地 址空间和内核地址空间,对于32位系统来说,虚拟地址空间为4GB,其中用户空间范围为0-3GB,内核空间范围为 3-4GB.Linux将3GB开始的内核虚拟地址空间的896M地址直接映射到物理地址空间的0-896M,这部分是永久性映 射,剩下的128M则可根据需要进行动态映射,也称临时性映射.如果没有动态映射,那么1GB的内核虚拟地址空间最 多只能访问1GB的物理内存,那么如果物理内存

Linux高端内存的由来

抱着拿来主义,自己挑选了部分,以下内容摘自网络. Linux内核地址空间划分 通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间.注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的. Linux内核高端内存的由来 当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0×3,0xc0000004对应的物理地址为0×4,- -,逻辑地址与物理地址对应的关系为

linux高端内存的理解

在linux中,地址空间映射是这样的,把0xc0000000-0xffffffff这1GB内核地址空间划分成2个部分低端的796MB + 高端的128MB,低端796MB就使用f映射,直接映射到物理内存的前796MB上,而高端128MB就用来随时变更g来映射到物理内存超过796MB的范围上,这里对应了3种映射算法:动态映射,永久内核映射,临时映射. 说下"映射"是什么,其实就是x86的内存分页机制,我们只要通过修改分页的页表项就可达到更改 "映射" 的目的. 查看系

linux用户空间和内核空间(内核高端内存)_转

转自:Linux用户空间与内核空间(理解高端内存) 参考: 1. 进程内核栈.用户栈 2. 解惑-Linux内核空间 3. linux kernel学习笔记-5 内存管理 Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中. Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地

Linux内核空间-理解高端内存

Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中. Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图. Linux内核地址空间划分 通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间.注意这里是32位

Linux内核高端内存 转

Linux内核地址映射模型x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图.   Linux内核地址空间划分 通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间.注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的.   Linux内核高端内存的由来 当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc00