HIT计算机系统大作业——hello的一生
摘 要
本文通過一個(gè)結(jié)構(gòu)簡單的hello程序,說明了由文本到可執(zhí)行的文件,再到一個(gè)進(jìn)程的整個(gè)過程中,計(jì)算機(jī)系統(tǒng)都起到了怎樣的作用。在這一過程中,我們會(huì)接觸到程序預(yù)處理、編譯、匯編、鏈接等過程,同時(shí)也介紹了有關(guān)進(jìn)程、存儲(chǔ)、I/O等相關(guān)的知識(shí),深入系統(tǒng)底層的軟硬件結(jié)合的部分,對計(jì)算機(jī)系統(tǒng)進(jìn)行了較為系統(tǒng)和廣泛的探討。
第1章 概述
1.1 Hello簡介
Hello.c是一個(gè)以字節(jié)序列方式儲(chǔ)存在文件中的源程序(Program),經(jīng)過預(yù)處理器(cpp)變?yōu)镠ello.i,然后在編譯器(ccl)的處理后變?yōu)镠ello.s的匯編程序,然后通過匯編器(as),生成了Hello.o的可重定位目標(biāo)程序(二進(jìn)制),然后在鏈接器(ld)的鏈接后,生成可執(zhí)行的Hello目標(biāo)程序。在編譯系統(tǒng)之后,調(diào)用shell命令行輸入,fork產(chǎn)生子進(jìn)程,Hello從program成為Process計(jì)算機(jī)完成了From Program to Process的過程:
1.2 環(huán)境與工具
1.2.1 硬件環(huán)境
AMD Ryzen 7 5800H 3.2GHz; 16G RAM;
1.2.2 軟件環(huán)境
Windows 11 21H2; VMware Workstation 16.2.3; Ubantu 20.04
1.2.3 開發(fā)工具
Visual Studio 2022; Codeblocks 20.03
1.3 中間結(jié)果
列出你為編寫本論文,生成的中間結(jié)果文件的名字,文件的作用等。
1.4 本章小結(jié)
本章主要介紹了Hello作為一個(gè)程序、進(jìn)程經(jīng)歷的生命周期,解釋了何為程序的P2P、020,記錄了實(shí)驗(yàn)時(shí)的軟硬件環(huán)境,以及生成的中間文件。
第2章 預(yù)處理
2.1 預(yù)處理的概念與作用
預(yù)處理是預(yù)處理器(cpp)根據(jù)#開頭的命令,修改原始的C程序,將源程序及引用的庫合并成完整的文件,得到了另一個(gè)C程序,一般以.i作為文件擴(kuò)展名。
C語言的預(yù)處理主要有三個(gè)方面的內(nèi)容:
1.宏定義 #define;
2.文件包含 #include;
3.條件編譯 #if #else #elif #ifndef #ifdef等;[1]
預(yù)處理的作用是從系統(tǒng)的頭文件包中將頭文件的源碼插入到目標(biāo)文件中,在編譯代碼前首先將標(biāo)識(shí)符替換好,確保程序的完整性,生成.i文件后再進(jìn)行接下來的編譯工作。
2.2在Ubuntu下預(yù)處理的命令
在命令行窗口中使用命令gcc -m64 -no-pie -fno-PIC -E hello.c > hello.i,生成hello.i文件,截圖如下:
2.3 Hello的預(yù)處理結(jié)果解析
hello.c經(jīng)過預(yù)處理后得到了hello.i文件,其文本量大大增加,達(dá)到了三千余行,其中main函數(shù)處于文檔的尾部。可以發(fā)現(xiàn),該文本文件已經(jīng)解析了hello.c中引用的頭文件,在文本的前幾行,我們可以看到如下內(nèi)容:
接下來還有很多對于庫中預(yù)置的函數(shù)的定義:
2.4 本章小結(jié)
本章主要介紹了對.c文件的預(yù)處理,通過對于生成的.i文件的分析,我們可以了解到預(yù)處理器對于源程序做了哪些處理——例如對于宏的替換,引入各種需要的頭文件等。
第3章 編譯
3.1 編譯的概念與作用
編譯,就是編譯器通過詞法分析和語法分析,確認(rèn)所有指令都是合法的,然后將其翻譯為等價(jià)的匯編代碼。
編譯器會(huì)將.i文件翻譯為.s文本文件,其中包含了一個(gè)匯編語言程序。匯編語言是一種通用的、接近底層的語言,即使是不同高級語言的不同編譯器,最后也會(huì)得到通用的匯編語言,進(jìn)而進(jìn)行下一步的實(shí)現(xiàn)。
3.2 在Ubuntu下編譯的命令
3.3 Hello的編譯結(jié)果解析
此部分是重點(diǎn),說明編譯器是怎么處理C語言的各個(gè)數(shù)據(jù)類型以及各類操作的。應(yīng)分3.3.1~ 3.3.x等按照類型和操作進(jìn)行分析,只要hello.s中出現(xiàn)的屬于大作業(yè)PPT中P4給出的參考C數(shù)據(jù)與操作,都應(yīng)解析。
3.3.1開始的聲明
在文本的開始,有如下的聲明
其中值得我們注意的是.section .rodata聲明了接下來的內(nèi)容是只讀類型的,該聲明用于維護(hù)只讀數(shù)據(jù),比如:常量字符串、帶 const 修飾的全局變量和靜態(tài)變量等[2],在本程序中,需要打印的字符串在.rodata中聲明;而.text則是聲明是程序代碼段;.align 8聲明了本匯編語言程序是以8個(gè)字節(jié)的倍數(shù)來進(jìn)行內(nèi)存對齊。
3.3.2 轉(zhuǎn)移控制
在main函數(shù)中,匯編語言用.cfi_startproc和.cfi_endproc聲明了函數(shù)的起始,其中涉及了條件跳轉(zhuǎn)語句,例如對于C語言中的if (argc != 4),有如下匯編語句,其中%rbp中存儲(chǔ)位置處是常量4:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
cmpl $4, -20(%rbp)
je .L2
對于C程序中for循環(huán)中的循環(huán)語句for(i=0;i<8;i++)
匯編代碼為
.L3:
cmpl $7, -4(%rbp)
jle .L4
3.3.3 數(shù)據(jù)
初始化的全局變量儲(chǔ)存在.data節(jié),它的初始化不需要匯編語句,而是直接完成的。而局部變量存儲(chǔ)在寄存器或棧中。在該程序中的局部變量i就用到了棧,匯編代碼如下:
.L2:
movl $0, -4(%rbp)
jmp .L3
3.3.4 算術(shù)操作
程序的for循環(huán)中有自加操作符,在匯編中每次循環(huán)都通過跳轉(zhuǎn)指令實(shí)現(xiàn),循環(huán)體中就包含了addl $1, -4(%rbp),對棧中存儲(chǔ)的i加1
3.3.5數(shù)組/指針/結(jié)構(gòu)操作
main函數(shù)的參數(shù)中含有指針數(shù)組char *argv[],在argv數(shù)組中,argv[0]指向輸入程序的路徑和名稱,argv[1]和argv[2]分別表示兩個(gè)字符串,我們不妨以它們?yōu)槔?#xff0c;追蹤指針數(shù)組存儲(chǔ)在程序中的哪部分。
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp) //argc存儲(chǔ)在edi中
movq %rsi, -32(%rbp)//argv存儲(chǔ)在rsi中
cmpl $4, -20(%rbp)
je .L2
movl $.LC0, %edi
call puts
movl $1, %edi
call exit
棧中%rsi-8和%rax-16的位置,分別存儲(chǔ)著argv[1]和argv[2]兩個(gè)字符串。
3.3.6 函數(shù)操作
main函數(shù):
參數(shù)傳遞:傳入?yún)?shù)argc和argv[],分別位于寄存器rdi和rsi中。
函數(shù)調(diào)用:被系統(tǒng)啟動(dòng)函數(shù)調(diào)用。
函數(shù)返回:設(shè)置%eax為0并且返回,對應(yīng)return 0
exit函數(shù):
參數(shù)傳遞:傳入的參數(shù)為1,再執(zhí)行退出命令
函數(shù)調(diào)用:if判斷條件滿足后被調(diào)用
函數(shù)返回:退出程序,正確退出,返回0;出現(xiàn)錯(cuò)誤,返回非0值
sleep函數(shù):
參數(shù)傳遞:傳入?yún)?shù)atoi(argv[3]),
函數(shù)調(diào)用:for循環(huán)下被調(diào)用,call sleep
匯編代碼如下:
.L4:
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
movl $.LC1, %edi
movl $0, %eax
call printf
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rax
movq %rax, %rdi
call atoi
movl %eax, %edi
call sleep
addl $1, -4(%rbp)
3.4 本章小結(jié)
本章主要分析了hello.s匯編語言文本文件的內(nèi)容,分析了編譯器對于源程序的操作,深入分析了匯編文件中是如何實(shí)現(xiàn)C語言的數(shù)據(jù)與操作的。
第4章 匯編
4.1 匯編的概念與作用
匯編是編譯后的文件到生成機(jī)器語言二進(jìn)制程序的過程,機(jī)器語言指令被打包成可重定位目標(biāo)程序的格式。
4.2 在Ubuntu下匯編的命令
4.3 可重定位目標(biāo)elf格式
可以使用指令:readelf -a hello.o > hello_0.elf得到.elf的文件。
ELF頭如下:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
類別: ELF64
數(shù)據(jù): 2 補(bǔ)碼,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
類型: REL (可重定位文件)
系統(tǒng)架構(gòu): Advanced Micro Devices X86-64
版本: 0x1
入口點(diǎn)地址: 0x0
程序頭起點(diǎn): 0 (bytes into file)
Start of section headers: 1192 (bytes into file)
標(biāo)志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
節(jié)頭如下:
[號] 名稱 類型 地址 偏移量
大小 全體大小 旗標(biāo) 鏈接 信息 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000008e 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000358
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000d0
0000000000000033 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 00000103
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000012f
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000130
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000150
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000418
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000188
0000000000000198 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000320
0000000000000032 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000430
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
重定位的信息包括了類型和偏移量,在鏈接階段會(huì)用到這些信息來進(jìn)行地址的計(jì)算,需要進(jìn)行重定位的信息包括了.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等函數(shù)
重定位節(jié)如下:
重定位節(jié) ‘.rela.text’ at offset 0x358 contains 8 entries:
偏移量 信息 類型 符號值 符號名稱 + 加數(shù)
00000000001a 00050000000a R_X86_64_32 0000000000000000 .rodata + 0
00000000001f 000b00000004 R_X86_64_PLT32 0000000000000000 puts - 4
000000000029 000c00000004 R_X86_64_PLT32 0000000000000000 exit - 4
000000000050 00050000000a R_X86_64_32 0000000000000000 .rodata + 26
00000000005a 000d00000004 R_X86_64_PLT32 0000000000000000 printf - 4
00000000006d 000e00000004 R_X86_64_PLT32 0000000000000000 atoi - 4
000000000074 000f00000004 R_X86_64_PLT32 0000000000000000 sleep - 4
000000000083 001000000004 R_X86_64_PLT32 0000000000000000 getchar - 4
重定位節(jié) ‘.rela.eh_frame’ at offset 0x418 contains 1 entry:
偏移量 信息 類型 符號值 符號名稱 + 加數(shù)
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
.symtab是一個(gè)符號表,其中存放著程序中定義和引用的函數(shù)和全局變量的信息:
Symbol table ‘.symtab’ contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 142 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND atoi
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND getchar
4.4 Hello.o的結(jié)果解析
objdump -d -r hello.o 分析hello.o的反匯編,并請與第3章的hello.s進(jìn)行對照分析。
分析hello.o的反匯編,并與第3章的 hello.s進(jìn)行對照分析:
操作數(shù):hello.s中的操作數(shù)采用十進(jìn)制來表示,而hello.o的反匯編代碼中的操作數(shù)采用了十六進(jìn)制。
分支轉(zhuǎn)移:在hello.s的跳轉(zhuǎn)語句中采用的是.L2和.LC1等段的名稱,而反匯編代碼中跳轉(zhuǎn)指令之后是間接地址,是每條語句之間相對偏移的地址。
函數(shù)調(diào)用:hello.s中,call指令直接使用了函數(shù)名稱,而反匯編代碼中call指令使用的是相對于main函數(shù)的偏移地址。同時(shí)在.rela.text節(jié)中為其添加了重定位條目,待在鏈接之后確定物理地址。
4.5 本章小結(jié)
本章主要介紹了匯編后程序的文本文件被轉(zhuǎn)化為可重定位目標(biāo)程序的過程,然后通過ELF文件格式分析了我們得到的.o文件,然后通過對所得的.o文件進(jìn)行反匯編,發(fā)現(xiàn)了經(jīng)過匯編后程序的改動(dòng)——用邏輯地址的相對偏移來約定跳轉(zhuǎn)指令和call指令。
第5章 鏈接
5.1 鏈接的概念與作用
鏈接是將各種代碼和數(shù)據(jù)片段收集并組合成一個(gè)單一文件的過程,這個(gè)文件可被復(fù)制到內(nèi)存并執(zhí)行。
鏈接使得分離編譯成為可能,我們不用將一個(gè)大型的應(yīng)用程序組織為一個(gè)巨大的源文件,而是可以把它分解為更小、更好管理的模塊,并對它們進(jìn)行獨(dú)立地修改和編譯。
5.2 在Ubuntu下鏈接的命令
5.3 可執(zhí)行目標(biāo)文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
在命令行中鍵入命令readelf -a hello > hello_1.elf得到hello的ELF格式。
ELF 頭:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
類別: ELF64
數(shù)據(jù): 2 補(bǔ)碼,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
類型: EXEC (可執(zhí)行文件)
系統(tǒng)架構(gòu): Advanced Micro Devices X86-64
版本: 0x1
入口點(diǎn)地址: 0x4010f0
程序頭起點(diǎn): 64 (bytes into file)
Start of section headers: 14208 (bytes into file)
標(biāo)志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 12
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
節(jié)頭描述了各個(gè)節(jié)的大小、偏移量以及其他屬性。鏈接時(shí),會(huì)將各個(gè)文件的相同段進(jìn)行合并,并且根據(jù)得到的段的大小以及偏移量重新設(shè)置各個(gè)符號的地址。:
[號] 名稱 類型 地址 偏移量
大小 全體大小 旗標(biāo) 鏈接 信息 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002e0 000002e0
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000400300 00000300
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.ABI-tag NOTE 0000000000400320 00000320
0000000000000020 0000000000000000 A 0 0 4
[ 4] .hash HASH 0000000000400340 00000340
0000000000000038 0000000000000004 A 6 0 8
[ 5] .gnu.hash GNU_HASH 0000000000400378 00000378
000000000000001c 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 0000000000400398 00000398
00000000000000d8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000400470 00000470
000000000000005c 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000004004cc 000004cc
0000000000000012 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 00000000004004e0 000004e0
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000400500 00000500
0000000000000030 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000400530 00000530
0000000000000090 0000000000000018 AI 6 21 8
[12] .init PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000401020 00001020
0000000000000070 0000000000000010 AX 0 0 16
[14] .plt.sec PROGBITS 0000000000401090 00001090
0000000000000060 0000000000000010 AX 0 0 16
[15] .text PROGBITS 00000000004010f0 000010f0
0000000000000145 0000000000000000 AX 0 0 16
[16] .fini PROGBITS 0000000000401238 00001238
000000000000000d 0000000000000000 AX 0 0 4
[17] .rodata PROGBITS 0000000000402000 00002000
000000000000003b 0000000000000000 A 0 0 8
[18] .eh_frame PROGBITS 0000000000402040 00002040
00000000000000fc 0000000000000000 A 0 0 8
[19] .dynamic DYNAMIC 0000000000403e50 00002e50
00000000000001a0 0000000000000010 WA 7 0 8
[20] .got PROGBITS 0000000000403ff0 00002ff0
0000000000000010 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000404000 00003000
0000000000000048 0000000000000008 WA 0 0 8
[22] .data PROGBITS 0000000000404048 00003048
0000000000000004 0000000000000000 WA 0 0 1
[23] .comment PROGBITS 0000000000000000 0000304c
000000000000002b 0000000000000001 MS 0 0 1
[24] .symtab SYMTAB 0000000000000000 00003078
00000000000004c8 0000000000000018 25 30 8
[25] .strtab STRTAB 0000000000000000 00003540
0000000000000158 0000000000000000 0 0 1
[26] .shstrtab STRTAB 0000000000000000 00003698
00000000000000e1 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進(jìn)程的虛擬地址空間各段信息,并與5.3對照分析說明。
從Data Dump中可以看到程序從0x400000開始:
edb中程序頭:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002a0 0x00000000000002a0 R 0x8
INTERP 0x00000000000002e0 0x00000000004002e0 0x00000000004002e0
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000005c0 0x00000000000005c0 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000245 0x0000000000000245 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x000000000000013c 0x000000000000013c R 0x1000
LOAD 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001fc 0x00000000000001fc RW 0x1000
DYNAMIC 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000320 0x0000000000400320 0x0000000000400320
0x0000000000000020 0x0000000000000020 R 0x4
GNU_PROPERTY 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001b0 0x00000000000001b0 R 0x1
其含義如下:
PHDR:程序頭表
INTERP:程序執(zhí)行前調(diào)用的解釋器
LOAD:程序目標(biāo)代碼和常量信息
DYNAMIC:動(dòng)態(tài)鏈接器所使用的信息
NOTE::輔助信息
GNU_EH_FRAME:異常信息保存
GNU_STACK:使用系統(tǒng)棧所需要的權(quán)限信息
GNU_RELRO:保存在重定位之后只讀信息的位置
可以看到最開始是GNU_STACK,而LOAD從0x400000開始,接著是PHDR,INTERP和NOTE。最后是DYNAMIC和GNU_RELRO部分。
5.5 鏈接的重定位過程分析
重定位:鏈接器在完成符號解析以后,會(huì)將代碼中的每個(gè)符號引用和對應(yīng)的符號定義關(guān)聯(lián)起來。此時(shí),鏈接器會(huì)獲得輸入目標(biāo)模塊中的代碼節(jié)和數(shù)據(jù)節(jié)的確切大小,進(jìn)而進(jìn)行重定位步驟。在這個(gè)步驟中,鏈接器會(huì)合并輸入模塊,并為每個(gè)符號分配運(yùn)行時(shí)的地址。然后在重定位節(jié)中的符號引用中,鏈接器會(huì)修改hello中的代碼節(jié)和數(shù)據(jù)節(jié)中的符號引用,使得他們指向正確的運(yùn)行地址。
我們可以發(fā)現(xiàn),通過對鏈接后的hello程序進(jìn)行反匯編,所有指令的地址都變成了絕對地址,而并非hello.o中的相對地址。
5.6 hello的執(zhí)行流程
使用edb執(zhí)行hello,從加載hello到_start到call main以及程序終止的所有函數(shù)及其地址:
401000 _init>
401020 .plt>
401030 puts@plt>
401040 printf@plt>
401050 getchar@plt>
401060 atoi@plt>
401070 exit@plt>
401080 sleep@plt>
401090 _start>
4010c0 _dl_relocate_static_pie>
4010c1 main>
401150 __libc_csu_init>
4011b0 __libc_csu_fini>
4011b4 _fini>
5.7 Hello的動(dòng)態(tài)鏈接分析
共享鏈接庫代碼是一個(gè)動(dòng)態(tài)的目標(biāo)模塊,在程序開始運(yùn)行或者調(diào)用程序加載時(shí),可以自動(dòng)加載該代碼到任意的一個(gè)內(nèi)存地址,并和一個(gè)在目標(biāo)模塊內(nèi)存中的應(yīng)用程序鏈接了起來,這個(gè)過程就是對動(dòng)態(tài)鏈接的重定位過程。
在elf文件中,有:
.got PROGBITS 0000000000403ff0 00002ff0
.got.plt PROGBITS 0000000000404000 00003000
0x0000000000000003 (PLTGOT) 0x404000
在edb中查看:
5.8 本章小結(jié)
本章介紹了有關(guān)鏈接的概念和作用,分析了hello的ELF格式以及虛擬地址空間是如何進(jìn)行分配的,介紹了重定位和動(dòng)態(tài)鏈接的過程。
第6章 hello進(jìn)程管理
6.1 進(jìn)程的概念與作用
進(jìn)程是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。在早期面向進(jìn)程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是程序的基本執(zhí)行實(shí)體;在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。
6.2 簡述殼Shell-bash的作用與處理流程
在Linux系統(tǒng)中,Shell是一個(gè)交互型應(yīng)用級程序,為用戶提供了與系統(tǒng)內(nèi)核進(jìn)行交互的方式。
主要功能是:Shell讀取輸入->處理輸入內(nèi)容,獲取輸入?yún)?shù)->如果是內(nèi)核命令則直接執(zhí)行,否則調(diào)用程序->當(dāng)程序運(yùn)行時(shí),shell會(huì)監(jiān)視用戶輸入并對此做出響應(yīng)。
具體的處理流程如下:
1.Shell從命令行中讀入特殊字符(元字符),在將元字符翻譯成間隔符號。元字符將命令行劃分成小塊tokens。Shell中的元字符如下所示:
SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |
2.處理tokens塊,檢查看他們是否是shell中所引用到的關(guān)鍵字。
3.當(dāng)程序塊tokens被確定以后,shell根據(jù)aliases文件中的列表來檢查命令的第1個(gè)關(guān)鍵詞。如果成功找到,執(zhí)行替換操作并且處理過程回到第1步再次處理程序塊tokens。
4.Shell對~符號進(jìn)行替換,對所有前面帶有符號的變量進(jìn)行替換。5.將命令行中內(nèi)嵌的符號的變量進(jìn)行替換。 5.將命令行中內(nèi)嵌的符號的變量進(jìn)行替換。5.將命令行中內(nèi)嵌的(command)命令表達(dá)式替換成命令
6.計(jì)算被$(expression)標(biāo)記的算術(shù)表達(dá)式。
7.Shell將命令串重新劃分為新的tokens塊。依欄位分割符號,稱為IFS。缺省的IFS變量包含有:SPACE , TAB和\n,并替換通配符,例如:* ? [ ]。
8.shell按照下面的順序檢查命令:
內(nèi)置命令->用戶自定義函數(shù)->按路徑尋找可執(zhí)行的腳本文件
9.對所有的輸入輸出重定向進(jìn)行初始化,最終執(zhí)行命令
6.3 Hello的fork進(jìn)程創(chuàng)建過程
根據(jù)shell的處理流程,可以推斷,輸入命令執(zhí)行hello后,父進(jìn)程如果判斷不是內(nèi)部指令,即會(huì)通過fork函數(shù)創(chuàng)建子進(jìn)程。子進(jìn)程與父進(jìn)程近似,并得到一份與父進(jìn)程用戶級虛擬空間相同且獨(dú)立的副本——包括數(shù)據(jù)段、代碼、共享庫、堆和用戶棧。父進(jìn)程打開的文件,子進(jìn)程也可讀寫。二者之間最大的不同或許在于PID的不同。Fork函數(shù)只會(huì)被調(diào)用一次,但會(huì)返回兩次,在父進(jìn)程中,fork返回子進(jìn)程的PID,在子進(jìn)程中,fork返回0。因?yàn)樽舆M(jìn)程的PID總是為非零,返回值就提供一個(gè)明確的方法來分辨程序是在父進(jìn)程里還是子進(jìn)程里執(zhí)行。
6.4 Hello的execve過程
execve函數(shù)在加載并運(yùn)行可執(zhí)行目標(biāo)文件Hello,且?guī)Я斜韆rgv和環(huán)境變量列表envp。該函數(shù)的作用就是在當(dāng)前進(jìn)程的上下文中加載并運(yùn)行一個(gè)新的程序。
只有當(dāng)出現(xiàn)錯(cuò)誤時(shí),例如找不到Hello時(shí),execve才會(huì)返回到調(diào)用程序,這里與一次調(diào)用兩次返回的fork不同。
在execve加載了Hello之后,它調(diào)用啟動(dòng)代碼。啟動(dòng)代碼設(shè)置棧,并將控制傳遞給新程序的主函數(shù),該主函數(shù)有如下的原型:
int main(intargc ,char **argv, char *envp)
結(jié)合虛擬內(nèi)存和內(nèi)存映射過程,可以更詳細(xì)地說明exceve函數(shù)實(shí)際上是如何加載和執(zhí)行程序Hello:
刪除已存在的用戶區(qū)域(自父進(jìn)程獨(dú)立)。
共享區(qū)映射:比如Hello程序與標(biāo)準(zhǔn)C庫libc.so鏈接,這些對象都是動(dòng)態(tài)鏈接到Hello的,然后再用戶虛擬地址空間中的共享區(qū)域內(nèi)。
私有區(qū)映射:為Hello的代碼、數(shù)據(jù)、.bss和棧區(qū)域創(chuàng)建新的區(qū)域結(jié)構(gòu),所有這些區(qū)域都是私有的、寫時(shí)才復(fù)制的。
設(shè)置PC:exceve會(huì)設(shè)置當(dāng)前進(jìn)程的上下文中的程序計(jì)數(shù)器,指向代碼區(qū)的入口。
6.5 Hello的進(jìn)程執(zhí)行
邏輯控制流:
一系列程序計(jì)數(shù)器 PC 的值的序列叫做邏輯控制流。由于進(jìn)程是輪流使用處理器的,同一個(gè)處理器每個(gè)進(jìn)程執(zhí)行它的流的一部分后被搶占,然后輪到其他進(jìn)程。
用戶模式和內(nèi)核模式:
處理器使用一個(gè)寄存器提供兩種模式的區(qū)分。用戶模式的進(jìn)程不允許執(zhí)行特殊指令,不允許直接引用地址空間中內(nèi)核區(qū)的代碼和數(shù)據(jù);內(nèi)核模式進(jìn)程可以執(zhí)行指令集中的任何命令,并且可以訪問系統(tǒng)中的任何內(nèi)存位置。
上下文:
上下文就是內(nèi)核重新啟動(dòng)一個(gè)被搶占的進(jìn)程所需要恢復(fù)的原來的狀態(tài),由寄存器、程序計(jì)數(shù)器、用戶棧、內(nèi)核棧和內(nèi)核數(shù)據(jù)結(jié)構(gòu)等對象的值構(gòu)成。
結(jié)合進(jìn)程上下文信息、進(jìn)程時(shí)間片,闡述進(jìn)程調(diào)度的過程,用戶態(tài)與核心態(tài)轉(zhuǎn)換等等。
初始時(shí),控制流位于hello進(jìn)程內(nèi),處于用戶模式
調(diào)用系統(tǒng)函數(shù)sleep后,進(jìn)入內(nèi)核態(tài),此時(shí)間片停止。
2s后,發(fā)送中斷信號,轉(zhuǎn)回用戶模式,繼續(xù)執(zhí)行指令。
調(diào)度的過程:
在進(jìn)程執(zhí)行的某些時(shí)刻,內(nèi)核可以決定搶占當(dāng)前進(jìn)程,并重新開始一個(gè)先前被搶占了的進(jìn)程,這種決策就叫做調(diào)度,是由內(nèi)核中稱為調(diào)度器的代碼處理的。當(dāng)內(nèi)核選擇一個(gè)新的進(jìn)程運(yùn)行,我們說內(nèi)核調(diào)度了這個(gè)進(jìn)程。在內(nèi)核調(diào)度了一個(gè)新的進(jìn)程運(yùn)行了之后,它就搶占了當(dāng)前進(jìn)程,并使用上下文切換機(jī)制來將控制轉(zhuǎn)移到新的進(jìn)程。
以執(zhí)行sleep函數(shù)為例,sleep函數(shù)請求調(diào)用休眠進(jìn)程,sleep將內(nèi)核搶占,進(jìn)入倒計(jì)時(shí),當(dāng)?shù)褂?jì)時(shí)結(jié)束后,hello程序重新?lián)屨純?nèi)核,繼續(xù)執(zhí)行。
用戶態(tài)與核心態(tài)轉(zhuǎn)換:
為了能讓處理器安全運(yùn)行,不至于損壞操作系統(tǒng),必然需要先知應(yīng)用程序可執(zhí)行指令所能訪問的地址空間范圍。因此,就存在了用戶態(tài)與核心態(tài)的劃分,核心態(tài)擁有最高的訪問權(quán)限,處理器以一個(gè)寄存器當(dāng)做模式位來描述當(dāng)前進(jìn)程的特權(quán)。進(jìn)程只有故障、中斷或陷入系統(tǒng)調(diào)用時(shí)才會(huì)得到內(nèi)核訪問權(quán)限,其他情況下始終處于用戶權(quán)限之中,保證了系統(tǒng)的安全性。
6.6 hello的異常與信號處理
hello執(zhí)行過程中可能會(huì)出現(xiàn)以下異常:
類別 原因 異步/同步 返回行為
中斷 來自I/O設(shè)備的信號 異步 返回到下一條指令
陷阱 有意的異常 同步 返回到下一條指令
故障 潛在的可恢復(fù)的錯(cuò)誤 同步 可能返回到當(dāng)前指令
終止 不可恢復(fù)的錯(cuò)誤 同步 不會(huì)返回
運(yùn)行程序時(shí),參數(shù)是學(xué)號+姓名+秒數(shù),在命令行中鍵入./hello 120L021327 DAIHONGQIAN 1,可以得到如下結(jié)果:
執(zhí)行ctrl+Z命令,可以將正在運(yùn)行的hello程序掛起,我們鍵入ps命令,可以查看正在運(yùn)行的進(jìn)程的PID,hello進(jìn)程的PID為3399,再使用jobs命令查看正在運(yùn)行的后臺(tái)任務(wù),也可以看到我們剛剛鍵入的./hello 120L021327 DAIHONGQIAN 1,此時(shí)使用fg命令,我們可以把后臺(tái)的hello調(diào)回到前臺(tái)繼續(xù)執(zhí)行。[4]
接下來,我們鍵入ctrl+C的終止指令,進(jìn)程收到 SIGINT 信號,我們可以結(jié)束徹底結(jié)束hello進(jìn)程,這時(shí)我們再通過ps和jobs命令檢查正在運(yùn)行的命令,我們發(fā)現(xiàn)hello進(jìn)程已經(jīng)結(jié)束。
中途隨意從鍵盤鍵入字符,都會(huì)被接收至緩沖區(qū),命令行無法解析無意義的字符串,會(huì)提示command not found。
kill命令會(huì)殺死掛起的程序,執(zhí)行kill命令后無法查詢到hello的PID。
6.7本章小結(jié)
本章主要介紹了有關(guān)hello進(jìn)程管理的內(nèi)容,通過shell我們可以管理計(jì)算機(jī)運(yùn)行的進(jìn)程。我們還介紹了hello程序的fork、execve過程,然后通過命令行研究了程序運(yùn)行當(dāng)中可能會(huì)遇見的異常,以及我們的系統(tǒng)是如何處理這些由異常發(fā)出的信號的。
第7章 hello的存儲(chǔ)管理
7.1 hello的存儲(chǔ)器地址空間
邏輯地址,是指由程序hello產(chǎn)生的與段相關(guān)的偏移地址部分,hello.o文件中的地址就是邏輯地址。
線性地址,是邏輯地址到物理地址變換之間的中間層。程序hello的代碼會(huì)產(chǎn)生邏輯地址——也就是段中的偏移地址,它加上相應(yīng)段的基地址就會(huì)生成線性地址。
虛擬地址,是由CPU生成的一個(gè)仰賴訪問主存的地址,它會(huì)被送到內(nèi)存之前先轉(zhuǎn)換成適當(dāng)?shù)奈锢淼刂?#xff0c;地址翻譯的任務(wù)由CPU芯片上的內(nèi)存管理單元MMU來承擔(dān),MMU會(huì)用存放在主存中的查詢表來動(dòng)態(tài)翻譯虛擬地址,該表的內(nèi)容由操作系統(tǒng)管理。在linux系統(tǒng)中,只會(huì)分頁而不會(huì)分段,因此對于我們的hello程序邏輯地址幾乎就是虛擬地址。
物理地址,是主存上被組織成一個(gè)由M個(gè)連續(xù)的字節(jié)大小的單元組成的數(shù)組,每個(gè)字節(jié)都有一個(gè)唯一的物理地址,hello的物理地址來自于虛擬地址的地址轉(zhuǎn)換,它也是程序運(yùn)行的最終地址。
7.2 Intel邏輯地址到線性地址的變換-段式管理
一個(gè)邏輯地址由兩部分組成,段標(biāo)識(shí)符,段內(nèi)偏移量。段標(biāo)識(shí)符是一個(gè)16位長的字段組成,稱為段選擇符,其中前13位是一個(gè)索引號。后面三位包含一些硬件細(xì)節(jié)。
索引號,可以通過段標(biāo)識(shí)符的前13位,直接在段描述符表中找到一個(gè)具體的段描述符,這個(gè)描述符就描述了一個(gè)段。
Base,它描述了一個(gè)段的開始位置的線性地址。
全局的段描述符,放在全局段描述符表(GDT)中,而局部的段描述符,處于局部段描述符表(LDT)之中。GDT在內(nèi)存中的地址和大小會(huì)存放在gdtr控制寄存器中,而LDT則在ldtr寄存器中。
一個(gè)完整的邏輯地址包括段選擇符+段內(nèi)偏移地址,
7.3 Hello的線性地址到物理地址的變換-頁式管理
頁式管理,是一種內(nèi)存空間存儲(chǔ)管理的技術(shù),分為靜態(tài)頁式管理和動(dòng)態(tài)頁式管理。頁式管理將各進(jìn)程的虛擬空間劃分成若干個(gè)長度相等的頁(page),再把內(nèi)存空間按頁的大小劃分成片,進(jìn)而建立起內(nèi)存地址與頁式虛擬地址一一對應(yīng)關(guān)系,存儲(chǔ)在頁表之中。為了解決離散地址變換問題,它會(huì)調(diào)用相應(yīng)的硬件地址變換構(gòu)件。頁式管理采用請求調(diào)頁或預(yù)調(diào)頁技術(shù)實(shí)現(xiàn)了內(nèi)外存的統(tǒng)一管理與調(diào)用。
優(yōu)點(diǎn):它有效地解決了碎片問題。由于頁式管理不需要進(jìn)程的程序段和數(shù)據(jù)在內(nèi)存中連續(xù)存放,從而提供了內(nèi)外存統(tǒng)一管理的虛擬內(nèi)存實(shí)現(xiàn)方式,使用戶可以利用的碎片化的空間大大增加,也非常有利于多個(gè)進(jìn)程同時(shí)執(zhí)行。
缺點(diǎn):需要相應(yīng)的硬件支持,增加了機(jī)器成本,并增加了系統(tǒng)開銷。例如缺頁中斷的產(chǎn)生和選擇淘汰頁面等都要求有相應(yīng)的硬件支持;缺頁中斷處理時(shí),請求調(diào)頁的算法如果不夠恰當(dāng),有可能產(chǎn)生抖動(dòng)現(xiàn)象。碎片式的管理,也使得每個(gè)進(jìn)程內(nèi)總有一部分空間得不到利用,當(dāng)頁面比較大的時(shí)候,這一部分的損失會(huì)非常顯著。
7.4 TLB與四級頁表支持下的VA到PA的變換
TLB:每次CPU產(chǎn)生一個(gè)虛擬地址,MMU就必須查閱相應(yīng)的PTE,每次都必然造成緩存不命中等一系列時(shí)間開銷,為了消除這樣的開銷,MMU中存在一個(gè)全相聯(lián)高速緩存,稱為TLB。
四級頁表:如果只采用兩級的頁表結(jié)構(gòu),當(dāng)需要使用的虛擬內(nèi)存很小,但仍然需要建立其一個(gè)龐大的頁表,這很容易造成內(nèi)存的浪費(fèi),檢索巨大的頁表也是非常浪費(fèi)性能的事。由此,在虛擬地址到物理地址的轉(zhuǎn)換過程中,又開發(fā)出了多級頁表的機(jī)制:上一級的頁表映射到下一級頁表,直到頁表映射到虛擬內(nèi)存。四級頁表,也就指共有四級的多級頁表。
7.5 三級Cache支持下的物理內(nèi)存訪問
物理地址被分為CT(標(biāo)記)+CL(索引)+CO(偏移量),然后到1級cache里去找對應(yīng)的標(biāo)記位為有效的。如果命中就直接返回想要的數(shù)據(jù),如果不命中,就依次去L2,L3,主存判斷是否命中,當(dāng)命中時(shí),將數(shù)據(jù)傳給CPU同時(shí)更新各級cache的cacheline。當(dāng)cache已滿時(shí),需要按一定策略,將cache中近期不太可能再次訪問到的數(shù)據(jù)替換為我們所需要的物理內(nèi)存訪問索引。
7.6 hello進(jìn)程fork時(shí)的內(nèi)存映射
當(dāng)fork函數(shù)被當(dāng)前進(jìn)程調(diào)用時(shí),內(nèi)核為新進(jìn)程創(chuàng)建各種數(shù)據(jù)結(jié)構(gòu),并分配給它一個(gè)唯一的PID,同時(shí)為這個(gè)新進(jìn)程創(chuàng)建虛擬內(nèi)存。它創(chuàng)建了當(dāng)前進(jìn)程的mm_struct、區(qū)域結(jié)構(gòu)和頁表的副本,將兩個(gè)進(jìn)程中的每個(gè)頁面都標(biāo)記位只讀,并將兩個(gè)進(jìn)程中的每個(gè)區(qū)域結(jié)構(gòu)都標(biāo)記為私有的寫時(shí)復(fù)制。
當(dāng)fork在新進(jìn)程中返回時(shí),新進(jìn)程現(xiàn)在的虛擬內(nèi)存剛好和調(diào)用fork時(shí)存在的虛擬內(nèi)存相同。當(dāng)這兩個(gè)進(jìn)程中的任一個(gè)后來進(jìn)行寫操作時(shí),寫時(shí)復(fù)制機(jī)制就會(huì)創(chuàng)建新頁面。因此,也就為每個(gè)進(jìn)程保持了私有空間地址的抽象概念。
7.7 hello進(jìn)程execve時(shí)的內(nèi)存映射
1)在bash中的進(jìn)程中執(zhí)行了execve調(diào)用:execve(“hello”,NULL,NULL);
2)execve函數(shù)在當(dāng)前進(jìn)程中加載并運(yùn)行包含在可執(zhí)行文件hello中的程序,用hello替代了當(dāng)前bash中的程序。
3)刪除已存在的用戶區(qū)域。
4)映射私有區(qū)域
5)映射共享區(qū)域
6)設(shè)置程序計(jì)數(shù)器(PC)
exceve做的最后一件事是設(shè)置當(dāng)前進(jìn)程的上下文中的程序計(jì)數(shù)器,是指指向代碼區(qū)域的入口點(diǎn)。而下一次調(diào)度這個(gè)進(jìn)程時(shí),他將從這個(gè)入口點(diǎn)開始執(zhí)行。Linux將根據(jù)需要換入代碼和數(shù)據(jù)頁面。
7.8 缺頁故障與缺頁中斷處理
整體的處理流程:
1.處理器生成一個(gè)虛擬地址,并將它傳送給MMU
2.MMU生成PTE地址,并從高速緩存/主存請求得到它
3.高速緩存/主存向MMU返回PTE
4.PTE中的有效位是0,所以MMU出發(fā)了一次異常,傳遞CPU中的控制到操作系統(tǒng)內(nèi)核中的缺頁異常處理程序。
5.缺頁處理程序確認(rèn)出物理內(nèi)存中的犧牲頁,如果這個(gè)頁已經(jīng)被修改了,則把它換到磁盤。
6.缺頁處理程序頁面調(diào)入新的頁面,并更新內(nèi)存中的PTE
7.缺頁處理程序返回到原來的進(jìn)程,再次執(zhí)行導(dǎo)致缺頁的命令。CPU將引起缺頁的虛擬地址重新發(fā)送給MMU。因?yàn)樘摂M頁面已經(jīng)換存在物理內(nèi)存中,所以就會(huì)命中。
7.9動(dòng)態(tài)存儲(chǔ)分配管理
基本方法與策略:通過維護(hù)虛擬內(nèi)存(堆),一種是隱式空閑鏈表,一種是顯式空閑鏈表。顯式空閑鏈表法是malloc(size_t size)每次聲明內(nèi)存空間都保證至少分配size_t大小的內(nèi)存,雙字對齊,每次必須從空閑塊中分配空間,在申請空間時(shí)將空閑的空間碎片合并,以盡量減少浪費(fèi)。分配器一般按以下策略進(jìn)行分配:首次適配、下一次適配合最佳適配。分配完后可以分割空閑塊減少內(nèi)部碎片。同時(shí)分配器在面對釋放一個(gè)已分配塊時(shí),可以合并空閑塊,其中便利用隱式空閑鏈表的邊界標(biāo)記來進(jìn)行合并。
7.10本章小結(jié)
本章主要介紹了 hello 的存儲(chǔ)器地址空間、 intel 的段式管理、 hello 的頁式管理,在指定環(huán)境下介紹了 VA 到 PA 的變換、物理內(nèi)存訪問,還介紹 hello 進(jìn)程 fork 時(shí)的內(nèi)存映射、 execve 時(shí)的內(nèi)存映射、缺頁故障與缺頁中斷處理、動(dòng)態(tài)存儲(chǔ)分配管理。
第8章 hello的IO管理
8.1 Linux的IO設(shè)備管理方法
設(shè)備的模型化
文件(所有的I/O設(shè)備都被模型化為文件,甚至內(nèi)核也被映射為文件)
設(shè)備管理
unix io接口
這種將設(shè)備映射為系統(tǒng)文件的方式,允許Linux內(nèi)核引出一個(gè)簡單的應(yīng)用接口,稱為Unix I/O。
文件操作包括:打開和關(guān)閉操作;讀寫操作;更改當(dāng)前文件位置……
8.2 簡述Unix IO接口及其函數(shù)
open函數(shù)
功能描述:打開或創(chuàng)建文件,可以指定文件的屬性及用戶的權(quán)限等各種參數(shù)。
函數(shù)原型:int open(const char *pathname,int flags,int perms)
參數(shù):pathname:被打開的文件所在路徑,flags:文件打開方式,
返回值:成功:返回文件描述符;失敗:返回-1
close函數(shù)
功能描述:用于關(guān)閉一個(gè)被打開的的文件
所需頭文件: #include <unistd.h>
函數(shù)原型:int close(int fd) 參數(shù):fd文件描述符
函數(shù)返回值:0成功,-1出錯(cuò)
read函數(shù)
功能描述: 讀取文件中數(shù)據(jù)
所需頭文件: #include <unistd.h>
函數(shù)原型:ssize_t read(int fd, void *buf, size_t count);
參數(shù):fd:目標(biāo)文件描述符。buf:緩沖區(qū)。count:表示調(diào)用一次read操作,應(yīng)該讀取的字符數(shù)量。
返回值:返回所讀取的字節(jié)數(shù);-1(出錯(cuò))。
write函數(shù)
功能描述:寫入數(shù)據(jù)。
所需頭文件:#include <unistd.h>
函數(shù)原型:ssize_t write(int fd, void *buf, size_t count);
返回值:寫入文件的字節(jié)數(shù)(成功);-1(出錯(cuò))
lseek()函數(shù)
功能描述: 用于在指定的文件描述符中將將文件指針定位到相應(yīng)位置。
所需頭文件:#include <unistd.h>,#include <sys/types.h>
函數(shù)原型:off_t lseek(int fd, off_t offset,int whence);
參數(shù):fd;文件描述符。offset:偏移量,每一個(gè)讀寫操作所需要移動(dòng)的距離,單位是字節(jié),可正可負(fù)(向前移,向后移)
返回值:成功:返回當(dāng)前位移;失敗:返回-1
8.3 printf的實(shí)現(xiàn)分析
(以下格式自行編排,編輯時(shí)刪除)
printf函數(shù)的函數(shù)體:
int printf(const char *fmt, …)
{
int i;
char buf[256];
}
vsprintf函數(shù)將所有的參數(shù)內(nèi)容格式化之后存入buf,然后返回格式化數(shù)組的長度。write函數(shù)將buf中的i個(gè)元素寫到終端。從vsprintf生成顯示信息,到write系統(tǒng)函數(shù),到陷阱-系統(tǒng)調(diào)用 int 0x80或syscall.字符顯示驅(qū)動(dòng)子程序:從ASCII到字模庫到顯示vram(存儲(chǔ)每一個(gè)點(diǎn)的RGB顏色信息)。顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個(gè)點(diǎn)。
顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個(gè)點(diǎn)(RGB分量),最后就在顯示器上實(shí)現(xiàn)了信息的打印。
8.4 getchar的實(shí)現(xiàn)分析
異步異常-鍵盤中斷的處理:
getchar可以理解為對于鍵盤中斷的異步異常的處理。當(dāng)鍵盤中斷處理子程序。系統(tǒng)會(huì)接受按鍵輸入的信息,并將其轉(zhuǎn)換為ascii碼,保存到鍵盤輸入的緩沖區(qū)。getchar等調(diào)用read系統(tǒng)函數(shù),通過系統(tǒng)調(diào)用讀取按鍵輸入數(shù)據(jù),當(dāng)接受到回車\n時(shí)才返回。getchar有一個(gè)int型的返回值。當(dāng)程序調(diào)用getchar時(shí),程序就等著用戶按鍵,用戶輸入的字符被存放在鍵盤緩沖區(qū)中直到用戶按回車為止,回車字符\n也會(huì)被讀入緩沖區(qū)中。
當(dāng)用戶鍵入回車之后,getchar開始從stdio流中每次讀入一個(gè)字符。getchar函數(shù)的返回值是用戶輸入的第一個(gè)字符的ascii碼,如出錯(cuò)返回-1,且將用戶輸入的字符回顯到屏幕。如用戶在按回車之前輸入了不止一個(gè)字符,其他字符會(huì)保留在鍵盤緩存區(qū)中,等待后續(xù)getchar調(diào)用讀取。也就是說,后續(xù)的getchar調(diào)用不會(huì)等待用戶按鍵,而直接讀取緩沖區(qū)中的字符,直到緩沖區(qū)中的字符讀完為后,才等待用戶按鍵。
8.5本章小結(jié)
本章介紹了 Linux 的 I/O 設(shè)備的基本概念和管理方法,以及Unix I/O 接口及其函數(shù)。最后分析了printf 函數(shù)和 getchar 函數(shù)的工作過程。
結(jié)論
最初接觸到C 語言的我們,在計(jì)算機(jī)的屏幕前,通過鍵盤鼠標(biāo)等I/O設(shè)備鍵入hello程序的代碼,將它保存為了.c的文本文件,我們點(diǎn)擊codeblocks中的編譯按鈕,再運(yùn)行可執(zhí)行的文件,hello便橫空出世了。對于一個(gè)初學(xué)者而言,編寫這樣結(jié)構(gòu)簡單的程序似乎不是一件難事,然而計(jì)算機(jī)系統(tǒng)這一節(jié)課把hello的神秘面紗緩緩解開了,我們看到了hello的“漫漫長征路”:
1.hello.c經(jīng)過預(yù)編譯,拓展得到hello.i文本文件
2.hello.i經(jīng)過編譯,得到匯編代碼hello.s匯編文件
3.hello.s經(jīng)過匯編,得到二進(jìn)制可重定位目標(biāo)文件hello.o
4.hello.o經(jīng)過鏈接,生成了可執(zhí)行文件hello
5.bash進(jìn)程調(diào)用fork函數(shù),生成子進(jìn)程;并由execve函數(shù)加載運(yùn)行當(dāng)前進(jìn)程的上下文中加載并運(yùn)行新程序hello
6.hello的變化過程中,會(huì)有各種地址,但最終我們真正期待的是PA物理地址。
7.hello再運(yùn)行時(shí)會(huì)調(diào)用一些函數(shù),比如printf函數(shù),這些函數(shù)與linux I/O的設(shè)備模擬化密切相關(guān)
8.hello最終被shell父進(jìn)程回收,內(nèi)核會(huì)收回為其創(chuàng)建的所有信息
hello的一生并不是一條平坦的大路,它更像是個(gè)通過神秘穿梭機(jī)來回變換的時(shí)間旅客,每一步的變換都極端復(fù)雜,讓人琢磨不透。然而通過計(jì)算機(jī)系統(tǒng)的學(xué)習(xí),我們可以將這些“魔術(shù)”背后的奧秘一一揭曉,由0和1,人類的智慧一步步將它變?yōu)楦邩谴髲B,一切復(fù)雜的系統(tǒng)背后都是非常質(zhì)樸的想法,為了貼切地把這個(gè)想法落地生根,精妙而復(fù)雜的系統(tǒng)就被建立了起來。
我們現(xiàn)在所使用的計(jì)算機(jī)系統(tǒng),經(jīng)過了UI的改革,變得直觀易懂,而繁瑣的系統(tǒng)實(shí)現(xiàn)的細(xì)節(jié)被悄悄地包裝在了我們看不見的深處里,這門課程之所以學(xué)習(xí)接近底層的那些細(xì)節(jié),不厭其煩地探討那些被藏起來的內(nèi)容,能很好地幫助我們認(rèn)清計(jì)算機(jī)系統(tǒng)的實(shí)質(zhì)。也許科技發(fā)展到怎樣的地步,我們提起駭客、提起極客這類名詞時(shí),腦海中浮現(xiàn)的都是那個(gè)手指飛快地敲擊鍵盤,屏幕上是熒光綠色的命令行,一切可能,皆系于其中。
附件
?預(yù)處理后的文件 hello.i
?編譯之后的匯編文件 hello.s
?匯編之后的可重定位目標(biāo)文件 hello.o
?鏈接之后的可執(zhí)行目標(biāo)文件 Hello
?Hello.o 的 ELF 格式 elf.txt
?Hello.o 的反匯編代碼 Disas_hello.s
?hello的ELF 格式 hello1.elf
hello 的反匯編代碼 hello1_objdump.s
參考文獻(xiàn)
[1] 條件編譯,C語言條件編譯詳解. http://c.biancheng.net/view/289.html
[2] .bss、.data 和 .rodata section 之間的區(qū)別. https://blog.csdn.net/wohenfanjian/article/details/106007978
[3] 信息安全系統(tǒng)設(shè)計(jì)基礎(chǔ). 異常控制流.
[4] linux后臺(tái)運(yùn)行、掛起、恢復(fù)進(jìn)程相關(guān)命令https://blog.csdn.net/koberonaldo24/article/details/103136125
[5] printf 函數(shù)實(shí)現(xiàn)的深入剖析. https://www.cnblogs.com/pianist/p/3315801.html
總結(jié)
以上是生活随笔為你收集整理的HIT计算机系统大作业——hello的一生的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Android]安装 Android
- 下一篇: [Python3]Python面向对象的