打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
内存映射及管理
以下介绍的均以32位系统4G内存为例说明。
linux是运行在虚拟地址空间的系统。我们往往说的内核空间和用户空间都是指的虚拟地址空间。linux系统对地址分段做了简化,虚拟地址也是对应0~4G。对于x86架构系统来说,对虚拟地址的分段是必须的,典型的分割是将最高的1G字节(从虚拟地址0xC00000000xFFFFFFFF给内核空间,而将较低的3G字节(从虚拟地址0x000000000xBFFFFFFF给用户空间;对于许多非x86的架构来说这个分段不是必须的,也就是内核空间可以直接拥有4G字节。具体用户空间的应用程序是不能直接进入内核空间的,而是通过系统调用进入内核空间,于是所有应用程序共享一个内核空间,对于x86架构来说,就是所有应用程序共享1G内核空间。而每个独立的应用程序之间的内存是不会相互干涉的,这意味着不同进程可以访问同一虚拟地址而绝对不会冲突。比如,一个进程从其用户空间的地址0x1234ABCD处可以读出整数8,而另外一个进程从其用户空间的地址0x1234ABCD处可以读出整数20,这取决于进程自身的逻辑。于是对于每个应用程序来说其实都拥有4G的虚拟地址空间(自己的3G加共享的1G)。任意一个时刻,在一个CPU上只有一个进程在运行。所以对于此CPU来讲,在这一时刻,整个系统只存在一个4GB的虚拟地址空间,这个虚拟地址空间是面向此进程的。当进程发生切换的时候,虚拟地址空间也随着切换。由此可以看出,每个进程都有自己的虚拟地址空间,只有此进程运行的时候,其虚拟地址空间才被运行它的CPU所知。在其它时刻,其虚拟地址空间对于CPU来说,是不可知的。所以尽管每个进程都可以有4 GB的虚拟地址空间,但在CPU眼中,只有一个虚拟地址空间存在。虚拟地址空间的变化,随着进程切换而变化。
内核事实上也是一个大的程序,只不过他承担的事情很多,包括系统内存的管理,进程调度等,他的代码和数据都放在内核空间中,而应用程序的代码和数据均放在用户空间中。但是所有的代码和数据最终都会放到实际的物理地址空间中。而且都是栈(也称堆栈),堆,BSS,数据data,代码这样的放置,这里从左到右是地址由高到低的存放。那么内核空间和用户空间的这种分离又是如何统一到最终的物理地址中的呢?这就是通过映射。

下图大概显示了内存的映射:


内核空间的地址映射:
内核中的虚拟地址分为内核逻辑地址和内核虚拟地址。对于逻辑地址而言,他的映射是一对一的,而且是线性的,这样其实就是只差一个偏移量(PAGE_OFFSET),在典型的分割中偏移量就是3G,需要注意的是虽然内核空间是占据了虚拟地址中的最高1G字节,但是他的映射都是从物理内存的最低地址0x00000000开始的。但是对于内核虚拟地址来说其映射不是一对一的,也就是不是线性的,而是离散的。于此同时,逻辑地址能够映射的物理地址也是有限的,只有低端部分(依赖与硬件和内核的设置,一般为1G到2G),高端部分是没有的。从一般的配置(1G到2G)中可以看到其实一般来说内核虚拟地址都是指的逻辑地址为主,这意味着内核一般用逻辑地址来引用物理地址存储数据代码等。

我们来看一下在include/asm/i386/page.h中对内核空间中地址映射的说明及定义:
 /** This handles the memory map.. We could make this a config
* option, but too many people screw it up, and too few need
* it.
*
* A __PAGE_OFFSET of 0xC0000000 means that the kernel has
* a virtual address space of one gigabyte, which limits the
 * amount of physical memory you can use to about 950MB.
 *
* If you want more physical memory than this then see the CONFIG_HIGHMEM4G
* and CONFIG_HIGHMEM64G options in the kernel configuration.
 */
#define __PAGE_OFFSET (0xC0000000)
 ……
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))

这里如果给定一个逻辑地址,宏__pa将其映射为物理地址,相反宏__va将物理地址返回逻辑地址。

用户空间地址映射:
用户空间的地址映射要远比内核空间的映射复杂很多,因为其需要通过内核管理以及一些页表机制来完成映射。
我们首先看一个图:

linux中,内核通过tast_struct结构体来对进程进行描述,在这个描述结构体中就有一个mm,这是对内存相关的描述,每个独立的进程都有一个对应的mm_struct,但是这个mm_struct可以共享,这就是线程的内存分配原理。在mm_struct结构体中描述的内存分配都是指的虚拟地址分配,而每个段如堆,栈,BSS等都对应一个vm_area_struct,即虚拟内存区域对应的结构体描述。这里描述的几个结构都是内核结构体,即内核实现的内存管理。

如下图所示:

每个虚拟内存区域(VMA)是一个虚拟地址空间上连续的区域;这些区域不会彼此覆盖。Vm_area_struct结构描述了一个内存区域,包括他的开始和技术地址、flags字段指定了他的行为和访问权限,vm_file字段指定了该区域映射的实际文件。一个没有映射文件的VMA成为匿名的。除了内存映射段以外,上面的每个内存段(堆、栈等等)相当于一个单独的VMA。这不是必须的,尽管在x86机器上通常是这样。VMA不会关心他在哪个段里面。接下来就是一些页表机制等将虚拟内存区域里的虚拟内存映射到实际物理地址中。

下图显示了其中一个虚拟内存区域对应的映射:

具体的映射原理可以看此文


补充知识内核映像

  在下面的描述中,我们把内核的代码和数据就叫内核映像(kernel image)。当系统启动时,Linux内核映像被安装在物理地址0x00100000开始的地方,即1MB开始的区间(1M留作它用)。然而,在正常运行时,整个内核映像应该在虚拟内核空间中,因此,连接程序在连接内核映像时,在所有的符号地址上加一个偏移量PAGE_OFFSET,这样,内核映像在内核空间的起始地址就为0xC0100000

例如,进程的页目录PGD(属于内核数据结构)就处于内核空间中。在进程切换时,要将寄存器CR3设置成指向新进程的页目录PGD,而该目录的起始地址在内核空间中是虚地址,但CR3所需要的是物理地址,这时候就要用__pa()进行地址转换。在mm_context.h中就有这么一行语句:

asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));

这是一行嵌入式汇编代码,其含义是将下一个进程的页目录起始地址next_pgd,通过__pa()转换成物理地址,存放在某个寄存器中,然后用mov指令将其写入CR3寄存器中。经过这行语句的处理,CR3就指向新进程next的页目录表PGD了。



本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Linux内存管理 下
内存观点
认真分析mmap:是什么 为什么 怎么用
浅谈mmap介绍
Linux内存管理
别再说你不懂 Linux 内存管理了,10 张图给你安排的明明白白
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服