听老歌 发表于 2012-01-05 19:05

linux高端内存管理之永久内核映射

linux高端内存管理之永久内核映射








与直接映射的物理内存末端、高端内存的始端所对应的线性地址存放在high_memory变量中,在x86体系结构上,高于896MB的所有物理内存的范围大都是高端内存,它并不会永久地或自动地映射到内核地址空间,尽管x86处理器能够寻址物理RAM的范围达到4GB(启用PAE可以寻址到64GB)。一旦这些页被分配,就必须in射到内核的逻辑地址空间上。在x86上,高端内存中的页被映射到3GB-4GB。

内核可以采用三种不同的机制将页框映射到高端内存;分别叫做永久内核映射、临时内核映射以及非连续内存分配。在这里,只总结前两种技术,第三种技术将在后面总结。

建立永久内核映射可能阻塞当前进程;这发生在空闲页表项不存在时,也就是在高端内存上没有页表项可以用作页框的“窗口”时。因此,永久内核映射不能用于中断处理程序和可延迟函数。相反,建立临时内核映射绝不会要求阻塞当前进程;不过,他的缺点是只有很少的临时内核映射可以同时建立起来。

使用临时内核映射的内核控制路径必须保证当前没有其他的内核控制路径在使用同样地映射。这意味着内核控制路径永远不能被阻塞,后者其他内核控制路径有可能使用同一个窗口来映射其他的高端内存页。


永久内存映射

永久内核映射允许内核建立高端页框到内核地址空间的长期映射。他们使用住内核页表中一个专门的页表,其地址存放在变量pkmap_page_table中,这在前面的页表机制管理区初始化中已经介绍过了。页表中的表项数由LAST_PKMAP宏产生。因此,内核一次最多访问2MB或4MB的高端内存。

view plaincopy to clipboardprint?/*这里由定义可以看出永久内存映射为固定映射下面的4M空间*/
#define PKMAP_BASE ((FIXADDR_BOOT_START - PAGE_SIZE * (LAST_PKMAP + 1)) \   
            & PMD_MASK)
/*这里由定义可以看出永久内存映射为固定映射下面的4M空间*/
#define PKMAP_BASE ((FIXADDR_BOOT_START - PAGE_SIZE * (LAST_PKMAP + 1))        \
                  & PMD_MASK)
该页表映射的线性地址从PKMAP_BASE开始。pkmap_count数组包含LAST_PKMAP个计数器,pkmap_page_table页表中的每一项都有一个。

高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项,对应于映射区内不同的逻辑页面。当分配项的值等于0时为自由项,等于1时为缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系统将进入等待状态。

view plaincopy to clipboardprint?/*
高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项,
对应于映射区内不同的逻辑页面。当分配项的值等于零时为自由项,等于1时为
缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由
项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系
统将进入等待状态。
*/
static int pkmap_count;
/*last_pkmap_nr:记录上次被分配的页表项在pkmap_page_table里的位置,初始值为0,所以第一次分配的时候last_pkmap_nr等于1*/
static unsigned int last_pkmap_nr;
/*
高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项,
对应于映射区内不同的逻辑页面。当分配项的值等于零时为自由项,等于1时为
缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由
项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系
统将进入等待状态。
*/
static int pkmap_count;
/*last_pkmap_nr:记录上次被分配的页表项在pkmap_page_table里的位置,初始值为0,所以第一次分配的时候last_pkmap_nr等于1*/
static unsigned int last_pkmap_nr;
为了记录高端内存页框与永久内核映射包含的线性地址之间的联系,内核使用了page_address_htable散列表。该表包含一个page_address_map数据结构,用于为高端内存中的每一个页框进行当前映射。而该数据结构还包含一个指向页描述符的指针和分配给该页框的线性地址。view plaincopy to clipboardprint? * Hash table bucket
*/
static struct page_address_slot {
    struct list_head lh;            /* List of page_address_maps */
    spinlock_t lock;            /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable;

/*
* Describes one page->virtual association
*/
struct page_address_map {
    struct page *page;
    void *virtual;
    struct list_head list;
};
* Hash table bucket
*/
static struct page_address_slot {
        struct list_head lh;                        /* List of page_address_maps */
        spinlock_t lock;                        /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable;

/*
* Describes one page->virtual association
*/
struct page_address_map {
        struct page *page;
        void *virtual;
        struct list_head list;
};
page_address()函数返回页框对应的线性地址

view plaincopy to clipboardprint? * Returns the page's virtual address.
*/
/*返回页框对应的线性地址*/
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_address_htable散列表中得到pas*/
    pas = page_slot(page);
    ret = NULL;
    spin_lock_irqsave(&pas->lock, flags);
    if (!list_empty(&pas->lh)) {/*如果对应的链表不空,
    该链表中存放的是page_address_map结构*/
      struct page_address_map *pam;
      /*对每个链表中的元素*/
      list_for_each_entry(pam, &pas->lh, list) {
            if (pam->page == page) {
                ret = pam->virtual;/*返回线性地址*/
                goto done;
            }
      }
    }
done:
    spin_unlock_irqrestore(&pas->lock, flags);
    return ret;
}
* Returns the page's virtual address.
*/
/*返回页框对应的线性地址*/
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_address_htable散列表中得到pas*/
        pas = page_slot(page);
        ret = NULL;
        spin_lock_irqsave(&pas->lock, flags);
        if (!list_empty(&pas->lh)) {/*如果对应的链表不空,
        该链表中存放的是page_address_map结构*/
                struct page_address_map *pam;
                /*对每个链表中的元素*/
                list_for_each_entry(pam, &pas->lh, list) {
                        if (pam->page == page) {
                                ret = pam->virtual;/*返回线性地址*/
                                goto done;
                        }
                }
        }
done:
        spin_unlock_irqrestore(&pas->lock, flags);
        return ret;
}
kmap()函数建立永久内核映射。

