操作系统-内存读取[半原创]

文章大部分表述图片来自 : https://www.jeanleo.com/2021/07/06/linux内存管理剖析/ 。 非原创

内存是如何给读取的

    计算机上电启动的时候,BIOS会检测并计算物理内存大小。比方说现在通用的内存都是DIMM针脚插槽类型的,它的PIN针脚有两百多个,各个针脚各有自己的定义,BIOS就是通过对不同针脚的高低电平设置,由内存反馈其规格信息给BIOS,然后BIOS计算出容量。大概原理就这样了。但是我们重点是操作系统需要感知主机的内存空间,它是怎么知道的呢?它是通过BIOS提供的接口去询问出来的。这个接口就是0x15中断,其中参数重点参数是ax寄存器中需要设置值e820。然后通过intcall(0x15, &ireg, &oreg)中断调用,由BIOS通过oreg.di出参将内存信息返回回来。该实现在/arch/x86/boot/memory.c中的detect_memory,由于代码出参oreg.di也是ireg.di传进去的值,所以代码里面直接读了buf空间内存。由于每调用一次intcall只会返回一条内存数据信息,所以会循环调用多次才能够探明整个内存空间。

1297993-20211114115224453-86847209.png

也就是说 操作系统 --> BIOS中断 中断来感知内存空间的,这个0x15是一个中断号 ,定义在中断表述表,而这个中断表又是从哪里来的呢?我们在上篇文章 里写道加载 BIOS 的环节会加载中断表。

    计算机刚开机的时候只有1M的内存可以使用。 此时内存会被各种外设瓜分,映射在内存的相应区域。同理,BIOS里的信息会被映射到内存的一段连续的区域中(0xC0000–0xFFFFF),其中最为关键的系统BIOS被映射到了0xF0000–0xFFFFF位置,CPU开机就是执行了系统BIOS这块内存区域中的代码,注意BIOS中的程序还会占用内存开头的一些区域,如把中断向量表写在了内存开始的位置。

    在开机的一瞬间,CPU中的PC寄存器被强制初始化为0xFFFF0。BIOS程序的入口地址就是0xFFFF0,然后CPU就开始跑起来,执行BIOS中的程序。

中断0x15有三个子功能,子功能号放到EAX或者AX中,其简要说明:

  • EAX = 0xE820 遍历所有内存
  • AX = 0xE801 分别检测低 15 MB 和 16 MB ~ 4 GB的内存,最大支持 4 GB。
  • AX = 0x88 最简单的子功能,只能检测64MB的内存,若超过也只返回 64 MB。

具体的子功能讲解见这篇文章

内存由谁来读取

    内存探测必然是kernel吗?答案是否定的。先说一下kernel的binary文件吧,它通常放在/boot/下面,名字通常命名为vmlinuz。这个文件是由setup.bin和vmlinux构造而成,其中vmlinux又由kernel编译目录arch/x86/boot/compressed下的cmdline.o、head.o、kaslr.o等连同压缩后的vmlinux.bin.gz合并构成。其中检测内存的detect_memory()函数就是在setup.bin里面,但是这仅限于Grub legacy(即Grup 0.97到1.97版本)引导kernel的时候,setup.bin才会被执行到,也就是仅在该情况下内存探测才是由kernel引导的领头羊去完成的。
    
    到了Grub2(即Grub 1.98到现在最新版本)引导linux系统的时候,则由Grub直接探明内存布局,然后解析vmlinuz文件,并且直接加载vmlinux部分的内容到内存中并跳转执行head_32函数,而内存布局则通过参数boot_params传递执行。
    
    谁探明内存布局对内存管理有影响吗?没影响的,所以这里是可以忽略的废话。既然废话就多说两句,为什么要分开setup.bin和vmlinux呢?这是因为setup.bin运行在实模式下面,而vmlinux则运行在保护模式下面。所以也就是说grub2是进入了保护模式后才加载引导的kernel。

1297993-20211114121928779-829326418.png

我们从上面的描述中知道了,内存读取这个动作发起人,不一定是内核,以现在GRUB作为操作系统引导程序来说,内存读取这个动作是由GRUB来发起触发BISO中断,进行内存探测的,这其中讲到实模式和保护模式,我们后续会讲到 。

内存读取以后

管理-探知的e820表如何处理?是如何被管理的

    前面已经知道了如何探明内存空间得到e820图,得知了内存的位置、大小和类型。在e820__memory_setup()函数内会将重叠的内存空间根据属性进行筛选,并将同属性的相邻内存空间进行合并处理。整个处理过程如右侧所示,虽然实现上会对内存进行分割合并处理,但是实际上内存并不会这么错乱重叠的。处理完毕后,会通过e820__print_table()对外打印。通过dmesg可以看到如图左侧所示的信息。

