首頁 資料庫 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脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 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)

熱門話題

Java教學
1665
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
大內存優化,電腦升級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會出現明顯區

消息稱三星電子、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

三星宣布完成 16 層混合鍵結堆疊製程技術驗證,預計在 HBM4 記憶體大面積應用 三星宣布完成 16 層混合鍵結堆疊製程技術驗證,預計在 HBM4 記憶體大面積應用 Apr 07, 2024 pm 09:19 PM

報告稱,三星電子的高層DaeWooKim表示,在2024年韓國微電子和封裝學會年會上,三星電子將完成採用16層混合鍵結HBM記憶體技術的驗證。據悉,這項技術已通過技術驗證。報告也稱,此次技術驗證將為未來若干年內的記憶體市場發展奠定基礎。 DaeWooKim表示,三星電子成功製造了基於混合鍵合技術的16層堆疊HBM3內存,該內存樣品工作正常,未來16層堆疊混合鍵合技術將用於HBM4內存量產。 ▲圖源TheElec,下同相較現有鍵合工藝,混合鍵結無需在DRAM記憶體層間添加凸塊,而是將上下兩層直接銅對銅連接,

已安裝的記憶體未顯示在Windows 11上 已安裝的記憶體未顯示在Windows 11上 Mar 10, 2024 am 09:31 AM

如果您已經安裝了新的RAM,但它沒有顯示在您的Windows電腦上,本文將幫助您解決此問題。通常,我們會透過升級RAM來提高系統的效能。然而,系統效能也取決於其他硬件,如CPU、SSD等。升級RAM也可以提升您的遊戲體驗。有些使用者註意到安裝的內存在Windows11/10中沒有顯示。如果您遇到這種情況,您可以使用此處提供的建議。已安裝的記憶體未顯示在Windows11上如果您的Windows11/10PC上沒有顯示已安裝的RAM,以下建議將對您有所幫助。安裝的記憶體是否與您的電腦主機板相容?在BIO

美光: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燈光模式,可

See all articles