天天看點

核心初始化-從start_kernel到init

如圖所示,核心的初始化過程由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。這樣核心就會試圖同步磁盤,確定其狀态一緻。如果超過了核心選項中定義的時間,它也可能會重新啟動機器。