关于高端内存的权威解释
注:本文是我见到的所有 关于 高端内存 解释 的最详细、最清晰的 解释 ,其他帖子寥寥数语写的都是垃圾,保存下来只为方便后来人和我自己,感谢原文作者! 原文地址: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);
}

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

기계식 하드 드라이브나 SATA 솔리드 스테이트 드라이브의 경우 소프트웨어 실행 속도의 증가를 느낄 수 있지만 NVME 하드 드라이브라면 느끼지 못할 수도 있습니다. 1. 레지스트리를 데스크탑으로 가져와 새 텍스트 문서를 생성하고, 다음 내용을 복사하여 붙여넣은 후 1.reg로 저장한 후 마우스 오른쪽 버튼을 클릭하여 병합하고 컴퓨터를 다시 시작합니다. WindowsRegistryEditorVersion5.00[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\MemoryManagement]"DisablePagingExecutive"=d

최근 샤오미는 스타일리시한 디자인은 물론 내부 및 외부 블랙 기술까지 갖춘 강력한 고급 스마트폰 샤오미 14Pro를 출시했다. 이 전화기는 최고의 성능과 뛰어난 멀티태스킹 기능을 갖추고 있어 사용자가 빠르고 원활한 휴대폰 경험을 즐길 수 있습니다. 하지만 성능은 메모리에 의해서도 영향을 받습니다. 많은 사용자들이 Xiaomi 14Pro의 메모리 사용량을 확인하는 방법을 알고 싶어하므로 한번 살펴보겠습니다. Xiaomi Mi 14Pro의 메모리 사용량을 확인하는 방법은 무엇입니까? Xiaomi 14Pro의 메모리 사용량을 확인하는 방법을 소개합니다. Xiaomi 14Pro 휴대폰의 [설정]에서 [애플리케이션 관리] 버튼을 엽니다. 설치된 모든 앱 목록을 보려면 목록을 탐색하고 보려는 앱을 찾은 다음 클릭하여 앱 세부 정보 페이지로 들어갑니다. 신청 세부정보 페이지에서

초보 사용자가 컴퓨터를 구입할 때 8g과 16g 컴퓨터 메모리의 차이점이 궁금할 것입니다. 8g 또는 16g을 선택해야 합니까? 이 문제에 대해 오늘 편집자가 자세히 설명해 드리겠습니다. 컴퓨터 메모리 8g과 16g 사이에 큰 차이가 있나요? 1. 일반 가족이나 일반 업무의 경우 8G 런닝 메모리가 요구 사항을 충족할 수 있으므로 사용 중에는 8g와 16g 사이에 큰 차이가 없습니다. 2. 게임 매니아가 사용하는 경우 현재 대규모 게임은 기본적으로 6g부터 시작하며, 8g가 최소 기준입니다. 현재 화면이 2k인 경우 해상도가 높아진다고 프레임 속도 성능이 높아지는 것은 아니므로 8g와 16g 사이에는 큰 차이가 없습니다. 3. 오디오 및 비디오 편집 사용자의 경우 8g와 16g 사이에는 분명한 차이가 있습니다.

3일 홈페이지 보도에 따르면 국내 언론 에트뉴스는 어제(현지시간) 삼성전자와 SK하이닉스의 'HBM형' 적층구조 모바일 메모리 제품이 2026년 이후 상용화될 것이라고 보도했다. 소식통에 따르면 두 한국 메모리 거대 기업은 적층형 모바일 메모리를 미래 수익의 중요한 원천으로 여기고 'HBM형 메모리'를 스마트폰, 태블릿, 노트북으로 확장해 엔드사이드 AI에 전력을 공급할 계획이라고 전했다. 이 사이트의 이전 보도에 따르면 삼성전자 제품은 LPWide I/O 메모리라고 하며 SK하이닉스는 이 기술을 VFO라고 부른다. 두 회사는 팬아웃 패키징과 수직 채널을 결합하는 것과 거의 동일한 기술 경로를 사용했습니다. 삼성전자 LPWide I/O 메모리의 비트폭은 512이다.

