
摘 要
關(guān)鍵詞:LINUX;預(yù)處理;編譯;鏈接;進(jìn)程管理;存儲管理;IO管理
本文的主要內(nèi)容是介紹了在linux環(huán)境下hello程序從預(yù)處理到編譯再到鏈接,最后執(zhí)行的全過程以及進(jìn)程管理,存儲管理及IO管理的實現(xiàn)方式。本文結(jié)合hello程序的生成詳細(xì)地講述了預(yù)處理、編譯、匯編、進(jìn)程管理、存儲管理、IO管理的概念、作用、命令等。本文的目的是幫助程序員了解在C語言的“外衣”下,程序是如何從產(chǎn)生、預(yù)處理、編譯、匯編,到最后被執(zhí)行的。對于深入了解操作系統(tǒng)和計算機(jī)編譯原理具有重要意義。
第1章 概述
1.1 Hello簡介
根據(jù)Hello的自白,利用計算機(jī)系統(tǒng)的術(shù)語,簡述Hello的P2P,020的整個過程。
P2P: Hello.c經(jīng)過cpp的預(yù)處理,ccl的編譯、as的匯編、ld的鏈接最終成為可執(zhí)行目標(biāo)程序Hello,在shell中鍵入啟動命令后,shell通過fork產(chǎn)生子進(jìn)程, hello便從program變成了process
020: shell通過execve映射虛擬內(nèi)存,進(jìn)入程序入口后載入物理內(nèi)存,然后進(jìn)入 main函數(shù)執(zhí)行目標(biāo)代碼,CPU為運行的hello分配時間片執(zhí)行邏輯控制流,當(dāng)結(jié)束后,shell父進(jìn)程負(fù)責(zé)回收hello進(jìn)程
1.2 環(huán)境與工具
列出你為編寫本論文,折騰Hello的整個過程中,使用的工具
軟件環(huán)境:Visual studio Community2017, Windows10 64位, Vmware 14;Ubuntu 16.04 LTS 64位;
硬件環(huán)境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
開發(fā)與調(diào)試工具:vim,gcc,as,ld,edb,readelf,HexEdit
GDB/OBJDUMP;DDD/EDB等
1.3 中間結(jié)果
列出你為編寫本論文,生成的中間結(jié)果文件的名字,文件的作用等。
| 文件名稱 | 文件屬性 |
|---|---|
| hello.i | 預(yù)處理得到的文本文件 |
| hello.s | 編譯后的匯編文件 |
| hello.o | 匯編后的可重定位目標(biāo)執(zhí)行 |
| hello | 鏈接之后的可執(zhí)行目標(biāo)文件 |
| hello.objdmp | hello.o的反匯編代碼 |
| hello.elf | hello.o的ELF格式 |
| hello1.elf | hello的 ELF格式 |
| hello.objdmp | hello的反匯編代碼 |
| hello.txt | 分析重定位過程時的可執(zhí)行文件hello反匯編代碼 |
1.4 本章小結(jié)
本章介紹根據(jù)Hello的自白,利用計算機(jī)系統(tǒng)的術(shù)語,簡述Hello的P2P,020的整個過程。列出使用的軟硬件環(huán)境和開發(fā)與調(diào)試工具。列出了生成的中間結(jié)果文件的名字,文件的作用等
第2章 預(yù)處理
2.1 預(yù)處理的概念與作用
2.11 概念:預(yù)處理一般是指在程序源代碼被翻譯為目標(biāo)代碼的過程中,生成二進(jìn)制代碼之前的過程。由預(yù)處理器對程序源代碼文本進(jìn)行處理,把源代碼分割或處理成為特定的單位,得到的結(jié)果再由編譯器核心進(jìn)一步編譯。這個過程并不對程序的源代碼進(jìn)行解析。
2.12 作用:
1: 宏定義。宏定義是用一個標(biāo)識符來表示一個字符串,這個字符串可以是常量、變量或表達(dá)式。在宏調(diào)用中將用該字符串代換宏名。
2:文件包含。文件包含是預(yù)處理的一個重要功能,它可用來把多個源文件連接成一個源文件進(jìn)行編譯,結(jié)果將生成一個目標(biāo)文件。
3:條件編譯。條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標(biāo)程序較短,從而減少了內(nèi)存的開銷并提高了程序的效率。
2.2 在Ubuntu下預(yù)處理的命令
預(yù)處理命令:
gcc –E hello.c > hello.i

圖2.2 在Ubuntu下預(yù)處理的命令
2.3 Hello的預(yù)處理結(jié)果解析

