0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)核編譯與啟動分析

汽車電子技術(shù) ? 來源:宅學(xué)部落 ? 作者:王利濤 ? 2023-02-17 09:43 ? 次閱讀

Linux環(huán)境下,我們想運(yùn)行一個應(yīng)用程序,在shell交互環(huán)境下直接敲命令就可以了,操作系統(tǒng)給程序提供了運(yùn)行環(huán)境和進(jìn)程管理。那Linux操作系統(tǒng)本身是如何運(yùn)行和啟動的呢?在分析之前,我們先做一個Linux內(nèi)核啟動的實(shí)驗(yàn):通過u-boot加載Linux內(nèi)核鏡像uImage到內(nèi)存不同地址,觀察Linux內(nèi)核啟動流程。

實(shí)驗(yàn)環(huán)境:

  • 硬件平臺:使用 QEMU 仿真ARM vexpress A9 開發(fā)板
  • RAM大小配置:512 MB
  • RAM內(nèi)存地址:0x60000000 ~ 0x7FFFFFFF

實(shí)驗(yàn)過程:

  • 編譯內(nèi)核鏡像,將uImage加載地址設(shè)置為0x60003000,編譯生成uImage
  • 將內(nèi)核加載到0x60003000地址,然后bootm 0x60003000
  • 將內(nèi)核加載到0x60004000地址 ,然后bootm 0x60004000

通過實(shí)驗(yàn)我們可以看到:雖然 uImage 被U-boot加載到了內(nèi)存 0x60003000 和 0x60004000 內(nèi)存不同地址,但是通過U-boot的bootm命令都可以正常引導(dǎo)和啟動運(yùn)行。bootm到底有什么魔法,即使我們把鏡像文件加載到了未指定的內(nèi)存地址,也能讓Linux神奇般地啟動起來呢?要想一探究竟,還得溯本求源:從Linux內(nèi)核的編譯鏈接說起。我們從編譯Linux內(nèi)核鏡像 uImage 的Log信息為切入點(diǎn)分析:

$ make uImage LOADADDR=0x60003000
  CC arch/arm/mm/mmu.o //上面省略的是編譯過程:將.c編譯為.o文件//前方高能預(yù)警
  LD      vmlinux
  SYSMAP  System.map
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  Kernel: arch/arm/boot/Image is ready
  LDS     arch/arm/boot/compressed/vmlinux.lds
  AS      arch/arm/boot/compressed/head.o
  GZIP    arch/arm/boot/compressed/piggy.gzip
  AS      arch/arm/boot/compressed/piggy.gzip.o
  CC      arch/arm/boot/compressed/misc.o
  CC      arch/arm/boot/compressed/decompress.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready
  Kernel: arch/arm/boot/Image is ready
  Kernel: arch/arm/boot/zImage is ready
  UIMAGE  arch/arm/boot/uImage
Image Name:   Linux-4.4.0+
Created:      Fri Apr 24 19:11:09 2020
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    3460776 Bytes = 3379.66 kB = 3.30 MB
Load Address: 60003000
Entry Point:  60003000
Image arch/arm/boot/uImage is ready

編譯Linux內(nèi)核鏡像整個過程比較漫長,大概需要5分鐘左右,并有大量的編譯信息打印出來。前期的打印信息比較簡單,就是分別使用編譯器和匯編器將對應(yīng)的.c文件、.S文件編譯成 .o 格式可重定位目標(biāo)文件。真正高能核心的過程在最后的鏈接和鏡像文件格式處理部分,編譯信息已經(jīng)截取如上。

圖片

