精华内容
下载资源
问答
  • 高端内存

    2016-03-28 15:39:11
    一 什么是高端内存二内核如何管理高端内存三 永久映射区permanet kernel mappings 注:本文提及的物理地址空间可以理解为就是物理内存,但是在某些情况下,把他们理解为物理内存是不对的。 本文讨论的环境是...
    
    
    注:本文提及的物理地址空间可以理解为就是物理内存,但是在某些情况下,把他们理解为物理内存是不对的。
    本文讨论的环境是NON-PAE的i386平台,内核版本2.6.31-14

    一. 什么是高端内存

    linux中内核使用3G-4G的线性地址空间,也就是说总共只有1G的地址空间可以用来映射物理地址空间。但是,如果内存大于1G的情况下呢?是不是超过1G的内存就无法使用了呢?为此内核引入了一个高端内存的概念,把1G的线性地址空间划分为两部分:小于896M物理地址空间的称之为低端内存,这部分内存的物理地址和3G开始的线性地址是一一对应映射的,也就是说内核使用的线性地址空间3G--(3G+896M)和物理地址空间0-896M一一对应;剩下的128M的线性空间用来映射剩下的大于896M的物理地址空间,这也就是我们通常说的高端内存区。
    所谓的建立高端内存的映射就是能用一个线性地址来访问高端内存的页。如何理解这句话呢?在开启分页后,我们要访问一个物理内存地址,需要经过MMU的转换,也就是一个32位地址vaddr的高10位用来查找该vaddr所在页目录项,用12-21位来查找页表项,再用0-11位偏移和页的起始物理地址相加得到paddr,再把该paddr放到前端总线上,那么我们就可以访问该vaddr对应的物理内存了。在低端内存中,每一个物理内存页在系统初始化的时候都已经存在这样一个映射了。而高端内存还不存在这样一个映射(页目录项,页表都是空的),所以我们必须要在系统初始化完后,提供一系列的函数来实现这个功能,这就是所谓的高端内存的映射。那么我们为什么不再系统初始化的时候把所有的内存映射都建立好呢?主要原因是,内核线性地址空间不足以容纳所有的物理地址空间(1G的内核线性地址空间和最多可达4G的物理地址空间),所以才需要预留一部分(128M)的线性地址空间来动态的映射所有的物理地址空间,于是就产生了所谓的高端内存映射。

    二.内核如何管理高端内存


    上面的图展示了内核如何使用3G-4G的线性地址空间,首先解释下什么是high_memory
    在arch/x86/mm/init_32.c里面由如下代码:

    #ifdef CONFIG_HIGHMEM
            highstart_pfn = highend_pfn = max_pfn;
            if (max_pfn > max_low_pfn)
                    highstart_pfn = max_low_pfn;
            e820_register_active_regions(0, 0, highend_pfn);
            sparse_memory_present_with_active_regions(0);
            printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",
                    pages_to_mb(highend_pfn - highstart_pfn));
            num_physpages = highend_pfn;
            high_memory = (void *) __va(highstart_pfn * PAGE_SIZE-1)+1; 
    #else
            e820_register_active_regions(0, 0, max_low_pfn);
            sparse_memory_present_with_active_regions(0);
            num_physpages = max_low_pfn;
            high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1)+1; 
    #endif


    high_memory是“具体物理内存的上限对应的虚拟地址”,可以这么理解:当内存内存小于896M时,那么high_memory = (void *) __va(max_low_pfn * PAGE_SIZE),max_low_pfn就是在内存中最后的一个页帧号,所以high_memory=0xc0000000+物理内存大小;当内存大于896M时,那么highstart_pfn = max_low_pfn, 此时max_low_pfn就不是物理内存的最后一个页帧号了,而是内存为896M时的最后一个页帧号,那么high_memory=0xc0000000+896M.总之high_memory是不能超过0xc0000000+896M.
    由于我们讨论的是物理内存大于896M的情况,所以high_memory实际上就是0xc0000000+896M,从high_memory开始的128M(4G-high_memory)就是用作用来映射剩下的大于896M的内存的,当然这128M还可以用来映射设备的内存(MMIO)。
    从上图我们看到有VMALLOC_START,VMALLOC_END,PKMAP_BASE,FIX_ADDRESS_START等宏术语,其实这些术语划分了这128M的线性空间,一共分为三个区域:VMALLOC区域(本文不涉及这部分内容,关注本博客的其他文章),永久映射区(permanetkernel mappings), 临时映射区(temporary kernel mappings).这三个区域都可以用来映射高端内存,本文重点阐述下后两个区域是如何映射高端内存的。


    三. 永久映射区(permanet kernel mappings)

    1. 介绍几个定义:
    PKMAP_BASE:永久映射区的起始线性地址。
    pkmap_page_table:永久映射区对应的页表。
    LAST_PKMAP:pkmap_page_table里面包含的entry的数量=1024
    pkmap_count[LAST_PKMAP]数组:每一个元素的值对应一个entry的引用计数。

    关于引用计数的值,有以下几种情况:

    0:说明这个entry可用。

    1:entry不可用,虽然这个entry没有被用来映射任何内存,但是他仍然存在TLB entry没有被flush, 

         所以还是不可用。

    N:有N-1个对象正在使用这个页面


    首先,要知道这个区域的大小是4M,也就是说128M的线性地址空间里面,只有4M的线性地址空间是用来作永久映射区的。至于到底是哪4M,是由PKMAP_BASE决定的,这个变量表示用来作永久内存映射的4M区间的起始线性地址。
    在NON-PAE的i386上,页目录里面的每一项都指向一个4M的空间,所以永久映射区只需要一个页目录项就可以了。而一个页目录项指向一张页表,那么永久映射区正好就可以用一张页表来表示了,于是我们就用pkmap_page_table来指向这张页表。

            pgd = swapper_pg_dir + pgd_index(vaddr);
            pud = pud_offset(pgd, vaddr);//pud==pgd
            pmd = pmd_offset(pud, vaddr);//pmd==pud==pgd
            pte = pte_offset_kernel(pmd, vaddr); 
            pkmap_page_table = pte;



    2. 具体代码分析(2.6.31)

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

    kmap()函数就是用来建立永久映射的函数:由于调用kmap函数有可能会导致进程阻塞,所以它不能在中断处理函数等不可被阻塞的上下文下被调用,might_sleep()的作用就是当该函数在不可阻塞的上下文下被调用是,打印栈信息。接下来判断该需要建立永久映射的页是否确实属于高端内存,因为我们知道低端内存的每个页都已经存在和线性地址的映射了,所以,就不需要再建立了,page_address()函数返回该page对应的线性地址。(关于page_address()函数,参考本博客的专门文章有解释)。最后调用kmap_high(page),可见kmap_high()才真正执行建立永久映射的操作。

    /**
     * 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)
                    vaddr = map_new_virtual(page);
            pkmap_count[PKMAP_NR(vaddr)]++;
            BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
            unlock_kmap();
            return (void*) vaddr;
    }

    kmap_high函数分析:首先获得对pkmap_page_table操作的锁,然后再调用page_address()来返回该page是否已经被映射,我们看到前面在kmap()里面已经判断过了,为什么这里还要再次判断呢?因为再获的锁的时候,有可能锁被其他CPU拿走了,而恰巧其他CPU拿了这个锁之后,也是执行这段code,而且映射的也是同一个page,那么当它把锁释放掉的时候,其实就表示该page的映射已经被建立了,我们这里就没有必要再去执行这段code了,所以就有必要在获得锁后再判断下。
    如果发现vaddr不为空,那么就是刚才说的,已经被其他cpu上执行的任务给建立了,这里只需要把表示该页引用计数的pkmap_count[]再加一就可以了。同时调用BUG_ON来确保该引用计数确实是不小于2的,否则就是有问题的了。然后返回vaddr,整个建立就完成了。
    如果发现vaddr为空呢?调用map_new_virtual()函数,到此我们看到,其实真正进行建立映射的代码在这个函数里面

    static inline unsigned long map_new_virtual(struct page *page)

            unsigned long vaddr;
            int count; 
                            
    start: 
            count = LAST_PKMAP;//LAST_PKMAP=1024
            /* 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)
                            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;
    }

    last_pkmap_nr:记录上次被分配的页表项在pkmap_page_table里的位置,初始值为0,所以第一次分配的时候last_pkmap_nr等于1。

    接下来判断什么时候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)个页表项已经被分配了,这时候就需要调用flush_all_zero_pkmaps()函数,把所有pkmap_count[]计数为1的页表项在TLB里面的entryflush掉,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置为1的时候也就是解除映射的同时把TLBflush呢?个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。

    再判断pkmap_count[last_pkmap_nr]是否为0,0的话就表示这个页表项是可用的,那么就跳出循环了到下面了。

    PKMAP_ADDR(last_pkmap_nr)返回这个页表项对应的线性地址vaddr.

    #define PKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT))


    set_pte_at(mm, addr, ptep, pte)函数在NON-PAE i386上的实现其实很简单,其实就等同于下面的代码:

    static inline void native_set_pte(pte_t *ptep , pte_t pte) 

            *ptep = pte;
    }

    我们已经知道页表的线性起始地址存放在pkmap_page_table里面,那么相应的可用的页表项的地址就是&pkmap_page_table[last_pkmap_nr],得到了页表项的地址,只要把相应的pte填写进去,那么整个映射不就完成了吗?
    pte由两部分组成:高20位表示物理地址,低12位表示页的描述信息。
    怎么通过page查找对应的物理地址呢(参考page_address()一文)?其实很简单,用(page - mem_map) 再移PAGE_SHIFT位就可以了。
    低12位的页描述信息是固定的:kmap_prot=(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_GLOBAL).
    下面的代码就是做了这些事情:

    mk_pte(page, kmap_prot));
    #define mk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))
    #define page_to_pfn __page_to_pfn
    #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + \
                                     ARCH_PFN_OFFSET)
    static inline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)
    {
            return __pte(((phys_addr_t)page_nr << PAGE_SHIFT) |
                         massage_pgprot(pgprot));
    }


    接下来把pkmap_count[last_pkmap_nr]置为1,1不是表示不可用吗,既然映射已经建立好了,应该赋值为2呀,其实这个操作是在他的上层函数kmap_high里面完成的(pkmap_count[PKMAP_NR(vaddr)]++).
    到此为止,整个映射就完成了,再把page和对应的线性地址加入到page_address_htable哈希链表里面就可以了(参考page_address一文)。


    我们继续看所有的页表项都已经用了的情况下,也就是1024个页表项全已经映射了内存了,如何处理。此时count==0,于是就进入了下面的代码:

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

    这段代码其实很简单,就是把当前任务加入到等待队列pkmap_map_wait,当有其他任务唤醒这个队列时,再继续goto start,重新整个过程。这里就是上面说的调用kmap函数有可能阻塞的原因。
    那么什么时候会唤醒pkmap_map_wait队列呢?当调用kunmap_high函数,来释放掉一个映射的时候。
    kunmap_high函数其实页很简单,就是把要释放的页表项的计数减1,如果等于1的时候,表示有可用的页表项了,再唤醒pkmap_map_wait队列

    /**
     * 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[nr]) {//减一
            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);
    }





    转自:http://blog.chinaunix.net/space.php?uid=21752164&do=blog&id=2689671

    展开全文
  • 修改高端内存占比

    2020-06-12 10:03:56
    剩下高端内存只有268 M. 高端内存中分三部分, vmalloc区域(240M),fixmap(3072k),vector(4k) vmalloc和fixmap的起始地址,arm硬编码的 一定不能修改vmalloc中间的8M gap 如果你需要的内存不多,可以使用...

    3:1的话,ARM默认只能768M低端内存

    1G内核,低端内存占用786M. 剩下高端内存只有268 M. 高端内存中分三部分, vmalloc区域(240M),fixmap(3072k),vector(4k)

    vmalloc和fixmap的起始地址,arm硬编码的

    一定不能修改vmalloc中间的8M gap在这里插入图片描述在这里插入图片描述在这里插入图片描述

    1. List item
    2. 如果你需要的内存不多,可以使用fixmap区间,分配的时候如下操作
    3. 在这里插入图片描述
    4. alloc_pages一次最大默认情况,不能超过128k
    展开全文
  • 高端内存有两种使用方式,一种是给内核使用 的,通过vmalloc分配,一种就是给应用程序使用 的,由于应用程序使用内存时都需要经过mms,所以内核给用户分配内存时首先使用高端内存


    0-3G为用户地址空间
    3G-4G为内核逻辑地址空间
    其中3G-3G+896M为内核空间的低端地址空间,超过896M的内存归属为高端地址空间,
    而高端内存有两种使用方式,一种是给内核使用 的,通过vmalloc分配,一种就是给应用程序使用 的,由于应用程序使用内存时都需要经过mms,所以内核给用户分配内存时首先使用高端内存。

    展开全文
  • 一:引子 我们在前面分析过,在linux...通常,我们把物理地址超过896M的区域称为高端内存。内核怎样去管理高端内存呢?今天就来分析这个问题。 内核有三种方式管理高端内存。第一种是非连续映射。这我们在前面的vmalloc

    一:引子

    我们在前面分析过,在linux内存管理中,内核使用3G—>4G的地址空间,总共1G的大小。而且有一部份用来做非连续空间的物理映射(vmalloc).除掉这部份空间之外,只留下896M大小供内核映射到物理地址。通常,我们把物理地址超过896M的区域称为高端内存。内核怎样去管理高端内存呢?今天就来分析这个问题。

    内核有三种方式管理高端内存。第一种是非连续映射。这我们在前面的vmalloc中已经分析过了,在vmalloc中请求页面的时候,请求的是高端内存,然后映射到VMALLOC_START与VMALLOC_END之间。这一过程不再赘述。第二种方式是永久内存映射。最后一种方式叫临时内核映射。

    接下来,详细的分析一下第二种和第三种方式。对于第一种方式,我们在之前已经分析过了。

    借鉴网上的一个图,来说明一下这三种方式的大概映射过程。

     

    二:永久内存映射

    永久内存映射在内核的接口为:kmap()/kunmap().在详细分析代码之前,有必须弄懂几个全局变量的含义:

    PKMAP_BASE:永久映射空间的起始地址。永久映射空间为4M。所以它最多能映射4M/4K=1024个页面。

    pkmap_page_table:永久映射空间对应的页目录。我们来看一下它的初始化:

    pkmap_page_table = pte_offset_kernel(pmd_offset(pgd_offset_k

                  (PKMAP_BASE), PKMAP_BASE), PKMAP_BASE);

                  实际上它就是PKMAP_BASE所在的PTE

    LAST_PKMAP:永久映射空间所能映射的页面数。在没有开启PAE的情况下被定义为1024

    highmem_start_page:高端内存的起始页面

    pkmap_count[PKMAP]:每一项用来对应映射区域的引用计数。关于引用计数,有以下几种情况:

                       为0时:说明映射区域可用。为1时:映射区域不可用,因为自从它最后一次使用以来。TLB还没有将它刷新

                  为N时,有N-1个对象正在使用这个页面

    last_pkmap_nr:在建立永久映射的时候,最后使用的序号

    代码如下:

    void *kmap(struct page *page)

    {

         //可能引起睡眠。在永久映射区没有空闲地址的时候

         might_sleep();

         //如果不是高端页面。那它在直接映射空间已经映射好了,直接计算即可

         if (page < highmem_start_page)

             return page_address(page);

         //如果是高端页面。即在永久映射区为其分配地址

         return kmap_high(page);

    }

    转到kmap_high():

    void fastcall *kmap_high(struct page *page)

    {

         unsigned long vaddr;

         spin_lock(&kmap_lock);

         //取页面地址

         vaddr = (unsigned long)page_address(page);

         //如果页面还没有映射到线性地址,为它建立好映射

         if (!vaddr)

             vaddr = map_new_virtual(page);

         //有一个引用了,计数加1

         pkmap_count[PKMAP_NR(vaddr)]++;

         //如果计数小于2,这种情况是无效的。

         if (pkmap_count[PKMAP_NR(vaddr)] < 2)

             BUG();

         spin_unlock(&kmap_lock);

         return (void*) vaddr;

    }

    map_new_virtual()用于将一个page映射到永久映射区域。它的实现如下:

    static inline unsigned long map_new_virtual(struct page *page)

    {

         unsigned long vaddr;

         int count;

     

    start:

         count = LAST_PKMAP;

         for (;;) {

             //从last_pkmap_nr开始搜索。大于LAST_PKMAP时,又将它从0开始

             //其中LAST_PKMAP_MASK被定义为:(LAST_PKMAP-1)

             last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;

             //如果last_pkmap_nr等于0,也就是从头开始了

    if (!last_pkmap_nr) {

         //扫描所有计数为1的项,将它置为零。如果还有映射到页面。断开它的映射关系

                  flush_all_zero_pkmaps();

                  count = LAST_PKMAP;

             }

             //如果计数为0,可用,就用它了,跳出循环

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

     

                  /* Somebody else might have mapped it while we slept */

                  //可能在睡眠的时候,其它进程已经映射好了,

                  if (page_address(page))

                       return (unsigned long)page_address(page);

     

                  //重新开始

                  goto start;

             }

         }

         // #define PKMAP_ADDR(nr)  (PKMAP_BASE + ((nr) << PAGE_SHIFT))

         //将序号转化为线性地址

         vaddr = PKMAP_ADDR(last_pkmap_nr);

         //将线性地址映射到page

         set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

         //将其引用计数置1

         pkmap_count[last_pkmap_nr] = 1;

         //更新page的线性地址

         set_page_address(page, (void *)vaddr);

     

         return vaddr;

    }

    Kunmap()的实现如下:

    void kunmap(struct page *page)

    {

         //不能在中断中

         if (in_interrupt())

             BUG();

         //如果不是高端页面,直接返回

         if (page < highmem_start_page)

             return;

         //清除掉映射关系

         kunmap_high(page);

    }

    转入kunmap_high():

    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();

         //将地址转换为序号

         // #define PKMAP_NR(virt)  ((virt-PKMAP_BASE) >> PAGE_SHIFT)

         nr = PKMAP_NR(vaddr);

         need_wakeup = 0;

         //计算引用计数

         switch (--pkmap_count[nr]) {

         case 0:

             BUG();

         case 1:

             //如果只有一个引用了,说明这页面是空闲的。看看是否有进程在等待

             //因为TLB刷新之后,会将其减1

             need_wakeup = waitqueue_active(&pkmap_map_wait);

         }

         spin_unlock(&kmap_lock);

     

    //唤醒等待的进程

         if (need_wakeup)

             wake_up(&pkmap_map_wait);

    }

    三:临时内存映射

    临时内存映射在内核中的接口为:kmap_atomic()/kunmap_atomic()。它映射的地址是从FIXADDR_START到FIXADDR_TOP的区域。其中,每个cpu都在里面占用了一段空间。

    在内核中,enum fixed_addresses表示各种临时映射所占的序号。结构如下:

    enum fixed_addresses {

         FIX_HOLE,

         FIX_VSYSCALL,

    #ifdef CONFIG_X86_LOCAL_APIC

         FIX_APIC_BASE,     /* local (CPU) APIC) -- required for SMP or not */

    #else

         FIX_VSTACK_HOLE_1,

    #endif

    #ifdef CONFIG_X86_IO_APIC

         FIX_IO_APIC_BASE_0,

         FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1,

    #endif

    #ifdef CONFIG_X86_VISWS_APIC

         FIX_CO_CPU,   /* Cobalt timer */

         FIX_CO_APIC,  /* Cobalt APIC Redirection Table */

         FIX_LI_PCIA,  /* Lithium PCI Bridge A */

         FIX_LI_PCIB,  /* Lithium PCI Bridge B */

    #endif

         FIX_IDT,

         FIX_GDT_1,

         FIX_GDT_0,

         FIX_TSS_3,

         FIX_TSS_2,

         FIX_TSS_1,

         FIX_TSS_0,

         FIX_ENTRY_TRAMPOLINE_1,

         FIX_ENTRY_TRAMPOLINE_0,

    #ifdef CONFIG_X86_CYCLONE_TIMER

         FIX_CYCLONE_TIMER, /*cyclone timer register*/

         FIX_VSTACK_HOLE_2,

    #endif

         FIX_KMAP_BEGIN,    /* reserved pte's for temporary kernel mappings */

         FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,

    #ifdef CONFIG_ACPI_BOOT

         FIX_ACPI_BEGIN,

         FIX_ACPI_END = FIX_ACPI_BEGIN + FIX_ACPI_PAGES - 1,

    #endif

    #ifdef CONFIG_PCI_MMCONFIG

         FIX_PCIE_MCFG,

    #endif

         __end_of_permanent_fixed_addresses,

         /* temporary boot-time mappings, used before ioremap() is functional */

    #define NR_FIX_BTMAPS  16

         FIX_BTMAP_END = __end_of_permanent_fixed_addresses,

         FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1,

         FIX_WP_TEST,

         __end_of_fixed_addresses

    }

    每一段序号都有自己的用途,例如APIC用,IDT用。FIX_KMAP_BEGIN与FIX_KMAP_END是分配给模块或者做做临时用途使用的。内核这样分配是为了保证同一个区不能有两上映射关系。我们在后面可以看到,如果一个区已经映射到了一个物理页面。如果再在这个区上建立映射关系,就会把它以前的映射覆盖掉。所以,内核应该根据具体的用途选择特定的序号,以免产生不可预料的错误。同时使用完临时映射之后应该立即释放当前的映射,这也是个良好的习惯.

    FIX_KMAP_END的大小被定义成:FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1。也就是FIX_KMAP_BEGIN到FIX_KMAP_END的大小是KM_TYPE_NR*NR_CPUS.

    KM_TYPE_NR的定义如下:

    enum km_type {

         /*

          * IMPORTANT: don't move these 3 entries, be wary when adding entries,

          * the 4G/4G virtual stack must be THREAD_SIZE aligned on each cpu.

          */

         KM_BOUNCE_READ,

         KM_VSTACK_BASE,

         KM_VSTACK_TOP = KM_VSTACK_BASE + STACK_PAGE_COUNT-1,

     

         KM_LDT_PAGE15,

         KM_LDT_PAGE0 = KM_LDT_PAGE15 + 16-1,

         KM_USER_COPY,

         KM_VSTACK_HOLE,

         KM_SKB_SUNRPC_DATA,

         KM_SKB_DATA_SOFTIRQ,

         KM_USER0,

         KM_USER1,

         KM_BIO_SRC_IRQ,

         KM_BIO_DST_IRQ,

         KM_PTE0,

         KM_PTE1,

         KM_IRQ0,

         KM_IRQ1,

         KM_SOFTIRQ0,

         KM_SOFTIRQ1,

         KM_CRASHDUMP,

         KM_UNUSED,

         KM_TYPE_NR

    }

    在smp系统中,每个CPU都有这样的一段映射区域

    kmap_pte:FIX_KMAP_BEGIN项所对应的页表项.它的初始化如下:

    #define kmap_get_fixmap_pte(vaddr)                      \

         pte_offset_kernel(pmd_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr))

     

    void __init kmap_init(void)

    {

         kmap_pte = kmap_get_fixmap_pte(__fix_to_virt(FIX_KMAP_BEGIN));

    }

    #define __fix_to_virt(x)    (FIXADDR_TOP - ((x) << PAGE_SHIFT))

    了解上述关系之后,可以看具体的代码了:

    void *kmap_atomic(struct page *page, enum km_type type)

    {

         enum fixed_addresses idx;

         unsigned long vaddr;

     

         //如果页面不是高端内存

         inc_preempt_count();

         if (page < highmem_start_page)

             return page_address(page);

         //在smp中所对应的序号

         idx = type + KM_TYPE_NR*smp_processor_id();

         //在映射断中求取序号所在的虚拟地址

         vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);

    #ifdef CONFIG_DEBUG_HIGHMEM

         if (!pte_none(*(kmap_pte-idx)))

             BUG();

    #endif

         //根据页面属性建立不同的页面项.并根据FIX_KMAP_BEGIN的页表项,求出序号所在的页表项

         if (PageReserved(page))

             set_pte(kmap_pte-idx, mk_pte(page, kmap_prot_nocache));

         else

             set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));

         //在TLB中刷新这个地址

         __flush_tlb_one(vaddr);

     

         return (void*) vaddr;

    }

    我们在这个过程看中,并没有去判断一个区域有没有被映射。但这样也有一个好处,就是不会造成睡眠,因为它总有一个区域可供其映射。与永久内核映射相比,速度显得稍微要快一点。

    临时内核映射的断开接口为:kunmap_atomic()

    void kunmap_atomic(void *kvaddr, enum km_type type)

    {

    //调试用,忽略

    #ifdef CONFIG_DEBUG_HIGHMEM

         unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK;

         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))

             BUG();

     

         /*

          * force other mappings to Oops if they'll try to access

          * this pte without first remap it

          */

         pte_clear(kmap_pte-idx);

         __flush_tlb_one(vaddr);

    #endif

     

         dec_preempt_count();

         preempt_check_resched();

    }

    我们在此看到,它并末对页面做特殊处理。

    四总结:

         其实,不管是那样的方式,原理都是一样的,都是在固定映射区外选定一个地址,然后再修改PTE项,使其指向相应的page。特别值得我们注意的是,因为kmap()会引起睡眠,所以它不能用于中断处理。但每一种映射方式都有自己的优点和缺点,这需要我们在写代码的时候仔细考虑了。






    分类: LINUX

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

    本文系本站原创,欢迎转载!

    转载请注明出处:http://ericxiao.cublog.cn/

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

    前面我们已经分析了linux如何利用伙伴系统,slab分配器分配内存,用这些方法得到的内存在物理地址上都是连续的,然而,有些时候,每次请求内存时,系统都分配物理地址连续的内存块是不合适的,可以利用小块内存“连接”成大块可使用的内存.这在操作系统设计中也被称为 “内存拼接”,显然,内存拼接在需要较大内存,而内存访问相比之下不是很频繁的情况下是比较有效的.

         在linux内核中用来管理内存拼接的接口是vmalloc/vfree.用vmalloc分配得到的内存在线性地址是平滑的,但是物理地址上是非连续的.

         一:准备知识:

         Linux用vm_struct结构来表示vmalloc使用的线性地址.vmalloc所使用的线性地址区间为: VMALLOC_START VMALLOC_END.借用<>中的一副插图,如下示:

     

    从上图中我们可以看到每一个vmalloc_area用4KB隔开,这样做是为了很容易就能捕捉到越界访问,因为中间是一个 “空洞”.

    二:相关的数据结构

    下面来分析一下vmalloc area的数据结构:

    struct vm_struct {

         void          *addr;             //虚拟地址

         unsigned long      size;         //vm的大小

         unsigned long      flags;        //vm的标志

         struct page        **pages;      //vm所映射的page

         unsigned int       nr_pages;     //page个数

         unsigned long      phys_addr;    //对应的起始物理地址  

         struct vm_struct   *next;        //下一个vm.用来形成链表

    }

    全局变量vmlist用来管理vm构成的链表

    全局变量vmlist用于访问vmlist所使用的信号量

    对于vm_struct有两个常用的操作: get_vm_area/remove_vm_area

    get_vm_area:用来分配一个合适大小的vm结构,分配成功之后,将其链入到vmlist中,代码在 mm/vmalloc.c中.如下示:

    //size为vm的大小

    struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)

    {

         //在VMALLOC_START与VMALLOC_END找到一段合适的空间

         return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);

    }

    //参数说明:

    //start:起始地址 end:结束地址 size 空间大小

    struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,

                       unsigned long start, unsigned long end)

    {

         struct vm_struct **p, *tmp, *area;

         unsigned long align = 1;

         unsigned long addr;

     

         //如果指定了VM_IOREMAP.则调整对齐因子

         if (flags & VM_IOREMAP) {

             int bit = fls(size);

     

             if (bit > IOREMAP_MAX_ORDER)

                  bit = IOREMAP_MAX_ORDER;

             else if (bit < PAGE_SHIFT)

                  bit = PAGE_SHIFT;

     

             align = 1ul << bit;

         }

         //将起始地址按照对齐因子对齐

         addr = ALIGN(start, align);

         //分配一个vm_struct结构空间

         area = kmalloc(sizeof(*area), GFP_KERNEL);

         if (unlikely(!area))

             return NULL;

     

         //PAGE_SIZE:在i32中为4KB,即上面所说的间隔空洞

         size += PAGE_SIZE;

         if (unlikely(!size)) {

             kfree (area);

             return NULL;

         }

     

         write_lock(&vmlist_lock);

         //遍历vmlist:找到合适大小的末使用空间

         for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {

             //若起始地址落在某一个vm区间,则调整起始地址为vm区间的末尾

             if ((unsigned long)tmp->addr < addr) {

                  if((unsigned long)tmp->addr + tmp->size >= addr)

                       addr = ALIGN(tmp->size +

                                (unsigned long)tmp->addr, align);

                  continue;

             }

             //size+addr < addr ?除非size == 0

             if ((size + addr) < addr)

                  goto out;

             //中间的空隙可以容纳下size大小的vm.说明已经找到了这样的一个vm

             if (size + addr <= (unsigned long)tmp->addr)

                  goto found;

             //调整起始地址为vm的结束地址

             addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);

             //如果超出了范围

             if (addr > end - size)

                  goto out;

         }

     

    found:

         //找到了合适大小的空间,将area->addr赋值为addr,然后链入vmlist中

         area->next = *p;

         *p = area;

     

         area->flags = flags;

         area->addr = (void *)addr;

         area->size = size;

         area->pages = NULL;

         area->nr_pages = 0;

         area->phys_addr = 0;

         write_unlock(&vmlist_lock);

     

         return area;

     

    out:

         //没有找到合适大小的空间,出错返回

         write_unlock(&vmlist_lock);

         kfree(area);

         if (printk_ratelimit())

             printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc= to increase size.\n");

         return NULL;

    }

    这段代码不是很复杂,在此不详细分析了.

    remove_vm_area用来将相应的vm从vmlist中断开,使其表示的空间可以被利用

    //addr:对应vm的超始地址

    struct vm_struct *remove_vm_area(void *addr)

    {

         struct vm_struct **p, *tmp;

     

         write_lock(&vmlist_lock);

         //遍历vmlist.找到超始地址为addr的vm

    for (p = &vmlist ; (tmp = *p) != NULL ;p = &tmp->next) {

              if (tmp->addr == addr)

                   goto found;

         }

         write_unlock(&vmlist_lock);

         return NULL;

     

    found:

         //断开tmp所对应的映射关系

         unmap_vm_area(tmp);

         //找到了这个vm,将其从vmlist上断开

         *p = tmp->next;

         write_unlock(&vmlist_lock);

         return tmp;

    }

    unmap_vm_area用来断开vm所在线性地址所对应的映射关系.它的代码如下:

    void unmap_vm_area(struct vm_struct *area)

    {

         //vm所对应的起始线性地址

         unsigned long address = (unsigned long) area->addr;

         //vm所对应的结束线性地址

         unsigned long end = (address + area->size);

         pgd_t *dir;

         //起始地址所在的内核页目录项

         dir = pgd_offset_k(address);

         flush_cache_vunmap(address, end);

         do {

             //断开地址所对应的pmd映射

             unmap_area_pmd(dir, address, end - address);

    //运行到这里的时候,已经断开了一个页目录所表示的线性地址,而每个页目录表示的线性地址//大小为PGDIR_SIZE

             address = (address + PGDIR_SIZE) & PGDIR_MASK;

             dir++;

         } while (address && (address < end));

         //当到达末尾时结束循环

         flush_tlb_kernel_range((unsigned long) area->addr, end);

    }

    //断开线性地址区间所在的pmd的映射

    static void unmap_area_pmd(pgd_t *dir, unsigned long address,

                         unsigned long size)

    {

         unsigned long end;

         pmd_t *pmd;

     

         if (pgd_none(*dir))

             return;

         if (pgd_bad(*dir)) {

             pgd_ERROR(*dir);

             pgd_clear(dir);

             return;

         }

     

         pmd = pmd_offset(dir, address);

         address &= ~PGDIR_MASK;

         end = address + size;

         if (end > PGDIR_SIZE)

             end = PGDIR_SIZE;

     

         do {

             //断开线性地址所在的pte的映射关系

             unmap_area_pte(pmd, address, end - address);

             address = (address + PMD_SIZE) & PMD_MASK;

             pmd++;

         } while (address < end);

    }

    static void unmap_area_pte(pmd_t *pmd, unsigned long address,

                         unsigned long size)

    {

         unsigned long end;

         pte_t *pte;

     

         if (pmd_none(*pmd))

             return;

         if (pmd_bad(*pmd)) {

             pmd_ERROR(*pmd);

             pmd_clear(pmd);

             return;

         }

     

         pte = pte_offset_kernel(pmd, address);

         address &= ~PMD_MASK;

         end = address + size;

         if (end > PMD_SIZE)

             end = PMD_SIZE;

     

         do {

             pte_t page;

             //清除pte的对应映射关系

    page = ptep_get_and_clear(pte);

             address += PAGE_SIZE;

             pte++;

             if (pte_none(page))

                  continue;

             if (pte_present(page))

                  continue;

             printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n");

         } while (address < end);

    }

    经过这几个过程之后,实际上,它只是找到线性地址所对应的pte,然后断开pte的映射.值得注意的是:为了效率起见,这里只是断开了pte的映射,即只是将pte置为none,表示pte末映射内存.并末断开pmd和pgd的映射


    分类: LINUX

    三:vmalloc的实现:

    void *vmalloc(unsigned long size)

    {

           return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);

    }

    实际上调用__vmalloc:

    void *__vmalloc(unsigned long size, int gfp_mask, pgprot_t prot)

    {

         struct vm_struct *area;

         struct page **pages;

         unsigned int nr_pages, array_size, i;

     

         //使请求的大小与页框对齐

         size = PAGE_ALIGN(size);

         //有效性检查

         if (!size || (size >> PAGE_SHIFT) > num_physpages)

             return NULL;

     

         //取得一个有效的VM,这个函数我们在前面已经详细的分析过了

         area = get_vm_area(size, VM_ALLOC);

         if (!area)

             return NULL;

     

         //所要映射的页面总数

         nr_pages = size >> PAGE_SHIFT;

         //页面描述符所占的空间

         array_size = (nr_pages * sizeof(struct page *));

     

         area->nr_pages = nr_pages;

         area->pages = pages = kmalloc(array_size, (gfp_mask & ~__GFP_HIGHMEM));

     

         //如果空间分配失败

         if (!area->pages) {

             remove_vm_area(area->addr);

             kfree(area);

             return NULL;

         }

         memset(area->pages, 0, array_size);

     

         //为每一个页面分配空间

         for (i = 0; i < area->nr_pages; i++) {

             area->pages[i] = alloc_page(gfp_mask);

             if (unlikely(!area->pages[i])) {

                  /* Successfully allocated i pages, free them in __vunmap() */

                  area->nr_pages = i;

                  goto fail;

             }

         }

     

         //为所分配的页面建立映射关系

         if (map_vm_area(area, prot, &pages))

             goto fail;

         return area->addr;

     

    fail:

         vfree(area->addr);

         return NULL;

    }

    map_vm_area为所分配的内存建立映射关系,它的程序流程与unmap_vm_area差不多,都是从pgd找到pte,如果同样的映射关系不存在,则新建之.(如:pgd对应的pmd不存在,则新建pmd项,使pgd指向建好的pmd.同理,如果pmd所映射的pte项不存在,则新建pte,然后建立映射),然后将pte映射到相应的页表.代码如下:

    int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)

    {

         unsigned long address = (unsigned long) area->addr;

         unsigned long end = address + (area->size-PAGE_SIZE);

         pgd_t *dir;

         int err = 0;

     

         //vm 起始地址所在的页目录

         dir = pgd_offset_k(address);

         spin_lock(&init_mm.page_table_lock);

         do {

             pmd_t *pmd = pmd_alloc(&init_mm, dir, address);

             if (!pmd) {

                  err = -ENOMEM;

                  break;

             }

             //轮到pmd了 ^_^

             if (map_area_pmd(pmd, address, end - address, prot, pages)) {

                  err = -ENOMEM;

                  break;

             }

     

             address = (address + PGDIR_SIZE) & PGDIR_MASK;

             dir++;

         } while (address && (address < end));

     

         spin_unlock(&init_mm.page_table_lock);

         flush_cache_vmap((unsigned long) area->addr, end);

         return err;

    }

    static int map_area_pmd(pmd_t *pmd, unsigned long address,

                         unsigned long size, pgprot_t prot,

                         struct page ***pages)

    {

         unsigned long base, end;

     

         base = address & PGDIR_MASK;

         address &= ~PGDIR_MASK;

         end = address + size;

         if (end > PGDIR_SIZE)

             end = PGDIR_SIZE;

     

         do {

             pte_t * pte = pte_alloc_kernel(&init_mm, pmd, base + address);

             if (!pte)

                  return -ENOMEM;

             //轮到pte了 ^_^

             if (map_area_pte(pte, address, end - address, prot, pages))

                  return -ENOMEM;

             address = (address + PMD_SIZE) & PMD_MASK;

             pmd++;

         } while (address < end);

     

         return 0;

    }

     

    //为页表页建立映射关系

    static int map_area_pte(pte_t *pte, unsigned long address,

                         unsigned long size, pgprot_t prot,

                         struct page ***pages)

    {

         unsigned long end;

     

         address &= ~PMD_MASK;

         end = address + size;

         if (end > PMD_SIZE)

             end = PMD_SIZE;

     

         do {

             struct page *page = **pages;

     

             WARN_ON(!pte_none(*pte));

             if (!page)

                  return -ENOMEM;

            

             //具体的映射在这里了 ^_^

             set_pte(pte, mk_pte(page, prot));

             address += PAGE_SIZE;

             pte++;

             (*pages)++;

         } while (address < end);

         return 0;

    }

    只要理解了断开映射的过程,这段代码是很好理解的.

    总而言之:linux在建立映射的时候,从pgd 到pte相应的建立映射关系,最后将pte映射到分配得到的物理内存.而在断开映射的时候,linux内核从pgd找到pte,然后将pte置为none,表示pte末建立映射关系.

    四:vfree的实现:

    代码如下:

    void vfree(void *addr)

    {

         BUG_ON(in_interrupt());

         __vunmap(addr, 1);

    }

    跟踪至__vunmap:

    void __vunmap(void *addr, int deallocate_pages)

    {

         struct vm_struct *area;

         //参数有效性检查

         if (!addr)

             return;

    //判断addr是否是按页框对齐的

         if ((PAGE_SIZE-1) & (unsigned long)addr) {

             printk(KERN_ERR "Trying to vfree() bad address (%p)\n", addr);

             WARN_ON(1);

             return;

         }

    //remove_vm_area:这个函数我们在之前已经分析过了 ^_^

         area = remove_vm_area(addr);

         if (unlikely(!area)) {

             //没有找到起始地址为addr的vm.则无效,退出

             printk(KERN_ERR "Trying to vfree() nonexistent vm area (%p)\n",

                       addr);

             WARN_ON(1);

             return;

         }

        

         if (deallocate_pages) {

             int i;

     

             for (i = 0; i < area->nr_pages; i++) {

                  if (unlikely(!area->pages[i]))

                       BUG();

                  //释放请求获得的页面

                  __free_page(area->pages[i]);

             }

     

             //释放分配的page 描述符

             kfree(area->pages);

         }

     

         //释放内核的vm 描述符

         kfree(area);

         return;

    }

    五:总结

    经过上面的分析,我们可以看到,vmalloc分配内存的过程是十分低效的,不仅要从伙伴系统中取内存而且要建立映射关系,显然,用vmalloc分配较小的内存是不合算的。此外。有个问题值得思考一下:为什么用__get_free_page不需要建立映射关系,而vmalloc就需要呢?

    其实,不管使用何种方式。线性地址到物理地址的转换最终都要经过硬件的页式管理去完成。所不同的是__get_free_page返回的线性地址是属于(PAGE_OFFSET,HIGH_MEMORY)之间的,这段线性地址在内核初始化的时候就完成了映射。而vmalloc使用的线性地址是属于(VMALLOC_START VMALLOC_END)之间的,也就是说属于一个临时映射区,所以必须为其建立映射关系。


    展开全文
  • 高端内存与低端内存

    千次阅读 2010-07-27 23:14:00
    高端内存:是指那些不存在逻辑地址的内存。 在装有大量内存的32位系统中,内核逻辑地址和内核虚拟地址的不同将非常突出。由于使用32位地址最多同时能在4GB内存中寻址,因此直到最近,32位系统的linux仍被限制使用...
  • 高端内存:是指那些不存在逻辑地址的内存。 在装有大量内存的32位系统中,内核逻辑地址和内核虚拟地址的不同将非常突出。由于使用32位地址最多同时能在4GB内存中寻址,因此直到最近,32位系统的linux仍被限制使用...
  • 高端内存的理解

    2019-09-24 22:49:56
    高端内存概念的目的是留出内核的一部分地址空间用来有选择的映射内存的任意部分,使内核能够使用整个内存,而不是只有1个G的内存。。 x86中,linux把4G的线性地址空间分成两份,0-3G作为用户态程序的地址空间,3-4G...
  • linux 高端内存简介

    2020-03-06 10:34:16
    两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。用户空间的内存映射采用段页式,而内核空间有自己的规则;...
  • 高端内存永久映射分析

    千次阅读 2013-04-24 18:00:03
     当你需要将高端页面长期映射到内核空间的时候,就要使用Kmap函数来实现,即高端内存永久映射。这样避免页表和TLB的更新而导致资源的占用。  使用的时候一般先通过alloc_page(__GFP_HIGHMEM)申请一个page,然后
  • 常用内存分配和高端内存分配使用
  • 使用kmap函数将高端页帧长期映射到内核地址空间中: .../* 参数page是要映射的页 */void *kmap(struct page *page){ /* 判断是不是高端内存 */ if (!PageHighMem(page)) return page_address(page); might_sleep(); ...
  • linux高端内存与内存映射——重要

    千次阅读 2018-04-17 20:20:25
    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在...
  • linux的高端内存映射

    2012-02-28 21:31:46
    我们在前面分析过,在...通常,我们把物理地址超过896M的区域称为高端内存。内核怎样去管理高端内存呢?今天就来分析这个问题。 内核有三种方式管理高端内存。第一种是非连续映射。这我们在前面的vmalloc中已经分析过
  • 我们知道系统中的高端内存是不能作为直接映射区映射到内核空间的,那么我们想要使用它怎么办呢?前面的文章我们已经有过相关的介绍,可以使用三种方法,分别是pkmap(永久映射)/fixmap(临时固定映射)/vmlloc,...
  • 我们知道系统中的高端内存是不能作为直接映射区映射到内核空间的,那么我们想要使用它怎么办呢?前面的文章我们已经有过相关的介绍,可以使用三种方法,分别是pkmap(永久映射)/fixmap(临时固定映射)/vmlloc,...
  • linux高端内存分析

    千次阅读 2013-03-26 15:09:14
    当然有些体系结构如MIPS使用2:2 的比率来划分虚拟内存:2 GB 的虚拟内存用于用户空间,2 GB 的内存用于内核空间,另外像ARM架构的虚拟空间是可配置(1:3、2:2、3:1)。内核线性地址空间用于为内核的运行提供最基本的...
  • Linux HighMemory(高端内存

    千次阅读 2018-07-03 17:07:11
    目前。32位的x86架构是最受欢迎的计算机架构。在这种架构中,传统上Linux内核将4GB的虚拟内存地址...使用从内核对象到另一个对象的指针。 另一种方法是跟踪物理地址,映射到物理地址,并在访问之前计算结构的虚拟...
  • 总结了高端内存区的固定内核映射区、临时内核映射与永久内核映射。但是对于高端内存中各个区间的布置我们任然不是很清楚,首先我们从整体上看看内核对高端内存的划分情况。 如果内存足够大(比如用户:内核线性...
  • linux 用户空间与内核空间——高端内存详解 摘要:Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 518
精华内容 207
关键字:

高端内存使用