Linux高端内存管理之永久内核映射

字体大小: 中小 标准 ->行高大小: 标准

与直接映射的物理内存末端、高端内存的始端所对应的线性地址存放在high_memory变量中,在x86体系结构上,高于896MB的所有物理内存的范围大都是高端内存,它并不会永久地或自动地映射到内核地址空间,尽管x86处理器能够寻址物理RAM的范围达到4GB(启用PAE可以寻址到64GB)。一旦这些页被分配,就必须in射到内核的逻辑地址空间上。在x86上,高端内存中的页被映射到3GB-4GB。

内核可以采用三种不同的机制将页框映射到高端内存;分别叫做永久内核映射、临时内核映射以及非连续内存分配。在这里,只总结前两种技术,第三种技术将在后面总结。

建立永久内核映射可能阻塞当前进程;这发生在空闲页表项不存在时,也就是在高端内存上没有页表项可以用作页框的“窗口”时。因此,永久内核映射不能用于中断处理程序和可延迟函数。相反,建立临时内核映射绝不会要求阻塞当前进程;不过,他的缺点是只有很少的临时内核映射可以同时建立起来。

使用临时内核映射的内核控制路径必须保证当前没有其他的内核控制路径在使用同样地映射。这意味着内核控制路径永远不能被阻塞,后者其他内核控制路径有可能使用同一个窗口来映射其他的高端内存页。

永久内存映射

永久内核映射允许内核建立高端页框到内核地址空间的长期映射。他们使用住内核页表中一个专门的页表,其地址存放在变量pkmap_page_table中,这在前面的页表机制管理区初始化中已经介绍过了。页表中的表项数由LAST_PKMAP宏产生。因此,内核一次最多访问2MB或4MB的高端内存。

[cpp]
  1. /*这里由定义可以看出永久内存映射为固定映射下面的4M空间*/   #define PKMAP_BASE ((FIXADDR_BOOT_START - PAGE_SIZE * (LAST_PKMAP + 1)) \   
  2.             & PMD_MASK)  

该页表映射的线性地址从PKMAP_BASE开始。pkmap_count数组包含LAST_PKMAP个计数器,pkmap_page_table页表中的每一项都有一个。

高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项,对应于映射区内不同的逻辑页面。当分配项的值等于0时为自由项,等于1时为缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系统将进入等待状态。

[cpp]
  1. /*  高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项, 
  2. 对应于映射区内不同的逻辑页面。当分配项的值等于零时为自由项,等于1时为  缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由 
  3. 项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系  统将进入等待状态。 
  4. */   static int pkmap_count[LAST_PKMAP];  
  5. /*last_pkmap_nr:记录上次被分配的页表项在pkmap_page_table里的位置,初始值为0,所以第一次分配的时候last_pkmap_nr等于1*/   static unsigned int last_pkmap_nr;  

为了记录高端内存页框与永久内核映射包含的线性地址之间的联系,内核使用了page_address_htable散列表。该表包含一个page_address_map数据结构,用于为高端内存中的每一个页框进行当前映射。而该数据结构还包含一个指向页描述符的指针和分配给该页框的线性地址。

[cpp]
  1.  * Hash table bucket    */  
  2. static struct page_address_slot {       struct list_head lh;            /* List of page_address_maps */  
  3.     spinlock_t lock;            /* Protect this bucket's list */   } ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];  
  4.    /* 
  5.  * Describes one page->virtual association   */  
  6. struct page_address_map {       struct page *page;  
  7.     void *virtual;       struct list_head list;  
  8. };  

page_address()函数返回页框对应的线性地址

[cpp]
  1.  * Returns the page's virtual address.    */  
  2.  /*返回页框对应的线性地址*/   void *page_address(struct page *page)  
  3. {       unsigned long flags;  
  4.     void *ret;       struct page_address_slot *pas;  
  5.     /*如果页框不在高端内存中*/       if (!PageHighMem(page))  
  6.         /*线性地址总是存在,通过计算页框下标          然后将其转换成物理地址,最后根据相应的 
  7.         物理地址得到线性地址*/           return lowmem_page_address(page);  
  8.     /*从page_address_htable散列表中得到pas*/       pas = page_slot(page);  
  9.     ret = NULL;       spin_lock_irqsave(&pas->lock, flags);  
  10.     if (!list_empty(&pas->lh)) {/*如果对应的链表不空,      该链表中存放的是page_address_map结构*/  
  11.         struct page_address_map *pam;           /*对每个链表中的元素*/  
  12.         list_for_each_entry(pam, &pas->lh, list) {               if (pam->page == page) {  
  13.                 ret = pam->virtual;/*返回线性地址*/                   goto done;  
  14.             }           }  
  15.     }   done:  
  16.     spin_unlock_irqrestore(&pas->lock, flags);       return ret;  
  17. }  

此文章由 http://www.ositren.com 收集整理 ,地址为: http://www.ositren.com/htmls/57860.html