圖2.3 hello.i文件
用文本編輯器打開hello.i,main函數(shù)的預(yù)處理解析結(jié)果如上圖。
在main函數(shù)前出現(xiàn)的是stdio.h unistd.h stdlib.h頭文件。
.i程序中是沒有#define的,并使用了大量的#ifdef #ifndef的語句。
預(yù)處理指令會對條件值進(jìn)行判斷來決定是否執(zhí)行包含其中的邏輯。
2.4 本章小結(jié)
本章介紹了預(yù)處理的概念與作用、命令,并展示了Hello的預(yù)處理結(jié)果解析。
第3章 編譯
3.1 編譯的概念與作用
- 編譯的概念:利用編譯程序從源語言編寫的源程序產(chǎn)生目標(biāo)程序的過程,用編譯程序產(chǎn)生目標(biāo)程序。 編譯程序把一個源程序翻譯成目標(biāo)程序的工作過程分為五個階段:詞法分析;語法分析;語義檢查和中間代碼生成;代碼優(yōu)化;目標(biāo)代碼生成。
- 編譯的作用:把高級語言變成計算機(jī)可以識別的2進(jìn)制語言,詞法分析、語法分析、語義檢查和中間代碼生成、代碼優(yōu)化、目標(biāo)代碼生成。
3.2 在Ubuntu下編譯的命令
gcc -S hello.i -o hello.s
圖3.2 在Ubuntu下編譯的命令
3.3 Hello的編譯結(jié)果解析
在linux用文本編輯器打開hello.s查看編譯結(jié)果
| 數(shù)據(jù)段 | 作用 |
|---|---|
| .text | 已編譯程序的機(jī)器代碼 |
| .rodata | 只讀數(shù)據(jù) |
| .data | 已初始化的全局C變量 |
| .bss | 未初始化和初始化為0的全局C變量。在目標(biāo)文件中這個節(jié)不占據(jù)實際的空間,僅僅是一個占位符 |
| .symtab | 一個符號表,它存放在程序中定義和引用的函數(shù)和全局變量的信息 |
| .rel.text | 一個.text節(jié)中位置的列表 |
| .rel.data | 被模塊引用或定義的任何全局變量的重定位信息 |
| .debug | 一個調(diào)試符號表。只有以-g選項調(diào)用編譯驅(qū)動程序才會得到這張表 |
| .line | 原始C源程序中的行號和.text節(jié)中機(jī)器指令之間的映射。只有以-g選項調(diào)用編譯驅(qū)動程序時才會得到這張表 |
| .strtab | 一個字符串表,內(nèi)容包括.symtab和.debug節(jié)中的符號表,以及節(jié)頭部中的節(jié)名字。 |
字符串表是以null結(jié)尾的字符串序列
3.31 數(shù)據(jù)
(1)字符串:
圖3.311 字符串 <div>
(2)整數(shù) sleepsecs
圖3.312 整數(shù) sleepsecs
3.32 賦值
(1) 全局變量sleepsecs =2
圖3.321
(2) 局部變量i =0
圖3.322 局部變量i =0
3.33 類型轉(zhuǎn)換
隱式類型轉(zhuǎn)換的是:int sleepsecs=2.5,將浮點數(shù)類型的2.5轉(zhuǎn)換為int類型
3.34 算術(shù)操作
圖3.340 算術(shù)操作符號
(1) 相加操作
addq $16, %rax
addq $8, %rax

(2) 相減操作
subq $32, %rsp

3.35 控制轉(zhuǎn)移
圖3.340 指令助記符
(1)比較i<10是否成立,若成立繼續(xù)循環(huán),否則退出循環(huán)

圖3.341 i<10對應(yīng)的匯編代碼
3.36 函數(shù)操作
a) int main(int argc, char *argv[])
(1)參數(shù)傳遞:從內(nèi)核中獲取命令行參數(shù)和環(huán)境變量地址
(2)函數(shù)調(diào)用:內(nèi)核執(zhí)行程序時調(diào)用特殊的啟動例程,執(zhí)行main函數(shù)
(3)函數(shù)返回:當(dāng)命令行參數(shù)數(shù)量不為3時輸出提示信息并調(diào)用exit(1)退出main函數(shù);當(dāng)命令行參數(shù)數(shù)量為3執(zhí)行循環(huán)和getchar函數(shù)后return 0的方式退出函數(shù)。
argc: 傳給main()的命令行參數(shù)個數(shù)
argv: 命令行參數(shù)字符型指針數(shù)組的首地址
b) exit()
(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1
c) printf()

圖3.363 printf匯編代碼
(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1
d) sleep()

圖3.364 sleep匯編代碼
(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1
e) getchar()

