Chinaunix首页 | 论坛 | 博客
  • 博客访问: 428739
  • 博文数量: 123
  • 博客积分: 2686
  • 博客等级: 少校
  • 技术积分: 1349
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-23 22:11
文章分类
文章存档

2012年(3)

2011年(10)

2010年(100)

2009年(10)

我的朋友

分类: LINUX

2010-10-19 21:40:12

The kmap function must be used if highmem pages are to be mapped into kernel address space for a longer period (as a persistent mapping).

The kernel make use of struct page_address_map to create the association between the page instances of physical pages and their position in the virtual memory area.
/*
 * Describes one page->virtual association
 */
struct page_address_map {
    struct page *page;
    void *virtual;
    struct list_head list;
};

page holds a pointer to the page instance in the global mem_map array, and virtual specifies the allocated position in the kernel virtual address space.

For ease of organization, the mappings are kept in a hash table where the list element is used to set up an overflow list to handle hash collisions.

The hash table is implemented by means of the page_address_htable array
in highmem.c:
/*
 * 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[1<

pkmap_count (defined in mm/highmem.m) is an integer array with LAST_PKMAP positions that contain an entry for each page that can be persistently mapped. It is, in fact, a usage counter for the mapped pages with slightly unusual semantics. The number of users in the kernel is not counted, but the number of users plus 1. If the value of the counter is 2, the mapped page is used at just one point in the kernel. The counter value 5 indicates that there are four users. Expressed more generally, the counter value n stands for n − 1 users in the kernel.

static int pkmap_count[LAST_PKMAP];

How to find page addresses?
Just refer to page_address()
It first check whether the passed page is normal memory or highmem.
If the former applies, the page address can be calculated from the position of page in the mem_map array. In the latter case, the above hash table is referenced to find the virtual address.

/**
 * page_address - get the mapped virtual address of a page
 * @page: &struct page to get the virtual address of
 *
 * 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);

    pas = page_slot(page);//see below
    ret = NULL;
    spin_lock_irqsave(&pas->lock, flags);
    if (!list_empty(&pas->lh)) {
        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;
}

EXPORT_SYMBOL(page_address);


static struct page_address_slot *page_slot(struct page *page)
{
    return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}



How to create a mapping?
Kernel delegates this work to kmap()
It first check if the desired page really is in highmem area. If not, it invoke page_address() as the return. Otherwise, it delegates work to kmap_high().
Mustn't call kmap() in interrupt context, because it may block.

void *kmap(struct page *page)
{
    might_sleep();
    if (!PageHighMem(page))
        return page_address(page);
    return kmap_high(page);
}



/**
 * 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);

    //check whether this page has been mapped.
    if (!vaddr)//if not mapped
        vaddr = map_new_virtual(page);//see below definition
    pkmap_count[PKMAP_NR(vaddr)]++;
    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
    unlock_kmap();
    return (void*) vaddr;
}

EXPORT_SYMBOL(kmap_high);



static inline unsigned long map_new_virtual(struct page *page)
{
    unsigned long vaddr;
    int count;

start:
    count = LAST_PKMAP;
    /* Find an empty entry */
    for (;;) {

        //see Comment 1
        last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;

       
        if (!last_pkmap_nr) {//if last_pkmap_nr==0 ?

            //start from the first element of pkmap_count[]
            flush_all_zero_pkmaps();//flush caches
            count = LAST_PKMAP;
        }


        //if pkmap_count[last_pkmap_nr]==0, indicates finding entry
        if (!pkmap_count[last_pkmap_nr])
            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);

            //see Comment 2
            add_wait_queue(&pkmap_map_wait, &wait);
            unlock_kmap();

            //give up processor
            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;
        }
    }

    //see Comment 3
    vaddr = PKMAP_ADDR(last_pkmap_nr);

   

    //see Comment 4

    set_pte_at(&init_mm, vaddr,
         &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

    pkmap_count[last_pkmap_nr] = 1;

    //see Comment 5
    set_page_address(page, (void *)vaddr);

    return vaddr;
}



Comment 1:
last_pkmap_nr is a global variable, which is used to record the last used position when pkmap_count[] is scanned.

Comment 2:
pkmap_map_wait is a global variable defined in highmem.c:
static DECLARE_WAIT_QUEUE_HEAD(pkmap_map_wait);
it is a header of wait queue.

Comment 3:
#define PKMAP_ADDR(nr)  (PKMAP_BASE + ((nr) << PAGE_SHIFT))
It is equal to PKMAP_BASE + nr * sizeof(struct page)

Comment 4:
???

Comment 5:

/**
 * set_page_address - set a page's virtual address
 * @page: &struct page to set
 * @virtual: virtual address to use
 */

void set_page_address(struct page *page, void *virtual)
{
    unsigned long flags;
    struct page_address_slot *pas;
    struct page_address_map *pam;

    BUG_ON(!PageHighMem(page));

    pas = page_slot(page);
    if (virtual) {        /* Add */
        BUG_ON(list_empty(&page_address_pool));

        spin_lock_irqsave(&pool_lock, flags);
        pam = list_entry(page_address_pool.next,
                struct page_address_map, list);
        list_del(&pam->list);
        spin_unlock_irqrestore(&pool_lock, flags);

        pam->page = page;
        pam->virtual = virtual;

        spin_lock_irqsave(&pas->lock, flags);
        list_add_tail(&pam->list, &pas->lh);
        spin_unlock_irqrestore(&pas->lock, flags);
    } else {        /* Remove */
        spin_lock_irqsave(&pas->lock, flags);
        list_for_each_entry(pam, &pas->lh, list) {
            if (pam->page == page) {
                list_del(&pam->list);
                spin_unlock_irqrestore(&pas->lock, flags);
                spin_lock_irqsave(&pool_lock, flags);
                list_add_tail(&pam->list, &page_address_pool);
                spin_unlock_irqrestore(&pool_lock, flags);
                goto done;
            }
        }
        spin_unlock_irqrestore(&pas->lock, flags);
    }
done:
    return;
}


阅读(523) | 评论(0) | 转发(0) |
0

上一篇:vmalloc()

下一篇:About Slab Allocator

给主人留下些什么吧!~~