首页 数据库 mysql教程 关于高端内存的权威解释

关于高端内存的权威解释

Jun 07, 2016 pm 03:11 PM
关于 内存 解释 高端

注:本文是我见到的所有 关于 高端内存 解释 的最详细、最清晰的 解释 ,其他帖子寥寥数语写的都是垃圾,保存下来只为方便后来人和我自己,感谢原文作者! 原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html 注:本文提及的物理地址空间可以理解

注:本文是我见到的所有关于高端内存解释的最详细、最清晰的解释,其他帖子寥寥数语写的都是垃圾,保存下来只为方便后来人和我自己,感谢原文作者!

原文地址:http://bbs.chinaunix.net/thread-1938084-1-1.html



注:本文提及的物理地址空间可以理解为就是物理内存,但是在某些情况下,把他们理解为物理内存是不对的。

本文讨论的环境是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区域(本文不涉及这部分内容,关注本博客的其他文章),永久映射区(permanetkernelmappings), 临时映射区(temporary kernelmappings).这三个区域都可以用来映射高端内存,本文重点阐述下后两个区域是如何映射高端内存的。

 

 

三.永久映射区(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没有被用来映射任何内存,但是他仍然存在TLBentry没有被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里面的entry给flush掉,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置为1的时候也就是解除映射的同时把TLB也flush呢?个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。

 

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

 

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

 

#definePKMAP_ADDR(nr) (PKMAP_BASE + ((nr)

 

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

 

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

#definemk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))

#definepage_to_pfn __page_to_pfn

#define__page_to_pfn(page) ((unsigned long)((page) - mem_map) + \

                               ARCH_PFN_OFFSET)

staticinline pte_t pfn_pte(unsigned long page_nr, pgprot_t pgprot)

{

       return __pte(((phys_addr_t)page_nr

                   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,于是就进入了下面的代码:

 

/*

                * Sleepfor 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 (unsignedlong)page_address(page);

 

                      /* Re-start */

                      goto start;

               }

这段代码其实很简单,就是把当前任务加入到等待队列pkmap_map_wait,当有其他任务唤醒这个队列时,再继续gotostart,重新整个过程。这里就是上面说的调用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

*

* IfARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called

* onlyfrom user context.

*/

voidkunmap_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:

               /*

                * Avoidan unnecessary wake_up() function call.

                * Thecommon case is pkmap_count[] == 1, but

                * nowaiters.

                * Thetasks queued in the wait-queue are guarded

                * by boththe lock in the wait-queue-head and by

                * thekmap_lock. As the kmap_lock is held here,

                * no needfor the wait-queue-head's lock. Simply

                * test ifthe queue is empty.

                */

               need_wakeup =waitqueue_active(&pkmap_map_wait);

       }

       unlock_kmap_any(flags);

 

       /* do wake-up, if needed, race-free outside ofthe spin lock */

       if (need_wakeup)

              wake_up(&pkmap_map_wait);

}

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

大内存优化,电脑升级16g/32g内存速度没什么变化怎么办? 大内存优化,电脑升级16g/32g内存速度没什么变化怎么办? Jun 18, 2024 pm 06:51 PM

对于机械硬盘、或SATA固态硬盘,软件运行速度的提升会有感觉,如果是NVME硬盘,可能感觉不到。一,注册表导入桌面新建一个文本文档,复制粘贴如下内容,另存为1.reg,然后右键合并,并重启电脑。WindowsRegistryEditorVersion5.00[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\MemoryManagement]"DisablePagingExecutive"=d

小米14Pro如何查看内存占用? 小米14Pro如何查看内存占用? Mar 18, 2024 pm 02:19 PM

最近,小米发布了一款功能强大的高端智能手机小米14Pro,它不仅外观设计时尚,而且拥有内在和外在的黑科技。该手机拥有顶级的性能和出色的多任务处理能力,让用户能够畅享快速而流畅的手机使用体验。但是性能也是会收到内存的影响的,很多用户们想要知道小米14Pro如何查看内存占用,赶快来看看吧。小米14Pro如何查看内存占用?小米14Pro查看内存占用方法介绍打开小米14Pro手机【设置】中的【应用管理】按钮。查看已安装的所有应用列表,浏览列表并找到你想查看的应用,点击它进入应用详细页面。在应用详细页面中

电脑8g和16g内存区别大吗?(电脑内存选8g还是16g) 电脑8g和16g内存区别大吗?(电脑内存选8g还是16g) Mar 13, 2024 pm 06:10 PM

  新手用户在购买电脑时,会好奇电脑内存8g和16g有什么区别?应该选择8g还是16g呢?针对这个问题,今天小编就来跟大家详细说明一下。  电脑内存8g和16g的区别大吗?  1、在一般家庭或者是普通工作使用,8G运行内存可以满足,因此在使用过程中8g和16g区别不大。  2、游戏爱好者使用时候,目前大型游戏基本上是6g起步,8g是最低标准。目前在屏幕是2k的情况下,更高分辨率并不会带来更高的帧数表现,因此对8g和16g也无较大差异。  3、对于音频、视频剪辑使用者来说,8g和16g会出现明显区