圖3.365 getchar匯編代碼
(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1
3.37關(guān)系操作
(1)argc!=3

圖3.371 !=匯編代碼
(2)i<10

圖3.371 <匯編代碼
3.4 本章小結(jié)
本章介紹了編譯的概念與作用,展示了編譯命令的使用,對編譯結(jié)果解析,并說明編譯器處理C語言的各個數(shù)據(jù)類型以及各類操作的過程。
第4章 匯編
4.1 匯編的概念與作用
- 概念:把匯編語言翻譯成機(jī)器語言的過程稱為匯編。在匯編語言中,用助記符代替操作碼,用地址符號或標(biāo)號代替地址碼。通過用符號代替機(jī)器語言的二進(jìn)制碼,可以把機(jī)器語言變成匯編語言。
- 作用:將匯編語言翻譯成機(jī)器語言。
編譯 VS 匯編
編譯:將高級語言程序變成計算機(jī)能識別的二進(jìn)制語言
匯編:將匯編語言翻譯成機(jī)器語言
4.2 在Ubuntu下匯編的命令
匯編的命令:as hello.s -o hello.o
圖4.2 在Ubuntu下匯編的命令
4.3 可重定位目標(biāo)elf格式
分析hello.o的ELF格式,用readelf等列出其各節(jié)的基本信息,特別是重定位項目分析。
(1) ELF頭
圖4.311 ELF頭
ELF頭包括一個16字節(jié)的序列、ELF頭的大小、目標(biāo)文件的類型(如可重定位、可執(zhí)行或共享的)、機(jī)器類型(如x86-64)、
節(jié)頭部表(section header table)的文件偏移,以及節(jié)頭部表中條目的大小和數(shù)量。
其結(jié)構(gòu)體表示:
define EI_NIDENT 16
typedef struct{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
}Elf32_Ehdr;
圖4.312 數(shù)據(jù)格式
(2) 節(jié)頭部表:文件中出現(xiàn)的各個節(jié)的語義,包括節(jié)的類型、位置和大小
圖4.32 節(jié)頭部表
根據(jù)節(jié)頭部表可知,當(dāng)號=1,符號在.text;當(dāng)號=3,符號在.data,以此類推。
三個特殊偽節(jié):
ABS:不該被重定位的符號,如main()函數(shù)。
UND:其它文件中定義,本文件中引用的符號,如swap()函數(shù)。
COM:還未分配位置的未初始化數(shù)據(jù)目標(biāo),如buf2,它最終放在.bss。
(3) 重定位節(jié)
(a)普通重定位由以下數(shù)據(jù)結(jié)構(gòu)定義:
typedef struct
{
Elf32_Addr r_offset; //指定需要重定位的項的位置
Elf32_Word r_info; //提供了符號表中的一個位置,包括重定位類型信息。
r_info == int symbol:24,type:8;
} Elf32_Rel;
(b)在ELF定義了32種不同的重定位類型,其中最基本的兩種是:
R X86_ 64 PC32。 重定位一個使用32位PC相對地址的引用。一個PC相對地址就是距程序計數(shù)器(PC)的當(dāng)前運行時值的偏移量。當(dāng)CPU執(zhí)行一條使用PC相對尋址的指令時,它就將在指令中編碼的32位值加上PC的當(dāng)前運行時值,得到有效地址(如call指令的目標(biāo)),PC值通常是下一條指令在內(nèi)存中的地址。
R X86_ 64 _32。 重定位一個使用32位絕對地址的引用。通過絕對尋址,CPU直接使用在指令中編碼的32位值作為有效地址,不需要進(jìn)一步修改。
(c)代碼重定位條目放在.rel.text中。已經(jīng)初始化數(shù)據(jù)的重定位條目放在.rel.data中。
main.c源文件引用了一個全局sleepsecs符號。
sleepsecs的重定位類型為相對重定位
并且由圖4.33(1)可以得到:sleepsecs的r_offset : 000000000060重定位的字節(jié)處, 由圖4.33(2)可以得到:sleepsecs的大小為4個字節(jié)
計算sleepsecs的重定位后的地址:Result = S-P+A
A代表加數(shù)值,S是符號表中保存的符號的值,P代表重定位的位置偏移量
圖4.331重定位節(jié)
圖4.332
(4) 符號表:存放著程序中定義和引用函數(shù)和全局變量的信息,不包含局部變量的條目
圖4.34 符號表
Value:在對應(yīng)節(jié)的偏移。
Size:目標(biāo)大小。
Type:是數(shù)據(jù)或函數(shù)。
Bind:本地或全局。
Vis:預(yù)留。
Ndx:符號所在的節(jié),其實是節(jié)頭部表中條目的索引。
Name:符號名,為空的為鏈接器內(nèi)部使用的本地符號,可以忽略。
4.4 Hello.o的結(jié)果解析
用命令行得到,比較hello.objdump與hello.o,進(jìn)行對照分析

圖4.40 命令行
(1) hello.objdump記錄了文件格式和.text代碼段:
而hello.s中除了記錄了文件格式和.text代碼段還包括.type .size .align以及.rodata
圖4.41 hello.objdump與hello.s文件內(nèi)容對比
(2) 分支轉(zhuǎn)移:
hello.objdump跳轉(zhuǎn)中地址為已確定的實際指令地址;
hello.s跳轉(zhuǎn)中地址為助記符如.L2,通過使用例如.L2等的助記符進(jìn)行跳轉(zhuǎn)。
圖4.42 hello.objdump與hello.s分支轉(zhuǎn)移對比
(3)函數(shù)調(diào)用
在.s文件中,call的地址是函數(shù)名稱,如puts@PLT,
而在反匯編程序中,call的目標(biāo)地址是指令,如callq 21 <main+0x21>。因為hello.c中調(diào)用的函數(shù)都是共享庫中的函數(shù),共享庫函數(shù)調(diào)用需要通過鏈接時重定位才能確定地址
圖4.43 hello.objdump與hello.s函數(shù)puts調(diào)用對比
(4)全局變量訪問
hello.objdump使用0+%rip訪問全局變量sleepsecs,如lea 0x0(%rip),%rdi。hello.s使用段名稱+%rip訪問全局變量sleepsecs,如leaq .LC0(%rip), %rdi
圖4.44 hello.objdump與hello.s全局變量sleepsecs訪問對比
4.5 本章小結(jié)
本章介紹了匯編的概念與作用,在linux下進(jìn)行匯編的指令,可重定位目標(biāo)文件elf的格式,將hello.o的結(jié)果解析與hello.s進(jìn)行對照分析,分析了機(jī)器語言的構(gòu)成以及與匯編語言的映射關(guān)系。
第5章 鏈接
5.1 鏈接的概念與作用
鏈接的概念:Linux 鏈接分兩種,一種被稱為硬鏈接(Hard Link),另一種被稱為符號鏈接(Symbolic Link)。默認(rèn)情況下,ln 命令產(chǎn)生硬鏈接。
(1)硬連接指通過索引節(jié)點來進(jìn)行連接。在 Linux 的文件系統(tǒng)中,保存在磁盤分區(qū)中的文件不管是什么類型都給它分配一個編號,稱為索引節(jié)點號(Inode Index)。在 Linux 中,多個文件名指向同一索引節(jié)點是存在的。
(2)軟連接。軟鏈接文件是一個特殊的文件。在符號連接中,文件實際上是一個文本文件,其中包含的有另一文件的位置信息。
作用:鏈接操作給系統(tǒng)中已有的某個文件指定另外一個可用于訪問它的名稱。我們可以為這個新的文件名指定不同的訪問權(quán)限。鏈用戶可以利用鏈接直接進(jìn)入被鏈接的目錄。即使刪除這個鏈接,也不會破壞原來的目錄。硬連接的作用是允許一個文件擁有多個有效路徑名,用戶就可以建立硬連接到重要文件,以防止“誤刪”的功能。
5.2 在Ubuntu下鏈接的命令
使用ld的鏈接命令,應(yīng)截圖,展示匯編過程! 注意不只連接hello.o文件
鏈接的命令:ld -o OUTPUT /lib/crt0.o hello.o –lc
鏈接的命令行:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
圖5.2 在Ubuntu下鏈接的命令
5.3 可執(zhí)行目標(biāo)文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用命令行readelf -a hello > hello1.elf生成hello1.elf文件
節(jié)頭表中包含了各段的起始地址,大小等信息。
圖5.31 節(jié)頭表
圖5.32 節(jié)頭表
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進(jìn)程的虛擬地址空間各段信息,并與5.3對照分析說明。
(1) 分析程序頭部表。
PHDR:程序頭表
INTERP:程序執(zhí)行前需要調(diào)用的解釋器
LOAD:程序目標(biāo)代碼和常量信息
DYNAMIC:動態(tài)鏈接器所使用的信息
NOTE::輔助信息
GNU_EH_FRAME:保存異常信息
GNU_STACK:使用系統(tǒng)棧所需要的權(quán)限信息
GNU_RELRO:保存在重定位之后只讀信息的位置
VirtAddr:本段首字節(jié)的虛擬地址
PhysAddr指出本段首字節(jié)的物理地址
pFileSiz指出本段在文件中所占的字節(jié)數(shù),可以為0
MemSiz指出本段在存儲器中所占字節(jié)數(shù),可以為0
Flags指出存取權(quán)限,Align指出對齊方式
圖5.41 程序頭部表
(2) 在edb查看hello的虛擬地址空間的各段信息
圖5.42 hello的虛擬地址空間
(3) 程序頭與Datadump的映射關(guān)系:例如PHDR對應(yīng)的虛擬內(nèi)存地址是0x400000—— 0x4001c0
圖5.43 程序頭與Datadump的映射關(guān)系
5.5 鏈接的重定位過程分析
通過命令行objdump –d –r hello > hello.txt得到反匯編文件hello.txt。
(1) hello的反匯編結(jié)果與hello.o的反匯編結(jié)果相比,hello.txt多了以下節(jié)頭表:
_init 程序初始化代碼
gmon_start call_gmon_start函數(shù)初始化
gmon profiling system,程序通過gprof可以輸出函數(shù)調(diào)用等信息
_dl_relocate_static_pie 靜態(tài)庫鏈接
.plt 動態(tài)鏈接-過程鏈接表
Puts(等函數(shù))@plt 動態(tài)鏈接各個函數(shù)
_start 編譯器為可執(zhí)行文件加上了一個啟動例程
__libc_csu_init 程序調(diào)用libc庫用來對程序進(jìn)行初始化的函數(shù),一般先于main函數(shù)執(zhí)行
_fini 當(dāng)程序正常終止時需要執(zhí)行的代碼
(2) 函數(shù)個數(shù):在使用ld命令鏈接的時候,指定了動態(tài)鏈接器為64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定義了程序入口_start、初始化函數(shù)_init,_start程序調(diào)用hello.c中的main函數(shù),libc.so是動態(tài)鏈接共享庫,鏈接器加入了以下函數(shù)printf、sleep、getchar、exit函數(shù)和_start中調(diào)用的__libc_csu_init,__libc_csu_fini,__libc_start_main。
函數(shù)調(diào)用:鏈接器解析重定條目時發(fā)現(xiàn)對外部函數(shù)調(diào)用的類型為R_X86_64_PLT32的重定位,此時動態(tài)鏈接庫中的函數(shù)已經(jīng)加入到了PLT中,.text與.plt節(jié)相對距離已經(jīng)確定,鏈接器計算相對距離,將對動態(tài)鏈接庫中函數(shù)的調(diào)用值改為PLT中相應(yīng)函數(shù)與下條指令的相對地址,指向?qū)?yīng)函數(shù)。
rodata引用:鏈接器解析重定條目時發(fā)現(xiàn)兩個類型為R_X86_64_PC32的對.rodata的重定位(printf中的兩個字符串),.rodata與.text節(jié)之間的相對距離確定,因此鏈接器直接修改call之后的值為目標(biāo)地址與下一條指令的地址之差,指向相應(yīng)的字符串。
(3) 重定位過程。hello反匯編文件中對應(yīng)全局變量已通過重定位絕對引用被替換為固定地址。
5.6 hello的執(zhí)行流程
(以下格式自行編排,編輯時刪除)
使用edb執(zhí)行hello,說明從加載hello到_start,到call main,以及程序終止的所有過程。請列出其調(diào)用與跳轉(zhuǎn)的各個子程序名或程序地址。
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
libc-2.27.so!__cxa_atexit
libc-2.27.so!__libc_csu_init
hello!_init
libc-2.27.so!_setjmp
libc-2.27.so!_sigsetjmp
libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!sleep@plt
hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave
ld-2.27.so!_dl_fixup
ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
5.7 Hello的動態(tài)鏈接分析
(1)編譯器無法確定動態(tài)鏈接庫中的函數(shù)地址,因為動態(tài)鏈接庫中的函數(shù)在程序執(zhí)行的時候才會確定地址。GNU編譯系統(tǒng)采用延遲綁定技術(shù)來解決動態(tài)庫函數(shù)模塊調(diào)用的問題。
(2)延遲綁定通過全局偏移量表(GOT)和過程鏈接表(PLT)實現(xiàn)。
(a)PLT是一個數(shù)組,其中每個條目是16字節(jié)代碼。每個庫函數(shù)都有自己的PLT條目,PLT[0]是一個特殊的條目,跳轉(zhuǎn)到動態(tài)鏈接器中。從PLT[2]開始的條目調(diào)用用戶代碼調(diào)用的函數(shù)。
(b)GOT同樣是一個數(shù)組,每個條目是8字節(jié)的地址,和PLT聯(lián)合使用時,GOT[2]是動態(tài)鏈接在ld-linux.so模塊的入口點,其余條目對應(yīng)于被調(diào)用的函數(shù),在運行時被解析。每個條目都有匹配的PLT條目。
(3)延遲綁定的實現(xiàn)步驟如下:
a.建立一個 GOT.PLT 表,用來放全局函數(shù)的實際地址
b.對每一個全局函數(shù),鏈接器生成一個與之相對應(yīng)的函數(shù),如 puts@plt。
c.所有的puts都換成對 puts@plt。
(4)下面分析在dl_init調(diào)用前后,項目的內(nèi)容的變化
a)dl_init調(diào)用前

