分类: LINUX
2014-02-11 13:57:25
原文地址:linux启动代码之detect_memory()函数 作者:liucw2012
本篇笔记主要针对平台i386进行讨论,我也是新手,不当之处,敬请指正。以下讨论中“/”代表“/内核源代码根目录/arch/x86/boot/”目录,我用到的内核源代码版本为2.6.26.5。
在被bootloader加载到内存后, cpu最初执行的linux内核代码是/header.S文件中的setup函数,这个函数在做了一些准备工作后会跳转到boot目下文件main.c的main函数执行,在这个main函数中我们可以第一次看到与内存管理相关的代码,这段代码调用detect_memeory()函数检测系统物理内存,代码如下图:
/main.c/main()代码片段
detect_memory()函数位于/memory.c中,定义如下图:
detect_memory()函数代码非常简单,由上图可知,linux内核会分别尝试调用detect_memory_e820()、detcct_memory_e801()、detect_memory_88()获得系统物理内存布局,这3个函数内部其实都会以内联汇编的形式调用bios中断以取得内存信息,该中断调用形式为int 0x15,同时调用前分别把AX寄存器设置为0xe820h、0xe801h、0x88h,关于0x15号中断的具体作用我这里就不再说明,有兴趣的可以去查询相关手册。下面我仅分析下detect_memory_e820()的代码,其它代码基本一样,源代码如下图所示:
/memory.c/detect_memory_e820(void)代码
该代码中用到的两个结构体定义如下:
detect_memory_e820功能概述:
由于历史原因,一些i/o设备也会占据一部分内存物理地址空间,因此系统可以使用的物理内存空间是不连续的,系统内存被分成了很多段,每个段的属性也是不一样的。int 0x15 查询物理内存时每次返回一个内存段的信息,因此要想返回系统中所有的物理内存,我们必须以迭代的方式去查询。detect_memory_e820()函数把int 0x15放到一个do-while循环里,每次得到的一个内存段放到struct e820entry里,而struct e820entry的结构正是e820返回结果的结构!而像其它启动时获得的结果一样,最终都会被放到boot_params里,e820被放到了 boot_params.e820_map。
detect_memory_e820主要注释如下:
21—25 定义一些程序中会用到的局部变量。其中:
count用于记录已检测到的物理内存段数目;
next用于存放一个用于一次迭代查询过程取得物理地址描述信息的值,这个变量的值在每次迭代完成后都会被bios中断自动填充为下一次迭代查询需要的值,当这个值返回0时则表明物理内存段查询完毕;
id用于存放bios返回的信号值(signature),这个值一般是‘SMAP’以表明中断调用正确。
size用于存放bios返回的内存段描述符结构大小;bios返回的内存描述符结构最小为20字节。
err用于存放bios中断调用结束后eflags中CF标志是否为1。若CF为1则也表明内存段查询结束,此时将err设置为1,若CF为0则设置err为0。
desc用于存放一个指针,该指针指向内核启动参数结构体boot_params中e820表中某一个元素。初始化时此指针指向该表的第一个元素。boot_params是一个全局变量。
27—53 迭代查询系统中的所有物理内存段。
28 初始化size为e820entry结构体大小。
30—35 这是一条GCC内联汇编语句,里面的汇编语言使用AT&T汇编格式编写的,关于GCC内联汇编的语法格式可以去查看相关资料,在此我不赘述。
在执行这条内联汇编语句时输入的参数有:eax寄存器=0xe820、edx寄存器=’SMAP’、edi寄存器=desc、ebx寄存器=next、ecx寄存器=size,返回给c语言代码的参数有:id=eax寄存器、err=edx寄存器、next=ebx寄存器、size=ecx寄存器、desc指向的内存地址在执行0x15中断调用时被设置。
这条内联汇编语句里包含两条汇编指令,第一条就是调用0x15号bios中断:int 0x15,这条指令需要的输入参数就是c语言代码提供给这条内联汇编语句的所有参数,它执行完后会设置以下一些寄存器的:调用成功则清除CF标志否则设置CF标志、调用成功设置eax为‘SMAP’否则设置eax为一个出错码、将返回的内存段描述符填入edi指向的内存空间、ecx填入返回的字节数、ebx填入下一次迭代获取内存段描述符需要的一个值。
第二条指令:setc %0,如果熟悉GCC内联汇编语法我们可以知道,在此%0就是指寄存器ax,这条汇编指令的作用是根据当前eflags标志寄存器中CF标志位是否设置来设定ax寄存器的值。
这两条指令执行完毕后,我们的一些变量就获得了一些值,后面就可以根据这些变量的值来判断获取内存段描述符那条指令是否成功。
39—40 根据err的值决定是否结束循环。由上面分析可知,err的值是根据int 0x15执行完后CF标志设置的,若err为1则说明所有的物理内存段都已探测完毕或出错了,此时应该停止循环,err为0则说明可能还有物理内存段未探测完毕。
46—49 判断内联汇编语句执行完后返回的id是否是‘SMAP’,若不是则说明在探测过程中出错了,此时linux认为整过探测过程都失败了,因此设置count为0并推出循环。
51—52 本次探测过程成功,desc指向的内存区域已被bios中断填充了正确的值,则增加内存段计数器count,移动desc指向e820表中下一个未设置值的元素。
53 根据next及count的值决定是否退出循环。若next为空,则说明所有的内存段都已被检测到,循环结束;若count值大于或等于E820MAX,我们也应该退出,否则会破坏boot_params结构体,因为e820表中仅有E820MAX个元素,见上面定义。
55 设置boot_params中e820_entries的值为物理内存段的个数并返回物理内存段个数给函数调用者。