天天看點

拷貝可用記憶體區資訊

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指定的記憶體區域中。每個項均有一定的格式:即2個8位元組字段,一個2位元組字段。我們前面看見了,對于記憶體探測的實作由函數detect_memory_e820來實作的,在這個函數中,使用了一個do...while()循環來實作,并将所探測的内容寫入boot_params.e820_map數組中。

那麼,x86_init.resources.memory_setup()是個什麼函數呢?在arch/x86/kernel/x86_init.c的32行,我們看到又有一個__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       

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                

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       

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_mm的start_code、end_code、end_data以及.brk等字段;同時也初始化code_resource的start、end字段和data_resource的start、end字段以及bss_resource的start、end字段。這個init_mm還記得嗎?初始化0号程序的時候,它的task_struct的active_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_code、end_code、end_data以及.brk等字段分别為_text、_etext、_edata和_brk_end,是一步極其重要的過程,表明了目前0号程序程序的虛拟記憶體技術就可以用了。至于後面那幾個resource,通過870~872行代碼将其作為一個IO resource儲存起來,有關IO端口的相關知識,請查閱部落格“Linux I/O體系結構”。