圖5.71 dl_init調(diào)用前GOT條目
b)dl_init調(diào)用后, GOT條目初始時指向其PLT條目的第二條指令的地址

圖5.72 dl_init調(diào)用后GOT條目
5.8 本章小結(jié)
本章介紹了鏈接的概念作用,分析hello的ELF格式和虛擬地址空間,通過實例分析了hello的動態(tài)鏈接、執(zhí)行流程、重定位過程、加載以及運行時函數(shù)調(diào)用順序,深入理解鏈接和重定位的過程。
第6章 hello的進(jìn)程管理
6.1 進(jìn)程的概念與作用
進(jìn)程的概念:進(jìn)程是正在運行的程序的實例,是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。進(jìn)程是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進(jìn)程既是基本的分配單元,也是基本的執(zhí)行單元。
進(jìn)程的作用:進(jìn)程提供兩個假象,程序獨占地使用處理器和程序在獨占地使用系統(tǒng)內(nèi)存。
6.2 簡述殼Shell-bash的作用與處理流程
Shell-bash的作用:shell和其他軟件一樣都是和內(nèi)核打交道,直接服務(wù)于用戶。但和其他軟件不同,shell主要用來管理文件和運行程序。
處理流程:shell對命令行的處理流程
(1)讀取輸入的命令行。
(2)解析引用并分割命令行為各個單詞,其中重定向所在的單詞會被保存下來,直到擴(kuò)展步驟(5)結(jié)束后才進(jìn)行相關(guān)處理。
(3)檢查命令行結(jié)構(gòu)。
(4)對第一個單詞進(jìn)行別名擴(kuò)展。
(5)進(jìn)行各種擴(kuò)展。擴(kuò)展順序為:大括號擴(kuò)展;波浪號擴(kuò)展;參數(shù)、變量和命令替換、算術(shù)擴(kuò)展;單詞拆分;文件名擴(kuò)展。
(6)引號去除。
(7)搜索和執(zhí)行命令。
(8)返回退出狀態(tài)碼。
6.3 Hello的fork進(jìn)程創(chuàng)建過程
普通的系統(tǒng)調(diào)用,調(diào)用一次就返回一次,而fork()調(diào)用一次,會返回兩次,一次是父進(jìn)程,另一個是子進(jìn)程,互不干擾,調(diào)用的先后順序由操作系統(tǒng)的調(diào)度算法決定。子進(jìn)程永遠(yuǎn)返回0,父進(jìn)程則返回子進(jìn)程的ID。
fork的進(jìn)程圖為:

