ULK3 内存分配( 高端内存映射)
下面两个函数返回page指针:
alloc_pages(gfp_mask,order);
alloc_page(gfp_mask);
下面几个函数返回线性地址:
__get_free_pages(gfp_mask,order);
__get_free_page(gfp_mask);
get_zeroed_page(gfp_mask);
__get_dma_pages(gfp_mask, order);
高端内存页框的映射:
永久映射:kmap 可能会休眠
临时映射:kmap_atomic 不会休眠
非连续内存分配
永久内核映射
永久内核映射允许内核建立高端页框到内核线性地址空间(128M)的长期映射。
主要使用主内核的一个专门页表:
pkmap_page_table
页表中的项数:
LAST_PKMAP
该页表映射的线性地址(开始):
PKMAP_BASE
pkmap_count:
这个数组包含LAST_PKMAP个计数器
计数器含义:
计数器为0:对应的页表没有映射高端内存也框,而且可用
计数器为1:对应的页表没有映射任何高端内存也框,但是它不可用,因为对应的TLB还没有刷新
计数器为N:共有N-1个内核成分在使用该页框
kmap函数分析
arch/i386/mm/highmem.h void *kmap(struct page *)
void *kmap(struct page *page){
might_sleep();
if (!PageHighMem(page)) /* 如果不是高端内存 则直接返回它的线性地址 */
return page_address(page);
return kmap_high(page); /* 否则调用kmap_high(page) 函数*/
mm/highmem.c void fastcall *kmap_high(struct page *page)
void fastcall *kmap_high(struct page *page)
{
unsigned long vaddr;
spin_lock(&kmap_lock); /* 获取自旋锁 */
vaddr = (unsigned long)page_address(page); /* 尝试获取该page的线性地址 */
if (!vaddr) /* 已经建立映射,则直接返回线性地址 */
vaddr = map_new_virtual(page); /* 否则调用map_new_virtual函数 */
pkmap_count[PKMAP_NR(vaddr)]++; /* 在pkmap_page_table空闲的页表项*/
if (pkmap_count[PKMAP_NR(vaddr)] < 2)
BUG();
spin_unlock(&kmap_lock);
return (void*) vaddr;
}
mm/highmem.h static inline unsigned long map_new_virtual(struct page *)
static inline unsigned long map_new_virtual(struct page *page)
{
unsigned long vaddr;
int count;
start:
count = LAST_PKMAP;
/* Find an empty entry */
for (;;) { /* 大的for循环为了找出空闲页表项 */
last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
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;
{
DECLARE_WAITQUEUE(wait, current); /* 如果找不到则休眠*/
__set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&pkmap_map_wait, &wait);
spin_unlock(&kmap_lock);
schedule();
remove_wait_queue(&pkmap_map_wait, &wait);
spin_lock(&kmap_lock);
if (page_address(page)) /* 在休眠时 可能有的内核成分 完成了该页框*/
return (unsigned long)page_address(page); /*映射*/
goto start;
}
}
vaddr = PKMAP_ADDR(last_pkmap_nr); /* 获取PKMAP_BASE开始的空闲线性地址*/
set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot)); /*设置页表*/
pkmap_count[last_pkmap_nr] = 1;
set_page_address(page, (void *)vaddr); /* 向page结构填入关联的线性地址*/
return vaddr;
}
kunmap函数
arch/i386/mm/highmem.h void *kunmap(struct page *)
void kunmap(struct page *page)
{
if (in_interrupt())
BUG();
if (!PageHighMem(page)) /* 如果是常规内存范围,直接返回*/
return;
kunmap_high(page); /* 释放映射的内存为高端内存页,调用*/
} /*kunmap_high()*/
mm/highmem.c void fastcall kunmap_high(struct page *)
void fastcall kunmap_high(struct page *page)
{
unsigned long vaddr;
unsigned long nr;
int need_wakeup;
spin_lock(&kmap_lock);
vaddr = (unsigned long)page_address(page); /* 获取线性地址 */
if (!vaddr)
BUG(); /* 。。。。*/
nr = PKMAP_NR(vaddr); /* 将线性地址转换成pkmap_page_table和*/
need_wakeup = 0; /* pkmap_count (计数数组)的下标*/
switch (--pkmap_count[nr]) {
case 0:
BUG(); /* bug*/
case 1:
need_wakeup = waitqueue_active(&pkmap_map_wait); /* 有空心的页表项,唤醒因此休眠代*/
} /* 码*/
spin_unlock(&kmap_lock);
if (need_wakeup)
wake_up(&pkmap_map_wait);
}
可以看出来,kmap休眠的部分就在于对空闲页表项的等待,如果一直没有,则一直休眠。
临时映射(fix-mapping):
临时映射比内核映射简单,而且不会休眠。
每个CPU都有13个候选的线性地址(页框)可以使用,
这13个窗口用km_type来表示:
include/asm-i386/kmap_types.h km_type
enum km_type {
D(0) KM_BOUNCE_READ,
D(1) KM_SKB_SUNRPC_DATA,
D(2) KM_SKB_DATA_SOFTIRQ,
D(3) KM_USER0,
D(4) KM_USER1,
D(5) KM_BIO_SRC_IRQ,
D(6) KM_BIO_DST_IRQ,
D(7) KM_PTE0,
D(8) KM_PTE1,
D(9) KM_IRQ0,
D(10) KM_IRQ1,
D(11) KM_SOFTIRQ0,
D(12) KM_SOFTIRQ1,
D(13) KM_TYPE_NR
2};
kmap_atomic函数:
arch/i386/mm/highmem.h void *kmap_atomic(struct page *page, enum km_type type)
void *kmap_atomic(struct page *page, enum km_type type)
{
enum fixed_addresses idx;
unsigned long vaddr;
inc_preempt_count();
if (!PageHighMem(page)) /* 不是高端内存直接返回 */
return page_address(page);
idx = type + KM_TYPE_NR*smp_processor_id(); /* 计算出线性地址的位置*/
vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); /* 转换成线性地址*/
set_pte(kmap_pte-idx, mk_pte(page, kmap_prot)); /*建立页表项*/
__flush_tlb_one(vaddr); /* 刷新线性地址对应的块表*/
return (void*) vaddr;
}
kunmap_atomic函数
arch/i386/mm/highmem.h void *kunmap_atomic(struct page *page, enum km_type type)
void kunmap_atomic(void *kvaddr, enum km_type type)
{
unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; /* 清空低12位*/
enum fixed_addresses idx = type + KM_TYPE_NR*smp_processor_id(); /* 计算出对应下标*/
if (vaddr < FIXADDR_START) { // FIXME /* 给出的线性地址不符合要求*/
dec_preempt_count();
preempt_check_resched();
return;
}
if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx)) /* 给出的线性地址与type对应的不符合*/
BUG(); /* BUG*/
pte_clear(kmap_pte-idx); /*将该页表项置空*/
__flush_tlb_one(vaddr); /*刷新该快表*/
dec_preempt_count();
preempt_check_resched();
}