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體系結構”。