圖6.3 fork的進(jìn)程圖
6.4 Hello的execve過程
execve 函數(shù)加載并運行可執(zhí)行目標(biāo)文件 filename, 且?guī)?shù)列表 argv 和環(huán)境變量列表 envp 。只有當(dāng)出現(xiàn)錯誤時,例如找不到 filename, execve 才會返回到調(diào)用程序。所以,與 fork 一次調(diào)用返回兩次不同, execve 調(diào)用一次并從不返回。
6.5 Hello的進(jìn)程執(zhí)行
結(jié)合進(jìn)程上下文信息、進(jìn)程時間片,闡述進(jìn)程調(diào)度的過程,用戶態(tài)與核心態(tài)轉(zhuǎn)換等等。
(1)上下文及上下文切換:進(jìn)程的物理實體(代碼和數(shù)據(jù)等)和支持進(jìn)程運行的環(huán)境。系統(tǒng)通過處理器調(diào)度讓處理器輪流執(zhí)行多個進(jìn)程,實現(xiàn)不同進(jìn)程中指令交替執(zhí)行的機(jī)制稱為進(jìn)程的上下文切換。
(2)進(jìn)程時間片:連續(xù)執(zhí)行同一個進(jìn)程的時間段稱為時間片
(3)用戶態(tài)與核心態(tài)轉(zhuǎn)換:處理器通過某個控制寄存器中的一個模式位來提供限制一個應(yīng)用可以執(zhí)行的指令以及它可以訪問的地址空間范圍的功能。當(dāng)設(shè)置了模式位時,進(jìn)程就運行在內(nèi)核模式中。沒有設(shè)置模式位時,進(jìn)程就運行在用戶模式中。
(4)Hello進(jìn)程調(diào)度的過程以及用戶態(tài)與核心態(tài)的轉(zhuǎn)換
調(diào)度是在進(jìn)程執(zhí)行的某些時刻,內(nèi)核可以決定搶占當(dāng)前進(jìn)程并重新開始一個先前被搶占了的進(jìn)程的決策。在切換的第一部分中,內(nèi)核代表進(jìn)程A在內(nèi)核模式下執(zhí)行指令。然后在某一時刻,shell加載可執(zhí)行目標(biāo)文件hello。在上下文切換之后,內(nèi)核代表進(jìn)程hello在用戶模式下執(zhí)行指令。之后進(jìn)程hello在用戶模式下運行,直到磁盤發(fā)出一個中斷信號,執(zhí)行一個從進(jìn)程hello到進(jìn)程A的上下文切換,將控制返回給進(jìn)程A,進(jìn)程A繼續(xù)運行,直到下一次異常發(fā)生。
圖6.5 Hello進(jìn)程調(diào)度的過程以及用戶態(tài)與核心態(tài)的轉(zhuǎn)換
6.6 hello的異常與信號處理
hello執(zhí)行過程中會出現(xiàn)哪幾類異常,會產(chǎn)生哪些信號,又怎么處理的。
程序運行過程中可以按鍵盤,如不停亂按,包括回車,Ctrl-Z,Ctrl-C等,Ctrl-z后可以運行ps jobs pstree fg kill 等命令,請分別給出各命令及運行結(jié)截屏,說明異常與信號的處理。
Hello執(zhí)行過程出現(xiàn)的異常為:中斷、故障
會產(chǎn)生的信號為:SIGSTP 來自終端的停止信號,SIGINT 來自鍵盤的中斷
(1) 正常終止
圖6.61 正常終止
(2) Ctrl + C
當(dāng)按下ctrl-c之后,shell父進(jìn)程收到SIGINT信號,信號處理程序結(jié)束hello,并回收hello進(jìn)程。
圖6.62 按Ctrl + C時
(3) Ctrl + Z
當(dāng)按下ctrl-z之后,
(a)shell父進(jìn)程收到SIGSTP信號,
(b)信號處理程序打印并將hello進(jìn)程掛起,
(c)通過ps命令看到hello進(jìn)程沒有被回收,
通過jobs命令看到hello進(jìn)程的號為1,
通過pstree命令可以看出:之后調(diào)用fg 1將其調(diào)到前臺,執(zhí)行相應(yīng)命令行
圖6.63 按Ctrl + Z時
(4) 中途亂按
中途亂按不導(dǎo)致異常和產(chǎn)生信號
圖6.64 中途亂按時
6.7 本章小結(jié)
本章首先介紹了進(jìn)程的概念與作用,并簡述殼Shell-bash的作用與處理流程,講解了Hello的fork進(jìn)程創(chuàng)建過程和execve過程,以及Hello的進(jìn)程是如何執(zhí)行的,如何處理hello的異常與產(chǎn)生的信號
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
(1) 邏輯地址:是指由程式產(chǎn)生的和段相關(guān)的偏移地址部分。
在反匯編hello得到的調(diào)用puts函數(shù)的指令是call 21<main + 0x21>,邏輯地址是[puts的代碼的段標(biāo)識符:21<main + 0x21>]
圖7.1 puts函數(shù)的地址
(2) 線性地址:是邏輯地址到物理地址變換之間的中間層。程式代碼會產(chǎn)生邏輯地址,或說是段中的偏移地址,加上相應(yīng)段的基地址就生成了一個線性地址。如果啟用了分頁機(jī)制,那么線性地址能再經(jīng)變換以產(chǎn)生一個物理地址。若沒有啟用分頁機(jī)制,那么線性地址直接就是物理地址。
(3)虛擬地址:也叫線性地址,是一個不真實的地址。
(4)物理地址:是指出目前CPU外部地址總線上的尋址物理內(nèi)存的地址信號,是地址變換的最終結(jié)果地址,用于內(nèi)存芯片級的單元尋址,與地址總線相對應(yīng)。
7.2 Intel邏輯地址到線性地址的變換-段式管理
圖7.2 邏輯地址到線性地址的變換-段式管理
1)基本原理。
在段式存儲管理中,將程序的地址空間劃分為若干個段,在段式存儲管理系統(tǒng)中,為每個段分配一個連續(xù)的分區(qū),而進(jìn)程中的各個段可以不連續(xù)地存放在內(nèi)存的不同分區(qū)中。程序加載時,操作系統(tǒng)為所有段分配其所需內(nèi)存,物理內(nèi)存的管理采用動態(tài)分區(qū)的管理方法。在為某個段分配物理內(nèi)存時,可以采用首先適配法、下次適配法、最佳適配法等方法。在回收某個段所占用的空間時,要注意將收回的空間與其相鄰的空間合并。段式存儲管理也需要硬件支持,實現(xiàn)邏輯地址到物理地址的映射。
2)段式管理的數(shù)據(jù)結(jié)構(gòu)。
為了實現(xiàn)段式管理,操作系統(tǒng)需要進(jìn)程段表、系統(tǒng)段表和空閑段表來實現(xiàn)進(jìn)程的地址空間到物理內(nèi)存空間的映射。
3)段式管理的地址變換。
在段式管理系統(tǒng)中,其邏輯地址由段號和段內(nèi)地址兩部分組成。處理器會查找內(nèi)存中的段表,由段號得到段的首地址,加上段內(nèi)地址,得到實際的物理地址,從而完成邏輯地址到物理地址的映射。
7.3 Hello的線性地址到物理地址的變換-頁式管理
(1)頁式存儲管理的基本原理:
1)分頁存儲器將主存劃分成多個大小相等的頁架;
2)程序的邏輯地址分成頁;
3)不同的頁可以放在不同頁架中,不需要連續(xù)
4)頁表用于維系進(jìn)程的主存完整性

