拷贝可用内存区信息

来源:互联网 发布:mac摔了下屏幕不亮 编辑:程序博客网 时间:2024/05/23 01:12

5.2.1 拷贝可用内存区信息

首先setup_arch第一步要做的就是保存arch/x86/kernel/head_32.S初始化的new_cpu_data数据到boot_cpu_data中。new_cpu_data主要是保存CPU的相关信息,在哪儿初始化的?还记得我们在arch/x86/kernel/head_32.S中忽略过的checkCPUtype吗?就在那里,感兴趣的同学可以去探究一下。

 

784行,setup_memory_map()函数,进入start_kernel内核初始化函数中第一个内存管理函数就是由它来实现。

 

1204 void __init setup_memory_map(void)

1205 {

1206       char *who;

1207

1208       who = x86_init.resources.memory_setup();

1209       memcpy(&e820_saved, &e820, sizeof(struct e820map));

1210       printk(KERN_INFO "BIOS-provided physical RAM map:/n");

1211       e820_print_map(who);

1212 }

 

setup_memory_map()最终调用x86_init.resources.memory_setup()实现对e820内存图的优化,并根据boot_params e820_map字段的值来设置全局变量e820的值。这个全局变量是一个e820map结构:

struct e820map {

       __u32 nr_map;

       struct e820entry map[E820_X_MAX];

};

还记得吗?我们在执行实模式下代码main函数的时候调用了一个detect_memory_e820()函数,当时该函数通过BIOS服务程序int 0x15获得系统启动后的所有可用空间,共有boot_params.e820_entries个可用空间,每块空间作为boot_params.e820_map[]数组的元素,存放着他们的起始地址、大小和元素。

 

这里说个题外话,探测一个PC机内存的最好方法是就是通过调用INT 0x15,eax = 0xe820来实现。这个功能在2002年以后被所有PC机所使用,这是唯一能够探测超过4G大小内存的方案,当然,这个方法也可以被认为是内存的最终检测方法。实际上,这个函数返回一个非排序列表,这个列表包含了那些没有使用的项,并且可能返回存在覆盖的区域。在linux中每个列表项被存放在ES:EDI指定的内存区域中。每个项均有一定的格式:即28字节字段,一个2字节字段。我们前面看见了,对于内存探测的实现由函数detect_memory_e820来实现的,在这个函数中,使用了一个do...while()循环来实现,并将所探测的内容写入boot_params.e820_map数组中。

 

那么,x86_init.resources.memory_setup()是个什么函数呢?在arch/x86/kernel/x86_init.c32行,我们看到又有一个__initdata的定义:

 

  32struct x86_init_ops x86_init __initdata = {

  33

  34        .resources = {

  35                .probe_roms             = x86_init_noop,

  36                .reserve_resources      = reserve_standard_io_resources,

  37                .memory_setup           = default_machine_specific_memory_setup,

  38        },

  39

  40        .mpparse = {

  41                .mpc_record             = x86_init_uint_noop,

  42                .setup_ioapic_ids       = x86_init_noop,

  43                .mpc_apic_id            = default_mpc_apic_id,

  44                .smp_read_mpc_oem       = default_smp_read_mpc_oem,

  45                .mpc_oem_bus_info       = default_mpc_oem_bus_info,

  46                .find_smp_config        = default_find_smp_config,

  47                .get_smp_config         = default_get_smp_config,

  48        },

  49

  50        .irqs = {

  51                .pre_vector_init        = init_ISA_irqs,

  52                .intr_init              = native_init_IRQ,

  53                .trap_init              = x86_init_noop,

  54        },

  55

  56        .oem = {

  57                .arch_setup             = x86_init_noop,

  58                .banner                 = default_banner,

  59        },

  60

  61        .paging = {

  62                .pagetable_setup_start  = native_pagetable_setup_start,

  63                .pagetable_setup_done   = native_pagetable_setup_done,

  64        },

  65

  66        .timers = {

  67                .setup_percpu_clockev   = setup_boot_APIC_clock,

  68                .tsc_pre_init           = x86_init_noop,

  69                .timer_init             = hpet_time_init,

  70        },

  71

  72        .iommu = {

  73                .iommu_init             = iommu_init_noop,

  74        },

  75

  76        .pci = {

  77                .init                   = x86_default_pci_init,

  78                .init_irq               = x86_default_pci_init_irq,

  79                .fixup_irqs             = x86_default_pci_fixup_irqs,

  80        },

  81};

 