view plaincopy to clipboardprint?/*高端内存映射,运用数组进行操作分配情况
分配好后需要加入哈希表中;*/
void *kmap(struct page *page)
{
    might_sleep();
    if (!PageHighMem(page))/*如果页框不属于高端内存*/
      return page_address(page);
    return kmap_high(page);/*页框确实属于高端内存*/
}
/*高端内存映射,运用数组进行操作分配情况
分配好后需要加入哈希表中;*/
void *kmap(struct page *page)
{
        might_sleep();
        if (!PageHighMem(page))/*如果页框不属于高端内存*/
                return page_address(page);
        return kmap_high(page);/*页框确实属于高端内存*/
}view plaincopy to clipboardprint?/**
* kmap_high - map a highmem page into memory
* @page: &struct page to map
*
* Returns the page's virtual memory address.
*
* We cannot call this from interrupts, as it may block.
*/
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)/*如果没有*/
      /*把页框的物理地址插入到pkmap_page_table的
      一个项中并在page_address_htable散列表中加入一个
      元素*/
      vaddr = map_new_virtual(page);
    pkmap_count++;/*分配计数加一,此时流程都正确应该是2了*/
    BUG_ON(pkmap_count < 2);
    unlock_kmap();
    return (void*) vaddr;/*返回地址*/
}
/**
* kmap_high - map a highmem page into memory
* @page: &struct page to map
*
* Returns the page's virtual memory address.
*
* We cannot call this from interrupts, as it may block.
*/
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)/*如果没有*/
                /*把页框的物理地址插入到pkmap_page_table的
                一个项中并在page_address_htable散列表中加入一个
                元素*/
                vaddr = map_new_virtual(page);
        pkmap_count++;/*分配计数加一,此时流程都正确应该是2了*/
        BUG_ON(pkmap_count < 2);
        unlock_kmap();
        return (void*) vaddr;/*返回地址*/
}view plaincopy to clipboardprint?static inline unsigned long map_new_virtual(struct page *page)
{
    unsigned long vaddr;
    int count;

start:
    count = LAST_PKMAP;
    /* Find an empty entry */
    for (;;) {
      /*加1,防止越界*/
      last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
      /*
      接下来判断什么时候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)个页表项已经被分配了
      ,这时候就需要调用flush_all_zero_pkmaps()函数,把所有pkmap_count[] 计数为1的页表项在TLB里面的entry给flush掉
      ,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置为1的时候也
      就是解除映射的同时把TLB也flush呢?
      个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。
      */
      if (!last_pkmap_nr) {
            flush_all_zero_pkmaps();
            count = LAST_PKMAP;
      }
      if (!pkmap_count)
            break;/* Found a usable entry */
      if (--count)
            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.*/
    vaddr = PKMAP_ADDR(last_pkmap_nr);
/*
v
    set_pte_at(mm, addr, ptep, pte)函数在NON-PAE i386上的实现其实很简单,其实就等同于下面的代码:
   
    static inline void native_set_pte(pte_t *ptep , pte_t pte)
    {
         *ptep = pte;
    }
*/set_pte_at(&init_mm, vaddr,/*设置页表项*/
         &(pkmap_page_table), mk_pte(page, kmap_prot));
    /*接下来把pkmap_count置为1,1不是表示不可用吗,
    既然映射已经建立好了,应该赋值为2呀,其实这个操作
    是在他的上层函数kmap_high里面完成的(pkmap_count++).*/
    pkmap_count = 1;
    /*到此为止,整个映射就完成了,再把page和对应的线性地址
    加入到page_address_htable哈希链表里面就可以了*/
    set_page_address(page, (void *)vaddr);

    return 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 (;;) {
                /*加1,防止越界*/
                last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
                /*
                接下来判断什么时候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)个页表项已经被分配了
                ,这时候就需要调用flush_all_zero_pkmaps()函数,把所有pkmap_count[] 计数为1的页表项在TLB里面的entry给flush掉
                ,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置为1的时候也
                就是解除映射的同时把TLB也flush呢?
                个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。
                */
                if (!last_pkmap_nr) {
                        flush_all_zero_pkmaps();
                        count = LAST_PKMAP;
                }
                if (!pkmap_count)
                        break;        /* Found a usable entry */
                if (--count)
                        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.*/
        vaddr = PKMAP_ADDR(last_pkmap_nr);
/*
v
        set_pte_at(mm, addr, ptep, pte)函数在NON-PAE i386上的实现其实很简单,其实就等同于下面的代码:
       
        static inline void native_set_pte(pte_t *ptep , pte_t pte)
        {
                   *ptep = pte;
        }
*/        set_pte_at(&init_mm, vaddr,/*设置页表项*/
                   &(pkmap_page_table), mk_pte(page, kmap_prot));
        /*接下来把pkmap_count置为1,1不是表示不可用吗,
        既然映射已经建立好了,应该赋值为2呀,其实这个操作
        是在他的上层函数kmap_high里面完成的(pkmap_count++).*/
        pkmap_count = 1;
        /*到此为止,整个映射就完成了,再把page和对应的线性地址
        加入到page_address_htable哈希链表里面就可以了*/
        set_page_address(page, (void *)vaddr);

        return vaddr;
}
kunmap()函数撤销先前由kmap()建立的永久内核映射

如果页确实在高端内存中,则调用kunmap_high()函数

view plaincopy to clipboardprint? * kunmap_high - map a highmem page into memory
* @page: &struct page to unmap
*
* If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called
* only from user context.
*/
void kunmap_high(struct page *page)
{
    unsigned long vaddr;
    unsigned long nr;
    unsigned long flags;
    int need_wakeup;

    lock_kmap_any(flags);
    vaddr = (unsigned long)page_address(page);
    BUG_ON(!vaddr);
    nr = PKMAP_NR(vaddr);/*永久内存区域开始的第几个页面*/

    /*
   * A count must never go down to zero
   * without a TLB flush!
   */
    need_wakeup = 0;
    switch (--pkmap_count) {/*减小这个值,因为在映射的时候对其进行了加2*/
    case 0:
      BUG();
    case 1:
      /*
         * Avoid an unnecessary wake_up() function call.
         * The common case is pkmap_count[] == 1, but
         * no waiters.
         * The tasks queued in the wait-queue are guarded
         * by both the lock in the wait-queue-head and by
         * the kmap_lock.As the kmap_lock is held here,
         * no need for the wait-queue-head's lock.Simply
         * test if the queue is empty.
         */
      need_wakeup = waitqueue_active(&pkmap_map_wait);
    }
    unlock_kmap_any(flags);

    /* do wake-up, if needed, race-free outside of the spin lock */
    if (need_wakeup)
      wake_up(&pkmap_map_wait);
}

蓝猫淘气啦啦 发表于 2012-01-05 19:05

谢谢分享
页: [1]
查看完整版本: linux高端内存管理之永久内核映射