三星宣布完成 16 层混合键合堆叠工艺技术验证,有望在 HBM4 内存大面积应用 三星宣布完成 16 层混合键合堆叠工艺技术验证,有望在 HBM4 内存大面积应用 Apr 07, 2024 pm 09:19 PM

报道称,三星电子的高管DaeWooKim表示,在2024年韩国微电子和封装学会年会上,三星电子将完成采用16层混合键合HBM内存技术的验证。据悉,这项技术已通过技术验证。报道还称,此次技术验证将为未来若干年内的内存市场发展奠定基础。DaeWooKim表示,三星电子成功制造了基于混合键合技术的16层堆叠HBM3内存,该内存样品工作正常,未来16层堆叠混合键合技术将用于HBM4内存量产。▲图源TheElec,下同相较现有键合工艺,混合键合无需在DRAM内存层间添加凸块,而是将上下两层直接铜对铜连接,

美光:HBM 内存消耗 3 倍晶圆量,明年产能基本预定完毕 美光:HBM 内存消耗 3 倍晶圆量,明年产能基本预定完毕 Mar 22, 2024 pm 08:16 PM

本站3月21日消息,美光在发布季度财报后举行了电话会议。在该会议上美光CEO桑杰・梅赫罗特拉(SanjayMehrotra)表示,相对于传统内存,HBM对晶圆量的消耗明显更高。美光表示,在同一节点生产同等容量的情况下,目前最先进的HBM3E内存对晶圆量的消耗是标准DDR5的三倍,并且预计随着性能的提升和封装复杂度的加剧,在未来的HBM4上这一比值将进一步提升。参考本站以往报道,这一高比值有相当一部分原因在HBM的低良率上。HBM内存采用多层DRAM内存TSV连接堆叠而成,一层出现问题就意味着整个

雷克沙推出 Ares 战神之翼 DDR5 7600 16GB x2 内存套条:海力士 A-die 颗粒,1299 元 雷克沙推出 Ares 战神之翼 DDR5 7600 16GB x2 内存套条:海力士 A-die 颗粒,1299 元 May 07, 2024 am 08:13 AM

本站5月6日消息,雷克沙Lexar推出Ares战神之翼系列DDR57600CL36超频内存,16GBx2套条5月7日0点开启50元定金预售,到手价1299元。雷克沙战神之翼内存采用海力士A-die内存颗粒,支持英特尔XMP3.0,提供以下两个超频预设:7600MT/s:CL36-46-46-961.4V8000MT/s:CL38-48-49-1001.45V散热方面,这款内存套装搭载1.8mm厚度的全铝散热马甲,配备PMIC专属导热硅脂垫。内存采用8颗高亮LED灯珠,支持13种RGB灯光模式,可

消息称三星电子、SK 海力士堆叠式移动内存 2026 年后商业化 消息称三星电子、SK 海力士堆叠式移动内存 2026 年后商业化 Sep 03, 2024 pm 02:15 PM

本站9月3日消息,韩媒etnews当地时间昨日报道称,三星电子和SK海力士的“类HBM式”堆叠结构移动内存产品将在2026年后实现商业化。消息人士表示这两大韩国内存巨头将堆叠式移动内存视为未来重要收入来源,计划将“类HBM内存”扩展到智能手机、平板电脑和笔记本电脑中,为端侧AI提供动力。综合本站此前报道,三星电子的此类产品叫做LPWideI/O内存,SK海力士则将这方面技术称为VFO。两家企业使用了大致相同的技术路线,即将扇出封装和垂直通道结合在一起。三星电子的LPWideI/O内存位宽达512

金邦推出全新 DDR5 8600 内存,提供 CAMM2、LPCAMM2 及常规型号可选 金邦推出全新 DDR5 8600 内存,提供 CAMM2、LPCAMM2 及常规型号可选 Jun 08, 2024 pm 01:35 PM

本站6月7日消息,金邦(GEIL)在2024台北国际电脑展上推出了其最新DDR5解决方案,而且给出了SO-DIMM、CUDIMM、CSODIMM、CAMM2和LPCAMM2等版本可选。▲图源:Wccftech如图所示,金邦展出的CAMM2/LPCAMM2内存采用非常紧凑的设计,最高可提供128GB的容量,速度最高可达8533MT/s,其中部分产品甚至可以在AMDAM5平台上稳定超频至9000MT/s,且无需任何辅助散热。据介绍,金邦2024款PolarisRGBDDR5系列内存最高可提供8400

See all articles