系統啓動 之 Linux系統啓動概述(2)


博客:http://blog.csdn.net/younger_china/article/details/51615916linux


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版本號等。spa

2. 初始化硬件設備:這個階段在現代基於PCI的體系機構中至關重要,由於它能夠保證全部的硬件設備操做不會引發IRQ線與I/O端口的衝突。在本階段的最後,會顯示系統中所安裝的全部PCI設備的一個列表。(實際上,由BIOS掃描的一系列設備數據都會被內存引用,尤爲是在PCI掃描的過程,通常只是對 BIOS掃描結果後的一種確認,而內核也通常不須要過多地從新掃描PCI資源)。操作系統

3. 搜索一個操做系統來啓動:實際上,根據BIOS的設置,這個過程可能要試圖訪問系統中的軟盤,硬盤和CDROM等設備的第一個扇區。.net

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」來實現對它的調 用。該函數返回時,會將返回結構保存在 r五、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偏移量組成.。相應的物理地址樣計算方法:segment16 + offset。此時,CPU尋址電路不須要全局描述表(GDT)、局部描述表(LDT)和頁表(PT)。顯然,對GDT,LDTPT進行初始化的代碼必須在實模式下運行。

那麼在BIOS階段,都是作了哪些工做哪?.BIOS啓動過程實際上執行一下4個操做:

1. 上電自檢:對計算機硬件執行一系列的測試,用來檢測如今都有什麼設備以及這些設備是否正常工做,這個階段一般稱爲POST(上電自檢)。這個階段中,會顯示一些信息,如BIOS版本號等。

2. 初始化硬件設備:這個階段在現代基於PCI的體系機構中至關重要,由於它能夠保證全部的硬件設備操做不會引發IRQ線與I/O端口的衝突。在本階段的最後,會顯示系統中所安裝的全部PCI設備的一個列表。(實際上,由BIOS掃描的一系列設備數據都會被內存引用,尤爲是在PCI掃描的過程,通常只是對 BIOS掃描結果後的一種確認,而內核也通常不須要過多地從新掃描PCI資源)

3. 搜索一個操做系統來啓動:實際上,根據BIOS的設置,這個過程可能要試圖訪問系統中的軟盤,硬盤和CDROM等設備的第一個扇區。

4. 裝載代碼(引導程序)只要找到一個有效的設備,就把第一個扇區的內容拷貝到RAM中從物理地址0x00007c00(X86)0x00008000ARM開始的位置,而後跳轉到這個地址處,開始執行剛纔轉載進來的代碼。

head-y          := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o

textofs-y       := 0x00008000

 

2. 遠古時代:BootLoader

引導裝入程序(Bootloader)是BIOS將操做系統內核映像裝載到RAM中執行的第一個程序。

軟盤啓動與磁盤啓動過程稍有不一樣,僅此處僅僅分析磁盤啓動方式。

從硬盤啓動時,硬盤的第一個扇區MBRMaster Boot Record)中包含分區表和一小段程序,這段小程序用來裝載被啓動的操做系統所在分區的第一個扇區的內容。針對Linux系統,第一個扇區中存放的是bootloader,能夠容許用戶選擇要啓動的操做系統。

從磁盤啓動linux內核,Bootloader通被BIOS加載到RAM0x00008000處開始執行。

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的寄存器必須知足如下條件:r00r1=處理器類型,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」來實現對它的調 用。該函數返回時,會將返回結構保存在 r5r6  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內核》

相關文章
相關標籤/搜索