linux内存管理之石器篇

本文详细探讨了Linux内核中的内存管理,从冯诺依曼体系结构出发,讲解了内存分段的概念,包括内存地址修正、分段地址计算以及分段机制的历史演变。接着,介绍了内存分页的引入及其优势,分析了多级页表的工作原理,并以64位系统为例展示了四级页表的寻址过程。最后,讨论了TLB快表在提高寻址效率中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

    该篇为linux内核内存管理第一篇,答主文中所述主要作学习记录使用,文章中难免有逻辑错误、思虑不周之处。欢迎路过的各位牛牛们告知并督促答主改正,不吝赐教。

初见内存

    在冯诺依曼体系结构中,存储器是5大部件之一。试想一下,我们编写了一个打印hello world的程序,如何才能让程序执行起来。

    程序执行需要借助cpu,也就是5大部件中的控制器和运算器,而程序运行过程的本质就是“CPU取指”,然后执行。在取指中,指令从何处来?

    其实,指令就存储在内存中的。程序执行的过程我们可以简单的看作:CPU将指令物理地址发送到总线上,由总线控制从内存的对应位置取出来,逐条执行。因此,想要执行的程序是加载到内存中的。

内存地址

内存分段    

简单设想

    现在我们编写一个helloworld程序,我们知道要让CPU可以运行这个程序,该程序文件是需要加载到内存中的。

#include <stdio.h>
void helloworld(void)
{
    printf("hello world !\n");
}
int main(void)
{
    helloworld();
    return 0;
}
......
gcc helloworld.c -o helloworld -m32
......

objdump -h helloworld
helloworld:     file format elf32-i386
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  08048134  08048134  00000134  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 ......

 12 .text         0000017c  08048330  08048330  00000330  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
......

 23 .data         00000004  08049680  08049680  00000680  2**2
                  CONTENTS, ALLOC, LOAD, DATA

    通过objdump等命令查看helloworld二进制文件,我们发现程序是分段的,比如上面看到的text代码段、还有data数据段等,之前我们已经想到了,ELF格式的可执行程序文件等如果想执行,是需要放到内存里面的,我们可以设想程序文件是从磁盘分段映射到内存中的。

内存地址修正

    在helloworld程序中,调用了helloworld函数用来打印信息,对应到指令代码可看成call ip1。我们设想一下,如果此处的ip1对应的是内存地址的话,也就是可执行程序文件中此处的指令是call 0x8048430,这要求我们在编译可执行程序的时候就提前知道了helloworld函数映射的位置,这是不现实的,因为你无法提前确定该地址对应的段是否已经被使用,也可能已经被其他未执行的程序文件占用了。

    其实,一般程序的段映射是发生在运行时的,既在运行时才动态选择了基地址为0x8048330的段来映射代码部分,所以process1内部的跳转值我们可以使用偏移值,比如call 0x100。当程序执行的时候重新进行地址计算。

    比如:

    新地址 = 段基址  + 偏移值 

    调用helloworld其实读取的是内存地址(0x8048330 + 0x100 = 0x8048430)处的指令。在这里0x8048330可称为某段内存的段基址。

分段地址计算

    上面是我们设想的一个简单的内存分段并取指的模型,真实情况下,我们一般用一个segment 段+ offset偏移 的方式表示一个地址,这些地址包含在机器指令中,表示某条指令或者某个立即数,可以叫做逻辑地址或者虚拟地址,其中所描述的segment段其实是某内存段的段基址。

    在所用到的寄存器中,CS是代码段寄存器,当然还有数据段寄存器DS、堆栈段寄存器SS和附加段寄存器ES\FS\GS,而IP是指令寄存器。某种意义上,我们会使用CS:IP表示某个指令地址,其中CS是代码段寄存器,表示段表的下标值(可称为段号),而IP我们可以当成相对偏移值,这就是逻辑地址的一种表示。

    通过上面我们知道,在使用内存分段管理过程中,我们需要进行地址转换计算出来内存区对应的真实地址,此时我们称为线性地址,既上图中我们所描述的内存区就是一整块线性地址区间。

    线性地址 =  段基址 + 偏移值,段表中存储的是段描述符集合,根据特定段号(index),我们就可以找到对应的段描述符,而得到我们所需要的段基地。

分段机制的前世今生

    在8086 cpu之前,可称为实模式,其寄存器和总线都是16位的。可以直接使用地址访问物理内存的,也就不存在逻辑地址、线性地址和物理地址。

    在8086 cpu时代,想把寻址内存空间扩充到1M(需要20bit地址长度),但是算数逻辑单元的带宽是16位的,没办法直接计算20bit的地址,这时候就引入了内存分段管理的概念,也就有了段寄存器(cs/ss/ds/es/fs/gs)。此时的段寄存器是16位的,其中存储的地址值为段地址,在给地址总线传递地址之前,在段寄存器上将16位的地址映射到20位地址的高16位上(既段寄存器的值左移4位),同时加上逻辑地址偏移值直接得到物理地址,发送给总线。

    在80286 cpu时代,出现了保护模式,总线扩展成了24位,寻址大小为16M,出现了保护模式。

    在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值