1297993-20211115224925206-612173490.png

那么是如何分割和合并的呢? 可以看到重叠的部分就会合并,而分散的部分则是合并 ,

使用-内存(指被 Memblock 管理的内存)划分分配

    内存是连续的吗?

    物理内存是连续的,但是细心的话,可以发现e820提供的数据中并不连续,中间0xA0000到0xFFFFF的内存并未在其中。这是历史原因遗留下来的,它并非不存在,而是被BIOS保留下来用作显卡显存的映射以及BIOS自留给ROM使用的空间。所以呈现出来有一个空洞位置。


    对此我们可以通过/proc/iomem查看到这些物理内存被如何划分分配。iomem主要呈现系统中设备的物理布局,包括未被e820所呈现的,它甚至能够将kernel在物理内存的加载位置呈现出来。

1297993-20211115231800698-890452300.png

我们这里就可以知道 dmesg 命令看到的是计算机开始探测到的内存空间 ,而 cat /proc/iomem 则是BIOS虚拟地址在物理内存上的一个映射可以看见貌似只使用了 1MB 的空间 ,关于着 1MB 的相关知识点见 A20

那么 Memblock 上的内存 又是如何被分配出去的呢 ? Memblock 内存维护着两个 struct :

  • memory : 指向系统可用物理内存区

  • reserved : 指向系统预留区 (就是给分配出去了)

      memblock管理表由命名为memblock的全局数据结构变量管理,它主要通过可用内存memory和保留内存reserved两个成员结构体变量区分管理。
      
      例如可用内存全部都挂入到memblock.memory.regions下,该可用内存同时又以全局变量数组memblock_memory_init_regions而命名,该数组成员主要记录内存的基址、大小和类型,如图显示的是该算法的管理结构关系。类似的被保留的内存则在memblock_reserved_init_regions全局数组结构下管理。
      
      于此阶段,我们可以通过memblock_alloc()和memblock_free()对内存进行申请释放,而分配的方式很简单,根据需要分配的size到可用的内存空间memblock_memory_init_regions中去查找连续的等大小空间,然后将其分割开来,将分配出去的挂入到memblock_reserved_init_regions管理区中,而剩余的则放回到memblock_memory_init_regions。尤其是如果我们需要申请永久保留的内存可在此申请,即后续内存管理将不会对此内存进行分配回收管理。
      
      memblock内存管理只是一个过渡形态,不会长期存在,毕竟如此任意分割内存的分配方式长久运行后会导致严重的碎片化。因此后面将会建立内存映射,构造内存管理框架。
    

来自参考资料中的描述我们知道此阶段的内存分配非常简单,就是在两个数组里任意分配,有可能会造成内存碎片化, 但是此时分配的内存是给什么的呢?上面分配的内存就是给上图用的。

具体相关的底层逻辑见 :

1297993-20211115233213443-178650984.png

1297993-20211115233237146-788941172.png

1297993-20211115233300536-4434451.png

查看读取出来的内存够空间

Linux dmesg 命令, kernel 会将开机信息存储在 ring buffer 中。您若是开机时来不及查看信息,可利用 dmesg 来查看。开机信息亦保存在 /var/log 目录中,名称为 dmesg 的文件里。
命令 :

 dmesg | less

1297993-20211115224016170-839141824.png

查看虚拟地的映射的分布

cat /proc/iomem

1297993-20211115224303385-779752500.png

虚拟地址映射的内容

其他

中断

关于“中断”,参考资料中有篇文章有详细介绍到。
中断,英文名为Interrupt,计算机的世界里处处都有中断,任何工作都离不开中断,可以说整个计算机系统就是由中断来驱动的。那么什么是中断?简单来说就是CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,这一过程便是中断。

两张图可以知道中断的类型

1297993-20211114120844382-1583466236.jpg

总结

这一节主要是讲介绍物理内存空间是如何被读取出来的,然后读取出来以后又是如何被管理和分配的,其中涉及到 BIOS 的相关知识点,比如 BIOS 中断调用,还有包括memblock 这个结构去管理内存的,在 使用-内存(指被 Memblock 管理的内存)划分分配 章节我们也知道了BIOS 在 memblock 上申请了 1MB 的空间来映射一些内容,包括 BIOS 映射区, DOS 暂存区等 。

最后我们还介绍了两个查看内存相关的方式 ,一个是查看物理空间被读取出来后的内存全貌,一个是虚拟地址上的内存分布, 各个位置放着什么东西 。

参考资料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章