×
嵌入式 > 嵌入式开发 > 详情

linux下内存管理学习心得(二)

发布时间:2020-10-23 发布时间:
|
接着上面的知识,这篇主要总结如下几个方面知识:

1、物理地址的页、区等概念

2、内核使用内存的函数

3、分配字节与分配页

一、区、页

前面linux下内存管理学习心得(一)也已经说了关于页的概念,在内核下面是把物理页(页框)作为分配的基本单元,内核下使用struct page结构体来表示系统中的物理页其中该结构体表示页是否被锁定在内存中,是否为脏页,该页被引用几次,同时还有页的虚拟地址等等。关键一点page结构只与物理页有关而与虚拟页无关,结构仅仅目的在于描述物理内存本身,而不是描述包含在其中物理页中的数据。

下面说明一下如何寻址到物理页:

机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到。

我们写个最简单的hello world程序,用gccs编译,再反编译后会看到以下指令:

mov 0x80495b0, %eax

这里的内存地址0x80495b0就是一个逻辑地址,必须加上隐含的DS数据段的基地址,才能构成线性地址。也就是说0x80495b0是当前任务的DS数据段内的偏移。

在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。

Linux中逻辑地址等于线性地址。为什么这么说呢?因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从0x00000000开始,长度4G,这样线性地址=逻辑地址+ 0x00000000,也就是说逻辑地址等于线性地址了。

前面说了Linux中逻辑地址等于线性地址,那么线性地址怎么对应到物理地址呢?这个大家都知道,那就是通过分页机制,具体的说,就是通过页表查找来对应物理地址。

准确的说分页是CPU提供的一种机制,Linux只是根据这种机制的规则,利用它实现了内存管理。

在保护模式下,控制寄存器CR0的最高位PG位控制着分页管理机制是否生效,如果PG=1,分页机制生效,需通过页表查找才能把线性地址转换物理地址。如果PG=0,则分页机制无效,线性地址就直接做为物理地址。

分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。注意,为了实现每个任务的平坦的虚拟内存,每个任务都有自己的页目录表和页表。

为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。

32位的线性地址被分成3个部分:

最高10位Directory页目录表偏移量,中间10位Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。

页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。

页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。

每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表可以提前分配好,也可以在用到的时候再分配。

还是以mov0x80495b0, %eax中的地址为例分析一下线性地址转物理地址的过程。

前面说到Linux中逻辑地址等于线性地址,那么我们要转换的线性地址就是0x80495b0。转换的过程是由CPU自动完成的,Linux所要做的就是准备好转换所需的页目录表和页表(假设已经准备好,给页目录表和页表分配物理内存的过程很复杂,后面再分析)。

内核先将当前任务的页目录表的物理地址填入cr3寄存器。

线性地址0x80495b0转换成二进制后是0000 1000 0000 0100 1001 0101 1011 0000,最高10位0000 1000 00的十进制是32,CPU查看页目录表第32项,里面存放的是页表的物理地址。线性地址中间10位00 0100 1001的十进制是73,页表的第73项存储的是最终物理页的物理起始地址。物理页基地址加上线性地址中最低12位的偏移量,CPU就找到了线性地址最终对应的物理内存单元。

我们知道Linux中用户进程线性地址能寻址的范围是0-3G,那么是不是需要提前先把这3G虚拟内存的页表都建立好呢?一般情况下,物理内存是远远小于3G的,加上同时有很多进程都在运行,根本无法给每个进程提前建立3G的线性地址页表。Linux利用CPU的一个机制解决了这个问题。进程创建后我们可以给页目录表的表项值都填0,CPU在查找页表时,如果表项的内容为0,则会引发一个缺页异常,进程暂停执行,Linux内核这时候可以通过一系列复杂的算法给分配一个物理页,并把物理页的地址填入表项中,进程再恢复执行。当然进程在这个过程中是被蒙蔽的,它自己的感觉还是正常访问到了物理内存。

但是页又可以有不同的使用方式,所以内核就引入区的概念,将页划分成为不同的区:

linux主要使用了四种区:

ZONE_DMA:这个区包含页能用来执行DMA操作。

ZONE_DMA32:和ZONE_DMA类似,不过只能被32位设备访问。

ZONE_NORMAL:这个区包含的都是能正常映射的页。

ZONE_HIGHEM:包含“高端内存”。

这样linux把系统划分为区,形成不同的内存池从而用于不同的用途。注意:区的概念只不过是内核的分配,而本身物理内存是无法这样分配的。



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ARM入门篇之(一)概念