結(jié)合編譯信息和上面的編譯流程圖我們可以看到,編譯器將所有的源文件編譯成對應(yīng)的目標(biāo)文件后,接下來就是鏈接過程:將所有的目標(biāo)文件鏈接成ELF格式的可執(zhí)行文件:vmlinux。ELF文件格式是Linux環(huán)境下的可執(zhí)行文件格式,無論是 gcc 還是 arm-linux-gcc 編譯器,生成的都是ELF這種格式的文件。在Linux環(huán)境下,加載器根據(jù)ELF文件里的地址信息,就可以把它加載到內(nèi)存指定的地址運(yùn)行,但是系統(tǒng)啟動過程中并沒有ELF文件的執(zhí)行環(huán)境,需要將ELF文件轉(zhuǎn)換為二進(jìn)制純指令文件。編譯器接著會調(diào)用objdump命令刪除不必要的section,只保留代碼段、數(shù)據(jù)段等必要的section,將ELF格式的vmlinux文件轉(zhuǎn)換為原始的二進(jìn)制內(nèi)核鏡像Image。Image可以在裸機(jī)環(huán)境下運(yùn)行,體積也比較大,我們可以使用gzip工具對其進(jìn)行壓縮,生成piggz.gzip壓縮的二進(jìn)制內(nèi)核鏡像。這樣做的好處是可提高程序的啟動速度:因?yàn)閮?nèi)核加載運(yùn)行時,從Flash 上讀取鏡像的速度是很慢的,我們通過先壓縮,加載到內(nèi)存后再解壓這種操作,不僅可以節(jié)省Flash存儲空間(尤其是Nor Flash還是很貴的),還可以節(jié)省了鏡像的加載時間。

因?yàn)閜iggz.gzip是壓縮文件無法運(yùn)行,所以我們還需要給它鏈接上一段解壓縮代碼。鏈接器只能處理ELF格式的目標(biāo)文件,因此在鏈接之前,要先將壓縮文件piggz.gzip轉(zhuǎn)換為可重定位的目標(biāo)文件:piggy.gzip.o。在ARM平臺下,解壓縮代碼是在arch/arm/boot/compressed/目錄下面的head.o、misc.o、 decompress.o,這部分使用 -fpic 參數(shù)編譯生成的指令是與位置無關(guān)的,放到哪里都可以執(zhí)行,它們通過鏈接器與piggy.gzip.o一起組裝成新的ELF文件vmlinux,然后再使用objcopy工具轉(zhuǎn)換為純二進(jìn)制鏡像zImage,就可以直接燒寫到Nor或nand flash上,隨系統(tǒng)啟動后加載到內(nèi)存運(yùn)行了。

不同的嵌入式系統(tǒng)平臺可能會使用不同的BootLoader來加載Linux內(nèi)核鏡像的運(yùn)行,常見的BootLoader有U-boot、vivi、g-bios等。使用U-boot的嵌入式平臺通常會對zImage進(jìn)一步轉(zhuǎn)換,給它添加一個64字節(jié)的數(shù)據(jù)頭,用來記錄鏡像文件的加載地址、入口地址、文件大小、CPU架構(gòu)等信息。我們可以使用U-boot提供的mkimage工具將zImage鏡像轉(zhuǎn)換為uImage:

$ mkimageA arm  -O linuxT kernelC nonea 0x60003000e 0x60003000  -d zImage    uImage

mkimage工具常見的參數(shù)說明如下:

  • -A:指定CPU架構(gòu)類型
  • -O:指定操作系統(tǒng)類型
  • -T:指定image類型
  • -C:采用的壓縮方式:none、gzip、bzip2等
  • -a:內(nèi)核加載地址
  • -e:內(nèi)核鏡像入口地址

走到這一步,U-boot可以引導(dǎo)的uImage內(nèi)核鏡像生成,這個Linux內(nèi)核鏡像編譯就完美結(jié)束了。接下來我們繼續(xù)分析U-boot是如何加載uImage運(yùn)行的:

圖片

U-boot加載的 dtb 文件和 bootargs 這里暫不考慮,我們重點(diǎn)關(guān)注uImage:當(dāng)uImage被加載到內(nèi)存不同的位置時,為什么都可以正常啟動。我們先考慮上面的第一種情況,當(dāng)加載到內(nèi)存中的地址等于編譯時指定的地址時:

圖片

U-boot提供的bootm機(jī)制用來啟動內(nèi)核的運(yùn)行。bootm會解析uImage文件64字節(jié)的數(shù)據(jù)頭,解析出指定的加載地址,并跟自己的參數(shù)進(jìn)行對比:若發(fā)現(xiàn)bootm參數(shù)地址和編譯時-a指定的加載地址0x60003000相同,就會直接跳過數(shù)據(jù)頭,跳到zImage的入口地址0x60003040執(zhí)行。

