目录
一、物理地址空间是什么?
二、物理地址空间的构成:不仅仅是内存
三、Linux内核如何管理物理地址空间
(1)物理内存的碎片化问题
(2)物理地址的分区管理
(3)物理地址与内核的直接映射
四、物理地址和虚拟地址的关系
一、物理地址空间是什么?
对于刚刚接触Linux的新手而言,物理地址空间是理解内存的第一道门槛。他是硬件与内核交互的核心纽带,也是程序最终运行时候的真实地点。
简单来说,物理地址空间是硬件层面的“内存地图”,他主要是由cpu和内存控制器定义的,可以直接访问的地址范围。就好比有许多个格子,每一个编号对应一个格子,这个编号就是物理地址。
物理地址空间有两个关键特性:
(1)硬件决定性:地址范围由CPU位数和内存控制器决定。比如32位CPU理论上最大支持4GB的物理地址空间,因为他有32根地址线,2^32次方刚好对应4GB的编号。而64位CPU目前理论值远超实际硬件的地址范围,主流硬件支持48位物理地址,即256TB。
(2)直接关联性:物理地址直接对应硬件存储单元,CPU可以通过物理地址直接访问这些硬件资源,而不需要额外的地址转换或映射。(因为许多硬件厂家就预定了物理地址范围,即CPU只能去这些地址访问,否则硬件识别不了)
二、物理地址空间的构成:不仅仅是内存
很多人在学习的时候,会误认为物理地址空间仅仅对应我们插在主板上的内存条,但实际上他包含了计算机中所有需要通过硬件访问的硬件资源,主要分为以下几类:
(1)DRAM动态随机存取存储器:就是我们平常说的内存,是物理地址空间中最为核心的部分,用于临时存放程序的数据和程序。他的特点是可读可写,速度快,掉电丢失。
(2)ROM/Flash:用于存储固件程序(如BIOS、UEFI等)在计算机启动的时候,CPU会从ROM的固定物理地址开始执行程序(在计算机启动的时候,CPU还没有加载操作系统,它不知道自己应该从哪里开始执行,所以必须预设一个固定地址,从这里开始初始化硬件,并加载操作系统)。这部分地址通常是只可读的(或者在烧录的时候可写)
(3)外设地址空间:计算机的显卡、网卡等外设。他们的控制寄存器和数据缓冲区也会被映射到物理地址空间中。例如CPU向网卡发送数据的时候,实际上是向物理地址空间的某个特定地址写入数据。
虽然他们都是物理地址空间的构成部分,但是我们说了CPU由于地址线的限制,比如32位CPU只能寻址到4GB空间,所以他们一定是属于这4GB的一部分。
当然我们这里说的是内存映射方式,至于IO映射方式不在物理地址空间的4GB中,我们更多使用的是内存映射,这里不再讨论IO映射。
为什么硬件必须有自己厂商固定好的地址?
其一,在早期没有操作系统的时候,不存在虚拟地址空间、页表等说法,所以CPU在执行的时候直接操作的就是物理地址(比如单片机)。但是各个硬件设备如果没有规定好的地址范围,可能网卡、和键盘起冲突了,都规定在了同样的地址上,那么此时CPU访问该物理地址就不知道是谁了。所以形成了一种约定,什么设备应该放在什么地址,如果不遵守这个规则,CPU就无法通过BIOS程序写死的地址中找到对应的硬件设备。
其二,CPU在上电之后会执行BIOS程序,在这个程序之中,首先要做的就是硬件检测,看看这些硬件设备能不能正常运行,有没有哪里损坏。这个过程中还没有加载操作系统,所以不可能存在虚拟地址以及页表的概念。之后才会进行硬件初始化,加载引导程序开始加载操作系统,并将控制权从BIOS交给操作系统。
但并非所有的硬件都是CPU直接通过物理地址访问(虽然硬件只认物理地址)。一般对于需要快速响应的设别,CPU会直接通过物理地址的对应设备的寄存器其访问,减少了页表这一层转换,提高了效率。
然而对于大块硬件资源(启动的时候仍然是通过物理地址),如显卡的显存、网卡的缓冲区,因为对于大量的数据信息,可能该设备的寄存器根本不够用,比如要渲染某个游戏画面,往往需要几个GB的数据。
操作系统会给这些物理地址分配一个虚拟地址,然后通过页表映射到实际物理地址。这样做的好处是应用程序不需要知道物理地址,只需要操作虚拟地址,且操作系统可以通过页表权限控制防止应用程序错误修改硬件资源。
三、Linux内核如何管理物理地址空间
物理地址是硬件资源,而操作系统的任务就是高效、安全的管理这些资源。其管理逻辑主要围绕着以下几个关键点展开:
(1)物理内存的碎片化问题
物理内存由连续的页框(通常为4kb)组成,就好像一张张A4纸,每次使用和回收的时候,由MMU内存管理单元一页一页的分配,但是实际软件的数据可能并不存放在一张A4纸中,即两张各存一部分。如果频繁的申请内存以及释放,就会导致碎片问题,即存在大量的页都被一部分小数据占用,就好像每张A4纸写几个孤零零的字,由于他们已经被使用了,所以不能被回收成完整的大页。也就是不能申请很大的连续空间,比如1MB的连续空间。
为了解决这个问题,Linux内核采用伙伴系统来管理空闲页。就和我们之前做过的一个项目tcmalloc类似的内存池技术类似。
(1)把空闲页按照1、2、4、8这种形式分组,每一组称为一个伙伴块。
(2)当需要分配 n 页内存时,寻找最小的、大于等于 n 的伙伴块,若块过大则拆分;释放时若相邻块空闲则合并。
(2)物理地址的分区管理
内核会将物理地址划分为不同的区域,针对不同区域采取不同的管理策略
(1)ZONE_DMA:直接内存访问(DMA)区域,地址范围较低(通常 16MB 以下),供需要 DMA 传输的外设使用(如老式硬盘控制器)。
(2)ZONE_HIGHMEM:高端内存区域(32 位系统中超过 896MB 的部分),内核不能直接映射,需要通过临时映射机制访问(64 位系统因地址空间充足,通常无需此区域)。
之所以直接的线性映射区只有896MB,是因为剩余的128MB需要留给其他用途,如映射IO设备的物理地址、内核自身的代码数据等。
(3)ZONE_NORMAL:常规可直接映射区域,内核可以直接访问的物理内存(32 位系统中通常为 1GB 左右)。常规映射区域是给内核使用的,内核范围是3GB-4GB。所以这部分的虚拟地址=物理地址0-896MB+3GB。
(3)物理地址与内核的直接映射
Linux 内核自身需要快速访问物理内存,因此会将物理地址空间的一部分(通常是 ZONE_DMA 和 ZONE_NORMAL)直接线性映射到内核虚拟地址空间中。例如,物理地址 0x100000 会被映射到内核虚拟地址 0xC0100000(32 位系统)(物理地址增加3GB),这种映射是固定的、一对一的,内核通过简单的地址计算就能访问物理内存,无需复杂的页表查询。
四、物理地址和虚拟地址的关系
- 物理地址空间是 “仓库的真实货架编号”,每个编号对应唯一的货架位置,仓库管理员(内核)必须知道它才能找到货物。
- 虚拟地址空间是 “给客户的取货编号”,客户(用户程序)只需要记住这个编号,无需关心货物实际放在哪个货架。
- 即客户来取货的时候,只能通过仓库管理员。而仓库管理员掌握了一张页表,知道用户取货编号和仓库真实编号的映射关系。
两者的映射关系由内核通过页表维护:用户程序使用虚拟地址访问内存时,CPU 的内存管理单元(MMU)会根据页表将虚拟地址转换为物理地址,最终访问实际的硬件内存。这种机制的好处是:
- 隔离进程:每个进程都有独立的虚拟地址空间,互不干扰。
- 灵活利用内存:即使物理内存不足,也能通过 swap 分区(虚拟内存)暂时存储数据。
- 安全性:用户程序无法直接访问物理地址,防止恶意程序破坏系统。