高端内存(持久内存映射)
使用kmap函数将高端页帧长期映射到内核地址空间中:
/* 参数page是要映射的页 */
void *kmap(struct page *page)
{
/* 判断是不是高端内存 */
if (!PageHighMem(page))
return page_address(page);
might_sleep();
/* 建立映射 */
return kmap_high(page);
}
page_address根据page返回对应的线性地址,这个函数就是区分处理了一下高端内存和非高端内存:
/* 取得对应的线性地址 */
void *page_address(struct page *page)
{
unsigned long flags;
void *ret;
/* 散列数组,具体定义如下 */
struct page_address_slot *pas;
/* 如果不是高端内存就直接返回地址 */
if (!PageHighMem(page))
return lowmem_page_address(page);
/* 如果是高端内存从page_addresss_htable数组中找到对应的项 */
pas = page_slot(page);
spin_lock_irqsave(&pas->lock, flags);
/* page_address_htable通过拉链表的方法来解决冲突,所以还得通过pas来找到正确的值 */
ret = __page_address(pas, page);
spin_unlock_irqrestore(&pas->lock, flags);
return ret;
}
static struct page_address_slot {
struct list_head lh; /* page_address_maps链表 */
spinlock_t lock;
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
/* 该结构用来维护page到线性地址的映射关系 */
static struct page_address_map {
struct page *page;
void *virtual;
struct list_head list;
} page_address_maps[LAST_PKMAP];
在介绍总的流程之前先看看几个单个作用的函数,这个是用来尝试去释放pos对应的项,还要根据返回值指示是否需要刷新缓存:
static int pkmap_try_free(int pos)
{
/* count如果不为1就返回-1 */
if (atomic_cmpxchg(&pkmap_count[pos], 1, 0) != 1)
return -1;
/* 减少空闲数 */
atomic_dec(&pkmap_free);
/* 检查low和high是否为空?是不是如果为空就代表没有映射过?这就代表需要flush TLB */
if (!pte_none(pkmap_page_table[pos])) {
struct page *page = pte_page(pkmap_page_table[pos]);
unsigned long addr = PKMAP_ADDR(pos);
pte_t *ptep = &pkmap_page_table[pos];
VM_BUG_ON(addr != (unsigned long)page_address(page));
/* 设置page对应的位置为NULL,这个是为了发现BUG? */
if (!__set_page_address(page, NULL, pos))
BUG();
flush_kernel_dcache_page(page);
pte_clear(&init_mm, addr, ptep);
return 1;
}
return 0;
}
取得空闲的项的方法是遍历数组:
static int pkmap_get_free(void)
{
int i, pos, flush;
restart:
for (i = 0; i < LAST_PKMAP; i++) {
/* LAST_PKMAP为持久映射的页数,LAST_PKMAP_MASK就是这么多位全被为1,防止溢出?*/
/* 这个是要遍历所有的映射项 */
pos = atomic_inc_return(&pkmap_hand) & LAST_PKMAP_MASK;
/* 尝试去释放该pos */
flush = pkmap_try_free(pos);
/* 如果成果就代表取得了一个地址 */
if (flush >= 0)
goto got_one;
}
/* 等待别人释放所占有的项 */
if (likely(!in_interrupt()))
wait_event(pkmap_wait, atomic_read(&pkmap_free) != 0);
/* 如果找不到的话就一直循环地等待去找 */
goto restart;
got_one:
if (flush) {
#if 0
/* 这一句会执行吗? */
flush_tlb_kernel_range(PKMAP_ADDR(pos), PKMAP_ADDR(pos+1));
#else
int pos2 = (pos + 1) & LAST_PKMAP_MASK;
int nr;
int entries[TLB_BATCH];
for (i = 0, nr = 0; i < LAST_PKMAP && nr < TLB_BATCH; i++, pos2 = (pos2 + 1) & LAST_PKMAP_MASK) {
/* 尝试去释放对应的项,如果没记错的话是去判断计数是不是0 */
flush = pkmap_try_free(pos2);
/* flush=1释放失败 */
if (flush < 0)
continue;
/* flush=1需要刷新缓存 */
if (!flush) {
/* 取得pos2的使用计数*/
atomic_t *counter = &pkmap_count[pos2];
VM_BUG_ON(atomic_read(counter) != 0);
atomic_set(counter, 2);
pkmap_put(counter);
} else {
/* flush=1不需要刷新缓存 */
entries[nr++] = pos2;
}
}
/* 刷新TLB */
flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
for (i = 0; i < nr; i++) {
/* pkmap_free中的值,0表示在被别人独占,1表示可以使用(不管映射与否),n表示正在被n-1个人使用 */
atomic_t *counter = &pkmap_count[entries[i]];
VM_BUG_ON(atomic_read(counter) != 0);
/* 设置counter为2 */
atomic_set(counter, 2);
/* counter减一,pkmap_free加一 */
pkmap_put(counter);
}
#endif
}
return pos;
}
感觉这个函数的名字是有点问题的吧,应该使用来返回page要映射到的位置的?:
static unsigned long pkmap_insert(struct page *page)
{
/* 取得一个空闲的项的位置 */
int pos = pkmap_get_free();
/* #define PKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT)) */
/* 这个应该是去pos的项的地址? */
unsigned long vaddr = PKMAP_ADDR(pos);
/* 取得对应页表项 */
pte_t *ptep = &pkmap_page_table[pos];
pte_t entry = mk_pte(page, kmap_prot);
atomic_t *counter = &pkmap_count[pos];
VM_BUG_ON(atomic_read(counter) != 0);
set_pte_at(&init_mm, vaddr, ptep, entry);
/* 如果设置page的线性地址失败 */
if (unlikely(!__set_page_address(page, (void *)vaddr, pos))) {
/* 有两个在这个选项上时并发的,并且另一个胜利了,那么我们在对方使用之前清除pte(不flush TLB) */
pte_clear(&init_mm, vaddr, ptep);
VM_BUG_ON(atomic_read(counter) != 0);
atomic_set(counter, 2);
/* 减少conter并增加pkmap_free */
pkmap_put(counter);
vaddr = 0;
} else
atomic_set(counter, 2);
/* 返回对应的地址 */
return vaddr;
}
下面就是这个函数真正的入口了:
void * kmap_high(struct page *page)
{
unsigned long vaddr;
/* 通过控制能拥有kmap的进程数来避免死锁 */
kmap_account();
again:
/* 取page对应的线性地址 */
vaddr = (unsigned long)page_address(page);
if (vaddr) {
/* 取对应的计数 */
atomic_t *counter = &pkmap_count[PKMAP_NR(vaddr)];
/* counter的值加1 */
if (atomic_inc_not_zero(counter)) {
/* atomic_inc_not_zero可能导致失败? */
unsigned long vaddr2 = (unsigned long)page_address(page);
/* 证明没有发生错误,返回对应的线性地址 */
if (likely(vaddr == vaddr2))
return (void *)vaddr;
/* 如果发生了错误,把数据变成原来的,重新来一次 */
pkmap_put(counter);
goto again;
}
}
vaddr = pkmap_insert(page);
if (!vaddr)
goto again;
return (void *)vaddr;
}
其实看清楚了每个函数的作用,整个流程还是非常清楚的,不像想象中的复杂。
-------------------------------------
个人理解,欢迎拍砖。