请升级浏览器版本

你正在使用旧版本浏览器。请升级浏览器以获得更好的体验。

香橙派(Orange Pi)-Orange pi官网logo

行业知识 知识列表 知识详情
物理Or虚拟内存?这两个Linux下的空间没有闹明白?
2021/09/14 星期二 16:22
为什么需要使用虚拟内存

一 为什么需要使用虚拟内存

大家都知道,进程需要使用的代码和数据都放在内存中,比放在外存中要快很多。问题是内存空间太小了,不能满足进程的需求,而且现在都是多进程,情况更加糟糕。所以提出了虚拟内存,使得每个进程用于 3G 的独立用户内存空间和共享的 1G 内核内存空间。(每个进程都有自己的页表,才使得 3G 用户空间的独立)这样进程运行的速度必然很快了。而且虚拟内存机制还解决了内存碎片和内存不连续的问题。为什么可以在有限的物理内存上达到这样的效果呢?

二 虚拟内存的实现机制

首先呢,提一个概念,交换空间(swap space),这个大家应该不陌生,在重装系统的时候,会让你选择磁盘分区,就比如说一个硬盘分几个部分去管理。其中就会分一部分磁盘空间用作交换,叫做 swap space。其实就是一段临时存储空间,内存不够用的时候就用它了,虽然它也在磁盘中,但省去了很多的查找时间啊。当发生进程切换的时候,内存与交换空间就要发生数据交换一满足需求。所以啊,进程的切换消耗是很大的,这也说明了为什么自旋锁比信号量效率高的原因。

那么我们的程序里申请的内存的时候,Linux 内核其实只分配一个虚拟内存( 线性地址),并没有分配实际的物理内存。只有当程序真正使用这块内存时,才会分配物理内存。这就叫做延迟分配和请页机制。释放内存时,先释放线性区对应的物理内存,然后释放线性区;"请页机制" 将物理内存的分配延后了,这样是充分利用了程序的局部性原来,节约内存空间,提高系统吞吐;就是说一个函数可能只在物理内存中呆了一会,用完了就被清除出去了,虽然在虚拟地址空间还在。(不过虚拟地址空间不是事实上的存储,所以只能说这个函数占据了一段虚拟地址空间,当你访问这段地址时,就会产生缺页处理,从交换区把对应的代码搬到物理内存上来)

三.  linux 系统虚拟内存空间一般布局示意图

说明:

1)线性地址空间:是指 Linux 系统中从 0x00000000 到 0xFFFFFFFF 整个 4GB 虚拟存储空间。

2)内核空间:内核空间表示运行在处理器最高级别的超级用户模式(supervisor mode)下的代码或数据,内核空间占用从 0xC0000000 到 0xFFFFFFFF 的 1GB 线性地址空间,内核线性地址空间由所有进程共享,但只有运行在内核态的进程才能访问,用户进程可以通过系统调用切换到内核态访问内核空间,进程运行在内核态时所产生的地址都属于内核空间。

3)用户空间:用户空间占用从 0x00000000 到 0xBFFFFFFF 共 3GB 的线性地址空间,每个进程都有一个独立的 3GB 用户空间,所以用户空间由每个进程独有,但是内核线程没有用户空间,因为它不产生用户空间地址。另外子进程共享(继承)父进程的用户空间只是使用与父进程相同的用户线性地址到物理内存地址的映射关系,而不是共享父进程用户空间。运行在用户态和内核态的进程都可以访问用户空间。

4)内核逻辑地址空间:是指从 PAGE_OFFSET(3G) 到 high_memory(物理内存的大小,最大 896) 之间的线性地址空间,是系统物理内存映射区,它映射了全部或部分(如果系统包含高端内存)物理内存。内核逻辑地址空间与系统 RAM 内存物理地址空间是一一对应的(包括内存孔洞也是一一对应的),内核逻辑地址空间中的地址与 RAM 内存物理地址空间中对应的地址只差一个固定偏移量(3G),如果 RAM 内存物理地址空间从 0x00000000 地址编址,那么这个偏移量就是 PAGE_OFFSET。

