轉自:http://www.javashuo.com/article/p-wfpjbhku-hy.htmlphp
Linux系統啓動那些事—基於Linux 3.10內核html
csdn 個人空間的下載地址 ,有些作的效果網頁上沒了,感興趣建議去上面地址下載pdf版的node
下載地址 http://download.csdn.net/detail/shichaog/8054005 shichaog@126.com
啓動流程概述
也許你會好奇Linux是如何啓動的?本文圍繞Linux如何啓動起來展開一些介紹。本文涉及grub、uboot、initrd、根文件系統、設備樹、以及Linux內核編譯等內容。linux
對那些好奇系統是如何啓動的人本文很是適合,固然對於因爲涉及操做系統的方方面面,bsp的開發人員也有點價值,可是這裏沒有對應用作介紹;本文討論兩種平臺下的啓動方式,由於它們均是對應體系架構下的典型。ios
一、 通用的PC平臺架構(X86)git
二、 嵌入式平臺架構(ARM,S3C2440)shell
本文可能一些實驗性的操做,如grub、內核編譯等,建議這些動手也作一下,可以加深對這一過程的理解。bootstrap
對於存儲器、磁盤、CPU具備必定的瞭解會比較好,不過這不是這裏的重點,而且會稍微說起一下,因此沒必要擔憂成爲障礙。 對於uboot、BIOS、grub、vmlinuz、initrd有點認識會更好,可是對操做系統是須要有個概念性的認識,不少基本概念性的沒有在這裏講解了。固然,若是你還不會C語言,那就別向下看了,趕忙學學C吧,有點彙編、編譯、連接的知識會更好,對Makefile、Kconfig、shell若是也知道那就更好。vim
PC下個人Linux系統:api
Fedora9, 桌面版 內核版本2.6.25;VMware9下虛擬機。
www.kerenel.org下載Linux-3.10.0,這裏將使用Linux-3.10.0升級Fedora 9內核。
全部的故事從升級Linux內核開始,首先,讓咱們來配置Linux內核,解壓Linux內核,我解壓的目錄爲/usr/src/下,不過我可不建議你解壓到該目錄,Linux內核的Header文件連接到該目錄下,儘管該版本下不會帶來大的問題,可是建議不要「污染」了這個目錄。
步驟一:而後使用menuconfig,生成配置文件。
cd/usr/src/linux-3.X
步驟二:編譯內核目標文件。
make {O=/home/name/build/kernel}menuconfig
步驟三:編譯module和內核。
make {O=/home/name/build/kernel}
步驟四:安裝module和內核以及啓動必備文件
sudo make {O=/home/name/build/kernel}modules_install install
通過如上四個步驟,以root權限reboot系統,進入系統,若是在編譯和連接時想看看詳細的執行過程能夠在步驟三中使用makeV=1 all該命令讓編譯時輸出更多的過程信息。
後面會將啓動代碼和啓動輸出的信息進行關聯分析,這樣的認識會更深入些;若是您手頭資源有限或者時間等緣由沒能完成這個步驟,那麼還有一個補救措施,筆者將上述的2.6和3.10內核的兩個版本的啓動顯示信息錄製了一個視頻,該視頻下載網址:XXX
若是您沒有本身升級,那麼我強烈建議您看一下,總時間不到十分鐘,您就能夠看完2.6和3.10兩個版本啓動的過程了。編譯命令中大括號裏的內容可選,這裏就是輸出路徑設置,能夠不關心,這和啓動沒有關係,升級完該放下它,後面會再說起它的,bios可能你們都據說過,PC啓動的最早代碼執行的就是這裏的代碼,第二個執行的代碼是grub或者lilo,grub會將initrd和內核拷貝到內存中,而後進行解壓並執行內核。
首先看一下在2.6內核版本下啓動時和Linux相關的一些文件。
[root@shichaog boot]# ls -lt
total 6282
drwxr-xr-x 2 root root 1024 2012-09-18 04:34 grub
drwxr-xr-x 3 root root 1024 2012-09-18 04:06 efi
drwx------ 2 root root 12288 2012-09-18 03:55 lost+found
-rw------- 1 root root 3312447 2012-09-1720:52 initrd-2.6.25-14.fc9.i686.img
-rw-r--r-- 1 root root 86348 2008-05-01 18:34config-2.6.25-14.fc9.i686
-rw-r--r-- 1 root root 892575 2008-05-01 18:34System.map-2.6.25-14.fc9.i686
-rwxr-xr-x 1 root root 2088288 2008-05-0118:34 vmlinuz-2.6.25-14.fc9.i686
grub啓動加載程序,當系統按下電源鍵開機時,第一條指令對應的邏輯地址(段:偏移)FFFF:0000,也就是物理地址的FFFF0H,這個在8086時代就這麼決定了,這個地址通常存放第一條BIOS的指令,這條指令通常又是個長跳轉指令,由於8086時代地址線只有20根,因此尋址只能是1M範圍內的空間,而到了酷睿或者i5系列早已經是32位或者64位了,因此Bios的大小和物理地址都進行了擴充,因此該指令通常跳轉到bios執行指令。Bios存放在ROM中掉電不會失去。
efi bios的升級方案, 您能夠將其理解爲功能和bios差很少,可是運行的是32爲指令而且地址有了突破,此外和pc操做系統的接口也有一點區別就能夠了。這裏仍是以bios爲主。
lost+found存放修復或者損壞的文件,正常的啓動過程用不到。
Initrd 這個是initial RAM disk的簡稱,系統在運行時創建在一個存儲系統上的,initrd使用軟件模擬磁盤系統,它就是個文件系統,通常的嵌入式系統的文件系統就是它,可是在pc環境下initrd只在啓動過程當中起做用。
config文件內核的配置,和make menuconfig生成的文件同樣,是關於當前系統的配置狀況,如處理器類型、mmu、cgroup功能是否支持等。
system.map,這個文件是內核映像文件vmlinux使用nm導出的符號表,vmlinux是ELF文件格式,含有一些信息,nm命令導出該文件的信息,該文件對於分析內核映像文件仍是有必定幫助的。vmlinuz,加工過的vmlinux,加工內容包括壓縮、去除ELF文件信息,而且添加了bst(bootstrap)部分代碼。
這裏也看一下升級後boot目錄下的文件內容,注意黃色加深部分,至於沒有着色的部分也許你也看到了系統有兩個,是的,沒看錯,這個以前我也編譯並安裝過另外一個2.6.25版本的內核了。
drwxr-xr-x 2 root root 1024 2014-08-17 14:43 grub
-rw------- 1 root root 3377030 2014-08-17 14:43 initrd-3.10.0.img
lrwxrwxrwx 1 root root 232014-08-17 14:40 System.map -> /boot/System.map-3.10.0
-rw-r--r-- 1 root root 1398825 2014-08-17 14:40 System.map-3.10.0
lrwxrwxrwx 1 root root 202014-08-17 14:40 vmlinuz -> /boot/vmlinuz-3.10.0
-rw-r--r-- 1 root root 3012160 2014-08-17 14:40 vmlinuz-3.10.0
drwxr-xr-x 3 root root 1024 2014-07-23 04:53 efi
drwx------ 2 root root 12288 2014-07-23 04:45 lost+found
-rw------- 1 root root 3311461 2014-07-2221:55 initrd-2.6.25-14.fc9.i686.img
-rw-r--r-- 1 root root 86348 2008-05-01 18:34config-2.6.25-14.fc9.i686
-rw-r--r-- 1 root root 892575 2008-05-01 18:34System.map-2.6.25-14.fc9.i686
-rwxr-xr-x 1 root root 2088288 2008-05-0118:34 vmlinuz-2.6.25-14.fc9.i686
-rwxr-xr-x 1 root root 822228 2008-04-26 01:25xen-syms-2.6.25-2.fc9.i686.xen
-rw-r--r-- 1 root root 86316 2008-04-26 01:18config-2.6.25-2.fc9.i686.xen
-rw-r--r-- 1 root root 907057 2008-04-26 01:18System.map-2.6.25-2.fc9.i686.xen
-rwxr-xr-x 1 root root 2495757 2008-04-2601:18 vmlinuz-2.6.25-2.fc9.i686.xen
-rw-r--r-- 1 root root 373850 2008-04-26 01:10xen.gz-2.6.25-2.fc9.i686.xen
PC啓動流程簡介
對於X86PC系統上電後,會執行特定地址上的一條指令,而目前這條指令就是一個長跳轉指令,該指令跳轉至真正的bios入口地址執行bios,bios程序會進行加電自檢(POST),其次會本地設備初始化,並按照啓動順序搜索能夠引導的設備,這裏咱們從硬盤引導咱們的系統,這是bios將硬盤的0磁道0柱面1扇區的內容拷貝至內存中運行,之因此拷貝至內存中,由於內存的存儲速率遠遠高於ROM或者硬盤,不過最近的固態盤速率提升的很快,好多pc開始採用固態盤(SSD)做爲PC的主存儲器了。一個扇區是512字節,當這些512字節拷貝至內存後,bios會將跳轉至該內存裏的程序(grub)繼續執行,grub將負責實際的內核加載,即將內核加載到內存中,而後將控制權交給內核,內核會解壓縮自身的內核映像到特定的地址,而後運行該內核,內核會啓動分頁機制,內存管理、設備初始化等一些的任務,執行init進程,建立三個內核線程,其中一個線程會建立內核守護進程如swap進程,還有一個線程會啓動shell進程,提供操做系統登陸這登陸。
Bios會將0磁道0柱面1扇區(sector)的512字節內容(grub stage1)拷貝至0x7c00所在的物理內存處,而且將控制權交給grub,該512B中只有446B是引導代碼,剩下的是分區表信息。grub的第二個階段代碼能夠能夠跨越一個或者多個sector。因此實際上grub大部分的工做是由stage2來完成的,其會被stage1 階段的代碼拷貝至內存運行。
圖1.1.1 Linux PC(X86)啓動流程
先來看看grub的配置文件的內容(/boot/grub/grub.conf)【該版本的grub是0.97,有stage1.5,grub2部分見2.1節】:
…
title Fedora (3.10.0)
root (hd0,0)
kernel /vmlinuz-3.10.0 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
initrd /initrd-3.10.0.img
title Fedora (2.6.25-14.fc9.i686)
root (hd0,0)
kernel /vmlinuz-2.6.25-14.fc9.i686 ro root=UUID=aeca4624-c3e9-45af-b01b-125b2bcbec7arhgb quiet
initrd /initrd-2.6.25-14.fc9.i686.img
root (hd0,0) root系統文件目錄,第一塊硬盤的第一個分區(主分區)。
kernel /vmlinuz-3.10.0 內核映像文件
ro只讀
root=/dev/VolGroup00/LogVol00 根分區用戶
rhgb quiet 圖形redhat graphic boot ,不顯示dmesg信息
initrd 啓動時的文件系統。
device.map文件(boot/grub/):
(hd0) /dev/sda
PC的1M內容以下:Documentation/x86/boot.txt
圖1.1.2內存佈局
grub執行完畢後就會將cpu轉交給Linux內核,內核的bzImage格式是通過壓縮的內核格式,首先進行解壓縮,而後纔是真正意義上的啓動內核。這一解壓縮和啓動的過程對應於下圖所示。
這一段信息中Decompressing Linux… done意味着內核剛解壓完畢,緊接着啓動內核,即圖中顯示的Booting the kernel。
圖1.1.3內核解壓縮
在Linux內核booting後期,會調用start_kernel()函數,該函數會建立內核守護進程, init進程,內核守護進程用於例行性管理類任務,如swapd等, /sbin/init會讀取/etc/inittab,根據不一樣的運行級別去初始化相關的服務。
</etc/inittab>
# Default runlevel. The runlevels used are:
# 0- halt (Do NOT set initdefault to this)
# 1- Single user mode
# 2- Multiuser, without NFS (The same as 3, if you do not have networking)
# 3- Full multiuser mode
# 4- unused
# 5- X11
# 6- reboot (Do NOT set initdefault to this)
#
id:5:initdefault:
接下來init進程會調用/etc/rc.sysinit腳本初始化不少換環境參數,如PATH、網絡的設定等。加載內核模塊。一樣是依賴於inittab中的運行級別,執行對應運行級別初始化腳本(/etc/rc0.d~/etc/rc6.d目錄下),最後還有一個/etc/rc.local目錄下腳本也會運行,這裏用戶能夠定製一些本身的服務在裏面,這樣每次系統啓動就能夠運行一些本身定製化的東東。至此全部的準備工做已經作好了,就是讓用戶登陸了,調用腳本/bin/login,給出用戶登陸界面。登陸進shell。
這一過程直觀上的感受就是啓動時下面會出現的啓動過程顯示1.1.4圖,這是一張啓動截屏圖。
圖1.1.4:啓動過程截圖
1.1.5運行腳本rc5.d目錄下內容。
圖中劃橫線的部分就是在啓動過程顯示的服務的由來。至此整個過程有了清楚的認識了。
http://www.linuxhomenetworking.com/wiki/index.php/Quick_HOWTO_:_Ch07_:_The_Linux_Boot_Process#.VCa1mOOSwU0
ARM啓動流程簡介
對於嵌入式平臺ARM平臺,說說其NANDFlash的啓動過程,請先看圖2.2描述的NAND flash中的程序佈局,上電時,首先cpu會自動將自動從NAND flash中拷貝必定代碼到內存中執行,這是任何支持nand方式啓動必須支持的,通常我見到的有2K還有4K的,這部分的代碼咱們將其稱爲bootstrap,這個有點相似MBR中的執行代碼,那部分代碼是grub的stage1代碼,bootstrap而後會拷貝bootloader到內存中,這個就相似PC中的grub的stage2,這部分代碼大小是有限的,遇到過32K,128K的bootloade。Bootstrap會將32K或者128K的空間裏的東西拷貝到內存中而無論實際的bootloader大小,而後將控制權移交給bootloader,相比bootstrap而言,bootloader發揮的空間較大了,它會讀取一個叫ptb裏的內容,這裏存的是分區信息,根據nand的存儲特色記錄app,kernel…bootstrap的大小,其實各部分的block,page的信息,而後bootloader將這些內容通通拷貝到內存中,至於拷貝到內存中的地址各個平臺的差別性就比較大了,不像PC加載內核到內存中的地址是固定的。
圖1.2.1嵌入式ARM啓動流程
Uboot主要仍是爲kernel服務的,因此它準備好外部環境後將控制權交給kernel,其實轉交過程就是jump,而且會將相關的信息傳遞給內核,好比設備樹表的地址,kernel部分的代碼而後開始執行,主要系統初始化工做仍是在startkernel中完成的,例如會解析命令,而後還有一個重要動做,那就是解析設備樹,並使用一個鏈表將其串接在一塊,在後續驅動註冊的probe方法中會用到這個鏈表,3.10的內核是這麼處理設備的。接着一系列的初始化,初始化的工做交給一個內核線程完成,該線程會執行/sbin/init的內容,這個內容就涉及到根文件系統了,在PC狀況下就是initrd了,ARM下initramfs了,這裏須要說明的是init並不屬於內核,爲了保持內核的精簡,底層服務搭建好了之後,初始化任務仍是交給了用戶空間去完成了,最後啓動shell,至此PC下的任務完成了,嵌入式下會把須要的應用,即app裏的內容放在init裏去加載,或者init下調用另外一個去加載。至此整個加載過程就完畢了。
圖1.2.2NAND Flash中全部的佈局
本文會講解兩種架構下的啓動流程,各個階段對用的概念是同樣的,如grub等價於uboot,PC下的initrd對應於嵌入式的根文件系統等,到Linux啓動時,他們的代碼概念上都是同樣的,如創建頁表、中斷等。
MBR& grub
Linux內核啓動時的一些信息就是MBR和bootloader給的,那麼就先將講的MBR的分區信息吧,bootloader和內核都會依賴分區,由於分區信息指明瞭代碼在存儲介質上的分佈信息,一些操做依賴於該分區信息,好比,內核在哪裏,通常在/boot分區,這些分區信息在安裝操做系統格式化磁盤時就會存在,或者新的硬盤的格式化,也會有分區信息存在裏面。這裏使用的兩個命令查看了硬盤的分區狀況,在MBR中有該分區信息。
圖2.1.1 分區信息
由fdisk命令能夠知道系統共兩個分區一個硬盤,一般第一塊硬盤被稱爲sda,第二塊硬盤被稱爲sdb,表示個人系統有一塊硬盤,兩個分區,/sda1和/sda2,其中/sda1是boot分區(星號),/sda2是LVM類型的分區,一個邏輯管理,適用於須要動態調整大小的場合。從df命令能夠看出/boot分區的確掛在/dev/sda1下。前面的device.map文件(boot/grub/):下的(hd0) /dev/sda,也就不難理解了,/sda表示整個第一塊硬盤。若是有興趣可使用dd if=/dev/sda1 of=XXX bs=512 count=1, od-x XXX 查看第一個扇區的內容。最後一個必然是aa55,由於這是一個合法的mbr的必然信息,前面是grub的stage1,後面是分區信息,每一個分區16個字節,因爲個人盤只有兩個分區,因此aa55的前面會有那麼多零是正常的,關於mbr的分析,若是感興趣本身查查,這裏講篇幅留給其它更有意義的部分吧。
2.1.2MBR內容
好了,接下來正式進入grub2,grub在2014年就一直沒有跟新過了,但uboot更新可真是勤快啊,建議按照這個步驟先作一下,有點成就感,這樣會有信心往下走,並且PC應該不難找到,找個虛擬機就能夠試,在弄個snapshot就不用擔憂系統壞掉了。先來一個grub2的啓動界面:
2.1.3 grub啓動界面
一、 首先去gnu的官網下載一個grub-1.97.2.tar.gz源碼包,地址
ftp://alpha.gnu.org/gnu/grub/
二、 解壓 tar xvzfgrub.1.97.2.tar.gz
三、 進入解壓後目錄 cdgrub-1.97.2
四、 配置grub, ./configure
五、 編譯 make
六、 安裝grub-install/dev/sda
七、 更新grub grub2-mkconfig-o /boot/grub/grub.cfg,這裏的grub.cfg和原來grub.conf的做用是同樣的,該命令會根據/boot下的kernel image和initrd信息生成啓動信息。若是是Ubuntu下,須要使用update-grub/dev/sda命令
八、 reboot重啓能夠看到該系統啓動grub的信息。
下面就來講說grub的啓動步驟吧:
一、Bios將MBR中的512B東西拷貝到0x7c00處,而後跳至該處執行,而後裝載start模塊。這部分代碼參看/boot/i386/pc/boot.S
二、start模塊加載剩餘的grub stage2部分,參看boot/i386/pc/diskboot.S。
三、 grub stage引導CPU保護模式,自解壓並釋放到0x10000開始內存處,解壓完成後再拷貝回原來位置,而後調用grub_main,見kern/main.c,grub_main初始化系統,加載模塊,並進入normal或者rescue模式。GRUB將根據配置文件grub.cfg或者用戶輸入,加載操做系統並執行操做系統。
在這裏就贅述Makefile以及鏡像文件的連接關係等相關的內容了,這方面的知識會在內核映像生成過程當中體現出來。針對愈來愈大的硬盤存儲容量,開始出現了GTP取代MBR的趨勢,GTP特性須要EFI特性的支持。
uboot
一個bootloader應當提供一下功能,
1. Setup and initialise the RAM.
2. Initialise one serial port.
3. Detect the machine type.
4. Setup the kernel tagged list or bdt.
5. Call the kernel image.
對其的介紹和使用這裏就不寫了,網上針對2440的文章可謂氾濫了,本身找找動手實踐一下。
Linux內核映像生成過程
連接的語法能夠參看:http://sourceware.org/binutils/docs-2.21/ld/
也能夠查看精簡版的連接腳本的書寫格式。
http://www.slac.stanford.edu/comp/unix/package/rtems/doc/html/ld/ld.info.Scripts.html
內核鏡像通常包括兩個部分,一個部分是針對特定處理器、特定平臺的啓動代碼,這部分一般被稱爲boot代碼,另外一部分就是Linux系統自己,這包括內存管理、進程調度、文件系統、進程間通信、網絡子系統等部分。
若是你查下Linux的代碼會發現,就arch/(x86,arm)/boot下的內容差異就很大,arch/arm/boot目錄下的內容以下:
圖2.3.1 ARM啓動代碼目錄
arch/x86/boot下的內容以下:
圖2.3.2 X86啓動代碼目錄
從上面就能夠看出X86的啓動代碼會比arm的啓動多一些,可是上述關於arm的boot代碼在arch/arm下還有一個板級的初始化,如s3c2440板級一些工做被放在了,arch/arm/mach-s3c24xx下了。一個內核映像的組成至少包括boot部分和kernel部分,咱們暫且這麼定,而且後面無特殊說明,也會boot指cpu和板級的初始化代碼,kernel指脫離了硬件差異的啓動代碼。
兩種平臺啓動時,其中一個重要的差異是文件系統,由於arm一般用於嵌入式系統,其內存和Flash相對PC而受限,比較出名的嵌入式文件系統有yaffs二、jffs二、cramfs,因爲工做的關係這裏咱們就說ubifs文件系統了,該文件系統是新一代爲NAND Flash設計的文件系統,但其自己相對也較大,8M左右。PC架構採用ext3文件系統。
另外一差異在對硬件的處理上,arm採用了設備樹,arch/arm/boot/dts,而且uboot如今也能夠採用設備方法來解析硬件了。嵌入式設備對硬件的處理(依賴設備樹)較PC差別較大。這裏先綜述PC下的啓動流程。
首先來看一個縮略的內核鏡像vmlinux(kernel部分,非boot)輸入文件編譯過程,使用的連接腳本是內核源碼文件/arch/x86/kernel/vmlinux.lds。
[root@ge linux-3.10]# make vmlinux
HOSTCC scripts/basic/fixdep
HOSTCC arch/x86/tools/relocs_32.o
HOSTCC arch/x86/tools/relocs_64.o
HOSTCC arch/x86/tools/relocs_common.o
HOSTLD arch/x86/tools/relocs
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/x86/kernel/asm-offsets.s
GEN include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
CC scripts/mod/empty.o
HOSTCC scripts/mod/mk_elfconfig
MKELF scripts/mod/elfconfig.h
CC scripts/mod/devicetable-offsets.s
GEN scripts/mod/devicetable-offsets.h
HOSTCC scripts/mod/file2alias.o
HOSTCC scripts/mod/modpost.o
HOSTCC scripts/mod/sumversion.o
HOSTLD scripts/mod/modpost
HOSTCC scripts/selinux/genheaders/genheaders
HOSTCC scripts/selinux/mdp/mdp
HOSTCC scripts/kallsyms
HOSTCC scripts/pnmtologo
HOSTCC scripts/conmakehash
HOSTCC scripts/sortextable
CC init/main.o
CHK include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
CC init/do_mounts_initrd.o
LD init/mounts.o
CC init/initramfs.o
CC init/calibrate.o
CC init/init_task.o
LD init/built-in.o
HOSTCC usr/gen_init_cpio
GEN usr/initramfs_data.cpio
AS usr/initramfs_data.o
LD usr/built-in.o
LD arch/x86/crypto/built-in.o
CC arch/x86/kernel/process_32.o
CC arch/x86/kernel/signal.o
AS arch/x86/kernel/entry_32.o
CC arch/x86/kernel/traps.o
…
CHK include/generated/uapi/linux/version.h
UPD include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
UPD include/generated/utsrelease.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/x86/kernel/asm-offsets.s
GEN include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
CC scripts/mod/empty.o
HOSTCC scripts/mod/mk_elfconfig
MKELF scripts/mod/elfconfig.h
CC scripts/mod/devicetable-offsets.s
GEN scripts/mod/devicetable-offsets.h
HOSTCC scripts/mod/file2alias.o
HOSTCC scripts/mod/modpost.o
HOSTCC scripts/mod/sumversion.o
HOSTLD scripts/mod/modpost
HOSTCC scripts/selinux/genheaders/genheaders
HOSTCC scripts/selinux/mdp/mdp
HOSTCC scripts/kallsyms
HOSTCC scripts/pnmtologo
HOSTCC scripts/conmakehash
HOSTCC scripts/sortextable
CC init/main.o
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
CC init/do_mounts_initrd.o
CC init/do_mounts_md.o
LD init/mounts.o
CC init/initramfs.o
CC init/calibrate.o
CC init/init_task.o
LD init/built-in.o
HOSTCC usr/gen_init_cpio
GEN usr/initramfs_data.cpio
AS usr/initramfs_data.o
LD usr/built-in.o
CC arch/x86/kernel/process_32.o
CC arch/x86/kernel/signal.o
AS arch/x86/kernel/entry_32.o
CC arch/x86/kernel/traps.o
CC arch/x86/kernel/irq.o
CC arch/x86/kernel/irq_32.o
CC arch/x86/kernel/dumpstack_32.o
CC arch/x86/kernel/time.o
CC arch/x86/kernel/ioport.o
CC arch/x86/kernel/ldt.o
CC arch/x86/kernel/dumpstack.o
CC arch/x86/kernel/nmi.o
CC arch/x86/kernel/setup.o
CC arch/x86/kernel/x86_init.o
CC arch/x86/kernel/i8259.o
CC arch/x86/kernel/irqinit.o
CC arch/x86/kernel/jump_label.o
CC arch/x86/kernel/irq_work.o
CC arch/x86/kernel/probe_roms.o
CC arch/x86/kernel/i386_ksyms_32.o
CC arch/x86/kernel/syscall_32.o
CC arch/x86/kernel/bootflag.o
CC arch/x86/kernel/e820.o
CC arch/x86/kernel/pci-dma.o
CC arch/x86/kernel/quirks.o
CC arch/x86/kernel/topology.o
CC arch/x86/kernel/kdebugfs.o
CC arch/x86/kernel/alternative.o
CC arch/x86/kernel/i8253.o
CC arch/x86/kernel/pci-nommu.o
CC arch/x86/kernel/hw_breakpoint.o
CC arch/x86/kernel/tsc.o
CC arch/x86/kernel/io_delay.o
CC arch/x86/kernel/rtc.o
CC arch/x86/kernel/pci-iommu_table.o
CC arch/x86/kernel/process.o
CC arch/x86/kernel/i387.o
CC arch/x86/kernel/xsave.o
CC arch/x86/kernel/ptrace.o
CC arch/x86/kernel/tls.o
CC arch/x86/kernel/step.o
CC arch/x86/kernel/stacktrace.o
CC arch/x86/kernel/acpi/boot.o
CC arch/x86/kernel/acpi/sleep.o
CC arch/x86/kernel/acpi/cstate.o
LD arch/x86/kernel/acpi/built-in.o
CC arch/x86/kernel/apic/apic.o
CC arch/x86/kernel/apic/apic_noop.o
CC arch/x86/kernel/apic/ipi.o
CC arch/x86/kernel/apic/hw_nmi.o
CC arch/x86/kernel/apic/io_apic.o
CC arch/x86/kernel/apic/probe_32.o
LD arch/x86/kernel/apic/built-in.o
CC arch/x86/kernel/cpu/intel_cacheinfo.o
MKCAP arch/x86/kernel/cpu/capflags.c
CC arch/x86/kernel/cpu/capflags.o
CC arch/x86/kernel/cpu/common.o
CC arch/x86/kernel/cpu/match.o
CC arch/x86/kernel/cpu/bugs.o
CC arch/x86/kernel/cpu/intel.o
CC arch/x86/kernel/cpu/amd.o
CC arch/x86/kernel/cpu/transmeta.o
CC arch/x86/kernel/cpu/perf_event.o
CC arch/x86/kernel/cpu/perf_event_amd.o
CC arch/x86/kernel/cpu/perf_event_amd_uncore.o
CC arch/x86/kernel/cpu/perf_event_p6.o
CC arch/x86/kernel/cpu/perf_event_knc.o
CC arch/x86/kernel/cpu/perf_event_p4.o
CC arch/x86/kernel/cpu/perf_event_intel_lbr.o
CC arch/x86/kernel/cpu/perf_event_intel_ds.o
CC arch/x86/kernel/cpu/perf_event_intel.o
CC arch/x86/kernel/cpu/perf_event_intel_uncore.o
CC arch/x86/kernel/cpu/mcheck/mce.o
CC arch/x86/kernel/cpu/mcheck/mce_intel.o
CC arch/x86/kernel/cpu/mcheck/mce_amd.o
CC arch/x86/kernel/cpu/mcheck/threshold.o
CC arch/x86/kernel/cpu/mcheck/therm_throt.o
LD arch/x86/kernel/cpu/mcheck/built-in.o
CC arch/x86/kernel/cpu/mtrr/main.o
CC arch/x86/kernel/cpu/mtrr/if.o
CC arch/x86/kernel/cpu/mtrr/generic.o
CC arch/x86/kernel/cpu/mtrr/cleanup.o
CC arch/x86/kernel/cpu/mtrr/amd.o
CC arch/x86/kernel/cpu/mtrr/cyrix.o
CC arch/x86/kernel/cpu/mtrr/centaur.o
LD arch/x86/kernel/cpu/mtrr/built-in.o
CC arch/x86/kernel/cpu/perfctr-watchdog.o
CC arch/x86/kernel/cpu/perf_event_amd_ibs.o
LD arch/x86/kernel/cpu/built-in.o
CC arch/x86/kernel/reboot.o
CC arch/x86/kernel/msr.o
CC arch/x86/kernel/cpuid.o
CC arch/x86/kernel/early-quirks.o
CC arch/x86/kernel/smp.o
CC arch/x86/kernel/smpboot.o
CC arch/x86/kernel/tsc_sync.o
CC arch/x86/kernel/setup_percpu.o
CC arch/x86/kernel/mpparse.o
CC arch/x86/kernel/machine_kexec_32.o
CC arch/x86/kernel/crash.o
CC arch/x86/kernel/crash_dump_32.o
CC arch/x86/kernel/module.o
CC arch/x86/kernel/doublefault_32.o
CC arch/x86/kernel/vm86_32.o
CC arch/x86/kernel/early_printk.o
CC arch/x86/kernel/amd_nb.o
CC arch/x86/kernel/microcode_core_early.o
CC arch/x86/kernel/microcode_intel_early.o
CC arch/x86/kernel/microcode_intel_lib.o
CC arch/x86/kernel/microcode_core.o
CC arch/x86/kernel/microcode_intel.o
CC arch/x86/kernel/microcode_amd.o
LD arch/x86/kernel/microcode.o
CC arch/x86/kernel/check.o
CC arch/x86/kernel/perf_regs.o
LD arch/x86/kernel/built-in.o
AS arch/x86/kernel/head_32.o
CC arch/x86/kernel/head32.o
CC arch/x86/kernel/head.o
LD arch/x86/built-in.o
….
CC kernel/fork.o
CC kernel/exec_domain.o
CC kernel/panic.o
CC kernel/printk.o
CC kernel/cpu.o
…
CC mm/filemap.o
CC mm/mempool.o
CC mm/oom_kill.o
CC mm/fadvise.o
…
CC mm/migrate.o
LD mm/built-in.o
CC fs/open.o
CC fs/read_write.o
CC fs/file_table.o
CC fs/super.o
…
CC fs/drop_caches.o
LD fs/built-in.o
CC ipc/util.o
CC ipc/msgutil.o
CC ipc/msg.o
CC ipc/sem.o
CC ipc/shm.o
CC ipc/ipcns_notifier.o
CC ipc/syscall.o
CC ipc/ipc_sysctl.o
CC ipc/mqueue.o
CC ipc/namespace.o
CC ipc/mq_sysctl.o
LD ipc/built-in.o
CC security/keys/gc.o
…
CC security/capability.o
CC security/lsm_audit.o
LD security/built-in.o
CC crypto/api.o
CC crypto/cipher.o
CC crypto/compress.o
…
CC arch/x86/lib/usercopy_32.o
AR arch/x86/lib/lib.a
LINK vmlinux
LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
KSYM .tmp_kallsyms1.o
KSYM .tmp_kallsyms2.o
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
對於boot部分,依賴於arch/x86/boot/setup.ld,其編譯過程以下,
CC arch/x86/boot/a20.o
AS arch/x86/boot/bioscall.o
CC arch/x86/boot/cmdline.o
AS arch/x86/boot/copy.o
HOSTCC arch/x86/boot/mkcpustr
CPUSTR arch/x86/boot/cpustr.h
CC arch/x86/boot/cpu.o
CC arch/x86/boot/cpucheck.o
CC arch/x86/boot/early_serial_console.o
CC arch/x86/boot/edd.o
VOFFSET arch/x86/boot/voffset.h
LDS arch/x86/boot/compressed/vmlinux.lds
AS arch/x86/boot/compressed/head_32.o
CC arch/x86/boot/compressed/misc.o
CC arch/x86/boot/compressed/string.o
CC arch/x86/boot/compressed/cmdline.o
CC arch/x86/boot/compressed/early_serial_console.o
OBJCOPY arch/x86/boot/compressed/vmlinux.bin
RELOCS arch/x86/boot/compressed/vmlinux.relocs
GZIP arch/x86/boot/compressed/vmlinux.bin.gz
HOSTCC arch/x86/boot/compressed/mkpiggy
MKPIGGY arch/x86/boot/compressed/piggy.S
AS arch/x86/boot/compressed/piggy.o
LD arch/x86/boot/compressed/vmlinux
ZOFFSET arch/x86/boot/zoffset.h
AS arch/x86/boot/header.o
CC arch/x86/boot/main.o
CC arch/x86/boot/mca.o
CC arch/x86/boot/memory.o
CC arch/x86/boot/pm.o
AS arch/x86/boot/pmjump.o
CC arch/x86/boot/printf.o
CC arch/x86/boot/regs.o
CC arch/x86/boot/string.o
CC arch/x86/boot/tty.o
CC arch/x86/boot/video.o
CC arch/x86/boot/video-mode.o
CC arch/x86/boot/version.o
CC arch/x86/boot/video-vga.o
CC arch/x86/boot/video-vesa.o
CC arch/x86/boot/video-bios.o
LD arch/x86/boot/setup.elf
OBJCOPY arch/x86/boot/setup.bin
OBJCOPY arch/x86/boot/vmlinux.bin
HOSTCC arch/x86/boot/tools/build
BUILD arch/x86/boot/bzImage
上述文件中總共着色了紅、橙、綠、藍、紫,(紫色的是和根文件系統相關的部分,暫時先不談。)
剩下的着色部分在啓動時會依上述順序執行,好了到這裏了,該稍微總結一下了,根據Makefile規則,能夠生成上述的啓動和內核文件。這基本上是一個可以啓動的內核了,可是還確實根文件系統,也就是紫色部分會用到的,這部分會留到後面講述,這裏仍之內核鏡像的生成和使用過程爲主剖析。先來看一張圖,而後再將映像文件是如何連接起來的。
圖2.4.3 啓動過程圖
這張圖的有半部分在1.1節就見過了,是內存中代碼的架構,其中X = 0x001000 + grub的大小,由於grub大小在編譯時才肯定,grub將上圖中紅色的512字節內容代碼拷貝到右圖紅色箭頭所指的地址處並將控制權較給這部分代碼,至此Linux內核代碼正式開始登場接管後續操做系統的啓動工做。而後將實模式代碼拷貝到墨綠色箭頭處執行,這部分代碼被稱爲setup代碼,開始的代碼是上圖中黃色和綠色的兩個代碼,實模式跳轉到保護模式開始執行,是藍色箭頭表示的。實際的過程和上面講的還請有點區別,由於內核時通過壓縮的,由/arch/x86/boot/compressed/head_32.S調用misc.c中的decompress_kernel()函數解壓到0x100000地址處的,因此是整個一次性有grub拷到內存中的,而後會進行自解壓等,這裏就簡化了這一處理過程,不過壓縮的內核代碼會被grub加載1M+的地方,這些地址的在連接時就肯定了的。
當內核跳到0x100000處時,控制權由bst轉交給了真正意義上的kernel,這就是vmlinux的入口(可以使用make vmlinux生成的),這時/arch/x86/kernel/head32.c中的i386_start_kerne()將會被調用,該函數會調用start_kernel()函數。該函數調用若干函數和腳本創建Linux的核心環境,start_kernel()以後調用init(),建立系統級線程,比較重要的如do_basic_setup()完成外設及驅動的加載,不少設備和驅動的源頭就來自這裏,同時這裏也會完成根文件系統的掛載工做。
完成上述工做後,init()會打開/dev/console設備,重定向stdin、stdout、stderr,最後使用execve()系統調用執行init程序。到這裏能夠說引導工做結束了。
init()進程開啓後,系統核心環境已經準備好了,接着init()讀取其配置文件/etc/inittab。
上述中setup大小是18K,這表示的是個人機器上是這樣的,也許你的會有差別,具體查看和計算方法以下:
[root@ge boot]# od -j 0x01f1 -N1/boot/vmlinuz-3.10.0
0000761 000036
0000762
0000036*512/1024=18K
連接過程就是將若干個輸入文件連接成一個文件,這編寫經典的helloword程序時,你可能使用了printf或者printk,這裏就涉及到了庫文件和動態仍是靜態編譯了,暫時先整個內核的連接過程按照上述也分爲boot和kernel兩個部分,接下來就來看看這兩個部分,若是細心,就會發現有不少built-in.o,如ipc/built-in.o,而且它們的得到方式不是cc而是ld,就是將對應目錄下文件連接成一個文件,如:
CC security/capability.o
CC security/lsm_audit.o
LD security/built-in.o
最後這些built-in.o文件和連接腳本一塊兒做爲輸入鏈接器的文件,最終將生成vmlinuz。
setup連接腳本,相關的註釋使用了華文隸書:
1 /*
2 * setup.ld
3 *
4 * Linker script for the i386setup code
5 */
6OUTPUT_FORMAT("elf32-i386", "elf32-i38 6","elf32-i386")
7OUTPUT_ARCH(i386)
8ENTRY(_start)
9
ENTRY表示程序的入口,在head.S文件的274行定義以下。
272 # offset 512, entry point
273
274 .globl _start
275 _start:
276 # Explicitly enter this asbytes, or the assembler
277 # tries to generate a 3-bytejump here, which causes
278 # everything else to push offto the wrong offset.
279 .byte 0xeb # short (2-byte) jump
280 .byte start_of_setup-1f
10SECTIONS
11 {
12 . = 0;
13 .bstext : { *(.bstext) }
下面顯示了爲何是bstext段的內容放在這裏了,在header.S文件中已經制定的生成的段類型了。.o類型文件的全部段的段類型使用對應平臺的readelf命令能夠查看到。
./boot/setup.ld: .bstext : {*(.bstext) }
Binary file ./boot/header.o matches
./boot/header.S: .section ".bstext", "ax"
下面同理
14 .bsdata : { *(.bsdata) }
15
16 . = 495;
17 .header : { *(.header) }
18 .entrytext : { *(.entrytext)}
19 .inittext : { *(.inittext) }
./boot/setup.ld: .inittext : { *(.inittext)}
./boot/tty.c: * These functions are in.inittext so they can be used to signal
./boot/tty.c:static void__attribute__((section(".inittext"))) serial_putchar(int ch)
./boot/tty.c:static void__attribute__((section(".inittext"))) bios_putchar(int ch)
./boot/tty.c:void__attribute__((section(".inittext"))) putchar(int ch)
./boot/tty.c:void __attribute__((section(".inittext")))puts(const char *str)
20 .initdata : { *(.initdata) }
21 __end_init = .;
22
23 .text : { *(.text) }
./kernel/entry_32.S: .section.entry.text, "ax"
24 .text32 : { *(.text32) }
./boot/pmjump.S: .section ".text32","ax"
25
26 . = ALIGN(16);
27 .rodata : { *(.rodata*) }
28
29 .videocards : {
30 video_cards = .;
31 *(.videocards)
./boot/video.h:#define __videocard structcard_info __attribute__((section(".videocards")))
32 video_cards_end = .;
33 }
34
35 . = ALIGN(16);
36 .data : { *(.data*) }
37
38 .signature : {
39 setup_sig = .;
40 LONG(0x5a5aaa55)
41 }
42
43
44 . = ALIGN(16);
45 .bss :
46 {
47 __bss_start = .;
48 *(.bss)
49 __bss_end = .;
50 }
51 . = ALIGN(16);
52 _end = .;
53
54 /DISCARD/ : { *(.note*) }
DISCARD 該段不會出如今輸出文件中,一般用於檢查映像生成的正確性
55
56 /*
57 * The ASSERT() sink to . is intentional, for binutils 2.14compatibility:
58 */
59 . = ASSERT(_end <= 0x8000, "Setup too big!");
Setup部分的實模式代碼,結尾處不能超過0x8000,這樣圖2.4.1中的x+0x08000纔有意義。
60 . = ASSERT(hdr == 0x1f1, "The setup header has the wrongoffset!");
在前面技術計算setup代碼大小時用到的這個數字0x1f1,該處存放的是setup的大小信息。其裏存放的內容爲buf[0x1f1] = setup_sectors-1;這就不能理解512存在的緣由了。
61 /* Necessary for the very-old-loader check to work... */
62 . = ASSERT(__end_init <= 5*512, "init sections too big!");
判斷init段大小,應在2.5K如下,才正確
63
64 }
好了上面就是圖2.4.1中關於bootsector和setup的內容了。下面看看kernel的連接腳本,有點長,這裏略去了64位總線PC狀況,只留下32的SECTIONS定義。註釋方法同上。
首先來看看大致的連接成什麼樣子,
# vmlinux
# ^
# |
# +-< $(KBUILD_VMLINUX_INIT)
# | +--< init/version.o + more
# |
# +--< $(KBUILD_VMLINUX_MAIN)
# | +--< drivers/built-in.omm/built-in.o + more
# |
# +-< ${kallsymso} (see description in KALLSYMS section)
上面深紅色KBUILD_VMLINUX_INIT表示的內容就是kernel image最開始存放的代碼了,接下來存放kernel的主要內容,一些核心、庫文件、驅動以及網絡代碼通通放在這個部分,想知道這裏KBUILD_VMLINUX_INIT的內容存放什麼內容, 看下面的Makefile腳本。
<Makefile>
755 # Externally visible symbols (used by link-vmlinux.sh)
756export KBUILD_VMLINUX_INIT:= $(head-y)$(init-y)
757export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
758export KBUILD_LDS :=arch/$(SRCARCH)/kernel/vmlinux.lds
763 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT)$(KBUILD_VMLINUX_MAIN)
其中756-758行很關鍵,前兩個指定了在kernel前兩部分放置的內容,後一個則指定了連接的腳本文件的存放路徑,該連接腳本指導連接的整個過程,後面會簡單註釋如下。
771 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
從上述的Makefile文件能夠看出導出符號KBUILD_VMLINUX_INIT 依賴於head-y和init-y符號表示的內容, :=是腳本一種賦值方法。再看head-y表示的是什麼。
<arch/x86/Makefile>
160 # Kernel objects
161
162 head-y := arch/x86/kernel/head_$(BITS).o
163 head-y += arch/x86/kernel/head$(BITS).o
164 head-y += arch/x86/kernel/head.o
$(BITS)=32
能夠看到依賴三個文件,這裏BITS直接給出32,由於咱們的系統是X86_32的,將全部符號帶入可獲得:
head-y := head_32.S head32.c head.c
關於init-y 的內容相似,而且init-y部分很是中要,不少系統開機的設置均由這裏代碼完成的,因此這部分會放在Linux系統啓動來說,若是想搶先看,狀況init/main.c 下的start_kernel()函數,該函數作了很是多的系統初始化工做。下面就給出arch/X86/kernel/vmlinux.lds的註釋。
程序加載地址和運行地址是對應的兩種地址,連接腳本使用兩種地址來表示(虛擬/運行地址VMA,加載地址LMA),LMA地址由 AT指定。
#include <asm/boot.h>
ENTRY(phys_startup_32)
SECTIONS
{
. = LOAD_OFFSET + LOAD_PHYSICAL_ADDR;
//.是一個位置計數符號,記錄當前位置在目標文件中的虛擬地址(VMA),自增; 初始值爲LOAD_OFFSET + LOAD_PHYSICAL_ADDR,前者是咱們熟知的內核虛擬地址空間起始地址0xC0000000,LOAD_PHYSICAL_ADDR是內核image加載的物理地址,由CONFIG_PHYSICAL_START計算獲得。
./kernel/vmlinux.lds.S: #define LOAD_OFFSET __PAGE_OFFSET
#define __PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET,UL)
.config CONFIG_PAGE_OFFSET=0xC0000000
這裏LOAD_OFFSET就是3G,
<asm/boot.h>
/* Physical address where kernel should be loaded. */
#define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \
+(CONFIG_PHYSICAL_ALIGN - 1)) \
&~(CONFIG_PHYSICAL_ALIGN - 1))
# CONFIG_KEXEC_JUMP is not set
CONFIG_PHYSICAL_START=0x1000000
CONFIG_PHYSICAL_ALIGN=0x1000000
上面是作了邊界對齊。綜合上面可獲得 .= 0xC0000000 + 0x1000000 ;對於0x1000000在圖2.4.1中找找看看在內存中的那個位置,這樣就能夠知道上述虛擬地址3G~4G部分是留給內核的。
phys_startup_32 = startup_32 - LOAD_OFFSET;
這裏暫且知道startup_32地址等於__HEAD,就是得到物理地址,就是程序的加載地址。
arch/x86/kernel/head_32.S
__HEAD
ENTRY(startup_32)
/* Text and read-only data */
.text : AT(ADDR(.text) -LOAD_OFFSET) {
return the absolute address (the VMA) of the named section.。
建立第一段,其它段類推,段名.text, 加載地址AT指定的地址,加載地址地址爲LOAD_PHYSICAL_ADDR
_text = .;
/* bootstrapping code */
HEAD_TEXT //bootsector對應的字段
./include/asm-generic/vmlinux.lds.h:#defineHEAD_TEXT *(.head.text)
. = ALIGN(8);
_stext = .;
TEXT_TEXT
#define TEXT_TEXT \
ALIGN_FUNCTION(); \
*(.text.hot) \
*(.text) \
*(.ref.text) \
DEV_KEEP(init.text) \
DEV_KEEP(exit.text) \
CPU_KEEP(init.text) \
CPU_KEEP(exit.text) \
MEM_KEEP(init.text) \
MEM_KEEP(exit.text) \
*(.text.unlikely)
其它的就不一一列舉了,init段的內容在啓動和驅動加載時可能會用到,這部分比較特殊一點。後續段內容參看./include/asm-generic/vmlinux.lds.h
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
ENTRY_TEXT
IRQENTRY_TEXT
*(.fixup)
*(.gnu.warning)
/* End of text section */
_etext = .;
} :text = 0x9090
對section中的空隙用0x9090進行填充。0x90是彙編指令NOP的機器碼,故至關於在不連續代碼間填充空操做。
NOTES :text :note
EXCEPTION_TABLE(16) :text = 0x9090
/* Data */
.data : AT(ADDR(.data) - LOAD_OFFSET) {
/* Start of data section */
_sdata = .;
/* init_task */
INIT_TASK_DATA(THREAD_SIZE)
/* 32 bit has nosave before_edata */
NOSAVE_DATA
PAGE_ALIGNED_DATA(PAGE_SIZE)
CACHELINE_ALIGNED_DATA(L1_CACHE_BYTES)
DATA_DATA
CONSTRUCTORS
/* rarely changed data like cpumaps */
READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES)
/* End of data section */
_edata = .;
} :data
/* Init code and data - will be freedafter init */
. = ALIGN(PAGE_SIZE);
.init.begin : AT(ADDR(.init.begin) - LOAD_OFFSET) {
__init_begin = .; /* pairedwith __init_end */
}
INIT_TEXT_SECTION(PAGE_SIZE)
INIT_DATA_SECTION(16)
.x86_cpu_dev.init : AT(ADDR(.x86_cpu_dev.init) - LOAD_OFFSET) {
__x86_cpu_dev_start = .;
*(.x86_cpu_dev.init)
__x86_cpu_dev_end = .;
}
/*
* start address and size of operations which during runtime
* can be patched with virtualization friendly instructions or
* baremetal native ones. Think page table operations.
* Details in paravirt_types.h
*/
. = ALIGN(8);
.parainstructions : AT(ADDR(.parainstructions) - LOAD_OFFSET) {
__parainstructions = .;
*(.parainstructions)
__parainstructions_end = .;
}
/*
* struct alt_inst entries. From the header (alternative.h):
* "Alternative instructions for different CPU types orcapabilities"
* Think locking instructions on spinlocks.
*/
. = ALIGN(8);
.altinstructions : AT(ADDR(.altinstructions) - LOAD_OFFSET) {
__alt_instructions = .;
*(.altinstructions)
__alt_instructions_end = .;
}
/*
* struct iommu_table_entry entries are injected in this section.
* It is an array of IOMMUs which during run time gets sorted depending
* on its dependency order. After rootfs_initcall is complete
* this section can be safely removed.
*/
.iommu_table : AT(ADDR(.iommu_table) - LOAD_OFFSET) {
__iommu_table = .;
*(.iommu_table)
__iommu_table_end = .;
}
. = ALIGN(8);
.apicdrivers : AT(ADDR(.apicdrivers) - LOAD_OFFSET) {
__apicdrivers = .;
*(.apicdrivers);
__apicdrivers_end = .;
}
. = ALIGN(8);
/*
* .exit.text is discard at runtime, not link time, to deal with
* references from.altinstructions and .eh_frame
*/
.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) {
EXIT_TEXT
}
.exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) {
EXIT_DATA
}
#if !defined(CONFIG_X86_64) ||!defined(CONFIG_SMP)
PERCPU_SECTION(INTERNODE_CACHE_BYTES)
#endif
. = ALIGN(PAGE_SIZE);
/* freed after init ends here */
.init.end : AT(ADDR(.init.end) - LOAD_OFFSET) {
__init_end = .;
}
/*
* smp_locks might be freed after init
* start/end must be page aligned
*/
. = ALIGN(PAGE_SIZE);
.smp_locks : AT(ADDR(.smp_locks) - LOAD_OFFSET) {
__smp_locks = .;
*(.smp_locks)
. = ALIGN(PAGE_SIZE);
__smp_locks_end = .;
}
/* BSS */
. = ALIGN(PAGE_SIZE);
.bss : AT(ADDR(.bss) - LOAD_OFFSET) {
__bss_start = .;
*(.bss..page_aligned)
*(.bss)
. = ALIGN(PAGE_SIZE);
__bss_stop = .;
}
. = ALIGN(PAGE_SIZE);
.brk : AT(ADDR(.brk) - LOAD_OFFSET) {
__brk_base = .;
. += 64 * 1024; /* 64k alignment slop space */
*(.brk_reservation) /* areas brk users have reserved */
__brk_limit = .;
}
_end = .;
STABS_DEBUG
DWARF_DEBUG
/* Sections to be discarded */
DISCARDS
/DISCARD/ : { *(.eh_frame) }
}
從上面的連接腳本你會發現圖2.4.3左邊的鏡像缺乏一部分,正確的形式以下,之因此上下沒有對齊,是由於地址啦,內核的VMA地址不是從零開始的,而是0xC0000000 + 0x1000000:
圖2.4.4內核鏡像和啓動內存佈局
initramfs
本節對PC下的根文件系統作個簡單的過程瀏覽,深刻的文件系統的部分放在嵌入式根文件系統的製做上。Initrd是initial ramdisk的簡稱,initramfs是initial ram filesystem的簡稱,是一種cpio格式的內存文件系統,在pc下init-3.10.0.img就是一個cpio格式的,目前initrd類型的啓動方式少見了。
PC下的initramfs
先來看看/boot/grub/grub.cfg文件裏的內容,下圖只截圖了3.10內核啓動用到的部分,有這麼一行:
Initrd /init-3.10.0.img
若是其在啓動配置文件grub.cfg裏,那麼能夠說該文件比較重要了,由於系統啓動須要使用它,實際上也能夠不使用initrd,只須要將上面那行改爲no initrd就能夠了,大多數狀況下系統仍然能夠正常啓動,可是對於服務器多半會啓動不了,這取決於具體的硬件配置。
圖2.4.1.1 grub配置信息
早期的Linux內核中是沒有initrd的,在Linux 0.11版本中,系統啓動時直接掛載floppy(軟盤),而後啓動系統的init進程,該進程在任何Linux系統中都存在,到目前是必不可少的,0.11將init存儲在floppy中,可是隨着時代的變遷新的存儲器也出現了,好比scsi硬盤,sd、usb存儲設備等相繼出現,這時若是將這些存儲設備的驅動編譯進內核,能夠想象,內核的將會很是大,Linux內核的一個宗旨就是簡單,爲了保持內核代碼簡潔,就將這些設備的驅動程序放在了文件系統裏。這就意味着要想得到init程序須要先掛載設備的驅動,可是驅動代碼在文件系統裏,因此係統要先掛載設備的驅動,而後從設備中找到對應的init,這個加載驅動和啓動init的工做就放在了initrd中來完成了,下面就來揭開initrd的神祕面紗,首先咱們看看initrd-3.10.0.img的內容,注意該文件爲cpio格式的,請按照下述方式操做。
cp /boot/initrd-3.10.0.img initrd.gz
lsinitrd initrd.gz
到這已經能夠看到initrd的內容,也可使用下述命令去詳細的看看該文件的組織結構。
gzip –d initrd.gz
cpio –ivdm < initrd.gz
cat init
文件輸出以下:
#!/bin/nash
mount -t proc /proc /proc
setquiet
echo Mounting proc filesystem
echo Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
echo "Loading ehci-hcd module"
modprobe -q ehci-hcd
echo "Loading ohci-hcd module"
modprobe -q ohci-hcd
echo "Loading uhci-hcd module"
modprobe -q uhci-hcd
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading ext3 module"
modprobe -q ext3
echo "Loading scsi_mod module"
modprobe -q scsi_mod
echo "Loading sd_mod module"
modprobe -q sd_mod
echo "Loading scsi_transport_spi module"
modprobe -q scsi_transport_spi
echo "Loading mptbase module"
modprobe -q mptbase
echo "Loading mptscsih module"
modprobe -q mptscsih
echo "Loading mptspi module"
modprobe -q mptspi
echo "Loading dm-mod module"
modprobe -q dm-mod
echo "Loading dm-mirror module"
modprobe -q dm-mirror
echo "Loading dm-zero module"
modprobe -q dm-zero
echo "Loading dm-snapshot module"
modprobe -q dm-snapshot
echo Making device-mapper control node
mkdmnod
mkblkdevs
echo Scanning logical volumes
lvm vgscan --ignorelockingfailure
echo Activating logical volumes
lvm vgchange -ay --ignorelockingfailure VolGroup00
resume /dev/VolGroup00/LogVol01
echo Creating root device.
mkrootdev -text3 -o defaults,ro /dev/VolGroup00/LogVol00
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
loadpolicy
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1
上述文件能夠看出,該文件的做用就是建立一些系統運行的依賴目標,而後掛載了幾個存儲設備的驅動,掛載了根文件系統,這裏的文件系統是真正操做系統起來後使用的文件系統,最後啓動init進程就結束了。
若是看cpio命令後的目錄下內容,黑色的那兩個文件不是cpio解壓出來的。主要包括啓動的程序、啓動工具和初始化服務,如網絡、終端、設備驅動的加載以及加載其它文件系統。
圖2.4.1.2 cpio文件解壓內容
在來看看bin目錄下的內容:
圖2.4.1.3 PC根文件系統bin內容
這裏能夠看到好多命令都在這裏了,可是對於嵌入式環境看到的也許有點不一樣哦,因爲嵌入式環境的存儲空間是很是珍貴的,多以這裏看到的這麼多命令在嵌入式環境就變成busybox了,全部的命令均是連接到busybox,由busybox解析命令。
這裏在總結一下這個過程,首先grub根據配置文件grub.cfg解析是否時能initrd,若是時能將內核鏡像和initrd拷貝到物理內存中,而後grub將控制權交給內核,內核加載initrd,解壓並將內容拷貝到/dev/ram0中,解壓後的內容就是一個文件系統了,該文件系統在RAM上,這就是說內存盤(ramdisk)中存在一個文件系統了,內核這是會將其掛載爲rootfs(root file system)根文件系統,而後內核建立線程執linuxrc(嵌入式系統),完成後會寫在initrd並進行最後的啓動,進程這是使用initrd去初始化一些系統資源,掛載根文件系統,調用init進程初始化系統資源並啓動shell。至此對用戶而言系統可用了。
對於嵌入式設備,若是實際的根設備(非易失性存儲器)存在/initrd目錄,Linux掛載並將其作爲最終的根文件系統,若是不存在initrd的鏡像將被丟棄。可是若是在內核命令行中指定了root=/dev/ram0之類,linuxrc的執行將被跳過而且也不會嘗試掛載其它文件系統作爲最終的根文件系統。
接下來會講嵌入式系統下根文件系統的製做,再也不講述PC下initrd的製做,有興趣能夠本身嘗試製做一個,嵌入式下的根文件系統製做基於busybox,而且我的以爲嵌入式下的根文件系統的製做更能讓人理解根文件系統的方方面面。
busybox嵌入式根文件系統的製做:
http://busybox.net/
一、 解壓busybox-1.22.1.tar.gz
二、 配置源碼 makemenuconfig ARCH=arm CROSS_COMPILE=arm-linux-
三、 make
四、 make install
圖2.4.2.1 安裝busybox過程
五、 進入上一步生成的_install目錄,該目錄下會生成如下幾個文件
圖2.4.2.2 安裝生成內容
六、 添加其它目錄mkdirdev etc mnt proc var tmp sys root lib
至於根文件系統的各個目錄的做用能夠參看FHS,《filesystem Hierarchy Standard》
/dev 設備文件目錄,應用程序和驅動程序的橋樑,udev支持的熱插拔設備自動建立。
/etc 配置文件,許多系統啓動的配置選項均在此,PC下和嵌入式下配置文件的內容差異比較大,有興趣本身比較。
/mnt 掛載目錄點,可掛載其它文件系統類型的存儲設備。
/proc 虛擬設備文件系統,一些內核參數由此導出,通常不容許更改權限,驅動程序的一些輔助調試接口一般導出到該目錄下。
/lib 存放動態、靜態庫等文件,命令以及應用程序會使用該庫文件,glibc庫必須有,不然基本的ls、cd命令可能沒法運行。
/sys虛擬文件系統,用於設備和驅動的管理,驅動的class接口位於此,udev自動建立設備節點的功能依賴於該文件系統。
/root根用戶登陸目錄
七、 添加動態庫cp –a$(TOOLCHIAN)arm-none-linux-gnueabi/sys-root/lib/*so* ./lib/
該目錄下還有靜態庫,系統命令會調用該庫裏的文件。
八、 添加系統文件
[root@ge _install]# cd etc/
[root@ge etc]# vim inittab
[root@ge etc]# vim fstab
[root@ge etc]# Vim passwd
[root@ge etc]# mkdir init.d
[root@ge etc]# cd init.d/
[root@ge init.d]# vim rcS
[root@ge init.d]# chmod +x rcS
[root@ge init.d]# vim profile
這裏的文件都是在/etc目錄下的文件,主要用於Linux系統啓動階段,inittab屬於內核標準文件,有其編寫標準,該文件是Linux系統啓動時該目錄下第一個使用到的文件。
Inittab內容以下:
#this is run first except when bootinginsingle-user mode.
::sysinit:/etc/init.d/rcS
# /bin/sh invocations on selected ttys
# Start an "askfirst" shell ontheconsole (whatever that may be)
ttySAC0::askfirst:-/bin/sh
# Stuff to do when restarting the initprocess
::restart:/sbin/init
#Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
/sbin/init --mount the root file system
能夠參看相關的語法講解,sysinit具備最高優先級,該行表示,運行/etc/init.d/rcS,關於該文件後面再說,ttySAC0表示終端運行的shell,嵌入式通常是/bin/sh,前面的-表示當該終端打開時會調用/etc/profile腳本,若是沒有-,則會運行對應用戶目錄下的~/.profile,ctrlaltdel表示當ctrl + Alt + Del 三鍵同時按下時的動做,運行/sbin/reboot命令即重啓,restart行表示重啓時運行/sbin/init,這裏能夠看出和冷啓動(斷電再上電)的區別。
Fstab文件內容以下:
#device mount-point type options dump fsch order
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
該文件一樣是內核標準文件,能夠沒有,在rcS下建立上述設備目錄。上述表示文件系統的掛載相關信息,如掛載點、文件系統類型等。
rcS的內容以下:
#!/bin/sh
#This is the firstscript called by initprocess
/bin/mount –a
一般這裏的內容會比較多,通常會調用其它的腳本啓動一些常駐的應用程序,爲shell腳本,內容差異可能很是大。
Profile文件內容
#!/bin/sh
exportHOSTNAME=ge
export USER=root
export HOME=root
#exportPS1="[$USER@$HOSTNAME\W]#"
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=lib:/usr/lib:$LD_LIBRARY_PATH
export PATHLD_LIBRARY_PATH
該文件設置一些環境參數。
在passwd文件中添加以下行:
root::0:0:root:/root:/bin/sh,即只保存與root相關項
九、 建立設備節點
mknod dev/console c 5 1
mknod dev/null c 1 3
一般必須有console這個目錄,null目錄也經常須要,若是沒有console目錄,啓動會致使失敗。到此busybox部分工做完成了,由於選擇的是動態編譯方式,因此ls等命令依賴的glibc庫文件須要添加到/lib目錄下,可是真正的根文件系統製做完畢還有/lib沒有完成,實際使用中一般會進行壓縮,通常兩種方式,一種.tar.gz,還有一種一種就是cpio格式。在根目錄下生成cpio格式的壓縮文件,使用的以下命令:
Find . | cpio–quiet –o –H newc | gzip > ./rootfs.cpio.gz
嵌入式文件系統深刻
上面已經構建了一個可使用的根文件系統了,ls、cd命令已經能夠正確運行,如今給兩個題目,接下來就這兩個題目想闡述如何針對項目需求去定製一個根文件系統。
題目一:物聯網發展趨勢下,之後的嵌入式設備會聯網,如何添加防火牆的功能呢?
題目二:據說具備加密功能的ssh可以爲在網絡上傳輸提供一層保障,該如何實現該功能呢?
對於題目一使用開源軟件包iptables,交叉編譯iptables-1.4.18.tar.gz軟件包,
./configure CC=arm-linux-gcc --enable-static –disable-shared–host=arm-linux –prefix=$(YOUR INSTALL DIRECTORY)
Make
Make install
這樣就編譯好了,能夠把install目錄下的相關內容拷貝到上述對應的目錄中,可執行文件到/sbin 、庫文件到/lib中,這樣在shell下就可使用iptables命令了。
題目二比題目一稍微複雜一點,ssh功能的支持包是dropbear,可是呢編譯該時它還依賴zlib這個壓縮包提供的功能,因此呢,須要首先編譯zlib包,然後纔是編譯dropbear包。
zlib-1.2.8.tar.gz編譯以下:
export CC=arm-linux-gcc
./configure –prefix=/$(YOUR INSTALLDIRECTORY)
Make && make install
Dropbear-2013.5.58 的編譯命令以下
./configrure –with-zlib=$(YOUR zlib INSTALLDIRECTORY) cc=arm-linux –host=arm-linux
Make && make install
一樣將生成的可執行文件以及庫文件複製到對應的根文件系統的目錄中,這樣就可使用該功能,若是對configure不太懂,./configure –-help命令能夠查看。
經過上面的兩個例子,來總結一下如何向根文件系統添加新功能的思路,首先根據要提供的功能,(通常多是/bin、/sbin之類下的命令,多是/lib下的庫(應用程序使用)文件,抑或二者兼有),查找有沒有現成的開源包,若是有,那麼交叉編譯,拷貝,若是沒有那就本身寫,而後交叉編譯,拷貝。一切都是那麼的順利成章啊~!
最後須要提醒一下上文提到的udev機制在也是須要編安裝和拷貝的。
根文件系統的加載
這裏的加載指的是操做系統啓動時的加載過程,在系統啓動時start_kernel()->vfs_cache_init()->mnt_init()->init_rootfs(),該函數會掛載rootfs做爲根文件系統,可使用cat /proc/mounts查看,而後會解壓用gzip壓縮的歸檔文件到rootfs目錄,接着執行解壓後的init程序,一旦該程序運行,那麼就稱其爲init進程,並將控制權移交給init進程,這也是系統由內核到用戶態的標誌,若是在解壓的rootfs目錄下找不到init程序,那麼會根據解析的cmdline參數,找「root=XXX」,並掛載,該參數有devicetree的chosen節點提供。在Linux內核編譯時CONFIG_INITRAMFS_SOURCE指明intramfs的位置。一般一個系統可用的文件系統在/etc/fstab文件中,可是嵌入式環境下一般不用。
設備樹
對於Linux系統,一般啓動分爲bld(bootloader),bst(bootstrap),vmlinux(這裏的vmlinux不包括bst,可是一般Linux內核源碼包,編譯出來的最終vmlinux是包含bst,這裏將其分開講解了)。當你在Linux-3.10根目錄下使用make vmlinux時,你會發現確實有該目標,但是這裏所說的vmlinux是用來生成vmlinuz的,因此概念上仍是有點區別的,區別在於前一個vmlinux並不包括bst部分,而最終的vmlinuz是包括bst的。這個關於Makefile的分析就略過了,關鍵的執行步驟會在下面展示出來的。
這裏先提一下,因爲當/sbin/init進程運行起來之後,其讀取/etc/inittab配置文件,根據配置信息肯定運行級別,筆者對應的運行級別爲5,即圖形界面方式,在啓動時的視頻中就顯示了該信息。Init進程會執行系統初始化腳本/etc/rc.d/rc.sysinit,對系統進行配置,以讀寫方式掛載根文件系統,至此,系統基本運行起來了,接着運行/etc/rc.d/rc,該文件會判斷配置文件中的啓動運行級別,並定義了服務啓動的順序,並啓動/etc/rc.d/rc5.d,該目錄下的內容連接至/etc/init.d目錄下,後面會啓動虛擬終端/sbin/mingetty,在運行級別5上啓動xwindow,這是就是你們看到的登陸界面了,用戶輸入用戶名和密碼,而後/etc/passwd完成密碼驗證,登陸了。這一過程的文檔確實不少,因此這裏打算深刻兩個細節分析一下,這也是筆者在設備驅動的道路上吃過苦頭才認認真真看了源碼的。
網上分析Linux內核啓動的文章不少了,就算不是針對3.10版本的內核,也能夠參考,並結合3.10版的代碼進行分析,這裏打算重點講下嵌入式下的device tree的解析和設備的構建,將來嵌入式系統解析設備信息將會採用設備樹的方式,這也是爲何這裏會詳細討論該解析過程了,可是這是和嵌入式處理器息息相關的,不一樣的處理器的設備樹的構建和解析也是有區別的,因此Linux內核會將這部分的解析代碼放在setup_arch()函數中完成。
devicetree設備樹
因爲3.10版本的嵌入式環境下大多數使用的是device tree的方法來描述設備,不只如此,uboot也能夠而且也正在採用device tree的方法,因此這裏會花點篇幅介紹它,而後纔是Linux系統的啓動。
爲何先介紹device tree:
在2.3節中bootloader功能的提到以下兩個步驟:
4. Setup the kernel tagged list or dtb.
5. Call the kernel image.
對於3.10版本的內核使用dtb(device tree block)方法來描述設備拓撲,因此上面的4裏的tagged list並無被使用,而是使用了dtb,對於上面的5,call內核前將前三個寄存器按以下方式設定。
- CPU register settings
r0= 0,
r1= machine type number.
r2= physical address of tagged list in system RAM, or
physical address of device tree block (dtb) in system RAM
對於r2寄存器的值爲RAM中dtb的地址。
內核在啓動時會查r2地址爲的4偏移處的值,若是r2地址偏移零處的值爲零,那麼表示沒有taglist或者dtb被傳遞過來,若是4的偏移處爲0xd00dfeed,那麼表示傳遞過來的是dtb。
同時也會傳遞系統內存和根文件系統。這是經過device tree的chosen節點實現的。關於device tree的語法參看:
http://www.devicetree.org/Device_Tree_Usage
關於device tree 的用法能夠參看:
https://wiki.freebsd.org/FlattenedDeviceTree
在操做系統源碼中/arch/arm/boot/dts/目錄下常見以.dts和.dtsi爲後綴的文件,一般來講.dtsi通常是cpu級別的描述,而.dts則是板級的描述,在編譯生成dtb是一般.dts會包含.dtsi,以構成完整的設備樹,當還有一個skeleton.dts的一般會被包含。
這裏給出一個
/proc/device-tree 是device tree的一個調試入口,
Documentation/devicetree/booting-without-of.txt.
devicetree的信息獲取
先來看看devicetree的解析過程:
首先看arch/arm/kernel/head.S,注意和arch/arm/boot/compressed下的head.S區別,後者屬於bst的部分, arch/arm/boot/compressed下的內容,最終會生成bst部分的代碼,這裏從內核的head.S開始講起。該函數調用須要知足時各個寄存器存儲的內容以下:
R0:內核所在地址 R1 : machine number
R2: dtb or atags R8: phys_offset R9:cupid R10:procinfo
head.S
79 ENTRY(stext)
119 bl __vet_atags
135 ldr, r13=__mmap_switched
142 b __enable_mmu
415 __enable_mmu:
441 b __turn_mmu_on
460 ENTRY(__turn_mmu_on)
467 mov r3, r13
468 mov pc, r3
上述119行跳轉地址定義位於/arch/arm/kernel/head-common.S
18 #ifdef CONFIG_CPU_BIG_ENDIAN
19#define OF_DT_MAGIC 0xd00dfeed
20#else
21#define OF_DT_MAGIC 0xedfe0dd0 /* 0xd00dfeed in big-endian */
22#endif
46__vet_atags:
47 tst r2, #0x3 @ aligned?
48 bne 1f
49
50 ldr r5, [r2, #0]
51#ifdef CONFIG_OF_FLATTREE
52 ldr r6, =OF_DT_MAGIC @ is it a DTB?
53 cmp r5, r6
54 beq 2f
55#endif
56 cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?
57 cmpne r5, #ATAG_CORE_SIZE_EMPTY
58 bne 1f
59 ldr r5, [r2, #4]
60 ldr r6, =ATAG_CORE
61 cmp r5, r6
62 bne 1f
63
642: mov pc, lr @ atag/dtb pointer isok
65
661: mov r2, #0
67 mov pc, lr
68ENDPROC(__vet_atags)
46行判斷r2寄存器內容(dtb或者atags的地址)是否四字對齊,若是不是那麼就是atags的地址能夠直接返回,將首地址內容賦值爲零,返回head.S函數的調用處。若是是四字對齊的,那麼多是dtb了,須要進一步判斷。50行獲取四字對齊的地址的起始內容,若是是dtb,那麼這裏應該放的就是devicetree的魔數了。52行存儲設備樹的魔數,定義見18~21行。56-62處理ATAG_CORE的狀況。
arch/arm/kernel/head.S的135~468行,跳轉到arch/arm/kernel/head-common.S執行,代碼以下:
80__mmap_switched:
81 adr r3, __mmap_switched_data
82
83 ldmia r3!, {r4, r5, r6, r7}
84 cmp r4, r5 @ Copy datasegment if needed
85 1: cmpne r5, r6
86 ldrne fp, [r4], #4
87 strne fp, [r5], #4
88 bne 1b
89
90 mov fp, #0 @Clear BSS (and zero fp)
91 1: cmp r6, r7
92 strcc fp, [r6],#4
93 bcc 1b
94
95 ARM( ldmia r3, {r4, r5, r6, r7, sp})
96 THUMB( ldmia r3, {r4, r5, r6, r7} )
97 THUMB( ldr sp, [r3, #16] )
98 str r9, [r4] @Save processor ID
99 str r1, [r5] @Save machine type
100 str r2, [r6] @ Save atags pointer
101 cmp r7, #0
102 bicne r4, r0, #CR_A @ Clear 'A' bit
103 stmneia r7, {r0, r4} @Save control register values
104 b start_kernel
105 ENDPROC(__mmap_switched)
106
107 .align 2
108 .type __mmap_switched_data,%object
109 __mmap_switched_data:
110 .long __data_loc @ r4
111 .long _sdata @ r5
112 .long __bss_start @ r6
113 .long _end @ r7
114 .long processor_id @ r4
115 .long __machine_arch_type @ r5
116 .long __atags_pointer @ r6
117 #ifdef CONFIG_CPU_CP15
118 .long cr_alignment @ r7
119 #else
120 .long 0 @ r7
121 #endif
122 .long init_thread_union +THREAD_START_SP @ sp
123 .size __mmap_switched_data, . -__mmap_switched_data
其中100行完成將__atags_pointer設置爲dtb的地址。104行跳轉到start_kernel函數執行。該函數定義init/main.c中,網上對start_kernel()的分析很是多,該函數會間接調用不少子系統的代碼該start_kernel()函數的501行以下:
setup_arch(&command_line);
上述函數會解析dtb。
該函數最終將解析的根節點存放於of_allnodes中,在後續設備註冊時,會掃描該節點,和該節點匹配上才進行實際的註冊,這裏設備分爲兩種狀況,舉個例子若是U盤想要工做,通常嵌入式Soc會集成usb控制器,這裏usb控制器和u盤都會用到of_allnodes,所不一樣的是usb控制器的初始化會在start_kernel()中的do_basic_setup()中完成,而U盤的初始化則會延遲到U盤插入,熱插拔機制會啓動相應的支持。
在setup_arch配置完成以後還會有一個board的初始化,arch/arm/match-XXX/目錄下的一些平臺相關的代碼在這裏會被調用,好比該目錄下的init_irq、init_machine函數就會被調用。
在init_machine函數中會調用of_platform_populate()註冊平臺總線和平臺設備。對於像i2c、spi之類設備則會在early_platform_add_devices()完成註冊。
開發中的Linux內核源碼,git.kernel.org/cgit/linux/kernel/git/Torvalds/linux.git
上述的vmlinux是沒壓縮過的內核映像,真正使用時會將內核壓縮省以減少內核映像的大小和以及節約映像從Flash或磁盤拷貝到內存的時間。去了壓縮連接的過程,這一過程在ARM的嵌入式平臺和PC平臺均存在,可是由上面的基礎來看壓縮部分的應該不難了。X86部分的壓縮實現見arch/x86/boot/compressed/。
最後來點收尾的,你可能發如今1.1節中說起Linux內核升級的時候講到了make modules 和make modules_install,這裏一直未提到該內容,從該命令能夠看出來是和module有關係的,config時會選擇編譯成module仍是編譯進內核抑或不編譯,若是選擇編譯成module,這裏兩個命令就是用來處理那部分代碼的,簡單點說就是作以下的工做(注意在現有ext3文件系統上操做,系統啓動時只有根文件系統可有):
cp -f/opt/linux-3.10/modules.order /lib/modules/3.10.0/
cp -f/opt/linux-3.10/modules.builtin /lib/modules/3.10.0/
…
該文件夾下的modules.dep,描述模塊的依賴關係,當有熱插拔事件發生時,內核會將處理過程推到用戶態處理,主要關係udev實現機制,該機制會建立必要的設備節點和加載對應的驅動須要的模塊,模塊的依賴關係就在modules.dep裏描述了。這裏就能夠看出來爲何和make vmlinux分開處理了,由於一個編譯生成可執行文件,一個是在現有文件系統中插入一部分「module」。
devicetree函數的解析
在爲了讓上面setup_arch()函數所表示的啓動過程更具備真實性,這裏咱們舉個devicetree的例子,首先知道一般嵌入式下使用的處理器被稱爲SOC 而不是cpu,由於該芯片一般集成了許多控制器,因此假設咱們的cpu是cotex-A9的,而且假設其集成有apb總線,在ahb總線上掛有以太網控制器,apb總線掛有i2c控制器,在實際的電路板上還有一個i2c控制的4951的音頻芯片。
這裏講Soc具備的東西寫成.dtsi的文件,將板上其它的外設寫成.dts文件,這樣在.dts文件中include該文件就能夠了,這樣可具備良好的可移植性。
ABCD.dtsi文件,文件在內核中的位置,arch/arm/boot/dts文件下,
/{
compatible = 「ABCD,exp」;
cpus {
#address-cells=<1>;
#size-cells =<0>;
cpu0{
device_type=」cpu」;
compatible=「ABCD,cortex-a9」;
reg = <0>;
};
ahb@e0000000{
compatible=」simple-bus」;
mac0:ethernet@e000d0000{
compatible=」 ABCD,eth」;
#address-cells=<1>;
#size-cells=<0>;
reg =<0xe0000000 0x3000>;
};
};
};
dts文件爲:
Include ABCD.dtsi
/ {
mode = 「ABCD corp Board」;
compatible = 「ABCD,exp」;
chosen = {
bootargs = 「console=ttySA0init=/linuxrc root=…」
};
apb@d0000000{
i2c0:i2cd0004000{
ak4951:code@5{
compatible=」A,ak4951」;
reg =<0x12>;
};
};
};
好了,設備樹構建完畢了,這裏省去內存信息,中斷等不少其它內容,關於devicetree內容參看,http://www.devicetree.org/Main_Page,接着向下看,能夠說devicetree的三分之一的內容體如今這裏。
圖2.7.1 setup_arch()函數調用過程
Setup_arch函數裏調用的第一個函數,也就是這裏惟一出現等號的地方,這裏mdesc,這就是和devicetree相關的一個很是重要的東西,
DT_MACHINE_START(exp_DT, 「Aexp(Flattened Device Tree)」)
.restart_mode = ABCD _map_io,
.init_early= ABCD _init_early,
.init_irq=irqchip_init,
.init_machine=ABCD_init_machine,
.dt_compat= exp_dt_board_compat;
首先使用DT_MACHINE_START定義一個體繫結構相關的函數,後續的的一些初始化函數將調用這裏,這就有一個問題,Linux如何知道是這個呢,根據dt_compat,其指向一個compatible屬性的字段,結合上面dts文件的定義,定義以下的exp_dt_board_compat;
Static const char *const exp_dt_board_compat= {
「ABCD,exp」,
NULL,
}
直覺告訴咱們,這二者匹配上了,可是具體的匹配過程是什麼呢?耐心往下看,
ach/arm/include/asm/mach/arch.h
#defineDT_MACHINE_START(_name, _namestr) \
static conststruct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init")))= { \
.nr =~0, \
.name =_namestr,
#endif
能夠看到該宏定義的,將放在.arch.info.init段中,再來看setup_arch中得到的對應代碼,這裏咱們使用該宏定義一個和上述devicetree相關的結構體出來,
for_each_machine_desc(mdesc){
score= of_flat_dt_match(dt_root, mdesc->dt_compat);
if(score > 0 && score < mdesc_score) {
mdesc_best= mdesc;
mdesc_score= score;
}
}
一看到到for就該笑了,就是從上面那個段,遍歷一遍查看字段是否相等就能夠了,爲了驗證咱們的想法,向下看:
#define for_each_machine_desc(p) \
for(p = __arch_info_begin; p < __arch_info_end; p++)
這裏用了一個很是巧妙的方法,__arch_info_begin和__arch_info_end在lds腳本里定義的,在連接時肯定的,參看腳本arch/arm/kernel/vmlinux.lds.S的180行。
到此咱們得到了體系結構相關的初始化代碼,接着看of_get_flat_dt_root(),該函數得到設備樹的根節點,這很好辦,由於前面話了不少篇幅說的r2就是設備樹的地址,可是若是看代碼,可能會以爲很複雜,這是由於設備樹在編譯時會加上頭信息,此外還要進行align操做。
setup_machine_fdt裏其它函數的意義從字面上就能夠看出來,這裏就是針對該machine解析一些devicetree的字段,由於它們的優先級比較高,unflatten_device_tree()函數,中解析設備樹,而且前面提到的ofallnode的信息來自於此。那咱們看看那些優先級不是很高的設備樹字段是如何使用的,這也是個初始化的過程哦,看前面設備樹中的mac字段,do_basic_setup()中調用驅動初始化相關函數,mac字段是以太網信息的表示,因此這裏會進行以太網相關的初始化的工做了,整個devicetree對設備的處理都是按下面的方式處理的。
設備驅動的框架通常是,寫一個driver結構體,裏面包括了設備的操做方法集,而後註冊該結構,該結構調用driver的probe方法。
module_platform_driver(ABCD_drvier);
struct staticplatform_drver ABCD_drvier={
.probe = ABCD_drv_probe;
.remove =
.drver ={
.name = 「ABCD-eth」,
.own = THIS_MODULE,
.of_match_table=ABCD_eth_dt_ids,
},
};
static conststrut of_device_id ABCD_eth_dt_ids[]={
.compatible=」ABCD,eth」,
{ }
};
ABCD_probe(struct platform_device *pdev),該函數的參數中的pdev,該函數中有一個得到devicetree的方法,如pdev->dev.of_node,這是匹配上,那就得到上了該設備相關的信息,後面的過程相似之前的處理了,註冊向下進行了。
linux310啓動
由2.5.3知道了初始化主要在start_kernel中完成,
圖2.6.1 start_kernel初始化
上圖中能夠知道,內存子系統已經初始化完畢了,後面初始化其它子系統時就能夠直接申請內存了。
圖2.6.2 rest_init初始化流程
rest_init首先調用kernel_thread建立連個進程,Linux內核規定init進程的ID等於1,因此先建立kernel_init,可是進程號等於一的進程,可是ID號等於1的進程在結束時會建立內核守護進程,守護進程相關的初始化會由後面的kernel_thread(kthread…)建立,這裏使用了competition機制在kernel_init得到ID號1後,該進程處於等待狀態,等待kernel_thread(kthread…)初始化完畢。
接下來的do_basic_setup()中很是重要的兩個函數driver_init()和do_initcalls(),
前者完成總線等子系統的初始化,然後這則是根據以下initcall的優先級調用。
#define pure_initcall(fn) __define_initcall(fn,0)
#define core_initcall(fn) __define_initcall(fn,1)
#define core_initcall_sync(fn) __define_initcall(fn,1s)
#define postcore_initcall(fn) __define_initcall(fn,2)
#define postcore_initcall_sync(fn) __define_initcall(fn,2s)
#define arch_initcall(fn) __define_initcall(fn,3)
#define arch_initcall_sync(fn) __define_initcall(fn,3s)
#define subsys_initcall(fn) __define_initcall(fn,4)
#define subsys_initcall_sync(fn) __define_initcall(fn,4s)
#define fs_initcall(fn) __define_initcall(fn,5)
#define fs_initcall_sync(fn) __define_initcall(fn,5s)
#define rootfs_initcall(fn) __define_initcall(fn,rootfs)
#define device_initcall(fn) __define_initcall(fn,6)
#define device_initcall_sync(fn) __define_initcall(fn,6s)
#define late_initcall(fn) __define_initcall(fn,7)
#define late_initcall_sync(fn) __define_initcall(fn,7s)
根據initcall級別,調用相應的函數,以下:
後面調用2.4介紹的文件系統執行/sbin/init,該/init會執行/etc下的配置文件,內容在2.4節介紹過了,init最終啓動shell給用戶。
最後一行的init_idle_bootup_task(current);是建立ideal進程,能夠知道該函數的ID等於0。