现在的位置: 首页 > 综合 > 正文

内存的检测

2013年05月12日 ⁄ 综合 ⁄ 共 4763字 ⁄ 字号 评论关闭

3.3.5 内存的检测

好啦回到main函数继续我们的旅程147detect_memory()用于探测物理内存的布局。注意哈,这里是第一次出现与内存管理相关的代码。

 

122int detect_memory(void)

 123{

 124        int err = -1;

 125

 126        if (detect_memory_e820() > 0)

 127                err = 0;

 128

 129        if (!detect_memory_e801())

 130                err = 0;

 131

 132        if (!detect_memory_88())

 133                err = 0;

 134

 135        return err;

 136}

 

detect_memory()函数主要根据物理内存的类型探测内存布局,代码非常简单,由上面可知,linux内核会分别尝试调用detect_memory_e820()detcct_memory_e801()detect_memory_88()获得系统物理内存布局,这3个函数内部其实都会以内联汇编的形式调用bios中断以取得内存信息。前面已经看到了该中断调用形式为int 0x15,同时调用前分别把AX寄存器设置为0xe820h0xe801h0x88h,关于0x15号中断的具体作用我这里就不再说明,有兴趣的可以去查询相关手册。下面我仅分析下detect_memory_e820()的代码,因为它是当今x86体系的一般情况,其它代码也基本一样:

 

  20static int detect_memory_e820(void)

  21{

  22        int count = 0;

  23        struct biosregs ireg, oreg;

  24        struct e820entry *desc = boot_params.e820_map;

  25        static struct e820entry buf; /* static so it is zeroed */

  26

  27        initregs(&ireg);

  28        ireg.ax  = 0xe820;

  29        ireg.cx  = sizeof buf;

  30        ireg.edx = SMAP;

  31        ireg.di  = (size_t)&buf;

  32

  33        /*

  34         * Note: at least one BIOS is known which assumes that the

  35         * buffer pointed to by one e820 call is the same one as

  36         * the previous call, and only changes modified fields.  Therefore,

  37         * we use a temporary buffer and copy the results entry by entry.

  38         *

  39         * This routine deliberately does not try to account for

  40         * ACPI 3+ extended attributes.  This is because there are

  41         * BIOSes in the field which report zero for the valid bit for

  42         * all ranges, and we don't currently make any use of the

  43         * other attribute bits.  Revisit this if we see the extended

  44         * attribute bits deployed in a meaningful way in the future.

  45         */

  46

  47        do {

  48                intcall(0x15, &ireg, &oreg);

  49                ireg.ebx = oreg.ebx; /* for next iteration... */

  50

  51                /* BIOSes which terminate the chain with CF = 1 as opposed

  52                   to %ebx = 0 don't always report the SMAP signature on

  53                   the final, failing, probe. */

  54                if (oreg.eflags & X86_EFLAGS_CF)

  55                        break;

  56

  57                /* Some BIOSes stop returning SMAP in the middle of

  58                   the search loop.  We don't know exactly how the BIOS

  59                   screwed up the map at that point, we might have a

  60                   partial map, the full map, or complete garbage, so

  61                   just return failure. */

  62                if (oreg.eax != SMAP) {

  63                        count = 0;

  64                        break;

  65                }

  66

  67                *desc++ = buf;

  68                count++;

  69        } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));

  70

  71        return boot_params.e820_entries = count;

  72}

 

23行的biosregs数据结构前面已经看到过了,现在主要看24行用到的两个数据结构,来自/arch/x86/include/asm/e820.h

 

  53struct e820entry {

  54        __u64 addr;     /* start of memory segment */

  55        __u64 size;     /* size of memory segment */

  56        __u32 type;     /* type of memory segment */

  57} __attribute__((packed));

  58

  59struct e820map {

  60        __u32 nr_map;

  61        struct e820entry map[E820_X_MAX];

  62};

 

由于历史原因,一些I/O设备除了占据I/O端口,也会占据一部分内存物理地址空间。因此当内核初始化程序第一次跟内存管理打交道时,其可以使用的物理内存空间是不连续的。此时的内存被分成了很多段,每个段的属性也是不一样的。

 

int 0x15 查询物理内存时每次返回一个内存段的信息,因此要想返回系统中所有的物理内存,我们必须以循环的方式去查询,这就是为什么第47行开始,有个do-while循环。

 

detect_memory_e820()函数把int 0x15放到一个do-while循环里,每次得到的一个内存段放到struct e820entry里,而struct e820entry的结构正是e820返回结果的结构!而像其它启动时获得的结果一样,最终都会被放到boot_params里,e820被放到了 boot_params.e820_map

 

有了这个概念后,我们来看detect_memory_e820的代码流程,22行,count用于记录已检测到的物理内存段数目。23行,函数体内部biosregs结构变量iregoreg24行,另一个内部变量desc,指向前面拷贝进来的启动参数boot_params.e820_map数组的头。注意,此时的启动参数的e820_map字段还是一个空数组,本函数的目的就是给这个数组初始化。25行,我们看到注释写得也很清楚,static关键字初始化的一个e820entry结构类型的变量,buf,其值全被清零了,它的作用是在函数体内作为一个临时缓存存放探测到的e820entry数据。

 

27行开始,执行initregs(),前面我们已经看过了,其目的在于把内存中的biosregs结构的iregdsesfsgs设置成当前值,并把eflags标志寄存器CF置位(X86_EFLAGS_CF=0x00000001)。2831行把iregaxcxedxdi字段初始化了。注意第30行‘SMAP’表明中断调用正确;第31行,di字段存放我们临时缓存的地址值。

 

好了,进入循环体。48行首先执行一个intcall,该函数前面已经遇到过,只是没有去说它,现在就来好好谈谈。在源代码中其只是在arch/x86/boot/Boot.h中声明了一下:

void intcall(u8 int_no, const struct biosregs *ireg, struct biosregs *oreg);

 

这个初始化的期间调用的函数是编译器在实模式环境下,才会编译成以下汇编过程:

 

首先调用0x15bios中断:int 0x15。这条指令需要的输入参数就是上面c语言代码种提供ireg参数,它执行完后会对输出参数oreg设置以下一些寄存器的:调用成功则清除CF标志否则设置CF标志、调用成功设置eax为‘SMAP’否则设置eax为一个出错码、将返回的内存段描述符填入edi指向的内存空间(临时缓存)、ecx填入返回的字节数、ebx填入下一次迭代获取内存段描述符需要的一个值。

 

这些指令执行完毕后,我们的oreg变量就获得了一些值,后面就可以根据oreg变量的值来判断获取内存段描述符那条指令是否成功。所以,49ireg.ebx = oreg.ebx准备好下次循环,然后第54行判断oregeflags是否置位,如果是则说明失败,跳出循环。第62行,如果eax不为‘SMAP’表明中断都执行失败了,那么需要把count清零然后退出循环。如果上面两个条件都满足,那么好,把找到的内存信息复制到启动参数的e820_map数组的对应元素中。

 

OK69行,如果ireg.ebx0了,或者count超过数组的最大下标,跳出循环。这里提一下,boot_params.e820_map数组的最大下标是前面我们看到的那个E820_X_MAX宏,其值是128

 

最后设置boot_paramse820_entries的值为物理内存段的个数并返回物理内存段个数给main函数。

 

启动程序走到此,我们来回顾一下boot_param结构已经填充了哪些内容了,只有两个:104行的启动头有了,在第一个函数copy_boot_params()就把位于header.S中的那个hdr段拷贝了进去;107行也有了,就是刚刚detect_memory()做的事情。

抱歉!评论已关闭.