5)高端线性地址空间:从 high_memory 到 0xFFFFFFFF 之间的线性地址空间属于高端线性地址空间。内核的高端线性地址是为了访问内核固定映射以外的内存资源。如果要加载一个设备,而这个设备需要映射其内存到内核中,它需要使用这段线性地址空间来完成,否则内核就不能访问设备上的内存空间了。另外,进程在使用内存时,触发缺页异常,具体将哪些物理页映射给用户进程是内核考虑的事情。在用户空间中没有高端内存这个概念。

其中 VMALLOC_START~VMALLOC_END 之间线性地址:

(1)被 vmalloc() 函数用来分配物理上不连续但线性地址空间连续的高端物理内存
(2)被 vmap() 函数用来映射高端或低端物理内存
(3)由 ioremap() 函数来重新映射 I/O 物理空间。

其中 PKMAP_BASE 开始的 LAST_PKMAP(一般等于 1024)页线性地址空间:

(1)被 kmap() 函数用来永久映射高端物理内存

其中 FIXADDR_START 开始的 KM_TYPE_NR*NR_CPUS 页线性地址空间:

(1)被 kmap_atomic() 函数用来临时映射高端物理内存

其他未用高端线性地址空间可以用来在系统初始化期间永久映射 I/O 地址空间(一些固定寄存器)。

6)用户空间内存的使用

(1)用户空间可以使用高端内存(物理内存),而且是正常的使用,内核在分配那些不经常使用的内存时,都用高端内存空间(如果有),所谓不经常使用是相对来说的,比如内核的一些数据结构就属于经常使用的,而用户的一些数据就属于不经常使用的。

用户在启动一个应用程序时,是需要内存的,而每个应用程序都有 3G 的线性地址,给这些地址映射页表时就可以直接使用高端内存。

(2)用户空间可以使用 ZONE_NORMAL 内存(物理内存),ZONE_NORMAL 被映射到内核空间是显然的,因为内核空间的表项已经写死。映射到用户空间的页面优先从 ZONE_HIGHMEM 区获取。当 ZONE_HIGHMEM 耗尽时,ZONE_NORMAL 就会映射给用户空间。两者之间没有矛盾,如果 ZONE_NORMAL 区的某个页面正在被内核使用,对应的怕个已经从 free_area 中摘下,不会将该页映射到用户空间,反过来也是一样。

四.  Linux 系统物理内存空间一般布局示意图

x86 架构中将内核物理空间划分三部分:ZONE_DMA、ZONE_NORMAL 和 ZONE_HIGHMEM

1)低端内存:内核逻辑地址空间所映射物理内存就是低端内存 (实际物理内存的大小,但是小于 896),低端内存在 Linux 线性地址空间中始终有永久的一一对应的内核逻辑地址,系统初始化过程中将低端内存永久映射到了内核逻辑地址空间,为低端内存建立了虚拟映射页表。低端内存内物理内存的物理地址与线性地址之间的转换可以通过__pa(x) 和__va(x) 两个宏来进行,#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) __pa(x) 将内核逻辑地址空间的地址 x 转换成对应的物理地址,相当于__virt_to_phys((unsigned long)(x)),__va(x) 则相反,把低端物理内存空间的地址转换成对应的内核逻辑地址,相当于 ((void *)__phys_to_virt((unsigned long)(x)))。

2)高端内存:低端内存地址之上的物理内存是高端内存(物理内存 896 之上),高端内存在 Linux 线性地址空间中没有没有固定的一一对应的内核逻辑地址,系统初始化过程中不会为这些内存建立映射页表将其固定映射到 Linux 线性地址空间,而是需要使用高端内存的时候才为分配的高端物理内存建立映射页表,使其能够被内核使用,否则不能被使用。高端内存的物理地址于线性地址之间的转换不能使用上面的__pa(x) 和__va(x) 宏。

