Kmap

来源:互联网 发布:源码交易系统 编辑:程序博客网 时间:2024/06/14 16:53

Kmap


void *kmap(struct page *page)

这个函数在高端内存或低端内存上都能用。如果page结构对应的是低端内存中的一页,函数只会单纯的返回该页的虚拟地址。如果页位于高端内存,则会建立一个永久映射,再返回地址。这个函数可以睡眠,因此kmap()只能在进程上下文中。
因为允许永久映射是有限的,当不需要高端内存时,应该接触映射,可以通过以下函数解除。

void kunmap(struct page *page)

高端内存

Linux将内核地址空间划分为三部分ZONE_DMA ZONE_NORMAL ZONE_HIGHMEM,高端内存HIGH_MEM地址空间范围为 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如内核是如何借助128MB高端内存地址空间是如何实现访问可以所有物理内存?
当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。如下图。
这里写图片描述

例 如内核想访问2G开始的一段大小为1MB的物理内存,即物理地址范围为0×80000000 ~ 0x800FFFFF。访问之前先找到一段1MB大小的空闲地址空间,假设找到的空闲地址空间为0xF8700000 ~ 0xF87FFFFF,用这1MB的逻辑地址空间映射到物理地址空间0×80000000 ~ 0x800FFFFF的内存。映射关系如下:

逻辑地址 物理内存地址 0xF8700000 0×80000000 0xF8700001 0×80000001 0xF8700002 0×80000002 … … 0xF87FFFFF 0x800FFFFF

当内核访问完0×80000000 ~ 0x800FFFFF物理内存后,就将0xF8700000 ~ 0xF87FFFFF内核线性空间释放。这样其他进程或代码也可以使用0xF8700000 ~ 0xF87FFFFF这段地址访问其他物理内存。


具体实现

void *kmap(struct page *page){    might_sleep();    if (!PageHighMem(page))//如果是低端内存,直接返回内存页对应的虚拟地址。        return page_address(page);    return kmap_high(page);//否则 需要进行高端内存映射}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);//建立新的映射    pkmap_count[PKMAP_NR(vaddr)]++;//如果建立成功,引用加1,表示已被映射,2表示此映射还有额外的引用    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);    unlock_kmap();    return (void*) vaddr;}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 + 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)//遍历整个kmap空间,如果不存在未使用的映射,就睡眠等待kunmap            continue;        /*         * Sleep for somebody else to unmap their entries         */        {            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 = PKMAP_ADDR(last_pkmap_nr);    set_pte_at(&init_mm, vaddr,           &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));    pkmap_count[last_pkmap_nr] = 1;    set_page_address(page, (void *)vaddr);    return vaddr;}

实现流程图

这里写图片描述


参考文档

linux 用户空间与内核空间——高端内存详解

0 0
原创粉丝点击