在用戶態(tài)應(yīng)用程序處理的任務(wù)中,elf 加載運行是一個比較重要的步驟,下面就分析一下在 rt-smart 操作系統(tǒng)中,想要將一個應(yīng)用程序運行起來要經(jīng)過哪些步驟。
ELF 格式介紹
ELF 代表 Executable and Linkable Format。它是一種對可執(zhí)行文件、目標文件和庫使用的文件格式。它在 Linux 下成為標準格式已經(jīng)很長時間,ELF 一個特別的優(yōu)點在于,同一文件格式可以用于內(nèi)核支持的幾乎所有體系結(jié)構(gòu)上。
RT-SMART 同樣也使用 ELF 作為可執(zhí)行文件的格式,下面簡單介紹一下 ELF 文件格式。
ELF 文件布局和結(jié)構(gòu)
下圖為 ELF 文件的基本布局:
上圖展示了 elf 文件的重要組成部分:
elf 文件頭,除了用于標識ELF文件的幾個字節(jié)之外,ELF頭還包含了有關(guān)文件類型和大小的有關(guān)信息,
及文件加載后程序執(zhí)行的入口點信息。
程序頭表(program header table)向系統(tǒng)提供了可執(zhí)行文件的數(shù)據(jù)在進程虛擬地址空間中組織
方式的相關(guān)信息。它還表示了文件可能包含的段數(shù)目、段的位置和用途。
各個段保存了與文件相關(guān)的各種形式的數(shù)據(jù)。例如,符號表、實際的二進制碼、固定值(如字
符串)或程序使用的數(shù)值常數(shù)。
節(jié)頭表(section header table)包含了與各段相關(guān)的附加信息。
使用 readelf 工具可以讀取該類型文件中的各種數(shù)據(jù)結(jié)構(gòu)。
關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
想要理解應(yīng)用程序的加載運行過程,就必須要先了解 ELF 文件中的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),知道可以通過 ELF 文件獲取那些程序加載所必須的關(guān)鍵信息,例如文件類型、目標體系架構(gòu)、版本號、程序入口點以及程序運行所需要的數(shù)據(jù)段存儲在什么位置等等信息。這些信息都存放在 ELF 的相關(guān)數(shù)據(jù)結(jié)構(gòu)中,那么現(xiàn)在就先了解一下 ELF 文件的相關(guān)數(shù)據(jù)結(jié)構(gòu)吧。
下面是在 ELF 加載過程上下文數(shù)據(jù)結(jié)構(gòu),這個結(jié)構(gòu)中包括了 eheader、pheader 和 sheader 三個 elf 的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。
elf 頭表
1typedefstruct 2{ 3unsignedchare_ident[EI_NIDENT];/*前四個字節(jié)為0x7fELF,其他的字節(jié)位置都有特定的語義*/ 4Elf64_Halfe_type;/*用于區(qū)分ELF的文件類型,例如可重定位、可執(zhí)行、動態(tài)庫、coredump文件*/ 5Elf64_Halfe_machine;/*指定了文件所需的體系結(jié)構(gòu)*/ 6Elf64_Worde_version;/*保存了版本信息,用于區(qū)分不同的ELF變體,目前該規(guī)范只定義了版本1*/ 7Elf64_Addre_entry;/*程序入口點*/ 8Elf64_Offe_phoff;/*程序頭表在二進制文件中的偏移量*/ 9Elf64_Offe_shoff;/*節(jié)頭表所在的偏移量*/ 10Elf64_Worde_flags;/*特定于處理器的標志*/ 11Elf64_Halfe_ehsize;/*指定了ELF頭的長度,單位為字節(jié)*/ 12Elf64_Halfe_phentsize;/*指定了程序頭表中一項的長度,單位為字節(jié)(所有項的長度都相同)*/ 13Elf64_Halfe_phnum;/*指定了程序頭表中項的數(shù)目*/ 14Elf64_Halfe_shentsize;/*指定節(jié)頭表中一項的長度,單位為字節(jié)(所有項的長度都相同)*/ 15Elf64_Halfe_shnum;/*指定節(jié)頭表中項的數(shù)目*/ 16Elf64_Halfe_shstrndx;/*包含各節(jié)名稱的字符串表在節(jié)頭表中的索引位置*/ 17}Elf64_Ehdr;
程序頭表
1typedefstruct 2{ 3Elf64_Wordp_type;/*當前項描述的段的種類,例如可裝載段、動態(tài)鏈接、程序解釋等段類型*/ 4Elf64_Wordp_flags;/*保存了標志信息,定義了該段的訪問權(quán)限,RWX*/ 5Elf64_Offp_offset;/*給出了所描述段在文件中的偏移量(從二進制文件起始處開始計算,單位為字節(jié))*/ 6Elf64_Addrp_vaddr;/*給出了段的數(shù)據(jù)映射到虛擬地址空間中的位置(對于可裝載段類型)*/ 7Elf64_Addrp_paddr;/*只支持物理尋址,不支持虛擬尋址的系統(tǒng),將使用p_paddr保存信息*/ 8Elf64_Xwordp_filesz;/*指定了段在二進制文件中的長度*/ 9Elf64_Xwordp_memsz;/*制定了段在虛擬地址空間中的長度(單位為字節(jié)),與文件中物理的長度差值可通過階段數(shù)據(jù)或者填充0字節(jié)來補償*/ 10Elf64_Xwordp_align;/*指定了段在內(nèi)存和二進制文件中對其的方式(p_vaddr和p_offset地址必須是模p_align的,也就是p_align的倍數(shù)),例如p_align的值為0x1000=4096,這意味著段必須對其到4KB頁*/ 11}Elf64_Phdr;
節(jié)頭表
1typedefstruct 2{ 3Elf64_Wordsh_name;/*指定了節(jié)的名稱,其值不是字符串本身,而是字符串表的一個索引*/ 4Elf64_Wordsh_type;/*指定了節(jié)的類型,例如不可用、保存程序相關(guān)信息、符號表、包含字符串表的節(jié)、重定位信息、散列表、動態(tài)鏈接信息等類型*/ 5Elf64_Xwordsh_flags;/*節(jié)是否可寫(SHF_WRITE),是否將為其分配虛擬內(nèi)存(SHF_ALLOC),節(jié)是否包含可執(zhí)行的機器代碼(SHF_EXECINSTR)*/ 6Elf64_Addrsh_addr;/*指定節(jié)映射到虛擬地址空間中的位置*/ 7Elf64_Offsh_offset;/*指定了節(jié)在文件中的開始位置*/ 8Elf64_Xwordsh_size;/*指定了節(jié)的長度,單位為字節(jié)*/ 9Elf64_Wordsh_link;/*引用另一個節(jié)頭表項,可能根據(jù)節(jié)類型而進行不同的解釋*/ 10Elf64_Wordsh_info;/*與上一項聯(lián)用*/ 11Elf64_Xwordsh_addralign;/*指定了節(jié)數(shù)據(jù)在內(nèi)存中對齊的方式*/ 12Elf64_Xwordsh_entsize;/*指定了節(jié)中各數(shù)據(jù)項的長度,前提是這些數(shù)據(jù)項的長度都相同,例如字符串表*/ 13}Elf64_Shdr;
在 rt-smart 實際編碼實現(xiàn)的過程中,為了方便數(shù)據(jù)傳遞,設(shè)計了一個包含上述三種數(shù)據(jù)類型的結(jié)構(gòu),利用該數(shù)據(jù)結(jié)構(gòu)可以使加載過程實現(xiàn)更加簡潔易懂,如下所示:
1structelf_load_context 2{ 3intfd;/*應(yīng)用程序文件fd*/ 4intlen;/*用于臨時使用的len*/ 5uint8_t*load_addr;/*用于臨時使用的加載地址*/ 6structrt_lwp*lwp;/*進程句柄*/ 7structprocess_aux*aux;/*進程輔助信息句柄*/ 8rt_mmu_info*m_info;/*進程mmu信息*/ 9Elf_Ehdreheader;/*elf頭表*/ 10Elf_Phdrpheader;/*程序頭表*/ 11Elf_Shdrsheader;/*節(jié)頭表*/ 12structmap_rangeuser_area[2];/*在用戶空間需要映射的地址空間,0用于代碼段,1用于數(shù)據(jù)段*/ 13};
ELF 標準節(jié)
ELF 標準定義了若干固定名稱的節(jié)。這些用于執(zhí)行大多數(shù)目標文件所需的標準任務(wù)。所有名稱都從點開始,以便與用戶定義節(jié)或非標準節(jié)相區(qū)分,最重要的標準節(jié)如下所示:
有了以上基礎(chǔ)概念,就可以來探索真正的代碼實現(xiàn)了。
探索程序加載代碼實現(xiàn)
執(zhí)行一個新的應(yīng)用程序功能由 lwp_execve 函數(shù)來實現(xiàn),該函數(shù)會初始化好一個進程所需要的運行環(huán)境,然后在該環(huán)境中啟動第一個線程,也就是 main 線程。
暫且先不關(guān)注進程 PID 申請以及的 mmu 表初始化等準備工作,將注意力集中在 lwp_load 函數(shù)上。該函數(shù)將執(zhí)行如下操作:
打開 elf 文件,返回文件 fd
調(diào)用 load_elf 函數(shù)開始執(zhí)行應(yīng)用程序加載
在 load_elf 函數(shù)中,將執(zhí)行如下操作:
檢查 elf 頭,判斷其魔數(shù)、架構(gòu)類型、版本號等是否符合要求
判斷是靜態(tài)加載還是動態(tài)加載
檢查程序入口地址是否為有效的用戶態(tài)地址
遍歷讀取程序頭表以及程序復(fù)制信息,將其加載到進程的用戶空間里
遍歷讀取節(jié)頭表,根據(jù)節(jié)頭表中的信息,計算在用戶態(tài)需要需要分配多大的地址空間用于存放 text 段以及 data 段
根據(jù)上一步驟的計算,修改進程的映射表,真正為數(shù)據(jù)段分配用戶態(tài)地址空間
遍歷節(jié)頭表,程序運行所需要數(shù)據(jù)段加載到用戶地址空間中
通過上面的操作,ELF 文件中所有關(guān)于程序啟動運行所需的數(shù)據(jù)就都準備好了,接下來就可以在此基礎(chǔ)上啟動第一個線程,也就是 main 線程了。相關(guān)的代碼細節(jié)在這里就不做贅述了,源代碼中都添加了詳盡的注釋,可以自行查看。
總結(jié)
用戶態(tài)進程代碼量較大,同時由于復(fù)雜度過高也不容易理解,現(xiàn)在代碼經(jīng)過完善,復(fù)雜度降低以后,可讀性方面有了巨大提升。想要深入了解用戶態(tài)的相關(guān)實現(xiàn),還需要至少了解另外三個主題:
進程切換過程中底層架構(gòu)級別的匯編代碼
進程資源管理相關(guān)內(nèi)容,例如 pid、tid 的分配,用戶態(tài)內(nèi)存空間映射等
SMP 多核調(diào)度原理與實現(xiàn)
后續(xù)還會繼續(xù)分享上述內(nèi)容給大家。
-
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
6684瀏覽量
123140 -
應(yīng)用程序
+關(guān)注
關(guān)注
37文章
3237瀏覽量
57547 -
elf
+關(guān)注
關(guān)注
0文章
12瀏覽量
2166
發(fā)布評論請先 登錄
相關(guān)推薦
評論