Linux驱动之内存管理(一):从硬件角度看内存

本文介绍了内存管理的发展历程,从早期的单道编程到多道编程,再到分段和分页机制。分页机制通过更小的粒度提高内存利用效率,解决内存保护和换入换出的问题。在Linux内核中,内存被划分为用户空间和内核空间,并使用页表进行虚拟地址到物理地址的映射。MMU在这一过程中起着关键作用,完成地址转换。文章还提及了不同内存区域的划分以及页表的多级结构,对于理解操作系统内存管理有深入的解析。
摘要由CSDN通过智能技术生成

       内存,未接触编程之前,只知道它是主板上接在某个接口的长方形条,偶尔电脑启动不起来,就拆下来,用橡皮擦擦一擦金手指接口。接触计算机编程后,应用层通常需要申请内存就要调用malloc()/new()函数(不同编程语言使用的接口不一致);不需要内存时,就要及时地调用free()函数,否则就会导致内存泄漏,出现不可预估的错误。对于驱动工程师来说,内存更是不可或缺的对象之一,小到为内核结构体分配空间,大到网络数据包、液晶显存等。

标题0

       在操作系统中,内存管理是最为复杂的一个模块,从最原始的内存管理到分段机制,乃至现在广泛使用的分页机制。因此,在学习内存管理之前,有必要了解一下内存管理中的一些基本概念。
       最先出现的是单道编程的内存管理,所谓的单道编程,就是整个系统中只存在两个进程,操作系统和应用程序。用户程序被加载到某个固定的位置上运行,可以直接访问所有的物理内存。这种方式存在几个弊端:一是当用户程序超过可用的物理内存时,由于找不到更大的物理内存用来装载程序,所以程序无法运行; 二是整个系统只运行了一个应用程序,造成资源浪费;三是无法迁移到其他计算机中运行。
       随后,为了解决上述问题,人们又提出了多道编程的内存管理,即系统中可以运行多个进程。将物理内存分为多个分区,用于装载小于等于分区大小的进程,称为固定分区。这种方式,操作系统管理开销比较小,但是对装入程序的大小有要求,其次活动进程的数据比较固定且地址无法增长。为此,又出现了动态分区。固定分区的地址空间不能增长,动态分区就根据实际进程的大小,进行分配。无论是采用固定分区,还是动态分区,进程都是可以直接访问全部的物理内存,想象一下,内存中进程A存放在物理内存的某个地址,用户进程B一个误操作,直接将那块内存区域的内容进行清零,这就会出现无法挽回的局面。对于一些恶意进程更是可以随意修改其他进程的内容。
       于是乎出现了一种新的机制——分段地址,系统将应用程序所使用的内存空间的虚拟地址映射到某个物理地址中,由于应用程序使用的是虚拟地址,尽管可能出现不同应用程序访问的虚拟地址是相同,但是不同的应用程序所使用的虚拟地址是被映射到不同的物理地址。比如进程A访问了虚拟地址0xbc00_0000,进程B也访问了虚拟地址0xb000_0000,这并不会出现“进程彼此之间访问其他进程的数据”,因为对于进程A的虚拟地址被映射到了物理地址0x8300_0000,而进程B的虚拟地址则被映射到物理地址0x8800_0000。为什么现在广泛使用的是分页机制,而不是分段机制呢?这也就说明了分段机制还不够完美,尽管解决了地址保护的问题。分段机制以进程为单位,当内存不够时,会将整个进程的内容进行换出,效率非常低,影响了系统性能。
分段机制——物理内存与虚拟内存
       人们在分段机制之上,实现了以更小粒度进行换入换出的机制——分页机制。分页机制以固定大小的页为最小单位,对物理内存地址和虚拟地址按照页进行分割,当内存不足时,只需要将那些不常用的页交换到磁盘中,提高了效率,也节省了物理内存。

分页机制

标题1
       对于32位机器使用的Linux内核,无论你的物理内存是512M,或者是1GB,所有进程拥有虚拟内存的大小都为4GB。Linux将这4GB的虚拟内存划分为两个部分:用户空间和内核空间,一般是采用3:1的比例进行划分,即用户空间占用3GB大小,内核空间占用1GB。可以通过修改内核配置CONFIG_PAGE_OFFSET,实现2:2的比例划分。
