今天是臘八節,說好的女票要給我做的臘八粥就這樣泡湯了,好傷心,好心酸呀,看來代碼寫久了真的是惹人煩滴,所以告誡各位技術男敲醒警鍾,不要想我看齊,不然就只能和代碼為伴了的~~話說沒了臘八粥但還是有代碼,還有各位讀者的支持呀,所以得繼續寫下去,靜下心來,完成Linux內核的學習,堅持,加油~
到目前為止,我們已經認識了Linux內核子系統,也探究了系統的初始化過程,並且深入探索了start_kernel()函數,同樣,了解內核映像的創建也是非常重要的,接下來將討論一下內核映像的編譯和鏈接過程,那么這些當然需要工具鏈了,工具鏈包含編譯程序、匯編程序、鏈接程序,是創建Linux內核映像的一組程序集合,下圖說明了工具鏈的鏈式關系:
ELF二進制目標文件
可執行ELF目標文件包括:ELF頭,程序頭表(用於加載的節),第1節,第2節。。。。節頭表(可選)
ELF頭文件
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT]; //標識該文件是否為ELF文件
Elf32_Half e_type; //指定目標文件類型,例如可執行文件,重定位文件,共享的目標文件
Elf32_Half e_machine; //被編譯文件所在系統的體系結構
Elf32_Word e_version; //目標文件的版本
Elf32_Addr e_entry; /* Entry point */ //程序的起始地址
Elf32_Off e_phoff; //保存程序頭表在文件中的偏移量
Elf32_Off e_shoff; //保存節頭表在文件中的偏移量
Elf32_Word e_flags; //保存於特定與處理器的標志
Elf32_Half e_ehsize; //字段保存ELF頭的大小
Elf32_Half e_phentsize; //保存程序頭表中的每一項的大小
Elf32_Half e_phnum; //程序頭中表項的個數
Elf32_Half e_shentsize; //節頭表中每一項的大小
Elf32_Half e_shnum; //保存節頭中項的數量,表明該文件中有多少節
Elf32_Half e_shstrndx; //保存節頭中節字符串的索引
} Elf32_Ehdr;
節頭表
typedef struct elf32_shdr {
Elf32_Word sh_name; //包含節名
Elf32_Word sh_type; //包含節的內容
Elf32_Word sh_flags; //各種屬性的內容
Elf32_Addr sh_addr; //節在內存映像中的地址
Elf32_Off sh_offset; //保存ELF文件中這一節中初始字節的偏移量
Elf32_Word sh_size; //包含節的大小
Elf32_Word sh_link; //表鏈接的索引
Elf32_Word sh_info; //包含附加信息
Elf32_Word sh_addralign; //包含地址對其的約束
Elf32_Word sh_entsize; //節中每項的大小
} Elf32_Shdr;
非可執行ELF文件節
節點 | 說明 |
.data | 已初始化的數據 |
bss | 為初始化的數據 |
.hash | 符號散列表 |
.init | 初始化代碼 |
.symtab | 符號表 |
.text | 可執行的指令 |
.plt | 過程鏈接表 |
.rodata | 只讀數據 |
dynamic | 動態鏈接信息 |
程序頭表
typedef struct elf64_phdr {
Elf64_Word p_type; //描述該段的類型
Elf64_Word p_flags; //以p_type而定
Elf64_Off p_offset; //<span style="font-family: Arial, Helvetica, sans-serif;">該段的開始相對於文件開始的偏移量</span>
Elf64_Addr p_vaddr; //段虛擬地址
Elf64_Addr p_paddr; //段的虛擬地址
Elf64_Xword p_filesz; //文件映像中該段的字節數
Elf64_Xword p_memsz; //內存映像中該段的字節數
Elf64_Xword p_align; //描述要對齊的段在內存中如何對齊,該值是2的整數次冪
} Elf64_Phdr;
通過這些信息,系統函數exec()和鏈接程序合作,為可執行程序在內存中創建進程映像,該過程如下:
- 將可執行文件的段加入內存
- 加載所有需要的共享庫
- 需要時重定向可執行文件及其共享對象
- 將控制權交給程序
那么內核是如何被編譯成二進制文件的呢,又是如何在執行前裝入內存。下面將開始介紹編譯內核源代碼。內存啟動始於執行arch/x86/boot/目錄中的實模式匯編代碼。查看arch/x86/kernel/setup_32.c文件可以看出保護模式的內核怎樣獲取實模式內核收集的信息。第一條信息來自於init/main.c中的代碼,深入挖掘init/calibrate.c可以對BogoMIPS校准理解得更清楚,而include/asm-your-arch/bugs.h則包含體系架構相關的檢查。
內核中的時間服務由駐留於arch/your-arch/kernel/中的體系架構相關的部分和實現於kernel/timer.c中的通用部分組成。從include/linux/time*.h頭文件中可以獲取相關的定義。
jiffies定義於linux/jiffies.h文件中。HZ的值與處理器相關,可以從include/asm-your-arch/ param.h找到,內存管理源代碼存放在頂層mm/目錄中。
Linux的官方源代碼發布網址是www.kernel.org。其源代碼目錄結構示意圖如下:
利用內核配置工具自動生成.config的內核配置文件,這是編譯的第一步,.config文件位於源代碼目錄下,其選項的位置根據它們在內核配置工具中的位置進行排序,我們來看看一個.config文件的節選:
1 #
2 # Automatically generated make config: don't edit
3 # 4 CONFIG_X86=y 5 CONFIG_MMU=y 6 CONFIG_UID16=y 7 CONFIG_GENERIC_ISA_DMA=y //這4行位於頂層菜單中 8 9 # 10 # Code maturity level options 11 # 12 CONFIG_EXPERIMENTAL=y 13 CONFIG_CLEAN_COMPILE= 14 CONFIG_STANDALONE=y 15 CONFIG_BROKEN_ON_SMP=y //這4行位於代碼成熟度選項菜單中 16 17 # 18 # General setup 19 # 20 CONFIG_SWAP=y 21 CONFIG_SYSVIPC=y 22 #CONFIG_POSIX_MQUEUE is not set 23 CONFIG_BSD_PROCESS_ACCT=y //這4行位於通用設置選項菜單中
最后來粗略的介紹一下Linux內核的Makefile文件,也只能簡單的介紹一下啦,這個可是重難點,這里我稍微說一下,以后會具體去學習。Linux內核是一種單體內核,但是通過動態加載模塊的方式,使它的開發非常靈活 方便。那么,它是如何編譯內核的呢?我們可以通過分析它的Makefile入手。以下是 一個簡單的hello內核模塊的Makefile.
ifneq ($(KERNELRELEASE),)
obj-m:=hello.o
else
KERNELDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
endif
首先,由於make 后面沒有目標,所以make會在Makefile中的第一個不是以.開頭的目標作為默認的目標執行。於是default成為make的目標。make會執行 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules shell是make內部的函數,make執行了兩次。
第一次執行時是讀hello模塊的源代碼所在目錄/home/s tudy/prog/mod/hello/下的Makefile。
第二次執行時是執行/usr/src/linux/下的Makefile時.
這其中很復雜,我也不知道怎么講了。關於make modules的更詳細的過程可以在scripts/Makefile.modpost文件的注釋 中找到。不過我找到了一個大牛寫的跟我一下學Makefile的博客,我把博客地址附在下面,供大家參考一下:http://blog.csdn.net/haoel/article/details/2886/
小結
本章探究了目標文件的編譯,鏈接過程,以及目標文件的結構,以便理解可執行代碼的最終形式,構建Linux內核涵蓋了內核編譯所需要的工具,最后還簡單的描述了Makefile,,這些都是難點,,得多加縮習啦,,盡管今天沒吃到臘八粥,但是轉轉鍋還是很給力的,吃到現在還不餓,是一個難忘的一天 ~~
版權所有,轉載請注明轉載地址:http://www.cnblogs.com/lihuidashen/p/4253752.html