linux内核中启动页面,Linux内核启动过程分析
下面給出內核映像完整的啟動過程:
arch/x86/boot/header.S:
--->header第一部分(以前的bootsector.S):? 載入bootloader到0x7c00處,設置內核屬性
--->_start()? bzImage映像的入口點(實模式),header的第二部分(以前的setup.S)
--->code32_start=0x100000? 0x100000為解壓后的內核的載入地址(1M高端地址)
--->設置大量的bootloader參數、創建棧空間、檢查簽名、清空BSS
--->arch/x86/boot/main.c:main()? 實模式內核的主函數
--->copy_boot_params() ?把位于第一個扇區的參數復制到boot_params變量中,boot_params位于setup的數據段
--->檢查內存布局、設置鍵盤擊鍵重復頻率、查詢Intel SpeedStep(IST)信息
--->設置視頻控制器模式、解析命令行參數以便傳遞給decompressor
--->arch/x86/boot/pm.c:go_to_protected_mode()? 進入保護模式
--->屏蔽PIC中的所有中斷、設置GDT和IDT
--->arch/x86/boot/pmjump.S:protected_mode_jump(boot_params.hdr.code32_start,...)? 跳轉到保護模式
--->in_pm32() ?跳轉到32位保護模式的入口處(即0x100000處)
--->jmpl *%eax 跳轉到arch/i386/boot/compressed/head_32.S:startup_32()處執行
arch/i386/boot/compressed/head_32.S:startup_32()? 保護模式下的入口函數
--->leal boot_stack_end(%ebx), %esp? 設置堆棧
--->拷貝壓縮的內核到緩沖區尾部
--->清空BSS
--->compressed/misc.c:decompress_kernel()? 解壓內核
--->lib/decompress_bunzip2.c:decompress()
--->lib/decompress_bunzip2.c:bunzip2()
--->lib/decompress_bunzip2.c:start_bunzip()? 解壓動作
--->parse_elf()? 將解壓后的內核ELF文件(.o文件)解析到內存中
--->計算vmlinux編譯時的運行地址與實際裝載地址的距離
--->jmp *%ebp? 跳轉到解壓后的內核的arch/x86/kernel/head_32.S:startup_32()處運行
arch/x86/kernel/head_32.S:startup_32()? 32位內核的入口函數,即進程0(也稱為清除進程)
--->拷貝boot_params以及boot_command_line
--->初始化頁表:這會創建PDE和頁表集
--->開啟內存分頁功能
--->為可選的浮點單元(FPU)檢測CPU類型
--->head32.c:i386_start_kernel()
--->init/main.c:start_kernel() ?Linux內核的啟動函數,包含創建rootfs,加載內核模塊和cpio-initrd
--->很多初始化操作
--->setup_command_line() ?把內核啟動參數復制到boot_command_line數組中
--->parse_early_param()? 體系結構代碼會先調用這個函數,做時期的參數檢查
--->parse_early_options()
--->do_early_param()? 檢查早期的參數
--->parse_args()? 解析模塊的參數
--->fs/dcache.c:vfs_caches_init()? 創建基于內存的rootfs(一個VFS)
--->fs/namespace.c:mnt_init()
--->fs/ramfs/inode.c:init_rootfs()
--->fs/filesystems.c:register_filesystem()? 注冊rootfs
--->fs/namespace.c:init_mount_tree()
--->fs/super.c:do_kern_mount()? 在內核中掛載rootfs
--->fs/fs_struct.c:set_fs_root() 將rootfs配置為當前內存中的根文件系統
--->rest_init()
--->arch/x86/kernel/process.c:kernel_thread(kernel_init,...)? 啟動一個內核線程來運行kernel_init函數,進行內核初始化
--->cpu_idle()? ? ? ? ? ? ? ? ? ? ? ? ? ? 進入空閑循環
--->調度器周期性的接管控制權,提供多任務處理
init/main.c:kernel_init() 內核初始化過程入口函數,加載initramfs或cpio-initrd,或傳統的image-initrd,把工作交給它
--->sys_open("/dev/console",...)? 啟動控制臺設備
--->do_basic_setup()
--->do_initcalls()? 啟動所有靜態編譯進內核的模塊
--->init/initramfs.c:populate_rootfs()? 初始化rootfs
--->unpack_to_rootfs()? 把initramfs或cpio-initrd解壓釋放到rootfs
--->如果是image-initrd則拷貝到/initrd.image
####################################### 傳統的image-initrd情形 ###########################################
--->rootfs中沒有/init文件
--->do_mounts.c:prepare_namespace() 加載image-initrd,并運行它的/linuxrc文件,以掛載實際的文件系統
--->do_mounts_initrd.c:initrd_load()? 把image-initrd數據加載到默認設備/dev/ram0中
--->do_mounts_rd.c:rd_load_image()? 加載image-initrd映像
--->identify_ramdisk_image() 識別initrd,確定是romfs、squashfs、minix,還是ext2
--->crd_load()? 解壓并為ramdisk分配空間,計算循環冗余校驗碼
--->lib/inflate.c:gunzip()? 對gzip格式的ramdisk進行解壓
--->do_mounts_initrd.c:handle_initrd() 指定的根設備不是/dev/ram0,由initrd來掛載真正的根文件系統
--->mount_block_root("/dev/root.old",...)? 將initrd掛載到rootfs的/root下
--->arch/x86/kernel/process.c:kernel_thread(do_linuxrc, "/linuxrc",...)? 啟動一個內核線程來運行do_linuxrc函數
--->do_mounts_initrd.c:do_linuxrc()
--->arch/x86/kernel/sys_i386_32.c:kernel_execve() 運行image-initrd中的/linuxrc
--->將initrd移動到rootfs的/old下
--->若在linuxrc中根設備重新設成Root_RAM0,則返回,說明image-initrd直接作為最終的根文件系統
--->do_mounts.c:mount_root() 否則將真正的根文件系統掛載到rootfs的/root下,并切換到這個目錄下
--->mount_block_root()
--->do_mount_root()
--->fs/namespace.c:sys_mount()? 掛載到"/root"
--->卸載initrd,并釋放它的內存
--->do_mounts.c:mount_root() 沒有指定另外的根設備,則initrd直接作為真正的根文件系統而被掛載
--->fs/namespace.c:sys_mount(".", "/",...)? 根文件掛載成功,移動到根目錄"/"
########################################################################################################
--->init/main.c:init_post()? 啟動用戶空間的init進程
--->run_init_process(ramdisk_execute_command) ? 若加載了initramfs或cpio-initrd,則運行它的/init
--->run_init_process("/sbin/init")? 否則直接運行用戶空間的/sbin/init
--->arch/x86/kernel/sys_i386_32.c:kernel_execve() ?運行用戶空間的/sbin/init程序,并分配pid為1
--->run_init_process("/bin/sh")? 當運行init沒成功時,可用此Shell來代替,以便恢復機器
/init? cpio-initrd(或initramfs)中的初始化腳本,掛載真正的根文件系統,啟動用戶空間的init進程
--->export PATH=/sbin:/bin:/usr/sbin:/usr/bin? 設置cpio-initrd的環境變量$PATH
--->掛載procfs、sysfs
--->解析命令行參數
--->udevd --daemon --resolve-names=never? 啟動udev
--->/initqueue/*.sh? 執行/initqueue下的腳本完成對應初始化工作(現在該目錄下為空)
--->/initqueue-settled/*.sh? 執行/initqueue-settled下的腳本(現在該目錄下為空)
--->/mount/*.sh? 掛載真正的根文件系統
--->/mount/99mount-root.sh? 根據/etc/fstab中的選項掛載根文件系統
--->/lib/dracut-lib.sh? 一系列通用函數
--->把根文件系統掛載到$NEWROOT下
--->尋找真正的根文件系統中的init程序并存放在$INIT中 /sbin/init, /etc/init, /bin/init, 或/bin/sh
--->從/proc/cmdline中獲取啟動init的參數并存放在$initargs中
--->switch_root "$NEWROOT" "$INIT" $initargs? 切換到根分區,并啟動其中的init進程
注意kernel_evecve調用的是與具體體系平臺相關的實現,但它是一個通用的系統調用,在linux/syscalls.h中聲明,這個頭文件中聲明了與體系結構無關的所有系統調用接口。只不過kernel_evecve在實現時是與體系結構相關的,每種體系結構都要提供它的實現。
從以上分析可以看出,如果使用新的cpio-initrd(或initramfs),kernel_init只負責內核初始化(包括加載內核模塊、創建基于內存的rootfs以及加載cpio-initrd)。后續根文件系統的掛載、init進程的啟動工作都交給cpio-initrd來完成。cpio-initrd相對于image-initrd承擔了更多的初始化責任,這種變化也可以看作是內核代碼的用戶層化的一種體現,實際上精簡內核代碼,將部分功能移植到用戶層必然是linux內核發展的一個趨勢。如果是使用傳統的image-initrd的話,根文件系統的掛載也會放在kernel_init()中,其中prepare_namespace完成掛載根文件系統,init_post()完成運行/sbin/init,顯然這樣內核的代碼不夠精簡。
5、init進程
init是第一個調用的使用標準C庫編譯的程序。在此之前,還沒有執行任何標準的C應用程序。在桌面Linux系統上,第一個啟動的程序通常是/sbin/init,它的進程號為1。init進程是所有進程的發起者和控制者,它有兩個作用:
(1)扮演終結父進程的角色:所有的孤兒進程都會被init進程接管。
(2)系統初始化工作:如設置鍵盤、字體,裝載模塊,設置網絡等。
在完成系統初始化工作之后,init進程將在控制臺上運行getty(登錄程序)等任務,我們熟悉的登錄界面就出現了!
init程序的運行流程需要分專門的一節來討論,因為它有不同的實現方式。傳統的實現是基于UNIX System V init進程的,程序包為sysvinit(以前的RedHat/Fedora用的就是這個)。目前已經有多種sysvinit的替代產品了,這其中包括initng,它已經可以用于Debian了,并且在Ubuntu上也能工作。在同一位置上,Solaris使用SMF(Service Management Facility),而Mac OS則使用 launchd。現在廣泛使用的是upstart init初始化進程,目前在Ubuntu和Fedora,還有其他系統中已經取代了sysvinit。
傳統的Sysvinit daemon是一個基于運行級別的初始化程序,它使用了運行級別(如單用戶、多用戶等)并通過從/etc/rcX.d目錄到/etc/init.d目錄的初始化腳本的鏈接來啟動與終止系統服務。Sysvinit無法很好地處理現代硬件,如熱插拔設備、USB硬盤、網絡文件系統等。upstart系統則是事件驅動的,事件可能被硬件改動觸發,也可被啟動或關機或任務所觸發,或者也可能被系統上的任何其他進程所觸發。事件用于觸發任務或服務,統稱為作業。比如連接到一個USB驅動器可能導致udev服務發送一個block-device-added事件,這可能引起一個預定任務檢查/etc/fstab和掛載驅動器(如果需要的話)。再如,一個Apache web服務器可能只有當網絡和所需的文件系統都可用時才能啟動。
Upstart作業在/etc/init目錄及其子目錄下被定義。upstart系統兼容sysvinit,它也會處理/etc/inittab和System V init腳本(如果有的話)。在諸如近來的Fedora版本的系統上,/etc/inittab可能只含有initdefault操作的id項。目前Ubuntu系統默認沒有/etc/inittab,如果您想要指定一個默認運行級別的話,您可以創建一個。Upstart也使用initctl命令來支持與upstart init守護進程的交互。這時您可以啟動或終止作業、列表作業、以及獲取作業的狀態、發出事件、重啟init進程,等等。
總的來說,x86架構的Linux內核啟動過程分為6大步,分別為:
(1)實模式的入口函數_start():在header.S中,這里會進入眾所周知的main函數,它拷貝bootloader的各個參數,執行基本硬件設置,解析命令行參數。
(2)保護模式的入口函數startup_32():在compressed/header_32.S中,這里會解壓bzImage內核映像,加載vmlinux內核文件。
(3)內核入口函數startup_32():在kernel/header_32.S中,這就是所謂的進程0,它會進入體系結構無關的start_kernel()函數,即眾所周知的Linux內核啟動函數。start_kernel()會做大量的內核初始化操作,解析內核啟動的命令行參數,并啟動一個內核線程來完成內核模塊初始化的過程,然后進入空閑循環。
(4)內核模塊初始化的入口函數kernel_init():在init/main.c中,這里會啟動內核模塊、創建基于內存的rootfs、加載initramfs文件或cpio-initrd,并啟動一個內核線程來運行其中的/init腳本,完成真正根文件系統的掛載。
(5)根文件系統掛載腳本/init:這里會掛載根文件系統、運行/sbin/init,從而啟動眾所周知的進程1。
(6)init進程的系統初始化過程:執行相關腳本,以完成系統初始化,如設置鍵盤、字體,裝載模塊,設置網絡等,最后運行登錄程序,出現登錄界面。
如果從體系結構無關的視角來看,start_kernel()可以看作時體系結構無關的Linux main函數,它是體系結構無關的代碼的統一入口函數,這也是為什么文件會命名為init/main.c的原因。這個main.c粘合劑把各種體系結構的代碼“粘合”到一個統一的入口處。
整個內核啟動過程如下圖:
圖1 Linux內核啟動過程
總結
以上是生活随笔為你收集整理的linux内核中启动页面,Linux内核启动过程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux打印机添加命令,Linux S
- 下一篇: linux系统安装服务器过程,Linux