千机游戏提供最新游戏下载和手游攻略!

操作系统高端内存

发布时间:2024-09-18浏览:22

虚拟地址物理地址

0xC000 00000x0000 0000

0xFFFF 8FFF0x3FFF 8FFF

……

0xFFFF FFFF0x4000 0000

这里有一个问题。内核空间只能映射到物理空间的前1GB。为了解决这个问题。内核将每个节点的物理内存空间分为三部分:zone_dma zone_normal zone_highmem。 zone_dma和zone_normal占用896MB的空间,而zone_highmem占用896MB的空间。内核虚拟地址空间的高128MB专门用于映射高端内存。然而,这个映射是动态的,这意味着这个区域没有办法永久映射到内核的虚拟地址空间。

2.建立高端内存映射

1 永久内核映射

1.1 数据结构

1 页地址h表

在函数page_address()中,为了加快页框指针到线性地址的转换速度,内核使用哈希表来保存页框指针和线性地址的关系。桶中的每一项都是一个page_address_map结构。

静态结构体page_address_slot {

struct list_head lh; /* page_address_map 列表*/

spinlock_t 锁; /* 保护该存储桶的列表*/

} page_address_htable[1

结构体page_address_map {

结构页*页;

无效*虚拟;

struct list_head 列表;

};

2 pkmap_count数组

永久映射区间的起始线性地址是PKMAP_BASE。内核使用主内核页目录表(swapper_pg_dir)中的页表建立永久映射。页表有指针。

pte_t * pkmap_page_table 来表示。页表中页表项的数量由宏LAST_PKMAP表示。当PAE打开时,页表项数为1024,否则为512。

与临时映射不同,为了保证映射的持久性,内核创建了一个数组int pkmap_count[LAST_PKMAP]。该数组中的元素数量就是页表条目的数量。数组是计数器的集合。

count=0:表示页表项可用,相关映射尚未建立。在TLB刷新之前,TLB没有相关的页表项。

count=1:表示该页当前没有映射到任何页框,但是TLB中最后一个映射的条目还没有被刷新。因此,无法创建该页面的映射。简单来说,映射仍然保存在TLB中

count=n : 表示相关页表项已经建立,并且n-1个进程正在使用该映射。

当然,仅仅有一个计数器是不够的。为了防止对页表项的并发访问,创建映射的过程需要用锁来控制。

永久映射由kmap(struct page *page)创建,它接收参数page作为映射的页框指针。该函数返回一个线性地址。 kmap的核心是函数kmap_high()

3公里地图高

kmap_high首先调用page_address来获取页框对应的线性地址。

如果页框尚未映射,则调用map_new_virtual。在map_new_virtual中,如果找到count为0的mapping,则将count设置为1,然后count加1。此时计数值等于2

否则,不调用mapp_new_virtual,计数直接加1。此时,计数应该大于2。

void fastcall *kmap_high(struct page *page)

{

无符号长vaddr;

//申请kmap_lock自旋锁

spin_lock(kmap_lock);

//检查页框是否已经映射

vaddr=(无符号长)page_address(页);

//如果尚未映射,则调用map_new_virtual创建映射并返回新的线性地址。

如果(!vaddr)

vaddr=map_new_virtual(页面);

pkmap_count[PKMAP_NR(vaddr)]++;

if (pkmap_count[PKMAP_NR(vaddr)] 2)

漏洞();

spin_unlock(kmap_lock);

返回(void*) vaddr;

}

4 地图新虚拟

当页框尚未映射到虚拟页时调用map_new_virtual。为了防止重复遍历pkmap_count数组,函数使用last_pkmap_nr来记录最后一次映射末尾的页表项的索引。 map_new_virtual 实际上大致做了三件事:

首先,如果pkmap_count 中存在计数器为0 的索引,则创建映射并将其计数设置为1。

其次,如果last_pkmap_nr=0,即整个页表中没有可用的页表项,则调用flush_all_zero_pkmaps将计数器为1的所有映射的计数器(即映射仅在TLB中)设置为0,并刷新TLB。

第三,如果pkmap_count大于1,则当前进程被阻塞,当前进程状态被设置为TASK_UNINTERRUPTIBLE并添加到等待队列中。然后调度其他进程,等其他进程的时间片完成后,将原来的进程从等待队列中移除。如果当前没有其他进程映射该页框,则执行下一个循环。

map_new_virtual 等价于下面的代码(摘自ULK)

静态内联无符号长map_new_virtual(struct page *page)

{

无符号长vaddr;

整数计数;

为了(;) {

整数计数;

DECLARE_WAITQUEUE(等待,当前);

//扫描页表中的每个页表项

for (count=LAST_PKMAP; count 0; --count) {

//last_pkmap_nr是函数最后一次结束时页表项的索引。

//这里使用了一个小技巧,相当于last_pkmap_nr=(last_pkmap_nr + 1) % (LAST_PKMAP) 但使用位运算更快

最后的pkmap_nr=(最后的pkmap_nr + 1) (LAST_PKMAP - 1);

如果(!last_pkmap_nr){

刷新_all_zero_pkmaps();

计数=LAST_PKMAP;

}

如果last_pkmap_nr指向的页表项可用

if (!pkmap_count[last_pkmap_nr]) {

无符号长vaddr=PKMAP_BASE + (last_pkmap_nr PAGE_SHIFT);

//创建并插入页表项,对应页的属性为0110_0011,即G D A PCD PWT US RW P=0110_0011

set_pte((pkmap_page_table[last_pkmap_nr]), mk_pte(页, __pgprot(0x63)));

pkmap_count[last_pkmap_nr]=1;

//向哈希表page_address_htable中插入一个新元素并返回线性地址

set_page_address(页, (void *) vaddr);

返回vaddr;

}

}

//如果没有找到可用的页表项则

当前状态=TASK_UNINTERRUPTIBLE;

add_wait_queue(pkmap_map_wait, 等待);

spin_unlock(kmap_lock);

日程( );

删除_等待_队列(pkmap_map_wait,等待);

spin_lock(kmap_lock);

if (页面地址(页面))

返回(无符号长)page_address(页);

}

}

2 临时内核映射

2.1

临时内核映射也称为原子映射。这里有一个问题:临时映射为什么叫原子映射。

临时内核映射区位于固定映射区。固定映射区的线性地址可以随意映射到任意物理地址,而不是使用物理地址=线性地址-0xC000_0000。

临时内核映射区的结构

每个CPU特有的块根据页面的用途被分为13个窗口。例如,KM_USER0和KM_USER1被内核用来存储来自用户上下文的局部变量和参数(通常是系统调用传递的局部变量和参数)。每个窗口实际上是一个页面。这13个窗口在内核中通过枚举km_type来表示,并且通过km_type中的常量作为索引来计算每个窗口的线性地址。 KM_TYPE_NR是窗口类别的数量,等于13。这样一来,临时映射区域就变成了这样:

临时映射由kmap_atomic 创建。与kmap相比,kmap_atomic不会阻塞当前进程,也不会刷新TLB,从而提高了速度。但是,由于kmap_atomic不会阻塞当前进程,如果同一个CPU上有两个进程想要在同一个窗口上建立映射,而前一个进程还没有释放该映射,那么后一个进程创建的映射就会覆盖前一个。进程创建的映射(其本质是页表项的覆盖)。因此,地图必须以原子方式创建和释放,这就是kmap_atomic 这个名称的由来。

2.2 kmap_原子

kmap_atomic接收两个参数,page是映射的页指针,type表示这个映射在临时区间中所处的窗口。

该函数返回一个线性地址。

void *kmap_atomic(struct page *page, enum km_type 类型)

{

//idx是用于完成线性地址转换的索引

枚举固定地址idx;

//vaddr用于保存映射建立后的线性地址

无符号长vaddr;

/* 甚至!CONFIG_PREEMPT 也需要这个,因为do_page_fault 中的in_atomic */

//task_struct的preempt_count字段加1

inc_preempt_count();

//如果映射的页框不在高端内存,则直接使用__va计算页框的线性地址

//__va(page_to_pfn(页) PAGE_SHIFT);

//首先获取页框的页框号并根据公式pn * 4K(页大小)获取页框的物理地址(PA),然后使用PA +0xC000 0000获取线性地址

if (!PageHighMem(页))

返回页面地址(页面);

/**如果页框位于高端内存

/* 下面的公式需要解释一下。 smp_processor_id表示发生映射,即当前进程的CPU ID。 type 指示发生此映射的窗口。

/*根据这个公式,我们可以得到实际发生映射的索引。请注意,该索引只是一个相对索引。

idx=类型+ KM_TYPE_NR*smp_processor_id();

vaddr=__fix_to_virt(FIX_KMAP_BEGIN + idx);

//设置页表项

set_pte(kmap_pte-idx, mk_pte(页面, kmap_prot));

//刷新TLB

__flush_tlb_one(vaddr);

返回(void*) vaddr;

}

2.3 __fix_to_virt 宏

值得注意的是__fix_to_virt。 __fix_to_virt 宏将索引转换为线性地址。注意,这里使用的绝对索引位于固定的映射区间内。 FIXADDR_TOP是固定映射区间的结束线性地址。固定映射位于线性地址FIXADDR_START 和FIXADDR_TOP 之间,FIXADDR_TOP=0xFFFF_F000。在固定映射间隔和虚拟地址空间顶部(4G)之间还有一个名为FIX_HOLE 的1 页大小的空洞。更重要的是,固定的映射区间向下扩展(类似于栈)。

内核中使用宏#define __fix_to_virt(x) (FIXADDR_TOP - ((x) PAGE_SHIFT))来完成索引到线性地址的转换。结合下图,FIX_VSYSCALL区域的起始线性地址为

0xFFFF_E000=0xFFFF_F000 - 1 *0x1000

枚举公里类型{

D(0) KM_BOUNCE_READ,

D(1) KM_SKB_SUNRPC_DATA,

D(2) KM_SKB_DATA_SOFTIRQ,

D(3) KM_USER0,

D(4) KM_USER1,

D(5) KM_BIO_SRC_IRQ,

D(6) KM_BIO_DST_IRQ,

D(7) KM_PTE0,

D(8) KM_PTE1,

D(9) KM_IRQ0,

D(10) KM_IRQ1,

D(11) KM_SOFTIRQ0,

D(12) KM_SOFTIRQ1,

D(13) KM_TYPE_NR

};

修复图.h

#ifdef CONFIG_HIGHMEM

FIX_KMAP_BEGIN, /* 为临时内核映射保留pte */

#define __FIXADDR_SIZE (__end_of_permanent_fixed_addresses PAGE_SHIFT)

#define FIXADDR_START (FIXADDR_TOP - __FIXADDR_SIZE)

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

#define FIXADDR_TOP ((unsigned long)__FIXADDR_TOP)

#define __FIXADDR_TOP0xfffff000

用户评论

你很爱吃凉皮

真是一篇难得的好文!讲解太到位了,终于理解了为什么高端内存对某些高性能应用这么关键!我之前一直搞不明白区别,现在感觉自己学到了很多东西。

    有10位网友表示赞同!

疲倦了

作为一名菜鸟程序员,我对操作系统本身不太了解。这篇文章让我开始对高端内存有了些许概念。不过,有些专业术语还是有点费解啊,希望能再多一些解释!哈哈

    有17位网友表示赞同!

高冷低能儿

文中提到的延迟和带宽的问题确实影响着系统性能。我感觉对于游戏玩家来说,选择支持高端内存的平台应该很重要吧!

    有11位网友表示赞同!

话扎心

我一直觉得内存就是内存呗,哪里会有高低区别?看完这篇文章我才恍然大悟了。原来有这么复杂的设计理念,让人不得不佩服工程师们的智慧。这篇文章开阔了我的视野!

    有19位网友表示赞同!

酒笙倾凉

我觉得高端内存这种东西对于普通用户来说实用性还不太高吧。平均人家的电脑配置也不会用到这样的功能啊,是不是有点过于鸡肋?

    有18位网友表示赞同!

你身上有刺,别扎我

我最近想升级自己的工作站配置,准备入手一些高端内存。看了这篇文章后,终于明白选择高端内存的优势了,我的项目效率估计会提升不少!

    有10位网友表示赞同!

笑傲苍穹

文章写得比较全面,从芯片设计到应用实例都涉及了。不过,对于某些深入的技术细节,我觉得可以更详细地阐述一下,让读者更加理解高端内存的核心原理。

    有14位网友表示赞同!

荒野情趣

我感觉文中对不同高端内存类型的对比有些少,例如DDR5和LPDDR5的差异没有具体解释,希望后续能补充一些内容!

    有10位网友表示赞同!

?亡梦爱人

这篇文章让我对操作系统有了更深的了解。原来内存这种常见硬件也有这么复杂的内部架构,真是太神奇了!我还会继续关注你关于系统架构的文章!

    有11位网友表示赞同!

长裙绿衣

高端内存确实可以提升系统性能,但我觉得价格也比较昂贵,普通用户可能不太容易接受吧?希望未来技术发展更加成熟,让高端内存成本更低一些。

    有7位网友表示赞同!

凉笙墨染

最近在研究深度学习模型的部署工作,发现高端内存对于加速训练速度非常有效。这篇文章让我进一步了解到高端内存的应用场景和优势!

    有14位网友表示赞同!

■孤独像过不去的桥≈

我主要用我的电脑来玩游戏,看了这篇文章后,我会优先考虑购买支持高端内存的游戏笔记本!

    有15位网友表示赞同!

像从了良

文章逻辑清晰,语言易懂,对于想要了解高端内存的用户来说非常有帮助。推荐给身边也想科普关于高端内存的朋友!

    有11位网友表示赞同!

玻璃渣子

我感觉高端内存这种技术发展得太快了,很难跟上最新信息。希望可以多一些关于最新的高端内存标准和未来趋势的文章!

    有5位网友表示赞同!

|赤;焰﹏゛

对于普通用户来说,买电脑的时候是不是选择高端内存很重要呢?还是其他配置更关键呢?希望能看到更加深入的评测和比较!

    有17位网友表示赞同!

余笙南吟

我个人觉得文章对高端内存的作用进行举例说明不够具体,比如真实使用场景下,升级高端内存可以带来多少性能提升。希望作者能够加入一些案例分析!

    有14位网友表示赞同!

心悸╰つ

我一直以为高端内存就是内存越大越好,看完这篇文章才知道还有很多细节需要注意!比如工作频率、容量等等,原来选择高端内存要这么讲究啊!

    有13位网友表示赞同!

今非昔比'

文章写的很不错,很有深度!对于想要了解操作系统原理的小伙伴们来说,这是一个不容错过的学习资源。建议 bookmark 收藏起来再慢慢研读!

    有17位网友表示赞同!

热点资讯