Linux内存分段分页管理机制

本文详细阐述了32位x86架构计算机中,从实模式到保护模式的内存管理变化,包括分段机制、段寄存器的作用,以及GDT和全局描述符表在内存隔离和保护中的作用。同时介绍了分页机制如何提高内存利用率和多任务隔离。

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

以32位x86架构计算机,linux内核说明

操作系统进入保护模式前的内存布局

                                     

保护模式下的分段机制 :

 实模式与保护模式:

       实模式出现于早期8088CPU时期。当时由于CPU的性能有限,一共只有20位地址线(所以地址空间只有1MB),以及8个16位的通用寄存器,以及4个16位的段寄存器。所以为了能够通过这些16位的寄存器去构成20位的主存地址,必须采取一种特殊的方式。当某个指令想要访问某个内存地址时,它通常需要用下面的这种格式来表示:

  (段基址:段偏移量)

  物理地址 = 段基址<<4 + 段内偏移

  所以假设段寄存器中的值是0xff00,段偏移量为0x0110。则这个地址对应的真实物理地址是 0xff00<<4 + 0x0110 = 0xff110。

实模式的"实"更多地体现在其地址是真实的物理地址。

        实模式的寻址方式很简单和直观,可以直接软件访问BIOS以及周边硬件,但是也有很多问题,主要就是:安全、多任务支持、cpu 特权模式(任务的权限不同)。比如随便一个程序都能修改别人的内存,别人的程序就挂了,理论上来说,每个人只能访问自己的内存,当然还有高任务级别可以访问低级别的内存等功能。

        虽然现代x86处理器已经支持了更灵活的64位和32位操作模式,但为了保持向后兼容性,16位段寄存器仍然存在。这意味着在新的x86架构中,16位段寄存器仍然可以用来加载段选择子,但实际上,大多数操作系统(包括Linux)和应用程序已经不再依赖于分段机制,而是使用分页机制进行内存管理。这允许更灵活的内存管理和更大的虚拟地址空间