这里,跟其他__initdata数据一样,在编译的时候就存放在init数据区了。那么x86_init.resources.memory_setup也就是37行的那个default_machine_specific_memory_setup函数了。所以我们看到这个函数:

 

1166char *__init default_machine_specific_memory_setup(void)

1167{

1168        char *who = "BIOS-e820";

1169        u32 new_nr;

1170        /*

1171         * Try to copy the BIOS-supplied E820-map.

1172         *

1173         * Otherwise fake a memory map; one section from 0k->640k,

1174         * the next section from 1mb->appropriate_mem_k

1175         */

1176        new_nr = boot_params.e820_entries;

1177        sanitize_e820_map(boot_params.e820_map,

1178                        ARRAY_SIZE(boot_params.e820_map),

1179                        &new_nr);

1180        boot_params.e820_entries = new_nr;

1181        if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)

1182          < 0) {

1183                u64 mem_size;

1184

1185                /* compare results from other methods and take the greater */

1186                if (boot_params.alt_mem_k

1187                    < boot_params.screen_info.ext_mem_k) {

1188                        mem_size = boot_params.screen_info.ext_mem_k;

1189                        who = "BIOS-88";

1190                } else {

1191                        mem_size = boot_params.alt_mem_k;

1192                        who = "BIOS-e801";

1193                }

1194

1195                e820.nr_map = 0;

1196                e820_add_region(0, LOWMEMSIZE(), E820_RAM);

1197                e820_add_region(HIGH_MEMORY, mem_size << 10, E820_RAM);

1198        }

1199

1200        /* In case someone cares... */

1201        return who;

1202}

 

首先1177行为所有可用内存区空间进行“消毒”。至于sanitize“消毒”算法,有兴趣的同学,特别是对信息安全感兴趣的同学可以去研究一下。最重点的是1181行调用append_e820_map函数,将boot_params.e820_map[]数组中所有的内存区物理信息拷贝到全局数据e820中,最终返回"BIOS-e820"字符串,赋给setup_memory_map()函数中的内部变量who

 

之后setup_memory_map函数将e820的数据保存到e820_saved中,相当于备份。最后调用e820_print_map打印出内存分布图,注意这里,e820.nr_map就是可以的内存区的数量:

 

151void __init e820_print_map(char *who)

 152{

 153        int i;

 154

 155        for (i = 0; i < e820.nr_map; i++) {

 156                printk(KERN_INFO " %s: %016Lx - %016Lx ", who,

 157                       (unsigned long long) e820.map[i].addr,

 158                       (unsigned long long)

 159                       (e820.map[i].addr + e820.map[i].size));

 160                e820_print_type(e820.map[i].type);

 161                printk(KERN_CONT "/n");

 162        }

 163}

 

793~796行,调用系统编译生成的数据来初始化init_mmstart_codeend_codeend_data以及.brk等字段;同时也初始化code_resourcestartend字段和data_resourcestartend字段以及bss_resourcestartend字段。这个init_mm还记得吗?初始化0号进程的时候,它的task_structactive_mm就被赋成了这个。它是一个mm_struct结构,在编译vmlinux的时候就只给他初始化了一下字段:

struct mm_struct init_mm = {

       .mm_rb          = RB_ROOT,

       .pgd        = swapper_pg_dir,

       .mm_users      = ATOMIC_INIT(2),

       .mm_count     = ATOMIC_INIT(1),

       .mmap_sem    = __RWSEM_INITIALIZER(init_mm.mmap_sem),

       .page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),

       .mmlist          = LIST_HEAD_INIT(init_mm.mmlist),

       .cpu_vm_mask       = CPU_MASK_ALL,

};

 

 

 

 

这里初始化它的start_codeend_codeend_data以及.brk等字段分别为_text_etext_edata_brk_end,是一步极其重要的过程,表明了当前0号进程进程的虚拟内存技术就可以用了。至于后面那几个resource,通过870~872行代码将其作为一个IO resource保存起来,有关IO端口的相关知识,请查阅博客“Linux I/O体系结构”。

原创粉丝点击