高端内存管理--持久映射-固定映射 -临时映射

FROM:http://blog.163.com/yangfan876@126/blog/static/80612456201321531341694

高端内存管理--持久映射  

高端内存页框的内核映射 - yangfan876@126 - yangfan876@126的博客
 
 
上篇介绍了vmalloc区空间的分配,下面介绍持久内核映射,固定映射及临时映射:
 
首先持久内核映射,使用内核页表中的也各专门页表,其地址存放在pkmap_page_table变量中,LAST_PKMAP为其页表项数,这个页表项数在IA32中如果打开PAE则为512,否则为1024。该页表映射的地址从PKMAP_BASE开始到ADDR_START结束,内核中同时会维护一个int型LAST_PKMAP大小的数组,用于表示对应页表的状态:
 
 0 表示对应页表没有映射任何高端内存页框,并且可用。
 1 表示对应页表没有映射任何高端内存页框,但是它不能使用因为自从它最后一次使用以来,其对应的TLB还未被刷新
 n (大于1) 表示相应的页表项映射一个高端内存页框,这意味着有n-1个内核成分在使用这个页框。
 
在内核中也维护着一张散列数组page_address_htable用来组织page_address_map结构体,这个结构体定义如下:

234/*
235 * Describes one page->virtual association
236 */
237struct page_address_map {
238struct page *page;
239void*virtual;
240struct list_head list;
241};

这里面page是指向页描述符的指针,virtual是指向虚拟内存的线性地址。也就是说这个结构体建立了高端内存中page和虚拟空间线性地址的映射。整个上述的关系可以借助《深入linux内核架构》中的一张结构图来表示:
高端内存页框的内核映射 - yangfan876@126 - yangfan876@126的博客
 这张图可以很清除的描述持久映射的管理结构关系。
 
下面介绍几个相关的函数:
 
page_address用于根据参数page页描述符指针来返回对应虚拟空间的线性地址:

262void*page_address(struct page *page)
263{
264unsignedlong flags;
265void*ret;
266struct page_address_slot *pas;
267
268if(!PageHighMem(page))//如果该页描述符不是高端内存的页框则直接调用lowmem_page_address来返回
269return lowmem_page_address(page);//其对应的虚拟地址空间的线性地址
270
271 pas = page_slot(page);//否则通过oage_slot函数查找该页描述符存放在散列表中的表项地址
272 ret = NULL;
273 spin_lock_irqsave(&pas->lock, flags);//获取对应表项的自旋锁
274if(!list_empty(&pas->lh)){//如果该表项之后的链表为空则说明没有建立任何映射关系
275struct page_address_map *pam;
276
277 list_for_each_entry(pam,&pas->lh, list){//如果不为空,则遍历该链表的元素
278if(pam->page == page){//找到所要的页描述符
279 ret = pam->virtual;
280gotodone;
281}
282}
283}
284done:
285 spin_unlock_irqrestore(&pas->lock, flags);
286return ret;
287}

下一个函数是kmap,该函数用于建立内核永久映射:

4void*kmap(struct page *page)
5{
6 might_sleep();
7if(!PageHighMem(page))
8return page_address(page);
9return kmap_high(page);
10}

可以看出该函数会进行判断,如果页描述符在低端内存中则直接调用page_address函数返回其虚拟空间线性地址,否则调用kmap_high来建立高端内存页框于内核页表之间的关系。其中page_address函数定义如下:

166void fastcall *kmap_high(struct page *page)
167{
168unsignedlong vaddr;
169
170/*
171 * For highmem pages, we can't trust "virtual" until
172 * after we have the lock.
173 *
174 * We cannot call this from interrupts, as it may block
175 */
176 spin_lock(&kmap_lock);//获得自旋锁
177 vaddr =(unsignedlong)page_address(page);
178if(!vaddr)//如果该页描述符未被映射则调用map_new_virtual来进行映射
179 vaddr = map_new_virtual(page);
180 pkmap_count[PKMAP_NR(vaddr)]++;//状态记录数组对应的位置值自增,注意即使这里没有执行map_new_virtual也会

//进行自增,因为这表明此页描述符被多个内核成分使用

181 BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
182 spin_unlock(&kmap_lock);
183return(void*) vaddr;
184}

so,这里面比较重要的还是map_new_virtual函数:

116staticinlineunsignedlong map_new_virtual(struct page *page)
117{
118unsignedlong vaddr;
119int count;
120
121 start:
122 count = LAST_PKMAP;
123/* Find an empty entry */
124for(;;){
125 last_pkmap_nr =(last_pkmap_nr +1)& LAST_PKMAP_MASK;//从上次使用的位置开始
126if(!last_pkmap_nr){//如果last_pkmap_nr又从头开始查询则调用flush_all_zero_pkmaps来输出CPU高速缓存,
127 flush_all_zero_pkmaps();//因为这个时候pkmap_count数组中可能会存在一些1值项,即未映射但不可使用页表项
128 count = LAST_PKMAP;
129}
130if(!pkmap_count[last_pkmap_nr])//找到了0值项,即可用页表项
131break;/* Found a usable entry */
132if(--count)
133continue;
134
135/*
136 * Sleep for somebody else to unmap their entries //因为该函数可以睡面,如果没有找到所需页表项则睡眠,直
137 *///到内核的其它线程释放一些页表项,该函数则从start处开始

138 { //重新运行
139 DECLARE_WAITQUEUE(wait, current);
140
141 __set_current_state(TASK_UNINTERRUPTIBLE);
142 add_wait_queue(&pkmap_map_wait,&wait);

143 spin_unlock(&kmap_lock);
144 schedule();
145 remove_wait_queue(&pkmap_map_wait,&wait);
146 spin_lock(&kmap_lock);
147
148/* Somebody else might have mapped it while we slept */
149if(page_address(page))
150return(unsignedlong)page_address(page);
151
152/* Re-start */
153goto start;
154}
155}
156 vaddr = PKMAP_ADDR(last_pkmap_nr);//所需页表项之后保存其线性地址
157 set_pte_at(&init_mm, vaddr,//设置页表项,建立映射
158&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
159
160 pkmap_count[last_pkmap_nr]=1;//因为这里只建立了映射关系,但是TLB中为更新,所以这里置1
161 set_page_address(page,(void*)vaddr);//将该页加入持久映射数据结构中
162
163return vaddr;
164}
最后介绍kunmap函数,用于解除映射,过程正好和kmap相反,就不详细介绍了。但是要注意的是,在kunmap中释放一页就将对应的pkmap_count数组中的值减1,但是在map_new_virtual中设置初始值即为1,所以kunmap是无法将对应值减为0的。也就是说用kunmap是无法完全解除映射关系,这个工作就需要flush_all_zero_pkmaps函数来进行了,这个函数主要做三个工作:
 
1.使用flush_cache_kmaps在内核映射上执行刷出,因为内核的全局页表已经修改。
 2.扫描pkmap_count数组,将所有值为1的项改为0.
3.使用flush_tlb_kernel_range函数刷出所有与PKMAP区域相关的TLB项。

持久映射就先介绍到这,后面会介绍固定映射和临时映射....
 

高端内存管理--固定映射  

 

高端内存管理--固定映射 - yangfan876@126 - yangfan876@126的博客
 
先来看看关于固定映射区的一些宏:

97unsignedlong __FIXADDR_TOP =0xfffff000;
98 EXPORT_SYMBOL(__FIXADDR_TOP);

 
41#define FIXADDR_TOP ((unsignedlong)__FIXADDR_TOP)
 
155#define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
156#define FIXADDR_BOOT_SIZE (__end_of_fixed_addresses << PAGE_SHIFT)
157#define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
固定映射地址在编译时对此类地址的处理类似于常数,内核一启动就为其分配了物理地址,内核会确保在上下文切换期间对应于固定映射的页表项不会从TLB刷出,所以在访问固定映射的内存时都是通过TLB高速缓存取得对应的物理地址,所以访问速度会比普通页面块。
 
关于固定映射的地址都会创建一个常数,在fixed_addresses枚举类型中:

73enum fixed_addresses {
74#ifdef CONFIG_X86_32
75 FIX_HOLE,
76 FIX_VDSO,
77#else
78 VSYSCALL_LAST_PAGE,
79 VSYSCALL_FIRST_PAGE = VSYSCALL_LAST_PAGE
80+((VSYSCALL_END-VSYSCALL_START)>> PAGE_SHIFT)-1,
81 VVAR_PAGE,
82 VSYSCALL_HPET,
83#endif
...........

在内核中通过函数fix_to_virt计算固定映射常数的虚拟地址:

200static __always_inline unsignedlong fix_to_virt(constunsignedint idx)
201{
202/*
203 * this branch gets completely eliminated after inlining,
204 * except when someone tries to use fixaddr indices in an
205 * illegal way. (such as mixing up address types or using
206 * out-of-range indices).
207 *
208 * If it doesn't get removed, the linker will complain
209 * loudly with a reasonably clear error message..
210 */
211if(idx >= __end_of_fixed_addresses)
212 __this_fixmap_does_not_exist();
213
214return __fix_to_virt(idx);
215}

其中__fix_to_virt是个宏,定义如下:

190#define __fix_to_virt(x)(FIXADDR_TOP -((x)<< PAGE_SHIFT))

这个宏表明寻找虚拟地址是从尾部开始向前偏移X页。对于固定映射来说,建立虚拟地址和物理内存页之间的关联是通过set_fixmap和set_fixmao_nocache函数来执行:

178#define set_fixmap(idx, phys) \
179 __set_fixmap(idx, phys, PAGE_KERNEL)

__set_fixmap函数最终会调用__native_set_fixmap来执行具体的映射工作:

431void __native_set_fixmap(enum fixed_addresses idx,pte_t pte)
432{
433unsignedlong address = __fix_to_virt(idx);//寻找idx对应的虚拟空间线性地址
434
435if(idx >= __end_of_fixed_addresses){
436 BUG();
437return;
438}
439 set_pte_vaddr(address, pte);//执行映射操作
440 fixmaps_set++;
441}

对于set_fixmap_nocache来说和set_fixmap不同之处在于在必要的情况下会停用所涉及的页帧的硬件高速缓存。
 

高端内存管理--临时映射   

 
高端内存管理--临时映射 - yangfan876@126 - yangfan876@126的博客
 为什么还是这张图???这里面明明就没有临时映射么.....呵呵....
 
临时映射相对于持久内核映射而言,在持久内核映射中使用函数kmap,这个函数可以睡眠,也就是如果pkmap_count数组中没有可用于分配的位置,这个函数就进入了睡眠状态,直到内核的其它部分释放某些空间,才可以继续运行。但是对于某些操作不能睡眠,解决方法是使用kmap_atomic函数,这个函数不会睡眠。在固定因射中有一部分定义如下:

108#ifdef CONFIG_X86_32
109 FIX_KMAP_BEGIN,/* reserved pte's for temporary kernel mappings */
110 FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,

在这段区域中系统中每个CPU都对应有一个窗口,每个窗口中每种映射类型都对应于一项,这个结构用《深入linux内核结构》中的图可以清晰的表示:
高端内存管理--临时映射 - yangfan876@126 - yangfan876@126的博客
这张图中每个CPU窗口中的项定义为km_type枚举类型:

4#ifdef __WITH_KM_FENCE
5# define KMAP_D(n) __KM_FENCE_##n ,
6#else
7# define KMAP_D(n)
8#endif
9
10enum km_type {
11 KMAP_D(0) KM_BOUNCE_READ,
12 KMAP_D(1) KM_SKB_SUNRPC_DATA,
13 KMAP_D(2) KM_SKB_DATA_SOFTIRQ,
14 KMAP_D(3) KM_USER0,
15 KMAP_D(4) KM_USER1,
16 KMAP_D(5) KM_BIO_SRC_IRQ,
17 KMAP_D(6) KM_BIO_DST_IRQ,
18 KMAP_D(7) KM_PTE0,
19 KMAP_D(8) KM_PTE1,
20 KMAP_D(9) KM_IRQ0,

.............

 好了下面看看kmap_atomic函数:

49void*kmap_atomic(struct page *page,enum km_type type)
50{
51return kmap_atomic_prot(page, type, kmap_prot);
52}

这个函数中要接收两个参数,page为指向高端内存页的页表项指针,type为km_type所定义的类型。好了,说道这对于刚开始所提出的问题就很明白了,临时映射区域是在固定映射区域中的一部分。kmap_atomic_prot函数定义如下:

29void*kmap_atomic_prot(struct page *page,enum km_type type,pgprot_t prot)
30{
31enum fixed_addresses idx;
32unsignedlong vaddr;
33
34/* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */
35 pagefault_disable();
36
37if(!PageHighMem(page))//判断指定页是否在高端内存
38return page_address(page);
39
40 idx = type + KM_TYPE_NR*smp_processor_id();//这两句是为了获取指定的虚拟
41 vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);//空间的线性地址
42 BUG_ON(!pte_none(*(kmap_pte-idx)));
43 set_pte(kmap_pte-idx, mk_pte(page, prot));//建立映射关系
44 arch_flush_lazy_mmu_mode();
45
46return(void*)vaddr;
47}

对于取消映射使用的是函数kunmap_atomic,就不做分析了...
posted @ 2014-05-21 15:25  二流  阅读(1334)  评论(0)    收藏  举报