圖片

如果bootm發(fā)現(xiàn)自己的參數(shù)地址跟-a指定的加載地址0x60003000不同時,它會將去掉64個字節(jié)數(shù)據(jù)頭的內(nèi)核鏡像zImage復(fù)制到編譯時 -a 指定的加載地址處,然后再跳到該地址處執(zhí)行。如上圖所示,zImage鏡像被加載到了編譯時指定的0x60003000地址處,然后跳過來,就可以直接執(zhí)行zImage了。

圖片

zImage是一個壓縮文件,在運(yùn)行之前要先解出真正要執(zhí)行的內(nèi)核鏡像Image,然后才能跳到內(nèi)核鏡像真正的入口處去啟動Linux內(nèi)核。解壓縮代碼head.o、decompress.o是一段與位置無關(guān)的代碼,放到內(nèi)存的任何位置都可以運(yùn)行。大家有興趣可以做一個實(shí)驗(yàn),使用U-boot的bootz命令直接引導(dǎo)內(nèi)核鏡像zImage運(yùn)行:將zImage加載到內(nèi)存的不同地址,你會發(fā)現(xiàn)zImage都可以正常啟動。

圖片

解壓縮代碼的主要作用就是將從zImage文件出解壓出真正的內(nèi)核鏡像Image,并將其重定位到Image內(nèi)核編譯時指定的鏈接地址0x80008000上。Linux運(yùn)行使用的是虛擬地址,需要CPU硬件管理單元MMU的支持,MMU會將虛擬地址轉(zhuǎn)換為對應(yīng)的物理地址。在ARM vexpress平臺上,內(nèi)核的鏈接地址0x80008000會映射到物理內(nèi)存0x60008000的地方。zImage的解壓縮代碼會將Image解壓到0x60008000處,然后跳過去就可以直接啟動Linux內(nèi)核了。

圖片

在zImage運(yùn)行解壓縮代碼的過程中會遇到這么一種情況:zImage自身剛好占據(jù)了0x60008000這片地址空間,那么當(dāng)zImage的重定位代碼將解壓出來的Image拷貝到指定的0x60008000處時,可能就會沖掉自身正在運(yùn)行的代碼。為了避免這種情況發(fā)生,zImage會將這部分重定位拷貝到一個安全的地方,比如Image的后面,然后再跳到這片重定位代碼處執(zhí)行,這樣就可以將Image鏡像安全地拷貝到0x60008000地址上了。

拷貝成功后,就可以直接跳到 0x60008000 地址去運(yùn)行Linux內(nèi)核真正的代碼了。因?yàn)镮mage鏡像鏈接時使用的是虛擬地址,所以在運(yùn)行Linux內(nèi)核的C語言函數(shù)之前,首先會有一段匯編代碼用來初始化堆棧環(huán)境,使能MMU。代碼跟蹤就不具體分析了,有興趣大家可以去看視頻教程:《C語言嵌入式Linux高級編程》第3期:程序的編譯、鏈接和運(yùn)行,或者參考下面的提示自行分析:

  • 運(yùn)行入口:arch/arm/kernel/head.S
  • 使能MMU:__create_page_tables
  • 跳入C語言函數(shù):__mmap_switched/start_kernel
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11329

    瀏覽量

    209969
  • 應(yīng)用程序
    +關(guān)注

    關(guān)注

    37

    文章

    3285

    瀏覽量

    57785
  • Shell
    +關(guān)注

    關(guān)注

    1

    文章

    366

    瀏覽量

    23428