保护模式

        随着CPU的发展,CPU的地址线的个数也从原来的20根变为现在的32根,所以可以访问的内存空间也从1MB变为现在4GB,寄存器的位数也变为32位(除了段寄存器,兼容16位实模式寻址)。所以实模式下的内存地址计算方式就已经不再适合了。 但是我们的内存寻址方式还是得兼容老办法(这也是没办法的,有时候是为了方便,有时候是一种无奈),即(段基址:段偏移量)的表示方式。

        所以就引入了现在的保护模式,实现更大空间的,更灵活也更安全的内存访问。

        我们的偏移值和实模式下是一样的,就是变成了32位而已,而段值仍旧是存放在原来16位的段寄存器中,但是这些段寄存器存放的却不再是段基址,实模式下寻址方式不安全,我们在保护模式下需要加一些限制,而这些限制可不是一个寄存器能够容纳的,于是我们把这些关于内存段的限制信息放在一个叫做全局描述符表(GDT)的结构里。全局描述符表中含有多个表项,每一个表项称为段描述符。而段寄存器在保护模式下存放的便是相当于一个数组索引的东西,通过这个索引,可以找到对应的表项。段描述符(全局描述符表的表项)存放了段基址、段界限、内存段类型属性(比如是数据段还是代码段,注意一个段描述符只能用来定义一个内存段)。

        相当于一个指针指向数组某个位置,段寄存器(指针)指向全局描述符表(数组)的一个位置,得到段描述符(在全局描述符表找到的表项),里面有段基址。(段寄存器里面不再是段基址,相当于全局描述符表的下标

        那段偏移量呢:

        段偏移量不是由代码自己指定的,而是由操作系统或编译器生成的。段偏移量是指在分段存储模型中,指定在特定段内的数据或指令相对于段的起始地址的偏移量。

        在x86架构中,当一个段被加载到段寄存器(如CS、DS、ES、SS)中时,该段的偏移量通常是由程序中的内存引用或者汇编指令中的地址自动生成的。这个偏移量是相对于段的基地址来计算的。程序员通常不需要手动指定偏移量,除非他们在编写汇编代码或进行底层内存操作时需要自行计算偏移量。在高级编程语言中,编译器和操作系统通常会自动处理这些细节,使程序员不必担心偏移量的计算。

        既然要分段,分段靠的是段基址,但段描述符里面不止有段基址,前辈们对(段描述符)32位的数据进行组合拆分,段基址及其他标志位如下图规定:

 

        这是x86架构中数据段描述符格式的特定设计,这个结构允许描述不同的物理内存段,同时使用相同的高位基地址,以减少冗余。

      一些关键位介绍:

  1. 段限制(Limit): 20位字段,指定了段的大小。Linux内核通常将段大小设置为0xFFFFFFFF,以允许对整个物理内存的访问。

  2. 访问权限(Access Rights): 12位字段,定义了段的访问权限和属性。这些位包括:

    • 段存在位(Present Bit): 1位,表示段是否存在。如果为1,表示段存在;为0,表示段不存在。

    • **特权级别(Privilege Level):2位,指定了段的特权级别。0表示内核态,3表示用户态。

    • **段类型(Segment Type):4位,定义段的类型。对于数据段,通常设置为0x2,表示数据读写段。

    • **执行许可位(Execute Disable Bit):1位,对于数据段通常设置为0,表示不允许执行。

    • **扩展位(Long Mode Active):1位,对于64位操作系统(如x86_64),这个位用于表示是否启用了长模式。

    • **操作数大小位(Operand Size):1位,表示段的操作数大小。通常设置为1,表示32位操作数。

    • **粒度位(Granularity Bit):1位,指定段的粒度。通常设置为1,表示使用4KB的粒度。

         有了全局描述符表(只有一个)后,有的人想把一些特定的进程用单独的描述符表,好处就是  在多任务操作系统中,不同的任务可以使用不同的 LDT 来确保内存隔离和保护。然后又有了局部描述符表,原理一样。

        然而许多现代操作系统更倾向于使用 GDT 来管理段描述符,以便更好地支持多任务管理。简而言之是全局描述符表由一个实体变成了一些描述符表的统称,包括局部描述符表。

                                                  

        任务(进程)之间的内存隔离和保护通常是通过操作系统的内核来管理,而不是依赖于 LDT。因此,LDT的使用相对较少,而 GDT 更为常见。

        全局描述符表(gdt)在内存中的位置是由操作系统把这个位置信息存储在一个叫 gdtr 的寄存器中。操作系统把全局描述符表存入特定位置。

        初始全局描述符表有三个段描述符

                                              

所以在操作系统把gdt装入内存后,就开启保护模式。

将 cr0 这个寄存器的位 0 置 1,模式就从实模式切换到保护模式了。

分页机制:

        为什么要分页:

        操作系统内存管理(一)分页机制的来由 - 不足为外人道也的文章 - 知乎 https://zhuanlan.zhihu.com/p/352188978

        换入换出的时候,因为地址必须连续,导致无法利用离散的小内存块!这种问题是内存利用率低!

        在没有开启分页机制时,由程序员给出的逻辑地址,需要先通过分段机制转换成物理地址。但在开启分页机制后,逻辑地址仍然要先通过分段机制进行转换,只不过转换后不再是最终的物理地址,而是叫做线性地址,然后再通过一次分页机制转换,得到最终的物理地址。

        开机后分页机制默认是关闭状态,需要我们手动开启,并且设置好页目录表(PDE)和页表(PTE)。其目的在于可以按需使用物理内存,同时也可以在多任务时起到隔离的作用,这个在后面将多任务时将会有所体会。

        在 Intel 的保护模式下,分段机制是没有开启和关闭一说的,它必须存在,而分页机制是可以选择开启或关闭的。

        如果没有开启分页机制,那么线性地址就和物理地址是一一对应的,可以理解为相等。如果开启了分页机制,那么线性地址将被视为虚拟地址,这个虚拟地址将会配合分页机制完成转换,最终转换成物理地址。

        操作系统实现分页机制通过二级页表来实现,第一级叫页目录表 PDE,第二级叫页表 PTE。他们的结构如下。

        操作系统首先把页目录表和页表放入内存。

在本例中只有一个页目录表,4个页表(可以有多个),总共可以使用的内存不会超过 16M,也即最大地址空间为 0xFFFFFF

而按照当前的页目录表和页表这种机制,1 个页目录表最多包含 1024 个页目录项(也就是 1024 个页表),1 个页表最多包含 1024 个页表项(也就是 1024 个页),1 页为 4KB(因为有 12 位偏移地址),因此,16M 的地址空间可以用 1 个页目录表 + 4 个页表搞定。

4(页表数)* 1024(页表项数) * 4KB(一页大小)= 16MB。

                                       

虚拟地址配合分页机制转换成物理地址

 

     

 

   

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值