• 高端内存(持久内存映射)


    使用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;
    }

    其实看清楚了每个函数的作用,整个流程还是非常清楚的,不像想象中的复杂。

    -------------------------------------

    个人理解,欢迎拍砖。

  • 相关阅读:
    关于For循环的性能
    CLR读书笔记
    轻量级自动化测试框架介绍
    loadrunner中如何将MD5加密的值转换为大写
    LoadRunner 中实现MD5加密
    新安装的soapui启动时报错及解决方法
    单元测试之驱动模块和桩模块的作用和区别
    接口自动化(Python)-利用正则表达式从返回的HTML文本中截取自己想要的值
    LoadRunner性能测试-loadrunner事务
    LoadRunner性能测试-loadrunner工具破解
  • 原文地址:https://www.cnblogs.com/ggzwtj/p/2132038.html
Copyright © 2020-2023  润新知