圖7.31 進(jìn)程頁表
(2)頁式存儲管理的邏輯地址由兩部分組成:

圖7.32頁式存儲管理的邏輯地址
(3) 頁式存儲管理的物理地址由兩部分組成:

圖7.33 頁式存儲管理的物理地址
(4) 頁式存儲管理的地址轉(zhuǎn)換思路:
圖7.34 從邏輯地址映射到物理地址
(5) 頁的共享:頁式存儲管理能夠?qū)崿F(xiàn)多個進(jìn)程共享程序和數(shù)據(jù),包括數(shù)據(jù)共享和程序共享
(6)頁式虛擬存儲管理的基本思想:把進(jìn)程全部頁面裝入虛擬存儲器,執(zhí)行時先把部分頁面裝入實際內(nèi)存,然后根據(jù)執(zhí)行行為,動態(tài)調(diào)入不在主存的頁,同時進(jìn)行必要的頁面調(diào)出
7.4 TLB與四級頁表支持下的VA到PA的變換
圖7.4 VA到PA的映射過程
(1) 首先介紹以下VA和PA。VA:virtual address稱為虛擬地址,PA:physical address稱為物理地址。MMU是內(nèi)存管理單元。MMU將VA翻譯成為PA發(fā)到CPU芯片的外部地址引腳上,也就是將VA映射到PA中。MMU將VA映射到PA是以頁為單位的,對于32位的CPU,通常一頁為4k,物理內(nèi)存中的一個物理頁面稱頁為一個頁框。
(2) TLB與四級頁表支持下的VA到PA的變換和TLB與二級頁表支持下的VA到PA的變換的原理相同,為了結(jié)合實例分析,下面介紹二級頁表的變換。如圖7.4,首先將CPU內(nèi)核發(fā)送過來的32位VA[31:0]分成三段,前兩段VA[31:20]和VA[19:12]作為兩次查表的索引,第三段VA[11:0]作為頁內(nèi)的偏移,查表的步驟如下:
a)從協(xié)處理器CP15的寄存器2(TTB寄存器)中取出保存在其中的第一級頁表的基地址PA
b)以TTB中的內(nèi)容為基地址,以VA[31:20]為索引值在一級頁表中查找出一項,一級頁表中保存著第二級頁表的基地址。
c)以VA[19:12]為索引值在第二級頁表中查出一項,第二級頁表中保存著物理頁面的基地址,從這里可以印證一個虛擬內(nèi)存的頁映射到一個物理內(nèi)存的頁框,因為查表是以頁為單位來查的。
d)有了物理頁面的基地址之后,加上VA[11:0]偏移量就可以取出相應(yīng)地址上的數(shù)據(jù)
7.5 三級Cache支持下的物理內(nèi)存訪問
(以下格式自行編排,編輯時刪除)
圖7.5 CPU訪問內(nèi)存時的硬件操作順序
(1) 以VA為索引到cache中查找是否緩存了要讀取的數(shù)據(jù),如果cache中已經(jīng)緩存了該數(shù)據(jù)則直接返回給CPU內(nèi)核,如果cache中沒有緩存該數(shù)據(jù),則發(fā)出PA從物理內(nèi)存中讀取數(shù)據(jù)并緩存到cache中,同時返回給CPU內(nèi)核。cache不只是緩存CPU內(nèi)核所需要的數(shù)據(jù),同時緩存相鄰的數(shù)據(jù)。
(2) 高速緩存確定一個請求是否命中,然后抽取出被請求的字的過程,分為三步,(1)組選擇、(2)行匹配、(3)字抽取。
下面是物理內(nèi)存的讀策略和寫策略。
a) 直接映射高速緩存E=1,即每組只有一行。組選擇是通過組索引位標(biāo)識組。高速緩存從w的地址中間抽取出s個組索引位,這些位被解釋為一個對應(yīng)于一個組號的無符號整數(shù),來進(jìn)行組索引。行匹配中,確定了某個組i,接下來需要確定是否有字w的一個副本存儲在組i包含的一個高速緩存行里,因為直接映射高速緩存只有一行,如果有效位為1且標(biāo)志位相同則緩存命中,根據(jù)塊偏移位即可查找到對應(yīng)字的地址并取出;若有效位為1但標(biāo)志位不同則沖突不命中,有效位為0則為冷不命中,此時都需要從存儲器層次結(jié)構(gòu)下一層取出被請求的塊,然后將新的塊存儲在組索引位指示的組中的一個高速緩存行中。
b) 組相聯(lián)高速緩存每個組都會保存多余一個的高速緩存行,組選擇與直接映射高速緩存的組選擇一樣,通過組索引位標(biāo)識組。行匹配時需要找遍組中所有行,找到標(biāo)記位有效位均相同的一行則緩存命中;如果CPU請求的字不在組的任何一行中,則緩存不命中,選擇替換時如果存在空行選擇空行,如果不存在空行則通過替換策略替換其中一行。
c) 全相聯(lián)高速緩存只包含一個組,其行匹配和字選擇與組相聯(lián)高速緩存中一樣
d)寫策略:分為直寫和寫回。
7.6 hello進(jìn)程fork時的內(nèi)存映射
函數(shù)fork()若成功調(diào)用一次則返回兩個值,子進(jìn)程返回0,父進(jìn)程返回子進(jìn)程ID。內(nèi)核為子進(jìn)程創(chuàng)建各種數(shù)據(jù)結(jié)構(gòu),并分配給它一個唯一的PID,新創(chuàng)建的子進(jìn)程獲得與父進(jìn)程完全相同的虛擬存儲空間中的一個備份這個進(jìn)程的每個頁面都標(biāo)記為只讀。
7.7 hello進(jìn)程execve時的內(nèi)存映射

