高端内存永久映射分析
2015-10-14 15:51
260 查看
chipset: MSM8X25Q
Codebase: Android4.1
Kernel: 3.4.0
使用的时候一般先通过alloc_page(__GFP_HIGHMEM)申请一个page,然后将这个page传给kmap,kmap会建立这个page的页表项,并返回一个虚拟地址供操作。
Pkmap_count是一个长度为LAST_PKMAP的数组,长度为512,,每个元素对应一个永久映射的页,所以可以映射512*4k = 2M页大小。
[html]
view plaincopy
static int pkmap_count[LAST_PKMAP];
#define LAST_PKMAP PTRS_PER_PTE
#define PTRS_PER_PTE 512
Pkmap_count元素的值不同时的意义对应如下:
0:相关也还没使用
1:表示页已经被映射,但是由于TLB没被更新而无法使用。
>=2:为2时,表示内核有一处使用该映射页。为n时,表示有n-1处使用该页。
另外,内核使用struct page_address_map来保存页和虚拟地址之前的关系。
[html]
view plaincopy
struct page_address_map {
struct page *page;
void *virtual;
struct list_head list;
};
另外,高端内存永久映射是通过page_address_htable这个变量来管理的,结构为:
[html]
view plaincopy
static struct page_address_slot {
struct list_head lh; /* List of page_address_maps */
spinlock_t lock; /* Protect this bucket's list */
}
它的管理机制使用了哈希表,对应的函数是page_slot(),对于哈希原理,可自行查资料学习。
[html]
view plaincopy
static struct page_address_slot *page_slot(const struct page *page)
{
return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}
[html]
view plaincopy
Start_kernel() –> setup_arch() -> paging_init() -> kmap_init()
static void __init kmap_init(void)
{
#ifdef CONFIG_HIGHMEM
/*该变量保存了PKMAP_BASE对应的页表项地址,PKMAP_BASE为永久映射的起始地址。*/
pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),
PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif
}
[html]
view plaincopy
void *kmap(struct page *page)
{
/*会sleep,所以不能用于中断上下文*/
might_sleep();
/*如果是低端内存,那么返回page对应虚拟地址*/
if (!PageHighMem(page))
return page_address(page);
/*否则执行高端内存映射*/
return kmap_high(page);
}
EXPORT_SYMBOL(kmap);
view plaincopy
void *page_address(const struct page *page)
{
unsigned long flags;
void *ret;
struct page_address_slot *pas;
/*又一次判断是低端还是高端内存,因为此函数开放给调用者调用。*/
if (!PageHighMem(page))
/*低端内存就按固定偏移返回虚拟地址*/
return lowmem_page_address(page);
/*高端内存就从哈希表中查找返回。*/
pas = page_slot(page);
ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;
/*页使用lh链表来管理。*/
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}
view plaincopy
void *kmap_high(struct page *page)
{
unsigned long vaddr;
/*
* For highmem pages, we can't trust "virtual" until
* after we have the lock.
*/
lock_kmap();
/*先判断是否已经被映射过了*/
vaddr = (unsigned long)page_address(page);
/*没有就新创建页表项*/
if (!vaddr)
vaddr = map_new_virtual(page);
/*当前页对应的元素值加1. PKMAP_NR表示相对于PKMAP_BASE的偏移。
#define PKMAP_NR(virt) (((virt) - PKMAP_BASE) >> PAGE_SHIFT) */
pkmap_count[PKMAP_NR(vaddr)]++;
/*<2肯定不正常了,因为1表示创建完成,如果跑到这里,2就表示有模块使用此映射了。*/
BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
unlock_kmap();
/*返回映射之后的虚拟地址*/
return (void*) vaddr;
}
view plaincopy
static inline unsigned long map_new_virtual(struct page *page)
{
unsigned long vaddr;
int count;
start:
count = LAST_PKMAP;
/* Find an empty entry */
for (;;) {
/* last_pkmap_nr 记录当前映射的页数,也可以认为是最后使用的位置。*/
last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
/*为0表示查找完一轮了,这时它会去刷新pkmap_count的值为1的TLB。*/
if (!last_pkmap_nr) {
flush_all_zero_pkmaps();
count = LAST_PKMAP;
}
/*找到空闲区了*/
if (!pkmap_count[last_pkmap_nr])
break; /* Found a usable entry */
/*未找到,继续找下一个*/
if (--count)
continue;
/*找了LAST_PKMAP 个之后就睡眠,等待其他模块unmap*/
{
DECLARE_WAITQUEUE(wait, current);
__set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&pkmap_map_wait, &wait);
unlock_kmap();
schedule();
remove_wait_queue(&pkmap_map_wait, &wait);
lock_kmap();
/*醒来之后看看是不是有其他进程已经做了映射了,如果是,
就直接返回。*/
/* Somebody else might have mapped it while we slept */
if (page_address(page))
return (unsigned long)page_address(page);
/*没有就再去重新新一轮查找*/
/* Re-start */
goto start;
}
}
/*从这里看到,永久映射的虚拟地址是继续PKMAP_BASE加上一个offset实现的。*/
vaddr = PKMAP_ADDR(last_pkmap_nr);
/*修改内核页表,将该页与页表进行关联,但还未更新TLB。*/
set_pte_at(&init_mm, vaddr,
&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
/*表示创建映射页表完成*/
pkmap_count[last_pkmap_nr] = 1;
/*将vaddr加入到pkmap_page_table 哈希表和struct page_address_map中管理以供后面调用page_address.*/
set_page_address(page, (void *)vaddr);
return vaddr;
}
view plaincopy
static void flush_all_zero_pkmaps(void)
{
int i;
int need_flush = 0;
flush_cache_kmaps();
for (i = 0; i < LAST_PKMAP; i++) {
struct page *page;
/*0的时候还没创建映射,不用管。
>=2的时候表示还有模块在使用,也不处理。
1表示已经没有模块使用了,即表示已经unmap了,但是页表还没释放,
这里就是针对页进行释放。*/
if (pkmap_count[i] != 1)
continue;
pkmap_count[i] = 0;
page = pte_page(pkmap_page_table[i]);
pte_clear(&init_mm, (unsigned long)page_address(page),
&pkmap_page_table[i]);
set_page_address(page, NULL);
need_flush = 1;
}
/*有改变,刷新永久映射区全部TLB。*/
if (need_flush)
flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
}
Codebase: Android4.1
Kernel: 3.4.0
基本概念:
当你需要将高端页面长期映射到内核空间的时候,就要使用Kmap函数来实现,即高端内存永久映射。这样避免页表和TLB的更新而导致资源的占用。使用的时候一般先通过alloc_page(__GFP_HIGHMEM)申请一个page,然后将这个page传给kmap,kmap会建立这个page的页表项,并返回一个虚拟地址供操作。
Pkmap_count是一个长度为LAST_PKMAP的数组,长度为512,,每个元素对应一个永久映射的页,所以可以映射512*4k = 2M页大小。
[html]
view plaincopy
static int pkmap_count[LAST_PKMAP];
#define LAST_PKMAP PTRS_PER_PTE
#define PTRS_PER_PTE 512
Pkmap_count元素的值不同时的意义对应如下:
0:相关也还没使用
1:表示页已经被映射,但是由于TLB没被更新而无法使用。
>=2:为2时,表示内核有一处使用该映射页。为n时,表示有n-1处使用该页。
另外,内核使用struct page_address_map来保存页和虚拟地址之前的关系。
[html]
view plaincopy
struct page_address_map {
struct page *page;
void *virtual;
struct list_head list;
};
另外,高端内存永久映射是通过page_address_htable这个变量来管理的,结构为:
[html]
view plaincopy
static struct page_address_slot {
struct list_head lh; /* List of page_address_maps */
spinlock_t lock; /* Protect this bucket's list */
}
它的管理机制使用了哈希表,对应的函数是page_slot(),对于哈希原理,可自行查资料学习。
[html]
view plaincopy
static struct page_address_slot *page_slot(const struct page *page)
{
return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}
初始化:
系统开机启动的时候有如下调用:[html]
view plaincopy
Start_kernel() –> setup_arch() -> paging_init() -> kmap_init()
static void __init kmap_init(void)
{
#ifdef CONFIG_HIGHMEM
/*该变量保存了PKMAP_BASE对应的页表项地址,PKMAP_BASE为永久映射的起始地址。*/
pkmap_page_table = early_pte_alloc_and_install(pmd_off_k(PKMAP_BASE),
PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif
}
创建映射:
调用是kmap().[html]
view plaincopy
void *kmap(struct page *page)
{
/*会sleep,所以不能用于中断上下文*/
might_sleep();
/*如果是低端内存,那么返回page对应虚拟地址*/
if (!PageHighMem(page))
return page_address(page);
/*否则执行高端内存映射*/
return kmap_high(page);
}
EXPORT_SYMBOL(kmap);
page_address():
[html]view plaincopy
void *page_address(const struct page *page)
{
unsigned long flags;
void *ret;
struct page_address_slot *pas;
/*又一次判断是低端还是高端内存,因为此函数开放给调用者调用。*/
if (!PageHighMem(page))
/*低端内存就按固定偏移返回虚拟地址*/
return lowmem_page_address(page);
/*高端内存就从哈希表中查找返回。*/
pas = page_slot(page);
ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;
/*页使用lh链表来管理。*/
list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}
kmap_high():
[html]view plaincopy
void *kmap_high(struct page *page)
{
unsigned long vaddr;
/*
* For highmem pages, we can't trust "virtual" until
* after we have the lock.
*/
lock_kmap();
/*先判断是否已经被映射过了*/
vaddr = (unsigned long)page_address(page);
/*没有就新创建页表项*/
if (!vaddr)
vaddr = map_new_virtual(page);
/*当前页对应的元素值加1. PKMAP_NR表示相对于PKMAP_BASE的偏移。
#define PKMAP_NR(virt) (((virt) - PKMAP_BASE) >> PAGE_SHIFT) */
pkmap_count[PKMAP_NR(vaddr)]++;
/*<2肯定不正常了,因为1表示创建完成,如果跑到这里,2就表示有模块使用此映射了。*/
BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
unlock_kmap();
/*返回映射之后的虚拟地址*/
return (void*) vaddr;
}
map_new_virtual():
[html]view plaincopy
static inline unsigned long map_new_virtual(struct page *page)
{
unsigned long vaddr;
int count;
start:
count = LAST_PKMAP;
/* Find an empty entry */
for (;;) {
/* last_pkmap_nr 记录当前映射的页数,也可以认为是最后使用的位置。*/
last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
/*为0表示查找完一轮了,这时它会去刷新pkmap_count的值为1的TLB。*/
if (!last_pkmap_nr) {
flush_all_zero_pkmaps();
count = LAST_PKMAP;
}
/*找到空闲区了*/
if (!pkmap_count[last_pkmap_nr])
break; /* Found a usable entry */
/*未找到,继续找下一个*/
if (--count)
continue;
/*找了LAST_PKMAP 个之后就睡眠,等待其他模块unmap*/
{
DECLARE_WAITQUEUE(wait, current);
__set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&pkmap_map_wait, &wait);
unlock_kmap();
schedule();
remove_wait_queue(&pkmap_map_wait, &wait);
lock_kmap();
/*醒来之后看看是不是有其他进程已经做了映射了,如果是,
就直接返回。*/
/* Somebody else might have mapped it while we slept */
if (page_address(page))
return (unsigned long)page_address(page);
/*没有就再去重新新一轮查找*/
/* Re-start */
goto start;
}
}
/*从这里看到,永久映射的虚拟地址是继续PKMAP_BASE加上一个offset实现的。*/
vaddr = PKMAP_ADDR(last_pkmap_nr);
/*修改内核页表,将该页与页表进行关联,但还未更新TLB。*/
set_pte_at(&init_mm, vaddr,
&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
/*表示创建映射页表完成*/
pkmap_count[last_pkmap_nr] = 1;
/*将vaddr加入到pkmap_page_table 哈希表和struct page_address_map中管理以供后面调用page_address.*/
set_page_address(page, (void *)vaddr);
return vaddr;
}
flush_all_zero_pkmaps():
[html]view plaincopy
static void flush_all_zero_pkmaps(void)
{
int i;
int need_flush = 0;
flush_cache_kmaps();
for (i = 0; i < LAST_PKMAP; i++) {
struct page *page;
/*0的时候还没创建映射,不用管。
>=2的时候表示还有模块在使用,也不处理。
1表示已经没有模块使用了,即表示已经unmap了,但是页表还没释放,
这里就是针对页进行释放。*/
if (pkmap_count[i] != 1)
continue;
pkmap_count[i] = 0;
page = pte_page(pkmap_page_table[i]);
pte_clear(&init_mm, (unsigned long)page_address(page),
&pkmap_page_table[i]);
set_page_address(page, NULL);
need_flush = 1;
}
/*有改变,刷新永久映射区全部TLB。*/
if (need_flush)
flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
}
相关文章推荐
- 深入理解Android之Gradle
- spring中注解的实现原理,帮助理解@autowired @resource区别
- Android游戏开发学习②焰火绽放效果实现方法
- 11g RAC LBA load balancing advisory
- [Android UI] graphics
- Unity3D研究院之手游开发中所有特殊的文件夹
- Myeclipse 全局搜索的时候报错problems encountered during text search
- WIN 系统操作快捷键大全
- Chrome浏览器扩展开发系列之十七:扩展中可用的chrome.events API
- tchar
- Android SDK国内镜像
- OOM(out_of_memory) killer分析
- opengl es学习笔记3(EGL使用流程,EGL命令)
- 【Android学习总结】之Activity:初识Activity及使用
- System.getProperty() 常用值
- 无向图中的深度优先生成森林
- vss简介
- 数组replaceObjectAtIndex
- linux硬盘分区格式化及挂载
- UsbAccessory和UsbDevice的区别