如果该页框之前没被映射到永久内核映射区,则要通过map_new_virtual()函数在永久内核映射区对应的页表中找到一个空闲的表项来映射这个页框,简单的说就是为这个页框分配一个线性地址。
static inline unsigned long map_new_virtual(struct page *page) { unsigned long vaddr; int count; start: /*LAST_PKMAP为永久映射区可以映射的页框数,在禁用PAE的情况下为512,开启PAE的情况下为1024, 也就是说内核通过kmap,一次最多能映射2M/4M的高端内存*/ count = LAST_PKMAP; /* Find an empty entry */ for (;;) { /*last_pkmap_nr记录了上次遍历pkmap_count数组找到一个空闲页表项后的位置,首先从 last_pkmap_nr出开始遍历,如果未能在pkmap_count中找到计数值为0的页表项,则last_pkmap_nr 和LAST_PKMAP_MASK相与后又回到0,进行第二轮遍历*/ last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK; /*last_pkmap_nr变为了0,也就是说第一次遍历中未能找到计数值为0的页表项*/ if (!last_pkmap_nr) { flush_all_zero_pkmaps(); count = LAST_PKMAP; } if (!pkmap_count[last_pkmap_nr])/*找到一个计数值为0的页表项,即空闲可用的页表项*/ break; /* Found a usable entry */ if (--count) continue; /* * Sleep for somebody else to unmap their entries */ /*在pkmap_count数组中,找不到计数值为0或1的页表项,即所有页表项都被内核映射了, 则声明一个等待队列,并将当前要求映射高端内存的进程添加到等待队列中然后 阻塞该进程,等待其他的进程释放了KMAP区的某个页框的映射*/ { 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;/*唤醒后重新执行遍历操作*/ } } /*寻找到了一个未被映射的页表项,获取该页表项对应的线性地址并赋给vaddr*/ vaddr = PKMAP_ADDR(last_pkmap_nr); /*将pkmap_page_table中对应的pte设为申请映射的页框的pte,完成永久内核映射区中的页表项到物理页框的映射*/ set_pte_at(&init_mm, vaddr, &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); pkmap_count[last_pkmap_nr] = 1; /*设置页面的虚拟地址,将该页面添加到page_address_htable散列表中*/ set_page_address(page, (void *)vaddr); return vaddr; }