内核配置CONFIG_PAGE_OFFSET
       所有的用户进程,都可以访问用户空间的全部内容,而访问内核空间则需要特权模式,除此之外,为了避免多次将内核空间映射到每个进程中所花费的时间,内核空间位于顶部3GB~4GB的地址范围内,方便所有的用户进程访问。
       Linux内核同样将物理内存和虚拟内存按照页为单位,一页为4096字节(4KB),进行划分。通常,我们称物理内存划分的页,为页帧;虚拟内存划分的页,称为页;存放它们之间的映射关系,称为页表。

内存模型
       Linux内核对1GB内核空间进行“定制”,又细分为:低内存区域(Low Memory Region)和高内存区域(High Memory Region)。低内存区域位于前896MB的地址空间,主要用于映射物理内存的前1GB的物理内存,这部分的地址可以通过特定的内核宏定义__pa(address)将虚拟地址转换物理地址。低内存区域根据用途,分为DMA专用区域(DMA_ZONE,物理内存0 ~ 16MB映射区域)、通用内存区域(NORMAL_ZONE,物理内存16 ~ 896MB映射区域)和高内存区域(HIGHMEM_ZONE,物理内存896MB以上映射区域)。

  • 对于内存只有512MB的硬件系统,低内存区域只被划分为DMA专用区域,通用内存区域。
  • 对于系统架构为ARMv7 32位的,内核版本使用4.19.35,__pa和__va内核宏定义,仅仅只是返回了自身地址,并没有返回物理地址
//位于源码/include/asm-generic/page.h
 #define __va(x) ((void *)((unsigned long) (x)))
 #define __pa(x) ((unsigned long) (x))

内核空间剩下128MB大小的高内存区域,主要用来动态映射地址超过1GB的物理内存,当用户访问了1GB以外的地址时,内核会将该物理地址暂时映射到该区域,供用户使用。当不需要时,就释放掉相应的映射,通过这种方式,实现访问所有物理内存地址。
在这里插入图片描述
       每个进程都拥有3GB大小的用户空间,Linux用户空间使用VMA(Virtual Memory Area)来描述,对于每一个进程,/proc目录下都会有一个对应的PID号目录,旗下的maps文件记录了该进程用户空间的内存使用情况。进程2786主要调用了mmap函数进行内存映射,可以看到红框中记录了该信息。该文件有6列信息组成,第一列表示了该VMA占用了哪个虚拟地址范围;第二列表示该VMA段具有的权限,有读®、写(w)、执行(x)以及私有映射§;第三列表示文件映射的偏移量offset;第四列对应设备的主设备号和次设备号;第五列是指文件的inode编号;第六列则是映射的文件/dev/mem。
在这里插入图片描述

在这里插入图片描述
       经常会看到这样的问答:“单片机能不能跑Linux系统?”,“不能,单片机没有MMU单元,跑不了Linux系统”。MMU到底是个什么东西,为什么缺了它,就跑不了Linux系统呢?MMU,全名为Memory Management Unit,译为内存管理单元,是现代CPU的一部分,主要用来进行虚拟地址和物理地址转换。前面提到过,物理内存和虚拟内存都是按照每页4096字节,进行划分的。假设,内核现在往虚拟地址为0xBAAD_F0DA的位置写入数据,那应该如何去寻找到实际的物理地址呢?
       内核中有一张大大的表,记录了虚拟页编号和物理页帧号(PFN)的对应关系,称为页表。虚拟地址被划分为两个部分,低12位用于描述偏移量(这里的12位跟实际的页大小有关系,2的12次方刚好对应了4096个字节),其余的全部位置记录了虚拟页编号。访问实际的物理地址时,内核根据虚拟地址得到虚拟页编号和偏移量,通过页表查询,得到了对应的物理页帧号,这时候再加上相应的偏移量,就可以知道数据应当写入到哪个物理地址了。
在这里插入图片描述

实际上,内核维护的页表并不可能是上图这样子的,假设用户空间有3GB的地址空间,在32位机上,一个记录通常是用4个字节表示,那么为了记录所有的3GB地址空间,则需要786,432条记录,可想而知,这对操作系统是一笔多么大的开销。为此,内核引入了多级页表:

  • 一级页表(PGD):第一级页表,共有1024个记录,用来记录第二级页表的地址;
  • 二级页表(PUD):第二级页表,只有在使用四级页表时存在,指向了第三级页表的地址;
  • 三级页表(PMD):第三级页表,只有在使用四级页表时存在,指向了真正存放映射关系的地址;
  • 四级页表(PTE):最后一级页表,保存了物理地址的映射关系。

       对于大多数32位机,通常只会使用两级页表,一级页表和四级页表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值