Chinaunix首页 | 论坛 | 博客
  • 博客访问: 363248
  • 博文数量: 161
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 345
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-13 11:04
文章分类

全部博文(161)

文章存档

2015年(15)

2014年(144)

2013年(2)

我的朋友

分类: LINUX

2014-11-26 15:49:21

原文地址:内核 kmap_atomic分析 作者:dogsun88

主要谈三个问题。

1、为什么会有这个函数?

我想主要的原因就是kmap_atomic在效率上要比kmap提升不少。

在kmap函数中,会有如下几个比较耗时的部分:

1)、page_address函数
2)、Sleep for somebody else to unmap their entries
代码如下:
171 ____________DECLARE_WAITQUEUE(wait, current);
173 ______________set_current_state(TASK_UNINTERRUPTIBLE);
174____________add_wait_queue(&pkmap_map_wait,&wait);                                                                          
175 ____________unlock_kmap();
176 ____________schedule();
177 ____________remove_wait_queue(&pkmap_map_wait, &wait);
3)、lock_kmap函数,需要加锁啊!!!
相反kmap_atomic函数,从名字就能看出,原子操作一气呵成。。没有sleep,没有锁。

2、怎么实现的?需要考虑什么问题?

首先得知道这个函数的主要目的是实现page 到 vaddr的转化。
其次我们得考虑多cpu和多任务,大家知道     kernel可以在多个cpu上同时运行不同的task,然而它们共同使用一个内存地址空间,因此如何能保证N个cpu调用kmap_atomic不会将page映射到一个虚拟地址(vaddr)呢?

我们来看看kmap_atomic是如何实现的?

1)、定义一个percpu变量__kmap_atomic_idx,同时在当前cpu上禁用抢占,直到unmap的时候才开启,这样就保证了同一cpu其它任务不会调用该函数。除非该进程在unmap之前睡眠,如果真的那样,别的进程就很可能在同一cpu重入kmap_atomic函数了,然后就可能映射到同一虚拟地址,因此在原子映射期间最好不要休眠。
2)、设计了一个完美的公式

    type = kmap_atomic_idx_push();  //递增一个percpu变量,返回递增后的结果,增加一个计算type的函数表示kmap_atomic函数可以重入,就是上面说的该进程在unmap之前睡眠情况,但一般情况下不会发生冲入,所以该值应该是1,unmap时该值会--。当然重入的次数是有限制的,不会超过KM_TYPE_NR。
    idx = type + KM_TYPE_NR*smp_processor_id();//不同的cpusmp_processor_id()的值是不同的,因此,不同的cpu得到的idx不同。
    vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);//不同的idx得到的vaddr就不同了。
    set_pte(kmap_pte-idx, mk_pte(page, prot)); //设置页表
    return (void *)vaddr;

总结,通过percpu变量+禁抢占+加计算公式,就实现了,不同cpu的不同进程调用kmap_atomic函数得到的vaddr是不同的。同时这也给我们实现atomic提供了一种思路,实际上atomic一般都会有一个percpu变量。

3、得到vaddr之后,页表的建立。
我们就以arm为例,来说明一下,arm初始化的时候,会通过create_mapping来创建一些页表,比如map_lowmem就会调用create_mapping函数来建立整个低端内存的映射。

同样地在函数devicemaps_init中会有如下代码:
 967 ____map.pfn = __phys_to_pfn(virt_to_phys(vectors_page));
 968 ____map.virtual = 0xffff0000;
 969 ____map.length = PAGE_SIZE;
 970 ____map.type = MT_HIGH_VECTORS;
 971 ____create_mapping(&map);
这里有一点需要说明一点,虽然只映射了一个PAGE_SIZE,但是如果内核采用的二级页表(即存在pgd,pte),而不是一级页表(只有pgd)的话,会在create_mapping函数中,分配512个pte项(即512个pte指针ptep),远远大于KM_TYPE_NR个数。但是如果采用一级页表的话,这就会有问题,这种情况应该不能用kmap_atomic函数,因为通过TOP_PTE(vaddr)宏是得不到ptep的,因此目前代码中有如下保护:
 67 #ifdef CONFIG_DEBUG_HIGHMEM
 68 ____/*
 69 ____ * With debugging enabled, kunmap_atomic forces that entry to 0.
 70 ____ * Make sure it was indeed properly unmapped.
 71 ____ */
 72____BUG_ON(!pte_none((TOP_PTE(vaddr))));                                                                                    
 73 #endif
 
由此,我们可以看到kmap_atomic使用的是地址空间顶部的一小段地址空间(0xffff0000开始),内核逻辑将这一小段地址空间分成了若干个节(slot),每一节的大小是一个page的大小,可以用来映射一个page。虽然总共可用512个slot,但是只能用KM_TYPE_NR个,有点遗憾!!!



阅读(1237) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~