보고서에 따르면 삼성전자 김대우 상무는 2024년 한국마이크로전자패키징학회 연차총회에서 삼성전자가 16단 하이브리드 본딩 HBM 메모리 기술 검증을 완료할 것이라고 밝혔다. 해당 기술은 기술검증을 통과한 것으로 알려졌다. 보고서는 이번 기술 검증이 향후 몇 년간 메모리 시장 발전의 초석을 마련하게 될 것이라고 밝혔다. 김대우 사장은 삼성전자가 하이브리드 본딩 기술을 바탕으로 16단 적층 HBM3 메모리를 성공적으로 제조했다고 밝혔다. ▲이미지 출처 디일렉, 아래와 동일 하이브리드 본딩은 DRAM 메모리층 사이에 범프를 추가할 필요 없이 상하층 구리를 직접 연결하는 방식이다.

21일 본 사이트의 소식에 따르면 마이크론은 분기별 재무보고서를 발표한 뒤 컨퍼런스콜을 가졌다. 컨퍼런스에서 Micron CEO Sanjay Mehrotra는 기존 메모리에 비해 HBM이 훨씬 더 많은 웨이퍼를 소비한다고 말했습니다. 마이크론은 동일한 노드에서 동일한 용량을 생산할 때 현재 가장 발전된 HBM3E 메모리는 표준 DDR5보다 3배 더 많은 웨이퍼를 소비하며 성능이 향상되고 패키징 복잡성이 심화됨에 따라 향후 HBM4 이 비율은 더욱 높아질 것으로 예상된다고 밝혔습니다. . 이 사이트의 이전 보고서를 참조하면 이러한 높은 비율은 부분적으로 HBM의 낮은 수율 때문입니다. HBM 메모리는 다층 DRAM 메모리 TSV 연결로 적층됩니다. 한 층에 문제가 있다는 것은 전체가 의미합니다.

새 RAM을 설치했지만 Windows 컴퓨터에 표시되지 않는 경우 이 문서가 문제 해결에 도움이 될 것입니다. 일반적으로 RAM을 업그레이드하여 시스템 성능을 향상시킵니다. 그러나 시스템 성능은 CPU, SSD 등과 같은 다른 하드웨어에 따라 달라집니다. RAM을 업그레이드하면 게임 경험도 향상될 수 있습니다. 일부 사용자는 설치된 메모리가 Windows 11/10에 표시되지 않는 것을 발견했습니다. 이런 일이 발생하면 여기에 제공된 조언을 사용할 수 있습니다. 설치된 RAM이 Windows 11에 표시되지 않습니다. 설치된 RAM이 Windows 11/10 PC에 표시되지 않는 경우 다음 제안 사항이 도움이 될 것입니다. 설치된 메모리가 컴퓨터 마더보드와 호환됩니까? 바이오에서

5월 6일 이 웹사이트의 소식에 따르면 Lexar는 Ares Wings of War 시리즈 DDR57600CL36 오버클럭 메모리를 출시했습니다. 16GBx2 세트는 5월 7일 0시에 예약 판매가 가능하며 가격은 50위안입니다. 1,299위안. Lexar Wings of War 메모리는 Hynix A-die 메모리 칩을 사용하고 Intel XMP3.0을 지원하며 다음 두 가지 오버클러킹 사전 설정을 제공합니다. 7600MT/s: CL36-46-46-961.4V8000MT/s: CL38-48-49 -1001.45V 방열 측면에서는 이 메모리 세트에는 1.8mm 두께의 올 알루미늄 방열 조끼가 장착되어 있으며 PMIC 독점 열 전도성 실리콘 그리스 패드가 장착되어 있습니다. 메모리는 8개의 고휘도 LED 비드를 사용하고 13개의 RGB 조명 모드를 지원합니다.
