如圖所示,核心的初始化過程由start_kernel函數開始,至第一個使用者程序init結束,調用了一系列的初始化函數對所有的核心元件進行初始化。其中,start_kernel、rest_init、kernel_init、init_post等4個函數構成了整個初始化過程的主線。
圖 核心初始化
本節接下來的内容會結合核心代碼,對核心初始化過程主線上的幾個函數進行分析,使讀者對該過程有個整體上的認識,以此為基礎,讀者可以根據自己的興趣或需要,選擇與某些元件相關的初始化函數,進行更進一步的研究分析。
u start_kernel函數
從start_kernel函數開始,核心即進入了C語言部分,它完成了核心的大部分初始化工作。實際上,可以将start_kernel函數看做核心的main函數。
代碼清單1 start_kernel函數
513 asmlinkage void __init start_kernel(void)
514 {
515 char * command_line;
516 extern struct kernel_param __start___param[], __stop___param[];
517
518 smp_setup_processor_id();
519
520
524 unwind_init();
525 lockdep_init();
526
527 local_irq_disable();
528 early_boot_irqs_off();
529 early_init_irq_lock_class();
530
531
535 lock_kernel();
536 tick_init();
537 boot_cpu_init();
538 page_address_init();
539 printk(KERN_NOTICE);
540 printk(linux_banner);
541 setup_arch(&command_line);
542 setup_command_line(command_line);
543 unwind_setup();
544 setup_per_cpu_areas();
545 smp_prepare_boot_cpu();
546
547
552 sched_init();
553
557 preempt_disable();
558 build_all_zonelists();
559 page_alloc_init();
560 printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
561 parse_early_param();
562 parse_args("Booting kernel", static_command_line, __start___param,
563 __stop___param - __start___param,
564 &unknown_bootoption);
565 if (!irqs_disabled()) {
566 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
567 "enabled *very* early, fixing it\n");
568 local_irq_disable();
569 }
570 sort_main_extable();
571 trap_init();
572 rcu_init();
573 init_IRQ();
574 pidhash_init();
575 init_timers();
576 hrtimers_init();
577 softirq_init();
578 timekeeping_init();
579 time_init();
580 profile_init();
581 if (!irqs_disabled())
582 printk("start_kernel(): bug: interrupts were enabled early\n");
583 early_boot_irqs_on();
584 local_irq_enable();
585
586
591 console_init();
592 if (panic_later)
593 panic(panic_later, panic_param);
594
595 lockdep_info();
596
597
602 locking_selftest();
603
604 #ifdef CONFIG_BLK_DEV_INITRD
605 if (initrd_start && !initrd_below_start_ok &&
606 initrd_start < min_low_pfn << PAGE_SHIFT) {
607 printk(KERN_CRIT "initrd overwritten
(0x%08lx < 0x%08lx) - "
608 "disabling it.\n",initrd_start,
min_low_pfn << PAGE_SHIFT);
609 initrd_start = 0;
610 }
611 #endif
612 vfs_caches_init_early();
613 cpuset_init_early();
614 mem_init();
615 kmem_cache_init();
616 setup_per_cpu_pageset();
617 numa_policy_init();
618 if (late_time_init)
619 late_time_init();
620 calibrate_delay();
621 pidmap_init();
622 pgtable_cache_init();
623 prio_tree_init();
624 anon_vma_init();
625 #ifdef CONFIG_X86
626 if (efi_enabled)
627 efi_enter_virtual_mode();
628 #endif
629 fork_init(num_physpages);
630 proc_caches_init();
631 buffer_init();
632 unnamed_dev_init();
633 key_init();
634 security_init();
635 vfs_caches_init(num_physpages);
636 radix_tree_init();
637 signals_init();
638
639 page_writeback_init();
640 #ifdef CONFIG_PROC_FS
641 proc_root_init();
642 #endif
643 cpuset_init();
644 taskstats_init_early();
645 delayacct_init();
646
647 check_bugs();
648
649 acpi_early_init();
650
651
652 rest_init();
653 }
2 reset_init函數
在start_kernel函數的最後調用了reset_init函數進行後續的初始化。
代碼清單2 reset_init函數
438 static void noinline __init_refok rest_init(void)
439 __releases(kernel_lock)
440 {
441 int pid;
442
443 kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
444 numa_default_policy();
445 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
446 kthreadd_task = find_task_by_pid(pid);
447 unlock_kernel();
448
449
453 init_idle_bootup_task(current);
454 preempt_enable_no_resched();
455 schedule();
456 preempt_disable();
457
458
459 cpu_idle();
460 }
3 kernel_init函數
kernel_init函數将完成裝置驅動程式的初始化,并調用init_post函數啟動使用者空間的init程序。
代碼清單3 kernel_init函數
813 static int __init kernel_init(void * unused)
814 {
815 lock_kernel();
816
819 set_cpus_allowed(current, CPU_MASK_ALL);
820
828 init_pid_ns.child_reaper = current;
829
830 __set_special_pids(1, 1);
831 cad_pid = task_pid(current);
832
833 smp_prepare_cpus(max_cpus);
834
835 do_pre_smp_initcalls();
836
837 smp_init();
838 sched_init_smp();
839
840 cpuset_init_smp();
841
842 do_basic_setup();
843
844
848
849 if (!ramdisk_execute_command)
850 ramdisk_execute_command = "/init";
851
852 if (sys_access((const char __user *)ramdisk_execute_command, 0) != 0) {
853 ramdisk_execute_command = NULL;
854 prepare_namespace();
855 }
856
857
862 init_post();
863 return 0;
864 }
4 init_post函數
到init_post函數為止,核心的初始化已經進入尾聲,第一個使用者空間程序init将姗姗來遲。
代碼清單4 init_post函數
774 static int noinline init_post(void)
775 {
776 free_initmem();
777 unlock_kernel();
778 mark_rodata_ro();
779 system_state = SYSTEM_RUNNING;
780 numa_default_policy();
781
782 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
783 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
784
785 (void) sys_dup(0);
786 (void) sys_dup(0);
787
788 if (ramdisk_execute_command) {
789 run_init_process(ramdisk_execute_command);
790 printk(KERN_WARNING "Failed to execute %s\n",
791 ramdisk_execute_command);
792 }
793
794
800 if (execute_command) {
801 run_init_process(execute_command);
802 printk(KERN_WARNING "Failed to execute %s. Attempting "
803 "defaults...\n", execute_command);
804 }
805 run_init_process("/sbin/init");
806 run_init_process("/etc/init");
807 run_init_process("/bin/init");
808 run_init_process("/bin/sh");
809
810 panic("No init found. Try passing init= option to kernel.");
811 }
第776行,到此,核心初始化已經接近尾聲了,所有的初始化函數都已經被調用,是以free_initmem函數可以舍棄記憶體的__init_begin至__init_end(包括.init.setup、.initcall.init等節)之間的資料。
所有使用__init标記過的函數和使用__initdata标記過的資料,在free_initmem函數執行後,都不能使用,它們曾經獲得的記憶體現在可以重新用于其他目的。
第782行,如果可能,打開控制台裝置,這樣init程序就擁有一個控制台,并可以從中讀取輸入資訊,也可以向其中寫入資訊。
實際上init程序除了列印錯誤資訊以外,并不使用控制台,但是如果調用的是shell或者其他需要互動的程序,而不是init,那麼就需要一個可以互動的輸入源。如果成功執行open,/dev/console即成為init的标準輸入源(檔案描述符0)。
第785~786行,調用dup打開/dev/console檔案描述符兩次。這樣,該控制台裝置就也可以供标準輸出和标準錯誤使用(檔案描述符1和2)。假設第782行的open成功執行(正常情況),init程序現在就擁有3個檔案描述符--标準輸入、标準輸出以及标準錯誤。
第788~804行,如果核心指令行中給出了到init程序的直接路徑(或者别的可替代的程式),這裡就試圖執行init。
因為當kernel_execve函數成功執行目标程式時并不傳回,隻有失敗時,才能執行相關的表達式。接下來的幾行會在幾個地方查找init,按照可能性由高到低的順序依次是: /sbin/init,這是init标準的位置;/etc/init和/bin/init,兩個可能的位置。
第805~807行,這些是init可能出現的所有地方。如果在這3個地方都沒有發現init,也就無法找到它的同名者了,系統可能就此崩潰。是以,第808行會試圖建立一個互動的shell(/bin/sh)來代替,希望root使用者可以修複這種錯誤并重新啟動機器。
第810行,由于某些原因,init甚至不能建立shell。目前面的所有情況都失敗時,調用panic。這樣核心就會試圖同步磁盤,確定其狀态一緻。如果超過了核心選項中定義的時間,它也可能會重新啟動機器。