系统启动 之 Linux系统启动概述(2)
博客:http://blog.csdn.net/younger_china/article/details/51615916
Linu系統啟動是一個”冗長乏味”的過程,那么我們現就需要去經歷一下這個冗長乏味的生活。我們按照如下流程來分析:
?
1.?史前時代:BIOS
計算機在上電那一刻幾乎是毫無用處的,此時,RAM中包含的全部是隨機數據。
在開始啟動時,一個特殊的硬件電路在CPU的一個引腳上產生一個RESET邏輯值,在RESET產生之后,就把處理器的一些寄存器設置成固定的值,并執行在物理地址0xFFFFFFF0處找到的代碼(Younger注:該代碼我稱之為BIOS代碼)。硬件會把這個地址映射到某個只讀、持久的存儲芯片中,該芯片被稱為ROM(即:Read-Only Memory)。
Linux一旦進入實模式,就不再使用BIOS,而是linux本身為計算機上的每個硬件設備提供各自的設備驅動程序。實際上,因為BIOS過程必須在實模式下運行,而內核在保護模式下運行,所以即使在二者之間共享函數是有益的,也不能共享。
BIOS是采用實模式尋址的,因為在上電啟動時,計算機只能尋址這么大的地址空間(請參閱實模式與保護模式的區別)。實模式地址由一個segment段和一個offset偏移量組成.。相應的物理地址樣計算方法:segment*16 + offset。此時,CPU尋址電路不需要全局描述表(GDT)、局部描述表(LDT)和頁表(PT)。顯然,對GDT,LDT和PT進行初始化的代碼必須在實模式下運行。
那么在BIOS階段,都是做了哪些工作哪?.BIOS啟動過程實際上執行一下4個操作:
1.?上電自檢:對計算機硬件執行一系列的測試,用來檢測現在都有什么設備以及這些設備是否正常工作,這個階段通常稱為POST(上電自檢)。這個階段中,會顯示一些信息,如BIOS版本號等。
2.?初始化硬件設備:這個階段在現代基于PCI的體系機構中相當重要,因為它可以保證所有的硬件設備操作不會引起IRQ線與I/O端口的沖突。在本階段的最后,會顯示系統中所安裝的所有PCI設備的一個列表。(實際上,由BIOS掃描的一系列設備數據都會被內存引用,尤其是在PCI掃描的過程,一般只是對?BIOS掃描結果后的一種確認,而內核也一般不需要過多地重新掃描PCI資源)。
3.?搜索一個操作系統來啟動:實際上,根據BIOS的設置,這個過程可能要試圖訪問系統中的軟盤,硬盤和CDROM等設備的第一個扇區。
4.?裝載代碼(引導程序):只要找到一個有效的設備,就把第一個扇區的內容拷貝到RAM中從物理地址0x00007c00(X86)或0x00008000(ARM)開始的位置,然后跳轉到這個地址處,開始執行剛才轉載進來的代碼。
head-y????????? := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
textofs-y?????? :=?0x00008000
?
2.?遠古時代:BootLoader
引導裝入程序(Bootloader)是BIOS將操作系統內核映像裝載到RAM中執行的第一個程序。
軟盤啟動與磁盤啟動過程稍有不同,僅此處僅僅分析磁盤啟動方式。
從硬盤啟動時,硬盤的第一個扇區MBR(Master Boot Record)中包含分區表和一小段程序,這段小程序用來裝載被啟動的操作系統所在分區的第一個扇區的內容。針對Linux系統,第一個扇區中存放的是bootloader,可以允許用戶選擇要啟動的操作系統。
從磁盤啟動linux內核,Bootloader通被BIOS加載到RAM的0x00008000處開始執行。
Bootloader的主要執行過程:
1.?初始化?RAM:因為?Linux?內核一般都會在?RAM?中運行,所以在調用?Linux?內核之前?bootloader?必須設置和初始化?RAM,為調用?Linux內核做好準備。初始化?RAM?的任務包括設置CPU?的控制寄存器參數,以便能正常使用?RAM?以及檢測RAM?大小等。
2.?初始化串口:串口在?Linux?的啟動過程中有著非常重要的作用,它是?Linux內核和用戶交互的方式之一。Linux?在啟動過程中可以將信息通過串口輸出,這樣便可清楚的了解?Linux?的啟動過程。雖然它并不是?Bootloader?必須要完成的工作,但是通過串口輸出信息是調試Bootloader?和Linux?內核的強有力的工具,所以一般的?Bootloader?都會在執行過程中初始化一個串口做為調試端口。
3. 檢測處理器類型:Bootloader在調用?Linux內核前必須檢測系統的處理器類型,并將其保存到某個常量中提供給?Linux?內核。Linux?內核在啟動過程中會根據該處理器類型調用相應的初始化程序。
4.?設置?Linux啟動參數:Bootloader在執行過程中必須設置和初始化?Linux?的內核啟動參數。
5.?調用?Linux內核映像:Bootloader完成的最后一項工作便是調用?Linux內核。如果?Linux?內核存放在?Flash?中,并且可直接在上面運行(該?Flash?指?Nor Flash),那么可直接跳轉到內核中去執行。但由于在?Flash?中執行代碼會有種種限制,而且速度也遠不及RAM?快,所以一般的嵌入式系統都是將?Linux內核拷貝到?RAM?中,然后跳轉到?RAM?中去執行,不論哪種情況,在跳到?Linux?內核執行之前?CPU的寄存器必須滿足以下條件:r0=0,r1=處理器類型,r2=標記列表在?RAM中的地址。
?
3.?內核啟動
?
3.1????????????原始時代:加載內核
在?bootloader將?Linux?內核映像拷貝到?RAM?以后,可以通過下例代碼啟動?Linux?內核:?
call_linux(0, machine_type, kernel_params_base)。?
其中,machine_tpye?是?bootloader檢測出來的處理器類型,?kernel_params_base?是啟動參?數在?RAM?的地址。通過這種方式將?Linux?啟動需要的參數從?bootloader傳遞到內核。?
Linux?內核有兩種映像:一種是非壓縮內核,叫?Image,另一種是它的壓縮版本,叫?zImage。
根據內核映像的不同,Linux?內核的啟動在開始階段也有所不同。zImage?是?Image?經過壓縮形成的,所以它的大小比?Image?小。但為了能使用?zImage,必須在它的開頭加上解壓縮的代碼,將?zImage?解壓縮之后才能執行,因此它的執行速度比?Image?要慢。但考慮?到嵌入式系統的存儲空容量一般比較小,采用?zImage?可以占用較少的存儲空間,因此犧牲一點性能上的代價也是值得的。所以一般的嵌入式系統均采用壓縮內核的方式。
對于?ARM?系列處理器來說,zImage?的入口程序即為?arch/arm/boot/compressed/head.S。?它依次完成以下工作:開啟?MMU?和Cache,調用?decompress_kernel()解壓內核,最后通過?調用?call_kernel()進入非壓縮內核?Image?的啟動。下面將具體分析在此之后?Linux?內核的啟?動過程。?
3.2????????????封建時代:Linux內核入口?
Linux?非壓縮內核的入口位于文件/arch/arm/kernel/head-armv.S?中的?stext?段。該段的基?地址就是壓縮內核解壓后的跳轉地址。如果系統中加載的內核是非壓縮的?Image,那么?bootloader將內核從?Flash中拷貝到?RAM?后將直接跳到該地址處,從而啟動?Linux?內核。?不同體系結構的?Linux?系統的入口文件是不同的,而且因為該文件與具體體系結構有?關,所以一般均用匯編語言編寫[3]?。對基于?ARM?處理的?Linux?系統來說,該文件就是?head-armv.S。該程序通過查找處理器內核類型和處理器類型調用相應的初始化函數,再建立頁表,最后跳轉到?start_kernel()函數開始內核的初始化工作。?
檢測處理器內核類型是在匯編子函數__lookup_processor_type中完成的。通過以下代碼?可實現對它的調用:?bl __lookup_processor_type。?__lookup_processor_type調用結束返回原程序時,會將返回結果保存到寄存器中。其中?r8保存了頁表的標志位,r9?保存了處理器的?ID?號,r10?保存了與處理器相關的?stru?proc_info_list?結構地址。?檢測處理器類型是在匯編子函數?__lookup_architecture_type?中完成的。與?__lookup_processor_type類似,它通過代碼:“bl __lookup_processor_type”來實現對它的調?用。該函數返回時,會將返回結構保存在?r5、r6?和?r7?三個寄存器中。其中?r5?保存了?RAM?的起始基地址,r6?保存了?I/O基地址,r7?保存了?I/O的頁表偏移地址。?
當檢測處理器內核和處理器類型結束后,將調用__create_page_tables?子函數來建立頁?表,它所要做的工作就是將RAM?基地址開始的?4M?空間的物理地址映射到?0xC0000000?開?始的虛擬地址處。當所有的初始化結束之后,使用如下代碼來跳到C程序的入口函數?start_kernel()處,開始之后的內核初始化工作:
?b SYMBOL_NAME(start_kernel)?
?
3.3????????????近代:start_kernel函數?
start_kernel是所有?Linux?平臺進入系統內核初始化后的入口函數,它主要完成剩余的與?硬件平臺相關的初始化工作,在進行一系列與內核相關的初始化后,調用第一個用戶進程-?init?進程并等待用戶進程的執行,這樣整個?Linux?內核便啟動完畢。該函數所做的具體工作?
有[4][5]?:?
1)?調用?setup_arch()函數進行與體系結構相關的第一個初始化工作;?對不同的體系結構來說該函數有不同的定義。對于?ARM?平臺而言,該函數定義在?arch/arm/kernel/Setup.c。它首先通過檢測出來的處理器類型進行處理器內核的初始化,然后?通過?bootmem_init()函數根據系統定義的?meminfo?結構進行內存結構的初始化,最后調用?paging_init()開啟MMU,創建內核頁表,映射所有的物理內存和?IO空間。?
2)?創建異常向量表和初始化中斷處理函數;?
3)?初始化系統核心進程調度器和時鐘中斷處理機制;
4)?初始化串口控制臺(serial-console);?
ARM-Linux?在初始化過程中一般都會初始化一個串口做為內核的控制臺,這樣內核在?啟動過程中就可以通過串口輸出信息以便開發者或用戶了解系統的啟動進程。?
5)?創建和初始化系統?cache,為各種內存調用機制提供緩存,包括;動態內存分配,虛擬文?件系統(VirtualFile System)及頁緩存。?
6)?初始化內存管理,檢測內存大小及被內核占用的內存情況;
7)?初始化系統的進程間通信機制(IPC);?
當以上所有的初始化工作結束后,start_kernel()函數會調用?rest_init()函數來進行最后的?
初始化,包括創建系統的第一個進程-init?進程來結束內核的啟動。Init?進程首先進行一系列的硬件初始化,然后通過命令行傳遞過來的參數掛載根文件系統。最后?init?進程會執行用戶傳遞過來的“init=”啟動參數執行用戶指定的命令,或者執行以下幾個進程之一:?
execve("/sbin/init",argv_init,envp_init);?
execve("/etc/init",argv_init,envp_init);?
execve("/bin/init",argv_init,envp_init);?
execve("/bin/sh",argv_init,envp_init)。?
當所有的初始化工作結束后,cpu_idle()函數會被調用來使系統處于閑置(idle)狀態并等待用戶程序的執行。至此,整個?Linux?內核啟動完畢。
?
4.??????參考文獻
1.??《深入理解linux內核》
?
Linu系統啟動是一個”冗長乏味”的過程,那么我們現就需要去經歷一下這個冗長乏味的生活。我們按照如下流程來分析:
?
1.?史前時代:BIOS
計算機在上電那一刻幾乎是毫無用處的,此時,RAM中包含的全部是隨機數據。
在開始啟動時,一個特殊的硬件電路在CPU的一個引腳上產生一個RESET邏輯值,在RESET產生之后,就把處理器的一些寄存器設置成固定的值,并執行在物理地址0xFFFFFFF0處找到的代碼(Younger注:該代碼我稱之為BIOS代碼)。硬件會把這個地址映射到某個只讀、持久的存儲芯片中,該芯片被稱為ROM(即:Read-Only Memory)。
Linux一旦進入實模式,就不再使用BIOS,而是linux本身為計算機上的每個硬件設備提供各自的設備驅動程序。實際上,因為BIOS過程必須在實模式下運行,而內核在保護模式下運行,所以即使在二者之間共享函數是有益的,也不能共享。
BIOS是采用實模式尋址的,因為在上電啟動時,計算機只能尋址這么大的地址空間(請參閱實模式與保護模式的區別)。實模式地址由一個segment段和一個offset偏移量組成.。相應的物理地址樣計算方法:segment*16 + offset。此時,CPU尋址電路不需要全局描述表(GDT)、局部描述表(LDT)和頁表(PT)。顯然,對GDT,LDT和PT進行初始化的代碼必須在實模式下運行。
那么在BIOS階段,都是做了哪些工作哪?.BIOS啟動過程實際上執行一下4個操作:
1.?上電自檢:對計算機硬件執行一系列的測試,用來檢測現在都有什么設備以及這些設備是否正常工作,這個階段通常稱為POST(上電自檢)。這個階段中,會顯示一些信息,如BIOS版本號等。
2.?初始化硬件設備:這個階段在現代基于PCI的體系機構中相當重要,因為它可以保證所有的硬件設備操作不會引起IRQ線與I/O端口的沖突。在本階段的最后,會顯示系統中所安裝的所有PCI設備的一個列表。(實際上,由BIOS掃描的一系列設備數據都會被內存引用,尤其是在PCI掃描的過程,一般只是對?BIOS掃描結果后的一種確認,而內核也一般不需要過多地重新掃描PCI資源)。
3.?搜索一個操作系統來啟動:實際上,根據BIOS的設置,這個過程可能要試圖訪問系統中的軟盤,硬盤和CDROM等設備的第一個扇區。
4.?裝載代碼(引導程序):只要找到一個有效的設備,就把第一個扇區的內容拷貝到RAM中從物理地址0x00007c00(X86)或0x00008000(ARM)開始的位置,然后跳轉到這個地址處,開始執行剛才轉載進來的代碼。
head-y????????? := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
textofs-y?????? :=?0x00008000
?
2.?遠古時代:BootLoader
引導裝入程序(Bootloader)是BIOS將操作系統內核映像裝載到RAM中執行的第一個程序。
軟盤啟動與磁盤啟動過程稍有不同,僅此處僅僅分析磁盤啟動方式。
從硬盤啟動時,硬盤的第一個扇區MBR(Master Boot Record)中包含分區表和一小段程序,這段小程序用來裝載被啟動的操作系統所在分區的第一個扇區的內容。針對Linux系統,第一個扇區中存放的是bootloader,可以允許用戶選擇要啟動的操作系統。
從磁盤啟動linux內核,Bootloader通被BIOS加載到RAM的0x00008000處開始執行。
Bootloader的主要執行過程:
1.?初始化?RAM:因為?Linux?內核一般都會在?RAM?中運行,所以在調用?Linux?內核之前?bootloader?必須設置和初始化?RAM,為調用?Linux內核做好準備。初始化?RAM?的任務包括設置CPU?的控制寄存器參數,以便能正常使用?RAM?以及檢測RAM?大小等。
2.?初始化串口:串口在?Linux?的啟動過程中有著非常重要的作用,它是?Linux內核和用戶交互的方式之一。Linux?在啟動過程中可以將信息通過串口輸出,這樣便可清楚的了解?Linux?的啟動過程。雖然它并不是?Bootloader?必須要完成的工作,但是通過串口輸出信息是調試Bootloader?和Linux?內核的強有力的工具,所以一般的?Bootloader?都會在執行過程中初始化一個串口做為調試端口。
3. 檢測處理器類型:Bootloader在調用?Linux內核前必須檢測系統的處理器類型,并將其保存到某個常量中提供給?Linux?內核。Linux?內核在啟動過程中會根據該處理器類型調用相應的初始化程序。
4.?設置?Linux啟動參數:Bootloader在執行過程中必須設置和初始化?Linux?的內核啟動參數。
5.?調用?Linux內核映像:Bootloader完成的最后一項工作便是調用?Linux內核。如果?Linux?內核存放在?Flash?中,并且可直接在上面運行(該?Flash?指?Nor Flash),那么可直接跳轉到內核中去執行。但由于在?Flash?中執行代碼會有種種限制,而且速度也遠不及RAM?快,所以一般的嵌入式系統都是將?Linux內核拷貝到?RAM?中,然后跳轉到?RAM?中去執行,不論哪種情況,在跳到?Linux?內核執行之前?CPU的寄存器必須滿足以下條件:r0=0,r1=處理器類型,r2=標記列表在?RAM中的地址。
?
3.?內核啟動
?
3.1????????????原始時代:加載內核
在?bootloader將?Linux?內核映像拷貝到?RAM?以后,可以通過下例代碼啟動?Linux?內核:?
call_linux(0, machine_type, kernel_params_base)。?
其中,machine_tpye?是?bootloader檢測出來的處理器類型,?kernel_params_base?是啟動參?數在?RAM?的地址。通過這種方式將?Linux?啟動需要的參數從?bootloader傳遞到內核。?
Linux?內核有兩種映像:一種是非壓縮內核,叫?Image,另一種是它的壓縮版本,叫?zImage。
根據內核映像的不同,Linux?內核的啟動在開始階段也有所不同。zImage?是?Image?經過壓縮形成的,所以它的大小比?Image?小。但為了能使用?zImage,必須在它的開頭加上解壓縮的代碼,將?zImage?解壓縮之后才能執行,因此它的執行速度比?Image?要慢。但考慮?到嵌入式系統的存儲空容量一般比較小,采用?zImage?可以占用較少的存儲空間,因此犧牲一點性能上的代價也是值得的。所以一般的嵌入式系統均采用壓縮內核的方式。
對于?ARM?系列處理器來說,zImage?的入口程序即為?arch/arm/boot/compressed/head.S。?它依次完成以下工作:開啟?MMU?和Cache,調用?decompress_kernel()解壓內核,最后通過?調用?call_kernel()進入非壓縮內核?Image?的啟動。下面將具體分析在此之后?Linux?內核的啟?動過程。?
3.2????????????封建時代:Linux內核入口?
Linux?非壓縮內核的入口位于文件/arch/arm/kernel/head-armv.S?中的?stext?段。該段的基?地址就是壓縮內核解壓后的跳轉地址。如果系統中加載的內核是非壓縮的?Image,那么?bootloader將內核從?Flash中拷貝到?RAM?后將直接跳到該地址處,從而啟動?Linux?內核。?不同體系結構的?Linux?系統的入口文件是不同的,而且因為該文件與具體體系結構有?關,所以一般均用匯編語言編寫[3]?。對基于?ARM?處理的?Linux?系統來說,該文件就是?head-armv.S。該程序通過查找處理器內核類型和處理器類型調用相應的初始化函數,再建立頁表,最后跳轉到?start_kernel()函數開始內核的初始化工作。?
檢測處理器內核類型是在匯編子函數__lookup_processor_type中完成的。通過以下代碼?可實現對它的調用:?bl __lookup_processor_type。?__lookup_processor_type調用結束返回原程序時,會將返回結果保存到寄存器中。其中?r8保存了頁表的標志位,r9?保存了處理器的?ID?號,r10?保存了與處理器相關的?stru?proc_info_list?結構地址。?檢測處理器類型是在匯編子函數?__lookup_architecture_type?中完成的。與?__lookup_processor_type類似,它通過代碼:“bl __lookup_processor_type”來實現對它的調?用。該函數返回時,會將返回結構保存在?r5、r6?和?r7?三個寄存器中。其中?r5?保存了?RAM?的起始基地址,r6?保存了?I/O基地址,r7?保存了?I/O的頁表偏移地址。?
當檢測處理器內核和處理器類型結束后,將調用__create_page_tables?子函數來建立頁?表,它所要做的工作就是將RAM?基地址開始的?4M?空間的物理地址映射到?0xC0000000?開?始的虛擬地址處。當所有的初始化結束之后,使用如下代碼來跳到C程序的入口函數?start_kernel()處,開始之后的內核初始化工作:
?b SYMBOL_NAME(start_kernel)?
?
3.3????????????近代:start_kernel函數?
start_kernel是所有?Linux?平臺進入系統內核初始化后的入口函數,它主要完成剩余的與?硬件平臺相關的初始化工作,在進行一系列與內核相關的初始化后,調用第一個用戶進程-?init?進程并等待用戶進程的執行,這樣整個?Linux?內核便啟動完畢。該函數所做的具體工作?
有[4][5]?:?
1)?調用?setup_arch()函數進行與體系結構相關的第一個初始化工作;?對不同的體系結構來說該函數有不同的定義。對于?ARM?平臺而言,該函數定義在?arch/arm/kernel/Setup.c。它首先通過檢測出來的處理器類型進行處理器內核的初始化,然后?通過?bootmem_init()函數根據系統定義的?meminfo?結構進行內存結構的初始化,最后調用?paging_init()開啟MMU,創建內核頁表,映射所有的物理內存和?IO空間。?
2)?創建異常向量表和初始化中斷處理函數;?
3)?初始化系統核心進程調度器和時鐘中斷處理機制;
4)?初始化串口控制臺(serial-console);?
ARM-Linux?在初始化過程中一般都會初始化一個串口做為內核的控制臺,這樣內核在?啟動過程中就可以通過串口輸出信息以便開發者或用戶了解系統的啟動進程。?
5)?創建和初始化系統?cache,為各種內存調用機制提供緩存,包括;動態內存分配,虛擬文?件系統(VirtualFile System)及頁緩存。?
6)?初始化內存管理,檢測內存大小及被內核占用的內存情況;
7)?初始化系統的進程間通信機制(IPC);?
當以上所有的初始化工作結束后,start_kernel()函數會調用?rest_init()函數來進行最后的?
初始化,包括創建系統的第一個進程-init?進程來結束內核的啟動。Init?進程首先進行一系列的硬件初始化,然后通過命令行傳遞過來的參數掛載根文件系統。最后?init?進程會執行用戶傳遞過來的“init=”啟動參數執行用戶指定的命令,或者執行以下幾個進程之一:?
execve("/sbin/init",argv_init,envp_init);?
execve("/etc/init",argv_init,envp_init);?
execve("/bin/init",argv_init,envp_init);?
execve("/bin/sh",argv_init,envp_init)。?
當所有的初始化工作結束后,cpu_idle()函數會被調用來使系統處于閑置(idle)狀態并等待用戶程序的執行。至此,整個?Linux?內核啟動完畢。
?
4.??????參考文獻
1.??《深入理解linux內核》
?
轉載于:https://www.cnblogs.com/youngerchina/p/5624441.html
總結
以上是生活随笔為你收集整理的系统启动 之 Linux系统启动概述(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BroadcastReceiver 广播
- 下一篇: 理解 OpenStack 高可用(HA)