3)高端内存概念的由来: Linux 将 4GB 的线性地址空间划分成两部分,从 0x00000000 到 0xBFFFFFFF 共 3GB 空间作为用户空间由用户进程独占,这部分线性地址空间并没有固定映射到物理内存空间上;

从 0xC0000000 到 0xFFFFFFFF 的第 4GB 线性地址空间作为内核空间,在嵌入式系统中,这部分线性地址空间除了映射物理内存空间之外还要映射处理器内部外设寄存器空间等 I/O 空间。0xC0000000~high_memory 之间的内核逻辑地址空间专用来固定映射系统中的物理内存,也就是说 0xC0000000~high_memory 之间空间大小与系统的物理内存空间大小是相同的(当然在配置了 CONFIG_DISCONTIGMEMD 选项的非连续内存系统中,内核逻辑地址空间和物理内存空间一样可能存在内存孔洞),如果系统中的物理内存容量远小于 1GB,那么内核线性地址空间中内核逻辑地址空间之上的 high_memory~0xFFFFFFFF 之间还有足够的空间来固定映射一些 I/O 空间。可是,如果系统中的物理内存容量(包括内存孔洞)大于 1GB,那么就没有足够的内核线性地址空间来固定映射系统全部物理内存以及一些 I/O 空间了,为了解决这个问题,在 x86 处理器平台设置了一个经验值:896MB,就是说,如果系统中的物理内存(包括内存孔洞)大于 896MB,那么将前 896MB 物理内存固定映射到内核逻辑地址空间 0xC0000000~0xC0000000+896MB(=high_memory)上,而 896MB 之后的物理内存则不建立到内核线性地址空间的固定映射,这部分内存就叫高端物理内存。此时内核线性地址空间 high_memory~0xFFFFFFFF 之间的 128MB 空间就称为高端内存线性地址空间,用来映射高端物理内存和 I/O 空间。896MB 是 x86 处理器平台的经验值,留了 128MB 线性地址空间来映射高端内存以及 I/O 地址空间,在嵌入式系统中可以根据具体情况修改这个阈值,比如,MIPS 中将这个值设置为 0x20000000B(512MB),那么只有当系统中的物理内存空间容量大于 0x20000000B 时,内核才需要配置 CONFIG_HIGHMEM 选项,使能内核对高端内存的分配和映射功能。什么情况需要划分出高端物理内存以及高端物理内存阈值的设置原则见上面的内存页区(zone)概念说明。

五.  Linux 系统物理空间与虚拟内存空间的映射

物理地址有 896M 直接映射到虚拟地址的内存空间,这是一一对应的映射,只有起始地址不一样,偏移是一样的。这个大小大多是固定的,哪怕你的内存超过一个 G,太小了就另外说了。注意:用户区的代码也是放在这段物理地址里面的,就是说物理地址可以进行二次映射。但不管怎么样,这段物理地址都是受内核管理。当你内存很大的时候,超过 896M 时,剩余的那些内存怎么办呢?这多出来的叫做高端内存,如果你使用 vmalloc 申请空间,就会在高端内存中分配,如果你使用 kmalloc 申请空间,就会在小于 896 的内存中分配。所以还是很讲究的啊!!如果你的程序需要使用高端内存,就要调用内核 API 来分配,所以高端内存并不是想用就能用的哦。不过通过系统把一些应用常住在高端内存到是个好注意。不过前提是你的内存灰常大啊。

说说逻辑地址,线性地址与物理地址的关系

linux 通过段机制把逻辑地址转换为虚拟地址(就是线性地址),再通过页机制把虚拟地址转换为物理地址。所谓分段就是基址不同,偏移一样,比如说 32 位,一般程序里面都不会使用这么多的位,可以把前 12 位用作基址,后 20 位用作偏移,这样在特定段就可以只使用偏移寻址了。寻址很方便,不过 linux 页基址做的更好。

即将跳转到外部网站
安全性未知,是否继续
官方微信
微信中长按识别二维码或
搜索“Orange Pi”公众号