圖7.7進(jìn)程的內(nèi)存映像
execve函數(shù)加載并運行hello需要以下幾個步驟:
1.刪除已存在的用戶區(qū)域
2.映射私有區(qū)域,為新程序創(chuàng)建所有新的區(qū)域結(jié)構(gòu)
3.映射共享區(qū)域
4.設(shè)置當(dāng)前進(jìn)程上下文的程序計數(shù)器
7.8 缺頁故障與缺頁中斷處理
(1)缺頁中斷及處理:在主存中查找頁表時相應(yīng)頁表條目有效位為0且物理頁號為NULL,則該頁表條目處于未分配,屬于缺頁中斷。缺頁中斷的異常處理程序為終止
(2)缺頁故障及處理:在主存中查找頁表時相應(yīng)頁表條目有效位為0但是物理頁號指向磁盤,屬于缺頁故障,缺頁故障的異常處理程序是從磁盤裝入相應(yīng)頁到內(nèi)存并更新頁表,再返回到故障指令開始執(zhí)行。
7.9 動態(tài)存儲分配管理
圖7.9塊的表示圖
(1)實現(xiàn)動態(tài)內(nèi)存分配要考慮空閑塊組織、放置、分割和合并。
(2)分配器分為兩種:顯式分配器、隱式分配器。顯式分配器:要求應(yīng)用顯式地釋放任何已分配的塊。隱式分配器:要求分配器檢測一個已分配塊何時不再使用,那么就釋放這個塊,自動釋放未使用的已經(jīng)分配的塊的過程叫做垃圾收集。隱式空閑鏈表的優(yōu)點是簡單,缺點是任何操作的開銷,例如放置分配的塊,要求空閑鏈表的搜索與堆中已分配塊和空閑塊的總數(shù)呈線性關(guān)系。
(3)當(dāng)接收到一個內(nèi)存分配請求時,從頭開始遍歷堆,找到一個空閑的滿足大小要求的塊,若有剩余,將剩余部分變成一個新的空閑塊,更新相關(guān)塊的控制信息。調(diào)整起始位置,返回給用戶。釋放內(nèi)存時,僅需把使用情況標(biāo)記為空閑即可。
(4)搜索可以滿足請求的空閑塊時,策略有以下幾種:首次適應(yīng)法、最佳適應(yīng)法、最壞適應(yīng)法和循環(huán)首次適應(yīng)法
7.10 本章小結(jié)
本章結(jié)合hello介紹了邏輯地址、線性地址、虛擬地址、物理地址的概念,對段式管理與頁式管理進(jìn)行比較分析,分析了進(jìn)程 fork 和 execve 時的內(nèi)存映射的內(nèi)容,描述了系統(tǒng)應(yīng)對缺頁異常的方法,最后描述了 malloc 的內(nèi)存分配管理機(jī)制
第8章 hello的IO管理
8.1 Linux的IO設(shè)備管理方法
IO設(shè)備管理方法:一個Linux文件就是一個m字節(jié)的序列:B1,B2,……,Bk,……,Bm-1。所有的I/O設(shè)備(例如網(wǎng)絡(luò)、磁盤和終端)都被模型化為文件,而所有的輸入和輸出都被當(dāng)做對相應(yīng)文件的讀和寫來執(zhí)行。這種將設(shè)備優(yōu)雅地映射為文件的方式,允許Linux內(nèi)核引出一個簡單、低級的應(yīng)用接口,稱為Unix I/O。這使得所有輸入和輸出都能以一種統(tǒng)一且一致的方式來執(zhí)行:a)打開文件b)Linux Shell創(chuàng)建的每個進(jìn)程開始時都有三個打開的文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤。c)改變當(dāng)前文件的位置。d)讀寫文件。e)關(guān)閉文件。
8.2 簡述Unix IO接口及其函數(shù)
(1) 打開和關(guān)閉文件。open函數(shù)的函數(shù)原型是int open(char * path,int flags,mode_t mode) open函數(shù)將filename轉(zhuǎn)換成一個文件描述符,并返回描述符數(shù)字。進(jìn)程是通過調(diào)用open函數(shù)來打開一個已經(jīng)存在的文件或者創(chuàng)建一個新文件。最后進(jìn)程通過調(diào)用close函數(shù)關(guān)閉一個打開的文件。close函數(shù)原型是int close(int fd)
(2)讀和寫文件應(yīng)用程序是通過分別調(diào)用read和write函數(shù)來執(zhí)行輸入和輸出的。read函數(shù)函數(shù)原型是ssize_t read(int fd ,void* buf , size_t n),從描述符為fd的當(dāng)前文件位置復(fù)制最多n個字節(jié)到內(nèi)存位置buf。write函數(shù)函數(shù)原型是ssize_t write(int fd , const void* buf,size_t n),從內(nèi)存位置buf復(fù)制最多n個字節(jié)到描述符為fd的當(dāng)前文件位置。
(3)lseek函數(shù)off_t lseek(int fd, off_t offset , int whence)
應(yīng)用程序通過lseek函數(shù)能夠顯示地修改當(dāng)前文件的位置
8.3 printf的實現(xiàn)分析
(1)首先來看看printf函數(shù)的函數(shù)體
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
va_list是一個字符指針
(2)printf函數(shù)中調(diào)用了vsprintf函數(shù),
來看看vsprintf(buf, fmt, arg)的代碼
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
vsprintf返回的是要打印出來的字符串的長度
(3)然后看printf中的一句:write(buf, i);printf函數(shù)調(diào)用了write函數(shù),把緩沖區(qū)的元素的值打印。
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
在write () 函數(shù)對應(yīng)的指令序列中,有用于系統(tǒng)調(diào)用的陷阱指令system_call。write通過執(zhí)行syscall指令調(diào)用系統(tǒng)服務(wù),執(zhí)行打印操作。內(nèi)核會通過字符顯示子程序,根據(jù)傳入的ASCII碼到字模庫讀取字符對應(yīng)的點陣,然后通過vram對字符串進(jìn)行輸出。顯示芯片將按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點,最終在終端輸出字符串。
8.4 getchar的實現(xiàn)分析
(1) 首先來看一下getchar函數(shù):getchar由宏實現(xiàn):#define getchar() getc(stdin),從標(biāo)準(zhǔn)輸入里讀取下一個字符,返回類型為int型,為用戶輸入的ASCII碼或EOF。
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return(--n>=0)?(unsigned char)*bb++:EOF;
}
(2)可以看到n=read(0,buf,BUFSIZ);語句調(diào)用了read函數(shù)。read函數(shù)可以通過sys_call中斷來調(diào)用內(nèi)核中的系統(tǒng)函數(shù)。鍵盤中斷處理子程序會接受按鍵掃描碼并將其轉(zhuǎn)換為ASCII碼后保存在緩沖區(qū),然后對緩沖區(qū)ASCII碼進(jìn)行讀取直到接受回車鍵返回。
8.5本章小結(jié)
本章介紹了Linux的IO設(shè)備管理方法,并簡述了Unix IO接口及其函數(shù),對printf和getchar的實現(xiàn)進(jìn)行分析。
結(jié)論
在linux環(huán)境下hello程序從預(yù)處理到編譯再到鏈接,最后執(zhí)行的全過程以及進(jìn)程管理,存儲管理及IO管理的實現(xiàn)方式。
hello經(jīng)歷的過程如下:
(1)首先通過各種預(yù)處理命令對C程序進(jìn)行處理,由hello.c得到hello.i。
(2)通過編譯由hello.i得到hello.s
(3)通過匯編由hello.s得到hello.o
(4)通過鏈接得到可執(zhí)行目標(biāo)文件hello,然后運行hello,在shell下輸入命令./hello 1170300826 ,shell調(diào)用fork創(chuàng)建子進(jìn)程,然后將構(gòu)造好的參數(shù)列表傳給execve作為參數(shù),啟動加載器并開始執(zhí)行hello
(5)訪問虛擬內(nèi)存,通過虛擬地址在TLB和主存頁表中查找轉(zhuǎn)換為相應(yīng)物理地址,從在虛擬內(nèi)存中讀取hello程序所需要的數(shù)據(jù)
(6)異常處理,對中斷產(chǎn)生信號進(jìn)行處理
(7)回收回收進(jìn)程
附件
列出所有的中間產(chǎn)物的文件名,并予以說明起作用。
| 文件名稱 | 文件屬性 |
|---|---|
| hello.i | 預(yù)處理得到的文本文件 |
| hello.s | 編譯后的匯編文件 |
| hello.o | 匯編后的可重定位目標(biāo)執(zhí)行 |
| hello | 鏈接之后的可執(zhí)行目標(biāo)文件 |
| hello.objdmp | hello.o的反匯編代碼 |
| hello.elf | hello.o的ELF格式 |
| hello1.elf | hello的 ELF格式 |
| hello.objdmp | hello的反匯編代碼 |
| hello.txt | 分析重定位過程時的可執(zhí)行文件hello反匯編代碼 |
參考文獻(xiàn)
為完成本次大作業(yè)你翻閱的書籍與網(wǎng)站等
[1] 蘭德爾E.布萊恩特 大衛(wèi)R.奧哈拉倫. 深入理解計算機(jī)系統(tǒng)(第3版).
機(jī)械工業(yè)出版社. 2018.4.
[2] 袁春風(fēng) 計算機(jī)系統(tǒng)基礎(chǔ) 機(jī)械工業(yè)出版社,2018.
[3] 關(guān)于unix系統(tǒng)接口 普通文件io的小結(jié)
https://www.cnblogs.com/chentest/p/5448483.html
[4] printf 函數(shù)實現(xiàn)的深入剖析
https://www.cnblogs.com/pianist/p/3315801.html
[5] getchar百度百科
https://baike.baidu.com/item/getchar/919709?fr=aladdin
[6] LINUX 邏輯地址、線性地址、物理地址和虛擬地址
https://www.cnblogs.com/zengkefu/p/5452792.html
[7] shell解析命令行的過程以及eval命令
https://www.cnblogs.com/f-ck-need-u/p/7426371.html