收藏 人收藏

    評論

    相關(guān)推薦

    Linux編譯驅(qū)動、內(nèi)核及應(yīng)用程序分析

    作為一名嵌入式Linux新手,在學(xué)習(xí)的過程中會遇到很多問題。寫了一個驅(qū)動程序怎么編譯?怎么加載進(jìn)內(nèi)核?
    的頭像 發(fā)表于 01-17 13:46 ?6672次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>編譯</b>驅(qū)動、<b class='flag-5'>內(nèi)核</b>及應(yīng)用程序<b class='flag-5'>分析</b>

    Linux內(nèi)核編譯主要過程

    Linux內(nèi)核編譯主要過程: 配置、編譯、安裝 。
    發(fā)表于 08-08 16:02 ?747次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>的<b class='flag-5'>編譯</b>主要過程

    linux內(nèi)核編譯

    Linux內(nèi)核編譯與安裝1從www.kernel.org 下載內(nèi)核linux-2.6.29.1.tar.bz2包解壓。并執(zhí)行清理命令mak
    發(fā)表于 10-26 14:14

    Linux內(nèi)核編譯啟動的相關(guān)資料分享

    Linux環(huán)境下,我們想運(yùn)行一個應(yīng)用程序,在shell交互環(huán)境下直接敲命令就可以了,操作系統(tǒng)給程序提供了運(yùn)行環(huán)境和進(jìn)程管理。那Linux操作系統(tǒng)本身是如何運(yùn)行和啟動的呢?在分析之前,
    發(fā)表于 12-20 06:28

    交叉編譯linux內(nèi)核(raspberry_3.6.y)

    一步一步教你交叉編譯linux內(nèi)核,RPI的內(nèi)核編譯教程,小眾的東西了
    發(fā)表于 11-03 17:58 ?0次下載

    linux內(nèi)核啟動內(nèi)核解壓過程分析

    linux啟動內(nèi)核解壓過程分析,一份不錯的文檔,深入了解內(nèi)核必備
    發(fā)表于 03-09 13:39 ?1次下載

    Linux內(nèi)核編譯詳談

    Linux內(nèi)核編譯詳談
    發(fā)表于 10-30 09:51 ?7次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>編譯</b>詳談

    基于Arm的Linux內(nèi)核編譯指導(dǎo)

    基于Arm的Linux內(nèi)核編譯指導(dǎo)
    發(fā)表于 10-30 10:13 ?15次下載
    基于Arm的<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>編譯</b>指導(dǎo)

    Linux內(nèi)核配置編譯分析的設(shè)計(jì)方案

    Linux內(nèi)核配置編譯分析的設(shè)計(jì)方案
    發(fā)表于 07-08 16:53 ?18次下載
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>配置<b class='flag-5'>編譯</b><b class='flag-5'>分析</b>的設(shè)計(jì)方案

    如何才能編譯Linux內(nèi)核

    內(nèi)核的bug ,并增加了許多新的特性。如果用戶想要使用這些新特性, 或想根據(jù)自己的系統(tǒng)度身定制一個更高效, 更穩(wěn)定的內(nèi)核, 就需要重新編譯內(nèi)核。本文將以RedHat
    發(fā)表于 11-04 18:04 ?8次下載

    動手編譯Linux內(nèi)核的教程免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是動手編譯Linux內(nèi)核的教程免費(fèi)下載。
    發(fā)表于 11-26 17:01 ?14次下載

    Linux內(nèi)核編譯與運(yùn)行

    本文檔的主要內(nèi)容詳細(xì)介紹的是Linux內(nèi)核編譯與運(yùn)行免費(fèi)下載。
    發(fā)表于 03-25 13:48 ?16次下載

    嵌入式Linux內(nèi)核編譯

    實(shí)驗(yàn)環(huán)境VMware Workstation PlayerUbuntu16.04kernel-3.2.tar.bz2Linux內(nèi)核編譯在ubuntu上編譯嵌入式
    發(fā)表于 11-01 17:07 ?19次下載
    嵌入式<b class='flag-5'>Linux</b>的<b class='flag-5'>內(nèi)核</b><b class='flag-5'>編譯</b>

    Linux內(nèi)核編譯和運(yùn)行

    想讓Linux內(nèi)核代碼跑起來,得先搭建編譯和運(yùn)行代碼的環(huán)境。
    發(fā)表于 06-23 11:56 ?1582次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>的<b class='flag-5'>編譯</b>和運(yùn)行

    Linux內(nèi)核編譯腳本

    獲得編譯命令及選項(xiàng) 編譯linux時,默認(rèn)不會顯示編譯的命令,如果你要獲得編譯命令及其選項(xiàng),可以在make命令后面加上宏定義: make V
    的頭像 發(fā)表于 09-27 11:52 ?675次閱讀