柚子快報激活碼778899分享:HITICS大作業(yè)——程序人生
柚子快報激活碼778899分享:HITICS大作業(yè)——程序人生
摘? 要
為了深入理解一個程序從無到有,從運行到終止的過程,本文論述了hello.c在編寫完成后運行在Linux系統(tǒng)中的生命歷程,運用相關(guān)工具展示了hello.c文件預(yù)處理、編譯、匯編、鏈接、運行、回收等階段并進(jìn)行分析。同時介紹了shell的內(nèi)存管理,IO管理,進(jìn)程管理等相關(guān)知識,了解虛擬內(nèi)存、異常信號等內(nèi)容,通過本次大作業(yè)實現(xiàn)對書本中知識的更深入理解。
關(guān)鍵詞:計算機(jī)系統(tǒng);編譯;進(jìn)程;虛擬內(nèi)存???????????????????????????
目? 錄
第1章 概述................................................................................... - 4 -
1.1 Hello簡介............................................................................ - 4 -
1.2 環(huán)境與工具........................................................................... - 4 -
1.3 中間結(jié)果............................................................................... - 5 -
1.4 本章小結(jié)............................................................................... - 5 -
第2章 預(yù)處理............................................................................... - 6 -
2.1 預(yù)處理的概念與作用........................................................... - 6 -
2.2在Ubuntu下預(yù)處理的命令................................................ - 6 -
2.3 Hello的預(yù)處理結(jié)果解析.................................................... - 7 -
2.4 本章小結(jié)............................................................................. - 10 -
第3章 編譯.................................................................................. - 11 -
3.1 編譯的概念與作用............................................................. - 11 -
3.2 在Ubuntu下編譯的命令.................................................. - 11 -
3.3 Hello的編譯結(jié)果解析...................................................... - 12 -
3.4 本章小結(jié)............................................................................. - 19 -
第4章 匯編................................................................................. - 20 -
4.1 匯編的概念與作用............................................................. - 20 -
4.2 在Ubuntu下匯編的命令.................................................. - 20 -
4.3 可重定位目標(biāo)elf格式...................................................... - 20 -
4.4 Hello.o的結(jié)果解析........................................................... - 24 -
4.5 本章小結(jié)............................................................................. - 27 -
第5章 鏈接................................................................................. - 28 -
5.1 鏈接的概念與作用............................................................. - 28 -
5.2 在Ubuntu下鏈接的命令.................................................. - 28 -
5.3 可執(zhí)行目標(biāo)文件hello的格式......................................... - 29 -
5.4 hello的虛擬地址空間....................................................... - 33 -
5.5 鏈接的重定位過程分析..................................................... - 34 -
5.6 hello的執(zhí)行流程............................................................... - 37 -
5.7 Hello的動態(tài)鏈接分析...................................................... - 38 -
5.8 本章小結(jié)............................................................................. - 39 -
第6章 hello進(jìn)程管理.......................................................... - 40 -
6.1 進(jìn)程的概念與作用............................................................. - 40 -
6.2 簡述殼Shell-bash的作用與處理流程........................... - 40 -
6.3 Hello的fork進(jìn)程創(chuàng)建過程............................................ - 40 -
6.4 Hello的execve過程........................................................ - 41 -
6.5 Hello的進(jìn)程執(zhí)行.............................................................. - 41 -
6.6 hello的異常與信號處理................................................... - 42 -
6.7本章小結(jié).............................................................................. - 47 -
第7章 hello的存儲管理...................................................... - 48 -
7.1 hello的存儲器地址空間................................................... - 48 -
7.2 Intel邏輯地址到線性地址的變換-段式管理.................. - 48 -
7.3 Hello的線性地址到物理地址的變換-頁式管理............. - 49 -
7.4 TLB與四級頁表支持下的VA到PA的變換................... - 50 -
7.5 三級Cache支持下的物理內(nèi)存訪問................................ - 52 -
7.6 hello進(jìn)程fork時的內(nèi)存映射......................................... - 52 -
7.7 hello進(jìn)程execve時的內(nèi)存映射..................................... - 53 -
7.8 缺頁故障與缺頁中斷處理................................................. - 54 -
7.9動態(tài)存儲分配管理.............................................................. - 55 -
7.10本章小結(jié)............................................................................ - 56 -
第8章 hello的IO管理....................................................... - 57 -
8.1 Linux的IO設(shè)備管理方法................................................. - 57 -
8.2 簡述Unix IO接口及其函數(shù).............................................. - 57 -
8.3 printf的實現(xiàn)分析.............................................................. - 59 -
8.4 getchar的實現(xiàn)分析.......................................................... - 61 -
8.5本章小結(jié).............................................................................. - 62 -
結(jié)論............................................................................................... - 62 -
附件............................................................................................... - 64 -
參考文獻(xiàn)....................................................................................... - 65 -
第1章 概述
1.1 Hello簡介
Hello的P2P是指,hello程經(jīng)過預(yù)處理,編譯,匯編,鏈接得到可執(zhí)行目標(biāo)文件。
程序員在文本編輯器中編寫程序的源代碼,保存為hello.c文件。預(yù)處理階段:預(yù)處理器根據(jù)以#開頭的命令來修改原始程序生成hello.i文本文件。編譯階段:編譯器將文本文件hello.i翻譯成文本文件hello.s,它包含一個匯編語言程序,該程序包含函數(shù)main的定義。匯編階段:匯編器將hello.s翻譯成機(jī)器語言指令,打包成可重定位目標(biāo)程序的格式,結(jié)果保存在hello.o。鏈接階段:鏈接器將目標(biāo)文件與庫文件鏈接在一起,生成可執(zhí)行文件。
圖 1 編譯系統(tǒng)
Hello的020是指hello如何在進(jìn)程中執(zhí)行并被回收的過程。
通過shell輸入./hello命令開始執(zhí)行程序,shell通過fork函數(shù)創(chuàng)建它的子進(jìn)程,再由子進(jìn)程執(zhí)行execve函數(shù)加載hello,在execve函數(shù)執(zhí)行hello程序后,內(nèi)核為其映射虛擬內(nèi)存、分配物理內(nèi)存,程序開始執(zhí)行,內(nèi)核為程序分配時間片執(zhí)行邏輯控制流,當(dāng)hello運行結(jié)束,shell接受到相應(yīng)的信號,啟動信號處理機(jī)制,對該進(jìn)程進(jìn)行回收處理,釋放其所占的內(nèi)存并刪除有關(guān)進(jìn)程上下文。
1.2 環(huán)境與工具
硬件環(huán)境:12th Gen Intel(R) Core(TM) i9-12900H?? 2.50 GHz? x64
軟件環(huán)境:windows 11 64位;Ubuntu 22.04.03;VMware 16.2.2 build-19200509
使用工具:codeblocks;gcc;gdb;edb;objdump
1.3 中間結(jié)果
(1)hello.c:源代碼
(2)hello.i:預(yù)處理后的文本文件
(3)hello.s:編譯后的匯編文件
(4)hello.o:匯編后的可重定位目標(biāo)執(zhí)行文件
(5)hello:鏈接之后的可執(zhí)行文件
(6)hello_elf.txt:用readelf讀取hello.o得到的ELF格式信息
(7)hello1_elf.txt:用readelf讀取hello得到的ELF格式信息
(8)hello.txt:hello反匯編的結(jié)果
(9)hello_o.txt:hello.o反匯編的結(jié)果
1.4 本章小結(jié)
???? 本章主要簡單介紹了hello的P2P,020過程,并且簡要介紹了P2P和020是什么,展示了一個源程序經(jīng)過預(yù)處理、編譯、匯編、鏈接等階段,最終成為一個可執(zhí)行目標(biāo)文件的過程,最后列出了本次實驗的實驗環(huán)境和使用工具以及實驗過程產(chǎn)生的中間文件。
第2章 預(yù)處理
2.1 預(yù)處理的概念與作用
預(yù)處理是編程中編譯源代碼的第一步,它在編譯器開始工作之前執(zhí)行。在C、C++等語言中,預(yù)處理器不是一個編譯器的組成部分,而是一個獨立的程序,它讀取源代碼文件,根據(jù)預(yù)處理指令(常見的有#include、#define、#ifndef、#endif等等)來處理源程序的文本,然后生成一個修改后的源文件,這個文件隨后將被編譯器處理。
預(yù)處理的作用:
宏替換:預(yù)處理器會處理源代碼中的宏定義(通常用#define指令定義),它會將所有宏調(diào)用替換為它們的值或定義的代碼塊,通過使用宏名來代替一段字符串,方便程序員編寫程序,有利于代碼的簡潔性和可讀性。文件包含:#include指令告訴預(yù)處理器將另一個文件的內(nèi)容包含進(jìn)來,可以用來共享代碼,比如頭文件,保證了之后的編譯過程能夠正確進(jìn)行。條件編譯:預(yù)處理器支持條件編譯,允許根據(jù)不同的條件包含或排除代碼塊,通常通過#ifdef、#ifndef、#endif等指令實現(xiàn),可以有效刪去一些無用代碼,從而減少編譯過程的工作量。
2.2在Ubuntu下預(yù)處理的命令
使用gcc -E hello.c -o hello.i指令進(jìn)行預(yù)處理
圖 2 gcc預(yù)處理指令
2.3 Hello的預(yù)處理結(jié)果解析
源程序hello.c
圖 3 源程序
預(yù)處理后hello.i文本文件(部分截圖)
圖 4 預(yù)處理后文件
圖 5 預(yù)處理后文件
預(yù)處理后,文件的格式仍為文本文件,文件的行數(shù)增加到了3092行,預(yù)處理后的文件中沒有了#include等代碼,說明預(yù)處理過程中將#include包含的文件加入到了源程序中。例如stdio.h是標(biāo)準(zhǔn)庫文件,預(yù)處理器會到Linux系統(tǒng)的環(huán)境變量下尋找stdio.h,打開/usr/include/stdio.h。若stdio.h使用了“#define”“#include” 等,對它們進(jìn)行遞歸展開替換,對于其中使用的“#ifdef”、“#ifndef”等條件編譯語句,預(yù)處理器會對條件值進(jìn)行判斷來決定是否對此部分進(jìn)行包含。
原來的hello.c文件中的注釋都被刪除,余下的原程序部分沒有發(fā)生任何變化,在hello.i文件的結(jié)尾。
hello.i文件開頭為源程序的相關(guān)信息。
圖 6 hello.i文件開頭信息
隨后依次進(jìn)行頭文件stdio.h unistd.h stdlib.h的展開。
圖 7 hello.i中頭文件展開
類型定義信息
圖 8 hello.i中類型定義信息
函數(shù)聲明信息
圖 9 hello.i中函數(shù)聲明信息
hello.c源代碼在hello.i文件結(jié)尾,除注釋和以“#”開頭的語句被刪除外,其他內(nèi)容保持不變。
圖 10 hello.i中結(jié)尾信息
2.4 本章小結(jié)
本章介紹了預(yù)處理的相關(guān)概念、作用和linux系統(tǒng)中的預(yù)處理指令,并通過查看hello.i文件和hello.c文件對比他們的不同,分析了預(yù)處理的過程與結(jié)果。預(yù)處理過程實質(zhì)上來說是文本增加、刪除和替換的過程,是一個通過宏展開、宏替換、插入頭文件等操作,使得程序中的宏引用被遞歸地替換掉的過程,生成.i文件后交給編譯器進(jìn)行處理。
第3章 編譯
3.1 編譯的概念與作用
C編譯器在進(jìn)行具體的程序翻譯之前,會先對源程序進(jìn)行詞法分析和語法分析,然后根據(jù)分析的結(jié)果進(jìn)行代碼優(yōu)化和存儲分配,最終把C語言源程序翻譯成匯編語言程序??偟恼f來,編譯是指將預(yù)處理后的.i文件翻譯成匯編語言程序.s文件的過程,這一過程由C編譯器(ccl)完成。
編譯的作用:
詞法分析:編譯器首先將源代碼分解成一個個的詞素,詞素是編程語言中的基本元素,如關(guān)鍵字、標(biāo)識符、操作符等。語法分析:編譯器根據(jù)編程語言的語法規(guī)則,將詞素組織成語法樹,確保源代碼的結(jié)構(gòu)符合語言規(guī)范。語義分析:編譯器檢查語法樹中的每個節(jié)點,確保它們在語義上是正確的。例如,它會檢查變量是否已被聲明、類型是否匹配等。中間代碼:源程序的一種內(nèi)部表示,或稱中間語言。中間代碼的作用是可使編譯程序的結(jié)構(gòu)在邏輯上更為簡單明確,特別是可使目標(biāo)代碼的優(yōu)化比較容易實現(xiàn)中間代碼。優(yōu)化:編譯器對中間代碼進(jìn)行優(yōu)化,以提高程序的執(zhí)行效率。優(yōu)化可以是局部的,也可以是全局的,目的是減少資源消耗和提高性能。目標(biāo)代碼:目標(biāo)代碼生成器把語法分析后或優(yōu)化后的中間代碼變換成目標(biāo)代碼,此處指目標(biāo)代碼為匯編代碼??梢哉f,編譯的作用是通過一系列步驟讓源代碼更接近機(jī)器語言,編譯是匯編階段翻譯成機(jī)器語言的前提。
3.2 在Ubuntu下編譯的命令
使用gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s指令進(jìn)行編譯
圖 11 gcc編譯指令
圖 12 編譯后文件
3.3 Hello的編譯結(jié)果解析
3.3.1數(shù)據(jù)
數(shù)據(jù)包括常量、變量(全局/局部/靜態(tài))、表達(dá)式、類型、宏,大作業(yè)程序中包含常量和局部變量,接下來對這兩部分進(jìn)行分析。
字符串常量
在hello.c中printf函數(shù)括號內(nèi)的字符串"用法: Hello 學(xué)號 姓名 秒數(shù)!\n"以及"Hello %s %s\n"。
圖 13 printf字符串
LC1里存放了"Hello %s %s\n",LC0存放了字符串"用法: Hello 學(xué)號 姓名 秒數(shù)!\n",漢字全部被替換成了“\+三個數(shù)字”的形式,這是因為在hello.s文件中漢字的表示是采用UTF-8編碼,并用8進(jìn)制表示,出來每個“\+三個數(shù)字”的結(jié)構(gòu)表示一個字節(jié)。例如,“用”的UTF-8編碼是E7 94 A8(16進(jìn)制),將其16進(jìn)制表示成8進(jìn)制就成為了\347 \224 \250,英語字符的表示在hello.s中是正常表示的。兩個字符串的信息都被放在了.rodata節(jié)中
圖 14 hello.s中的字符串
整型數(shù)
在匯編語言中,$后加數(shù)字表示立即數(shù)。如c程序中argc!=5中的5就表示為$5。
圖 15 hello.s中的整型數(shù)
局部變量i
局部變量在匯編語句中被放在寄存器里或棧里,本程序中有局部變量int i,i被存儲在-4(%rbp)中,初始化為0,i占據(jù)了4字節(jié)的地址空間,關(guān)于局部變量i的進(jìn)一步使用將在后面解析for循環(huán)時展示。
圖 16 hello.s中的局部變量
參數(shù)argc
argc是用戶傳遞給main函數(shù)的參數(shù),被放在了堆棧中。
圖 17 hello.s中的參數(shù)argc
數(shù)組argv[]
在hello.s中,其首地址保存在棧中,訪問時通過寄存器尋址的方式訪問。起始地址為%rbp-32,通過addq $8,%rax ?addq $16,%rax ?addq $24,%rax分別得到argv[1]和argv[2]和argv[3]。
圖 18 hello.s中的數(shù)組argv
3.3.2賦值
賦值主要由mov指令實現(xiàn),mov指令根據(jù)操作數(shù)的字節(jié)大小可以分為:movb:一個字節(jié),movw:兩個字節(jié),movl:四個字節(jié),movq:八個字節(jié)。
圖 19 hello.s中的賦值
3.3.3算數(shù)運算
++操作,如將i++操作表示為
圖 20 hello.s中的算數(shù)操作
3.3.4控制轉(zhuǎn)移
(1)if語句
程序里開頭使用了一個條件判斷if(argc!=5),它被編譯器翻譯為cmp加上條件轉(zhuǎn)移指令的形式。
圖 21 源程序中的if語句
圖 22 if語句的匯編實現(xiàn)
cmpl語句將edi寄存器的值和立即數(shù)5比較(實際上就是計算%edi-4),如果相等(計算結(jié)果為0),就將ZF標(biāo)志設(shè)為1;如果不相等(計算結(jié)果不為0),就將ZF標(biāo)志設(shè)為0。根據(jù)ZF的值進(jìn)行條件跳轉(zhuǎn),從而實現(xiàn)了if語句的條件分支。根據(jù)je .L2語句,當(dāng)ZF=1時,即argc等于5的時候,就會執(zhí)行.L2標(biāo)簽內(nèi)的代碼;當(dāng)ZF=0時,即argc不等于5的時候,程序就往下順序執(zhí)行。
(2)for循環(huán)語句
圖 23 源程序中的for循環(huán)語句
圖 24 for循環(huán)語句的匯編實現(xiàn)
首先為i賦初值為0。for循環(huán)的判斷條件(i<10),當(dāng)i<10的時候程序會執(zhí)行for循環(huán)的循環(huán)體,通過cmpl語句比較寄存器ebp的值和常量9,此時cmpl會根據(jù)%ebp-7的結(jié)果修改多個標(biāo)志的值,如果ebp的值小于9時,cmpl會將OF標(biāo)志的值設(shè)為1,將SF標(biāo)志的值設(shè)為1;如果ebp的值等于9,cmpl會將ZF標(biāo)志的值設(shè)為1,jle條件跳轉(zhuǎn)的跳轉(zhuǎn)條件是(SF^OF|ZF),因此當(dāng)ebp的值小于等于7的時候,jle的跳轉(zhuǎn)條件成立,從而會執(zhí)行for循環(huán)的循環(huán)體。
每次for循環(huán)后執(zhí)行i++操作(上面介紹算數(shù)運算時提到),繼續(xù)執(zhí)行.L3標(biāo)簽內(nèi)的代碼,進(jìn)行比較,若i小于等于9操作同上,若大于9則跳出循環(huán)執(zhí)行后續(xù)操作。
3.3.5main函數(shù)
main函數(shù)被系統(tǒng)函數(shù)調(diào)用執(zhí)行。
參數(shù)傳遞:main函數(shù)有兩個參數(shù)int argc和char *argv[],兩個參數(shù)分別通過寄存器edi和rsi傳遞表示。
圖 25 main函數(shù)參數(shù)傳遞
參數(shù)char*argv[]表示的是一個字符指針數(shù)組,在參數(shù)-m64下進(jìn)行編譯,因此每一個指針的大小應(yīng)該為8個字節(jié),數(shù)組的訪問在數(shù)據(jù)部分已經(jīng)介紹。
返回值: main函數(shù)的返回值通過代碼return 0實現(xiàn),在hello.s中是通過設(shè)置eax寄存器實現(xiàn)。通過movl語句,將eax寄存器的值設(shè)置為0,從而設(shè)置了main函數(shù)的返回值。
圖 26 hello.s中main函數(shù)返回值實現(xiàn)
3.3.6普通函數(shù)
參數(shù)傳遞:64位棧結(jié)構(gòu)中是通過寄存器和內(nèi)存共同實現(xiàn)的,第1~6個參數(shù)存放在寄存器%rdi,%rsi,%rdx,%rcx,%r8,%r9中,寄存器不夠用時把多的參數(shù)存放在棧中。
函數(shù)調(diào)用:call指令會將返回地址壓入棧中,并且將%rip的值設(shè)置為指向所調(diào)用函數(shù)的地址(等函數(shù)執(zhí)行完之后調(diào)用ret彈出原來的%rip并且將棧幀結(jié)構(gòu)恢復(fù))。
函數(shù)的返回值通過寄存器eax保存。
printf函數(shù)
圖 27 調(diào)用printf函數(shù)
4個參數(shù),把第一個參數(shù)字符串地址放在rdi中,第二個參數(shù)argv[1]放在rsi中,第三個參數(shù)argv[2]放在rdx中,第四個參數(shù)argv[4]放在rcx中,然后call printf,從printf返回。
exit函數(shù)
圖 28 調(diào)用exit函數(shù)
將1傳給%edi,完成參數(shù)傳遞,call exit進(jìn)行函數(shù)調(diào)用,從exit返回。
sleep函數(shù)
圖 29 調(diào)用sleep函數(shù)
編譯器關(guān)于函數(shù)嵌套的處理與一般的處理是類似的,先處理內(nèi)層函數(shù),再將內(nèi)層函數(shù)的返回值作為外層函數(shù)的參數(shù),最后再處理外層函數(shù)。
將atoi的返回值%eax通過%rdi傳遞給sleep函數(shù),call sleep調(diào)用sleep函數(shù),從sleep中返回。
atoi函數(shù)
將argv[4]通過%rdi傳遞給atoi函數(shù),call atoi進(jìn)行函數(shù)調(diào)用,從atoi中返回。
getchar函數(shù)
圖 30 調(diào)用getchar函數(shù)
無參數(shù),調(diào)用后從getchar返回。
3.4 本章小結(jié)
本章介紹了編譯的概念與作用,編譯是將文本文件翻譯成匯編語言程序,為后續(xù)將其轉(zhuǎn)化為二進(jìn)制機(jī)器碼做準(zhǔn)備的過程,由之前的C語言程序,轉(zhuǎn)變成了匯編語言程序。以hello.c源程序為例進(jìn)行編譯,對結(jié)果進(jìn)行解析,解析了匯編代碼如何實現(xiàn)數(shù)據(jù)、賦值、算術(shù)操作、控制轉(zhuǎn)移、函數(shù)調(diào)用等,加深對編譯以及匯編語言的理解。
第4章 匯編
4.1 匯編的概念與作用
匯編的概念:匯編是將編譯后產(chǎn)生的.s匯編文件翻譯成機(jī)器語言指令的過程。這一過程由匯編器(as)完成。
注意:這兒的匯編是指從 .s 到 .o 即編譯后的文件到生成機(jī)器語言二進(jìn)制程序的過程。
匯編的作用:匯編的作用是將匯編語言代碼轉(zhuǎn)換成機(jī)器語言代碼,并將結(jié)果保存在可重定位目標(biāo)文件(.o二進(jìn)制文件)中。匯編過程從匯編程序得到一個可重定位目標(biāo)文件,以便后續(xù)進(jìn)行鏈接。
4.2 在Ubuntu下匯編的命令
使用gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o指令進(jìn)行匯編
圖 31 gcc匯編指令
4.3 可重定位目標(biāo)elf格式
ELF可重定位目標(biāo)文件的典型格式
圖 32 ELF可重定位文件典型格式
使用readelf -a hello.o > hello_elf.txt指令查看hello.o的ELF格式并保存到hello_elf.txt文件中。
圖 33 查看hello.o的ELF格式文件
圖 34 hello.o的ELF格式文件
4.3.1ELF頭
ELF頭以一個16字節(jié)的序列(Magic)開始,這個序列描述了生成該文件的系統(tǒng)的字的大小和字節(jié)順序。ELF頭剩下的部分包含幫助鏈接器語法分析和解釋目標(biāo)文件的信息,其中包括ELF頭的大小,目標(biāo)文件的類型(可重定位、可執(zhí)行或者是共享的)、機(jī)器類型(x86-64)、節(jié)頭部表的文件偏移、以及節(jié)頭部表中條目的大小和數(shù)量等。
圖 35 ELF頭信息
4.3.2節(jié)頭部表
在節(jié)頭部表里描述了不同節(jié)的名稱、類型、地址、偏移量和大小等信息。
圖 36 節(jié)頭部表
4.3.3重定位節(jié)
當(dāng)匯編器生成一個目標(biāo)模塊時,它并不知道數(shù)據(jù)和代碼最終將放在內(nèi)存中的什么位置,它也不知道這個模塊引用的任何外部定義的函數(shù)或者全局變量的位置。所以,無論何時匯編器遇到對最終位置未知的目標(biāo)引用,它就會生成一個重定位條目,告訴鏈接器在將目標(biāo)文件合并成可執(zhí)行文件時如何修改這個引用。
重定位節(jié)記錄了各段引用的符號相關(guān)信息,在鏈接時,需要通過重定位節(jié)對這些位置的地址進(jìn)行重定位。鏈接器會通過重定位條目的類型判斷如何計算地址值并使用偏移量等信息計算出正確的地址。
圖 37 重定位節(jié)
4.3.4symtab節(jié)
符號表(.symtab)存放著程序中定義和引用的函數(shù)和全局變量的信息。
圖 38 symtab節(jié)
4.4 Hello.o的結(jié)果解析
objdump -d -r hello.o? 分析hello.o的反匯編,并與第3章的 hello.s進(jìn)行對照分析。
圖 39 hello.o反匯編
(1)數(shù)的表示:hello.s中的操作數(shù)是十進(jìn)制,hello.o反匯編代碼中的操作數(shù)是十六進(jìn)制。
圖 40 hello.s中操作數(shù)
圖 41 hello.o反匯編中的操作數(shù)
(2)函數(shù)調(diào)用:在hello.s中調(diào)用函數(shù),是call +函數(shù)名,而在hello.o反匯編中調(diào)用函數(shù)是通過PC相對尋址的方式,定位函數(shù)的地址。因為函數(shù)只有在鏈接之后才能確定運行執(zhí)行的地址,因此在.rela.text節(jié)中為其添加了重定位條目。
圖 42 hello.s中的函數(shù)調(diào)用
圖 43 hello.o反匯編中的函數(shù)調(diào)用
(3)匯編中mov、push、sub等指令都有表示操作數(shù)大小的后綴(b : 1字節(jié)、w :2 字節(jié)、l :4 字節(jié)、q :8字節(jié)),反匯編得到的代碼中則沒有。hello.s中提供給匯編器的輔助信息在反匯編代碼中不再出現(xiàn),如“.cfi_def_cfa_offset 16”等。
圖 44 hello.s中的指令
圖 45 hello.o反匯編中的指令
(4)在hello.s中進(jìn)行跳轉(zhuǎn),是跳轉(zhuǎn)到某一個標(biāo)簽所關(guān)聯(lián)的代碼,使用格式串的時候,也是使用標(biāo)簽。而在hello.o的反匯編中,不再使用標(biāo)簽,而是用地址來代替(只不過由于需要重定位,因此hello.o的反匯編中跳轉(zhuǎn)指令之后是相對偏移的地址,即間接地址)。
圖 46 hello.s中的跳轉(zhuǎn)
圖 47 hello.o反匯編中的跳轉(zhuǎn)
(5)反匯編代碼除了匯編代碼之外,還顯示了機(jī)器代碼,在左側(cè)用16進(jìn)制表示。
機(jī)器語言是用二進(jìn)制代碼表示的計算機(jī)能直接識別和執(zhí)行的一種機(jī)器指令的集合。一條機(jī)器語言指令由操作碼+操作數(shù)構(gòu)成。操作碼規(guī)定了指令的操作,是指令中的關(guān)鍵字,不能缺省。操作數(shù)表示該指令的操作對象。
機(jī)器語言與匯編語言的映射關(guān)系:匯編語言通過助記符來表示機(jī)器指令,并通過匯編器轉(zhuǎn)換成機(jī)器語言。對于匯編語言的每一個指令如movq、leaq、popq等在機(jī)器語言中都有操作碼與之對應(yīng),而且對于操作的寄存器不同,操作碼也會有不同。
在匯編語言中,操作數(shù)可以是立即數(shù)、寄存器、內(nèi)存地址或常量。然而,在機(jī)器語言中,所有的操作數(shù)都必須通過地址來引用。
在匯編語言中,可以使用標(biāo)簽或條件語句(如JMP, JE, JNE)來實現(xiàn)分支轉(zhuǎn)移,匯編器將這些轉(zhuǎn)換為包含跳轉(zhuǎn)地址的機(jī)器指令。
在匯編語言中,函數(shù)調(diào)用通常通過調(diào)用指令(如CALL)和返回地址來實現(xiàn)。匯編器需要處理這些指令,生成包含目標(biāo)函數(shù)地址的機(jī)器指令,并在棧上保存返回地址。
4.5 本章小結(jié)
本章介紹了匯編的概念與作用, hello.s文件匯編為hello.o文件,并生成hello.o的ELF格式文件寫入hello_elf.txt,查看并總結(jié)了hello.o的elf文件格式的信息。同時使用反匯編查看hello.o經(jīng)過反匯編過程生成的代碼并與hello.s相比較,發(fā)現(xiàn)機(jī)器語言與匯編語言之間的映射關(guān)系和差別。
第5章 鏈接
5.1 鏈接的概念與作用
鏈接的概念:鏈接是將各種代碼和數(shù)據(jù)片段收集并合成為一個單一文件的過程,這個文件可被加載(復(fù)制)到內(nèi)存并執(zhí)行,這一過程由鏈接器完成。鏈接可以執(zhí)行于編譯時,也就是在源代碼被翻譯成機(jī)器代碼時;也可以執(zhí)行于加載時,也就是在程序被加載器加載到內(nèi)存并執(zhí)行時;甚至執(zhí)行于運行時,也就是由應(yīng)用程序來執(zhí)行。
注意:這兒的鏈接是指從 hello.o 到hello生成過程。
鏈接的作用:鏈接使我們的程序能夠成功訪問到它引用的所有目標(biāo)模塊,從而保證了我們的可執(zhí)行程序可以在機(jī)器上順利執(zhí)行。鏈接可以實現(xiàn)分離編譯,可以借助鏈接的優(yōu)勢將大型的應(yīng)用程序分解成更小、更加易于管理的模塊,使得各模塊之間的修改都和編譯相互獨立。
5.2 在Ubuntu下鏈接的命令
使用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指令進(jìn)行鏈接
圖 48 ld鏈接指令
5.3 可執(zhí)行目標(biāo)文件hello的格式
使用readelf -a hello > hello1_elf.txt指令查看hello的ELF格式并保存到hello1_elf.txt文件中。
圖 49 查看hello的ELF格式
5.3.1ELF頭
以描述了生成該文件的系統(tǒng)的字的大小和字節(jié)順序的16字節(jié)序列Magic開始,剩下的部分包含幫助鏈接器語法分析和解釋目標(biāo)文件的信息,如程序入口點的地址,程序頭起點等。在可重定位目標(biāo)文件的elf中,入口點地址和程序頭起點是0,當(dāng)程序被鏈接后生成的可執(zhí)行文件中的elf中都被填入了正確的地址。
圖 50 ELF頭
5.3.2節(jié)頭
在節(jié)頭里描述了節(jié)的名稱、類型、地址、偏移量、大小等信息。
圖 51 節(jié)頭部表
5.3.3程序頭
程序頭里描述了不同段的類型,段在文件中的偏移,段在虛擬內(nèi)存中的地址,段在物理內(nèi)存里的地址,段在文件中的大小,段在內(nèi)存中的大小,段的屬性(可讀、可寫、可執(zhí)行)以及對齊等信息,描述了可執(zhí)行文件中的節(jié)與虛擬空間中的存儲段之間的映射關(guān)系
圖 52 程序頭
5.3.4重定位節(jié)
圖 53 重定位節(jié)
5.3.5符號表
符號表中增加了許多內(nèi)容。
圖 54 symtab表
5.3.6Dynamic section
與可重定位目標(biāo)文件相比,可執(zhí)行文件增加了一個Dynamic section。
圖 55 Dynamic section
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進(jìn)程的虛擬地址空間各段信息,并與5.3對照分析說明。
虛擬地址空間的起始地址為0x400000。
圖 56 虛擬地址空間起始地址
使用edb查看Loaded Symbols
圖 57 Loaded Symbols
據(jù)節(jié)頭部表,我們可以知道.rodata節(jié)開始于虛擬地址0x402000處,因此在edb中通過查看地址0x402000的內(nèi)容可以找到位于.rodata節(jié)的格式串。
圖 58 虛擬內(nèi)存中的.rodata節(jié)
.inerp段的起始地址為04002e0。
圖 59 虛擬內(nèi)存中的.inerp段
.text段的起始地址為0x4010f0,0x4010f0處是程序的起始位置。
圖 60 虛擬內(nèi)存中的.text段
5.5 鏈接的重定位過程分析
通過objdump -d -r hello 對hello文件進(jìn)行反匯編,結(jié)果如下:
圖 61 hello的反匯編
在hello.o中跳轉(zhuǎn)指令和call指令后為相對地址,而在hello中已經(jīng)是重定位之后的虛擬地址。
圖 62 hello.o中的相對地址
圖 63 hello反匯編中的虛擬地址
hello反匯編中還多了一些節(jié)如.plt節(jié)等、多了外部函數(shù)的PLT信息、以及_start入口的代碼。
圖 64 .plt節(jié)內(nèi)容
圖 65 外部函數(shù)的PLT信息
圖 66 _start入口
鏈接的過程:鏈接的過程主要包括符號解析和重定位兩個步驟。在符號解析的過程中,鏈接器將每一個符號引用與它輸入的可重定位目標(biāo)文件中的符號表中的一個確定的符號定義關(guān)聯(lián)起來。一旦鏈接器完成了符號解析這一步,就會開始重定位步驟。在重定位步驟里,鏈接器將所有相同類型的節(jié)合并為同一類型的新的聚合節(jié),然后鏈接器將運行時內(nèi)存地址賦給新的聚合節(jié),賦給輸入模塊定義的每一個節(jié),以及賦給輸入模塊定義的每個符號。之后,鏈接器依賴重定位條目,針對重定位條目中的每個符號,修改代碼節(jié)和數(shù)據(jù)節(jié)對這些符號的引用,使它們指向正確的運行時的地址。
重定位:一開始,鏈接器會進(jìn)行節(jié)的合并以及將運行時內(nèi)存地址賦給聚合節(jié),賦給輸入模塊定義的每一個節(jié),以及賦給輸入模塊定義的每個符號。完成了這一步的時候,代碼中引用的每個符號都有了唯一的運行時的內(nèi)存地址。根據(jù)重定位條目中的內(nèi)容,修改代碼節(jié)和數(shù)據(jù)節(jié)中對每個符號的引用。由于在重定位條目中有描述符號的類型(是PC相對引用或是絕對引用),也描述了重定位符號的偏移量(引用重定位符號的相對于.text節(jié)或者.data節(jié)的位置)以及一個特別的加數(shù)。用offset表示重定位符號的偏移量,用addend表示這個特別的加數(shù)。
如果符號是PC相對引用的,由于已經(jīng)知道每一個節(jié)在運行時的內(nèi)存地址(ADDRs)。那么引用這個重定位符號的位置refaddr,可以通過refaddr=offset+ADDRs計算得到,同時由于該重定位符號運行時內(nèi)存地址已知(設(shè)為ADDRb),那么修改后的地址應(yīng)該為ADDRb-refaddr+addend。如果符號是絕對引用,那么修改后的地址應(yīng)該就是這個重定位符號運行時的內(nèi)存地址。
由下圖中可以知道exit函數(shù)運行時在內(nèi)存中的地址為0x4010d0。在第四章的重定位條目中知道exit的偏移量為0x29,加數(shù)為-4(因為32位地址,占了4個字節(jié))。由第五章之前的節(jié)頭部表知.text節(jié)運行時在內(nèi)存的地址為0x4010f0,從而可以計算得需要修改對exit的引用的位置為0x4010f0+0x29=0x401119,從而可以計算得修改后的結(jié)果為0x4010d0-0x401119+(-0x4)=0xffffff7e,從而重定位后修改的結(jié)果為0xffffff7e,在hello的反匯編中是小端表示,因此操作碼后面應(yīng)該是7e ff ff ff。
圖 67 exit運行時的內(nèi)存地址
圖 68 .text節(jié)運行時的內(nèi)存地址
圖 69 hello中調(diào)用exit
5.6 hello的執(zhí)行流程
調(diào)用了動態(tài)鏈接庫linux-x86-64.so.2、libc.so中的幾個函數(shù)_start__libc_start_main__cxa_atexitlibc.so中幾個函數(shù)動態(tài)鏈接庫libc.so.6里的函數(shù)hello!mainhello!puts@plthello!exit@plthello! printf@plthello!sleep@plthello!getchar@plt
5.7 Hello的動態(tài)鏈接分析
節(jié)頭部表中有如下信息:
在dl_init前,.got節(jié).got.plt節(jié)的內(nèi)容如下:
在dl_init后,.got節(jié).got.plt節(jié)的內(nèi)容如下:
.got節(jié)、.got.plt節(jié)在內(nèi)存里的內(nèi)容都發(fā)生了改變。
在dl_init調(diào)用之前,對于每一條PIC函數(shù)調(diào)用,調(diào)用的目標(biāo)地址都實際指向PLT中的代碼,GOT存放的是PLT中函數(shù)調(diào)用指令的下一條指令地址。在函數(shù)調(diào)用時,首先跳轉(zhuǎn)到PLT執(zhí)行.plt中操作,第一次訪問跳轉(zhuǎn)時GOT地址為下一條指令,將函數(shù)序號入棧,然后跳轉(zhuǎn)到PLT[0],之后將重定位表地址入棧,訪問動態(tài)鏈接器,在動態(tài)鏈接器中使用在棧里保存的函數(shù)序號和重定位表計算函數(shù)運行時的地址,重寫GOT,返回調(diào)用函數(shù)。之后如果還有對該函數(shù)的訪問,就不用執(zhí)行第二次跳轉(zhuǎn),直接參看GOT信息。
5.8 本章小結(jié)
本章對hello.o進(jìn)行鏈接,獲得可執(zhí)行文件hello,查看了hello的elf格式并對其進(jìn)行了分析,查看了hello的虛擬空間地址的各段信息,利用objdump查看hello.o與hello的不同,分析說明了在鏈接過程中的重定位過程,查看了hello的執(zhí)行流程,經(jīng)歷的函數(shù),最后對hello的動態(tài)鏈接進(jìn)行了簡要的分析。
第6章 hello進(jìn)程管理
6.1 進(jìn)程的概念與作用
進(jìn)程的定義是一個可執(zhí)行中程序的實例。系統(tǒng)中的每個程序都運行在某個進(jìn)程上下文中。上下文是由程序正確運行所需的狀態(tài)組成的,包括存放在內(nèi)存中的程序的代碼和數(shù)據(jù)、它的棧、通用目標(biāo)寄存器的內(nèi)容、程序計數(shù)器、環(huán)境變量以及打開文件描述符的集合。
在現(xiàn)代系統(tǒng)上運行一個程序時,我們會得到一個假象,就好像我們的程序是系統(tǒng)中當(dāng)前運行的唯一的程序一樣,我們的程序好像是獨占地使用處理器和內(nèi)存,處理器就好像是無間斷地一條接一條地執(zhí)行我們程序中的擅令,我們程序中的代碼和數(shù)據(jù)好像是系統(tǒng)內(nèi)存中唯一的對象,這些假象都是通過進(jìn)程的概念提供給我們的。
6.2 簡述殼Shell-bash的作用與處理流程
shell是一個交互型應(yīng)用級程序,代表用戶運行其他程序。它通過執(zhí)行一系列的讀/求值步驟,讀取用戶的命令行,解析命令,然后代表用戶運行程序。Shell的功能主要有負(fù)責(zé)各進(jìn)程創(chuàng)建與程序加載運行及前后臺控制,作業(yè)調(diào)用,信號發(fā)送與管理等。
處理流程:
(1)終端進(jìn)程讀取用戶由鍵盤輸入的命令行。
(2)分析命令行字符串,獲取命令行參數(shù),并構(gòu)造傳遞給execve的argv向量。
(3)檢查第一個命令行參數(shù)是否是一個內(nèi)置的shell命令。
(3)如果不是內(nèi)部命令,調(diào)用fork( )創(chuàng)建新進(jìn)程/子進(jìn)程。
(4)在子進(jìn)程中,用步驟2獲取的參數(shù),調(diào)用execve( )執(zhí)行指定程序。
(5)如果用戶沒要求后臺運行(命令末尾沒有&號)否則shell使用waitpid(或wait...)等待作業(yè)終止后返回。
(6)如果用戶要求后臺運行(如果命令末尾有&號),則shell返回。
6.3 Hello的fork進(jìn)程創(chuàng)建過程
輸入命令執(zhí)行 hello 后,父進(jìn)程如果判斷不是內(nèi)部指令,即會通過 fork 函數(shù)創(chuàng)建子進(jìn)程。子進(jìn)程與父進(jìn)程相似,并得到一份與父進(jìn)程用戶級虛擬空間相同且獨立的副本(包括數(shù)據(jù)段、代碼、共享庫、堆和用戶棧)。父進(jìn)程打開的文件子進(jìn)程也可讀寫,二者之間的PID的不同。fork函數(shù)只會被調(diào)用一次,但會返回兩次,在父進(jìn)程中,fork返回子進(jìn)程的PID,在子進(jìn)程中,fork返回0。
父進(jìn)程與子進(jìn)程是并發(fā)運行的獨立進(jìn)程,內(nèi)核能夠以任意方式交替執(zhí)行它們的邏輯控制流的指令。在子進(jìn)程執(zhí)行期間,父進(jìn)程默認(rèn)選項是顯示等待子進(jìn)程的完成。
6.4 Hello的execve過程
當(dāng)fork之后,子進(jìn)程調(diào)用execve函數(shù)(傳入命令行參數(shù))在當(dāng)前進(jìn)程的上下文中加載并運行一個新程序即hello程序,execve調(diào)用駐留在內(nèi)存中的被稱為啟動加載器的操作系統(tǒng)代碼來執(zhí)行hello程序,加載器刪除子進(jìn)程現(xiàn)有的虛擬內(nèi)存段,并創(chuàng)建一組新的代碼、數(shù)據(jù)、堆和棧段。新的棧和堆段被初始化為零,通過將虛擬地址空間中的頁映射到可執(zhí)行文件的頁大小的片,新的代碼和數(shù)據(jù)段被初始化為可執(zhí)行文件中的內(nèi)容。最后加載器設(shè)置PC指向_start地址,_start最終調(diào)用hello中的main函數(shù)。除了一些頭部信息,在加載過程中沒有任何從磁盤到內(nèi)存的數(shù)據(jù)復(fù)制。直到CPU引用一個被映射的虛擬頁時才會進(jìn)行復(fù)制,這時,操作系統(tǒng)利用它的頁面調(diào)度機(jī)制自動將頁面從磁盤傳送到內(nèi)存。需要注意的是,execve成功調(diào)用的時候不會返回到調(diào)用程序,只有出現(xiàn)錯誤了,才會返回到調(diào)用程序。
6.5 Hello的進(jìn)程執(zhí)行
上下文信息:上下文就是內(nèi)核重新啟動一個被搶占的進(jìn)程所需要的狀態(tài),它由通用寄存器、浮點寄存器、程序計數(shù)器、用戶棧、狀態(tài)寄存器、內(nèi)核棧和各種內(nèi)核數(shù)據(jù)結(jié)構(gòu)等對象的值構(gòu)成。
邏輯控制流:一系列程序計數(shù)器PC的值的序列叫做邏輯控制流,進(jìn)程是輪流使用處理器的,在同一個處理器核心中,每個進(jìn)程執(zhí)行它的流的一部分后被搶占(暫時掛起),然后輪到其他進(jìn)程。
時間片:一個進(jìn)程執(zhí)行它的控制流的一部分的每一時間段叫做時間片。為了實現(xiàn)多個進(jìn)程輪流運行,操作系統(tǒng)會給進(jìn)程分配時間片。當(dāng)操作系統(tǒng)調(diào)度hello進(jìn)程的時候,系統(tǒng)會給hello進(jìn)程分配時間片,在這一時間片里,hello進(jìn)程可以獨占地使用處理器。
用戶模式和內(nèi)核模式:處理器通常使用一個寄存器提供兩種模式的區(qū)分,該寄存器描述了進(jìn)程當(dāng)前享有的特權(quán),當(dāng)沒有設(shè)置模式位時,進(jìn)程就處于用戶模式中,用戶模式的進(jìn)程不允許執(zhí)行特權(quán)指令,也不允許直接引用地址空間中內(nèi)核區(qū)內(nèi)的代碼和數(shù)據(jù);設(shè)置模式位時,進(jìn)程處于內(nèi)核模式,該進(jìn)程可以執(zhí)行指令集中的任何命令,并且可以訪問系統(tǒng)中的任何內(nèi)存位置。而一個進(jìn)程從用戶模式變?yōu)閮?nèi)核模式的唯一方法是通過諸如中斷、故障或者陷阱這樣的異常。上下文切換一定發(fā)生在內(nèi)核模式下。
hello sleep進(jìn)程調(diào)度的過程:調(diào)用sleep之前,若hello程序不被搶占則順序執(zhí)行,若發(fā)生被搶占的情況,則進(jìn)行上下文切換,并進(jìn)行如下操作:
(1)保存以前進(jìn)程的上下文
(2)恢復(fù)新恢復(fù)進(jìn)程被保存的上下文
(3)將控制傳遞給這個新恢復(fù)的進(jìn)程 ,來完成上下文切換。
hello初始運行在用戶模式,在hello進(jìn)程調(diào)用sleep之后陷入內(nèi)核模式,內(nèi)核處理休眠請求主動釋放當(dāng)前進(jìn)程,并將hello進(jìn)程從運行隊列中移出加入等待隊列,定時器開始計時,內(nèi)核進(jìn)行上下文切換將當(dāng)前進(jìn)程的控制權(quán)交給其他進(jìn)程,當(dāng)定時器到時時發(fā)送一個中斷信號,此時進(jìn)入內(nèi)核狀態(tài)執(zhí)行中斷處理,將hello進(jìn)程從等待隊列中移出重新加入到運行隊列,成為就緒狀態(tài),hello進(jìn)程就可以繼續(xù)進(jìn)行自己的控制邏輯流了。
圖 70 進(jìn)程上下文切換的剖析
6.6 hello的異常與信號處理
6.6.1正常執(zhí)行
圖 71 正常執(zhí)行狀態(tài)
6.6.2亂按鍵盤
在按鍵盤的時候會發(fā)生中斷異常,hello進(jìn)程會進(jìn)入內(nèi)核模式,將控制轉(zhuǎn)移給中斷異常處理程序。鍵盤的中斷處理程序,會從鍵盤控制器的寄存器讀取掃描碼并翻譯成ASCII碼,并存入鍵盤緩沖區(qū)。在按了回車鍵的時候,輸入的字符串會被shell識別為命令。
圖 72 亂按鍵盤后的執(zhí)行結(jié)果
6.6.3Ctrl-Z
進(jìn)程收到SIGTSTP信號,信號的動作是將hello掛起,用ps命令可以查看當(dāng)前的所有進(jìn)程的進(jìn)程號,用jobs命令看到j(luò)ob ID是1,狀態(tài)是“已停止”。
圖 73 ctrl+z
輸入pstree命令:以樹狀圖顯示進(jìn)程間的關(guān)系。
圖 74 pstree
用fg命令可以將指定的作業(yè)放在前臺運行,此時會給指定的進(jìn)程組發(fā)送SIGCONT信號,讓掛起的進(jìn)程重新運行。用kill命令可以向指定的進(jìn)程組發(fā)送信號,kill -9表示發(fā)送SIGINT信號,會讓進(jìn)程組內(nèi)每一個進(jìn)程終止。
圖 75 fg kill
6.6.4Ctrl-C
在hello程序運行時輸入CTRL+C會導(dǎo)致內(nèi)核發(fā)送一個SIGINT信號到前臺進(jìn)程組的每個進(jìn)程,默認(rèn)情況下,結(jié)果是終止前臺作業(yè)。
圖 76 ctrl+c
6.6.5總結(jié)
1. 中斷:異步發(fā)生,是來自處理器外部的I/O設(shè)備的信號的結(jié)果。
處理:在當(dāng)前指令的執(zhí)行過程中,中斷引腳電壓變高,在當(dāng)前指令完成后,控制傳遞給處理程序,中斷處理程序運行,處理程序返回下一條指令。
2. 陷阱:同步發(fā)生,有意的異常,是執(zhí)行一條指令的結(jié)果。
處理:應(yīng)用程序執(zhí)行一次系統(tǒng)調(diào)用,控制傳遞給處理程序,陷阱處理程序運行,處理程序返回到syscall之后的指令。
3. 故障:同步發(fā)生,由錯誤情況引起的,可能能夠被故障處理程序修正。
處理:當(dāng)前指令導(dǎo)致一個故障,控制傳遞給處理程序,故障處理程序運行
處理程序要么重新執(zhí)行當(dāng)前指令,要么終止。
4. 終止:同步發(fā)生,不可恢復(fù)的致命錯誤造成的結(jié)果,不將控制返回給應(yīng)用程序。
處理:發(fā)生致命的硬件錯誤,控制傳遞給處理程序,終止處理程序運行,處理程序返回到abort例程。
6.7本章小結(jié)
本章主要介紹了hello在shell中是如何運行的,分析了hello執(zhí)行過程中的進(jìn)程管理。通過進(jìn)程的概念和shell的工作流程,分析shell是如何通過調(diào)用fork函數(shù)為hello創(chuàng)建子進(jìn)程,execve函數(shù)加載hello函數(shù)的,利用時間片的概念,分析的內(nèi)核的進(jìn)程調(diào)度過程,用戶態(tài)和內(nèi)核態(tài)的轉(zhuǎn)換,最后分析了hello執(zhí)行過程中遇到的異常和信號。
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
邏輯地址:是指由程序產(chǎn)生的與段相關(guān)的偏移地址部分,是程序代碼經(jīng)過編譯后出現(xiàn)在匯編程序中地址。一個邏輯地址由一個段和偏移量組成。
線性地址:邏輯地址經(jīng)過段機(jī)制后轉(zhuǎn)化為線性地址(虛擬地址),是邏輯地址到物理地址變換之間的中間層。在分段部件中邏輯地址是段中的偏移地址,然后加上基地址就是線性地址。如果啟用了分頁機(jī)制,那么線性地址可以再經(jīng)過變換以產(chǎn)生一個物理地址;如果沒有啟用分頁機(jī)制,那么線性地址直接就是物理地址。
虛擬地址:虛擬地址空間和線性地址空間是相同的。
物理地址:是指出現(xiàn)在CPU外部地址總線上的尋址物理內(nèi)存的地址信號,是地址變換的最終結(jié)果地址。對于系統(tǒng)中物理內(nèi)存的M個字節(jié),都有{0,1,2,...,M-1}中的一個數(shù)與之一一對應(yīng),這個數(shù)就是該字節(jié)的物理地址。
7.2 Intel邏輯地址到線性地址的變換-段式管理
(以下格式自行編排,編輯時刪除)
一個邏輯地址由兩部分組成:段標(biāo)識符:段內(nèi)偏移量。段標(biāo)識符是一個16位長的字段(段選擇符),可以通過段標(biāo)識符的前13位,直接在段描述符表中找到一個具體的段描述符,這個描述符就描述了一個段。
索引號就是“段描述符”的索引,段描述符具體地址描述了一個段。很多個段描述符,就組成了一個數(shù)組,叫“段描述符表”,可以通過段標(biāo)識符的前13位,直接在段描述符表中找到一個具體的段描述符,每一個段描述符由8個字節(jié)組成。全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
在保護(hù)模式下,段寄存器中存放著段選擇符。16位段選擇符(由13位索引,1位TI,2位RPL組成)。TI位表示索引的描述符表類別(TI=0,選擇全局描述符表(GDT),TI=1,選擇局部描述符表(LDT)),高13位索引可以用來確定當(dāng)前使用的段描述符在描述符表中的位置,RPL表示特權(quán)級別。
在實際轉(zhuǎn)換的時候,通過TI位決定去訪問GDT還是LDT,然后根據(jù)13位索引去查找選定段的段描述符,通過段描述符中的內(nèi)容可以得到段基址,將段基址與EA(偏移量)相加就得到了線性地址。
圖 77 邏輯地址向線性地址轉(zhuǎn)換
7.3 Hello的線性地址到物理地址的變換-頁式管理
將程序的邏輯地址空間劃分為固定大小的頁,而物理內(nèi)存劃分為同樣大小的頁框。程序加載時可將任意一頁放入內(nèi)存中任意一個頁框,這些頁框不必連續(xù),從而實現(xiàn)了離散分配。在頁式存儲管理方式中地址結(jié)構(gòu)由兩部分構(gòu)成,前一部分是虛擬頁號(VPN),后一部分是虛擬頁偏移量(VPO)。
頁表:頁表將虛擬內(nèi)存映射到物理地址空間,每次地址翻譯硬件將一個虛擬地址轉(zhuǎn)換為一個物理地址時,都會讀取頁表。頁表是一個頁表條目(PTE)的數(shù)組。虛擬地址空間中每個頁在頁表中一個固定偏移量處都有一個PTE,假設(shè)每個PTE是由是由一個有效位和n位地址字段組成的。有效位表明了該虛擬頁當(dāng)前是否被緩存在DRAM中,如果設(shè)置了有效位,那么地址字段就表示DRAM中相應(yīng)的物理頁的起始位置,這個物理頁緩存了這個虛擬頁;如果沒有設(shè)置有效位,那么一個空地址表示這個頁還沒有被分配。
CPU里面有一個控制寄存器PTBR(頁表基址寄存器),指向當(dāng)前頁表。通過PTBR找到頁表的首地址,再根據(jù)VPN的值可以得到對應(yīng)的頁表條目的地址(PTEA)。PTEA=%PTBR+VPN*頁表條目大小。
找到了頁表條目后,如果有效位=1,說明該虛擬頁緩存進(jìn)了內(nèi)存,從而根據(jù)PTE可以找到該虛擬頁對應(yīng)的物理頁號。由于虛擬頁和物理頁大小相等,物理頁中的頁內(nèi)偏移PPO=VPO。從而物理地址由PPN與VPO組合而成。
圖 78 使用頁表的地址翻譯
7.4 TLB與四級頁表支持下的VA到PA的變換
快表
CPU產(chǎn)生一個虛擬地址(VPN和VPO的組合)
圖 79 虛擬地址中用以訪問TLB的組成部分
根據(jù)TLB索引(TLBI)去選擇快表(TLB)中對應(yīng)的組號,再根據(jù)TLB標(biāo)記去匹配相應(yīng)的路,如果匹配成功了,那么可以得到頁表條目PTE,之后將PTE中的PPN與VPO組合,從而可以得到物理地址(PA)。如果匹配失敗,快表中沒有緩存這一頁表條目,那么需要按照7.3中的步驟,從頁表基址寄存器中得到頁表的首地址,然后根據(jù)VPN的值,可以得到頁表條目的地址PTEA=%PTBR+VPN*頁表條目大小。根據(jù)PTEA的值,在高速緩存或者內(nèi)存中取出PTE的值,并將新取出的PTE存放在快表中,得到了PTE后,就可以按照同樣的方法得到PA。
圖 80 TLB命中和不命中的操作圖
四級頁表
CPU產(chǎn)生一個虛擬地址,虛擬地址由VPN1,VPN2,VPN3,VPN4和VPO組合而成。
由于PTBR頁表基址寄存器存放了一級頁表的首地址,因此通過VPN1可以查看一級頁表中,存放的相應(yīng)的二級頁表的地址,再通過VPN2查看二級頁表中,存放的相應(yīng)的三級頁表的地址,再通過VPN3查看三級頁表中,存放的相應(yīng)的四級頁表的地址,最后通過VPN4得到四級頁表中相應(yīng)的頁表條目(PTE)。根據(jù)PTE可以得到物理頁號PPN,通過將PPN與VPO組合從而得到了物理地址PA。
圖 81 使用k級頁表的地址翻譯
7.5 三級Cache支持下的物理內(nèi)存訪問
高速緩存存儲器(Cache)組織結(jié)構(gòu):
根據(jù)PA、L1高速緩存的組數(shù)和塊大小確定高速緩存塊偏移(CO)、組索引(CI)和高速緩存標(biāo)記(CT),使用CI進(jìn)行組索引,對組中每行的標(biāo)記與CT進(jìn)行匹配。如果匹配成功且塊的valid標(biāo)志位為1,則命中,然后根據(jù)CO取出數(shù)據(jù)并返回數(shù)據(jù)給CPU。
若未找到相匹配的行或有效位為0,則L1未命中,繼續(xù)在下一級高速緩存(L2)中進(jìn)行類似過程的查找。若仍未命中,還要在L3高速緩存中進(jìn)行查找。三級Cache均未命中則需訪問主存獲取數(shù)據(jù)。若進(jìn)行了上述操作,說明至少有一級高速緩存未命中,則需在得到數(shù)據(jù)后更新未命中的Cache。首先判斷其中是否有空閑塊,若有空閑塊(有效位為0),則直接將數(shù)據(jù)寫入;若不存在,則需根據(jù)替換策略(如LRU、LFU策略等)驅(qū)逐一個塊再寫入。
7.6 hello進(jìn)程fork時的內(nèi)存映射
當(dāng)fork函數(shù)被當(dāng)前進(jìn)程調(diào)用時,內(nèi)核為新進(jìn)程創(chuàng)建各種數(shù)據(jù)結(jié)構(gòu),并分配給它一個唯一的PID。為了給這個新進(jìn)程創(chuàng)建虛擬內(nèi)存,它創(chuàng)建了當(dāng)前進(jìn)程的mm_struct、區(qū)域結(jié)構(gòu)和頁表的原樣副本。它將兩個進(jìn)程中的每個頁面都標(biāo)記為只讀,并將兩個進(jìn)程中的每個區(qū)域結(jié)構(gòu)都標(biāo)記為私有的寫時復(fù)制。
當(dāng)fork在新進(jìn)程中返回時,新進(jìn)程現(xiàn)在的虛擬內(nèi)存剛好和調(diào)用fork時存在的虛擬內(nèi)存相同。當(dāng)這兩個進(jìn)程中的任何一個后來進(jìn)行寫操作時,寫時復(fù)制機(jī)制就會創(chuàng)建新頁面,因此,也就為每個進(jìn)程保持了私有地址空間的抽象概念。
7.7 hello進(jìn)程execve時的內(nèi)存映射
execve函數(shù)調(diào)用駐留在內(nèi)核區(qū)域的啟動加載器代碼,在當(dāng)前進(jìn)程中加載并運行包含在可執(zhí)行目標(biāo)文件hello中的程序,用hello程序有效地替代了當(dāng)前程序。
加載并運行hello需要以下幾個步驟:
刪除已存在的用戶區(qū)域,刪除當(dāng)前進(jìn)程虛擬地址的用戶部分中的已存在的區(qū)域結(jié)構(gòu)。映射私有區(qū)域,為新程序的代碼、數(shù)據(jù)、bss和棧區(qū)域創(chuàng)建新的區(qū)域結(jié)構(gòu),所有這些新的區(qū)域都是私有的、寫時復(fù)制的。代碼和數(shù)據(jù)區(qū)域被映射為hello文件中的.text和.data區(qū),bss區(qū)域是請求二進(jìn)制零的,映射到匿名文件,其大小包含在hello中,棧和堆地址也是請求二進(jìn)制零的,初始長度為零。映射共享區(qū)域, hello程序與共享對象libc.so鏈接,libc.so是動態(tài)鏈接到這個程序中的,然后再映射到用戶虛擬地址空間中的共享區(qū)域內(nèi)。設(shè)置程序計數(shù)器(PC),execve做的最后一件事情就是設(shè)置當(dāng)前進(jìn)程上下文的程序計數(shù)器,使之指向代碼區(qū)域的入口點。
圖 82 加載器是如何映射用戶地址空間的區(qū)域的
7.8 缺頁故障與缺頁中斷處理
若程序想要訪問某個虛擬頁中的數(shù)據(jù)的時候,會產(chǎn)生一個虛擬地址。當(dāng)MMU(內(nèi)存管理單元)在試圖翻譯這個虛擬地址的時候,會發(fā)現(xiàn)該地址所在的虛擬頁沒有緩存進(jìn)內(nèi)存(即PTE中有效位為0),必須從磁盤中取出,這時候就會發(fā)生缺頁故障。
缺頁中斷處理:
判斷虛擬地址是否合法。缺頁處理程序搜索區(qū)域結(jié)構(gòu)的鏈表,把虛擬地址和每個區(qū)域結(jié)構(gòu)中的vm_start和vm_end做比較。如果指令不合法,缺頁處理程序會觸發(fā)一個段錯誤,從而終止這個進(jìn)程。判斷內(nèi)存訪問是否合法。比如缺頁是否由一條試圖對只讀頁面進(jìn)行寫操作的指令造成的。如果訪問不合法,缺頁處理程序會觸發(fā)一個保護(hù)異常,從而終止這個進(jìn)程。內(nèi)核知道缺頁是由合法的操作造成的。內(nèi)核會選擇一個犧牲頁面,如果這個犧牲頁面被修改過,那么就將它交換出去,換入新的頁面并更新頁表。處理程序返回時,CPU重新執(zhí)行引起缺頁的指令,這條指令將再次發(fā)送給MMU。這次,MMU能正常地進(jìn)行地址翻譯,不會再產(chǎn)生缺頁中斷。
圖 83 Linux缺頁處理
圖 84 缺頁操作圖
7.9動態(tài)存儲分配管理
動態(tài)內(nèi)存分配器維護(hù)著一個稱為堆的進(jìn)程的虛擬內(nèi)存區(qū)域。分配器將堆視為一組不同大小的塊的集合來維護(hù)。每個塊就是一個連續(xù)的虛擬內(nèi)存片,要么是已分配的,要么是空閑的。已分配的塊顯式地保留為供應(yīng)用程序使用??臻e塊可用來分配??臻e塊保持空閑,直到它顯式地被應(yīng)用所分配。一個已分配的塊保持已分配狀態(tài),直到它被釋放,這種釋放可以由應(yīng)用程序顯式執(zhí)行或內(nèi)存分配器自身隱式執(zhí)行。
分配器分為兩種基本風(fēng)格:顯式分配器、隱式分配器。
1.顯式分配器:要求應(yīng)用顯式地釋放任何已分配的塊。
2. 隱式分配器:要求分配器檢測一個已分配塊何時不再使用,那么就釋放這個塊,自動釋放未使用的已經(jīng)分配的塊的過程叫做垃圾收集。
分配器有以下幾種實現(xiàn)方式:
用隱式空閑鏈表的方式來組織空閑塊。堆中的空閑塊通過頭部中的大小字段隱含地連接,分配器通過遍歷堆中所有的塊,從而間接遍歷整個空閑塊的集合。
分配器的放置策略有:首次適配(從頭搜索,遇到第一個合適的塊就停止)、下一次適配(從鏈表中上一次查詢結(jié)束的地方開始,遇到下一個合適的塊停止)和最佳適配(全部搜索,選擇合適的塊停止)等。
分割策略:不分割,使用整個空閑塊;或者將空閑塊分成兩部分,第一部分變成分配塊,剩下部分變成空閑塊,獲取額外的堆內(nèi)存得到合適的空閑塊。
合并空閑塊的策略:立即合并(每次釋放一個塊的時候就合并)、推遲合并、帶邊界標(biāo)記的合并。
用顯式空閑鏈表的方式來組織空閑塊。顯式空閑鏈表結(jié)構(gòu)將堆組織成一個雙向空閑鏈表,在每個空閑塊的主體中,都包含一個pred(前驅(qū))和succ(后繼)指針。使用雙向鏈表而不是隱式空閑鏈表,使首次適配的分配時間從塊總數(shù)的線性時間減少到了空閑塊數(shù)量的線性時間。
維護(hù)鏈表的順序:
后進(jìn)先出的順序,將新釋放的塊放置在鏈表的開始處。按照地址順序來維護(hù)鏈表,其中鏈表中每個塊的地址都小于它后繼的地址。
分離的空閑鏈表:維護(hù)多個空閑鏈表,其中,每個鏈表的塊具有相同的大小。將所有可能的塊大小分成一些等價類,從而進(jìn)行分離存儲。有兩種基本方法:簡單分離存儲,分離適配。
7.10本章小結(jié)
本章主要介紹了hello的存儲器地址空間、intel的段式管理、頁式管理,TLB與四級頁表支持下的VA到PA的變換、三級cache支持下物理內(nèi)存訪問, hello進(jìn)程fork時的內(nèi)存映射、execve時的內(nèi)存映射、缺頁故障與缺頁中斷處理、動態(tài)存儲分配管理等內(nèi)容。
第8章 hello的IO管理
8.1 Linux的IO設(shè)備管理方法
設(shè)備的模型化:文件
所有IO設(shè)備都被模型化為文件,所有的輸入和輸出都能被當(dāng)做相應(yīng)文件的讀和寫來執(zhí)行。
設(shè)備管理:unix io接口
Linux內(nèi)核有一個簡單、低級的接口,稱為Unix I/O,所有的輸入和輸出都能以一種統(tǒng)一且一致的方式來執(zhí)行。
8.2 簡述Unix IO接口及其函數(shù)
將設(shè)備優(yōu)雅地映射成文件的方式,允許Linux內(nèi)核引出一個簡單、低級的應(yīng)用接口,稱為Unix I/O,這使得所有的輸入和輸出都能以一種統(tǒng)一且一致的方式來執(zhí)行。
打開文件:一個應(yīng)用程序通過要求內(nèi)核打開相應(yīng)的文件,來宣告它想要訪問一個I/O設(shè)備,內(nèi)核返回一個小的非負(fù)整數(shù),叫做描述符,它在后續(xù)對此文件的所有操作中標(biāo)識這個文件,內(nèi)核記錄有關(guān)這個打開文件的所有信息。應(yīng)用程序只需記住這個描述符。Linux shell創(chuàng)建的每個進(jìn)程開始時都有三個打開的文件:標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤。改變當(dāng)前的文件位置。讀寫文件:從文件復(fù)制n個字節(jié)到內(nèi)存,從當(dāng)前文件位置k開始,然后將k增加到k+n;寫操作:從內(nèi)存復(fù)制n個字節(jié)到文件,當(dāng)前文件位置為k,然后更新k。關(guān)閉文件:當(dāng)應(yīng)用完成了對文件的訪問之后,它就通知內(nèi)核關(guān)閉這個文件,作為響應(yīng),內(nèi)核釋放文件打開時創(chuàng)建的數(shù)據(jù)結(jié)構(gòu),并將這個描述符恢復(fù)到可用的描述符池中。
8.2.1打開文件
進(jìn)程通過調(diào)用open函數(shù)來打開一個已存在的文件或者創(chuàng)建一個新文件。
圖 85 open函數(shù)
open函數(shù)將filename轉(zhuǎn)換為一個文件描述符,并且返回描述符數(shù)字,返回的描述符總是在進(jìn)程中當(dāng)前沒有打開的最小描述符,flags參數(shù)指明了進(jìn)程打算如何訪問這個文件,mode參數(shù)指定了新文件的訪問權(quán)限位。
8.2.2關(guān)閉文件
進(jìn)程通過調(diào)用close函數(shù)關(guān)閉一個打開的文件。
圖 86 close函數(shù)
fd是需要關(guān)閉的文件的描述符,關(guān)閉一個已關(guān)閉的描述符會出錯。
8.2.3讀寫文件
應(yīng)用程序是通過分別調(diào)用read和write函數(shù)來執(zhí)行輸入和輸出的。
圖 87 read和write函數(shù)
read函數(shù)從描述符為fd的當(dāng)前文件位置復(fù)制最多n個字節(jié)到內(nèi)存位置buf。返回值-1表示一個錯誤,而返回值0表示EOF。否則,返回值表示的是實際傳送的字節(jié)數(shù)量。
write函數(shù)從內(nèi)存buf復(fù)制至多n個字節(jié)到描述符fd的當(dāng)前文件位置。若成功則返回值為寫的字節(jié)數(shù),若出錯則為-1。
8.3 printf的實現(xiàn)分析
Printf函數(shù)如下
圖 88 print函數(shù)
參數(shù)采用了可變參數(shù)的定義, *fmt是一個char 類型的指針,指向字符串的起始位置。這個指針指向第一個const參數(shù)(const char *fmt)中的第一個元素。fmt也是個變量,它的位置,是在棧上分配的,它也有地址。
printf調(diào)用的外部函數(shù)vsprintf如下:
圖 89 vsprintf函數(shù)
vsprintf的作用就是格式化,它接受確定輸出格式的格式字符串fmt,用格式字符串對個數(shù)變化的參數(shù)進(jìn)行格式化,產(chǎn)生格式化輸出,寫入buf供系統(tǒng)調(diào)用write輸出時使用。
write系統(tǒng)函數(shù)如下:
圖 90 write系統(tǒng)函數(shù)
在write函數(shù)中,將棧中參數(shù)放入寄存器,ecx是字符個數(shù),ebx存放第一個字符地址,int INT_VECTOR_SYS_CALLA代表通過系統(tǒng)調(diào)用syscall。
sys_call實現(xiàn)如下:
圖 91 sys_call實現(xiàn)
ecx中是要打印出的元素個數(shù) ,ebx中的是要打印的buf字符數(shù)組中的第一個元素,這個函數(shù)的功能就是不斷的打印出字符,直到遇到:’\0’ 停止。
總結(jié):函數(shù)printf的實現(xiàn)過程調(diào)用了vsprintf和write函數(shù),接受一個格式串之后將匹配到的參數(shù)按照格式串的形式輸出。vsprintf的作用就是格式化。它接受確定輸出格式的格式字符串fmt,用格式字符串對個數(shù)變化的參數(shù)進(jìn)行格式化,產(chǎn)生格式化輸出。vsprintf函數(shù)生成顯示信息到write系統(tǒng)函數(shù),到陷阱-系統(tǒng)調(diào)用 int 0x80或syscall。字符顯示驅(qū)動子程序:從ASCII到字模庫到顯示vram(存儲每一個點的RGB顏色信息)。顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點。
8.4 getchar的實現(xiàn)分析
getchar代碼如下:
圖 92 getchar函數(shù)
當(dāng)用戶按鍵時,鍵盤接口會得到一個代表該按鍵的鍵盤掃描碼,同時產(chǎn)生一個中斷請求,中斷請求搶占當(dāng)前進(jìn)程運行鍵盤中斷子程序,鍵盤中斷子程序先從鍵盤接口取得該按鍵的掃描碼,然后將該按鍵掃描碼轉(zhuǎn)換成ASCII碼,保存到系統(tǒng)的鍵盤緩沖區(qū)中。
getchar調(diào)用read系統(tǒng)函數(shù),read通過syscall(系統(tǒng)調(diào)用)調(diào)用內(nèi)核中的系統(tǒng)函數(shù),讀取存儲在鍵盤緩沖區(qū)中的ASCII碼,直到讀入回車符,然后返回整個字符串,getchar函數(shù)只從中讀取第一個字符,其他字符被存放在輸入緩沖區(qū)。
異步異常-鍵盤中斷的處理:鍵盤中斷處理子程序。接受按鍵掃描碼轉(zhuǎn)成ascii碼,保存到系統(tǒng)的鍵盤緩沖區(qū)。
getchar等調(diào)用read系統(tǒng)函數(shù),通過系統(tǒng)調(diào)用讀取按鍵ascii碼,直到接受到回車鍵才返回。
8.5本章小結(jié)
本章介紹了Linux的IO設(shè)備管理方法,以及Unix IO接口和函數(shù),同時分析了printf函數(shù)和getchar函數(shù)的實現(xiàn)方法,體會到Unix IO接口在Linux系統(tǒng)中的重要作用,同時也了解了作為異步異常之一的鍵盤中斷的處理。
結(jié)論
用計算機(jī)系統(tǒng)的語言,逐條總結(jié)hello所經(jīng)歷的過程。
你對計算機(jī)系統(tǒng)的設(shè)計與實現(xiàn)的深切感悟,你的創(chuàng)新理念,如新的設(shè)計與實現(xiàn)方法。
程序員通過編輯器等進(jìn)行編寫,將hello的源代碼寫入,存為.c文件,hello.c源程序誕生。預(yù)處理。hello.c文件通過預(yù)處理器cpp將調(diào)用的庫,所有的宏替換掉,合并到hello.i的文本文件中。編譯。在編譯器中經(jīng)過編譯,產(chǎn)生了hello.s。匯編。經(jīng)過匯編器as將匯編文件轉(zhuǎn)換為機(jī)器語言的可重定位二進(jìn)制目標(biāo)文件hello.o,此時的hello.o能夠被機(jī)器識別但是由于不完整無法被執(zhí)行。鏈接。通過符號解析與重定位,將可重定位目標(biāo)文件與所依賴的模塊鏈接起來,產(chǎn)生了可以被機(jī)器執(zhí)行的可執(zhí)行文件hello。運行。在shell中輸入命令運行。shell調(diào)用fork函數(shù)為hello創(chuàng)建子進(jìn)程,在子進(jìn)程中調(diào)用execve函數(shù), 啟動加載器,映射虛擬內(nèi)存,進(jìn)入程序入口之后開始載入物理內(nèi)存,然后進(jìn)入main函數(shù)。CPU為其分配時間片,在每一個時間片中hello享有私有的地址空間,獨立執(zhí)行自己的邏輯控制流。CPU訪問hello的時候,請求一個虛擬地址,MMU把虛擬地址轉(zhuǎn)換成物理地址,malloc動態(tài)申請內(nèi)存。hello在運行中可以收到來自鍵盤輸入的信號,比如CTRL-C,CTRL-Z等。hello子進(jìn)程收到終止信號后就死亡了,在父進(jìn)程沒有調(diào)用waitpid函數(shù)將其回收前,他一直是僵死進(jìn)程。shell父進(jìn)程調(diào)用waitpid函數(shù)將其回收,內(nèi)核刪除了為hello進(jìn)程創(chuàng)建的所有數(shù)據(jù)結(jié)構(gòu)。
計算機(jī)系統(tǒng)這門課程很復(fù)雜,從一個很簡單的程序hello.c產(chǎn)生到消失經(jīng)歷了復(fù)雜的過程,從中我也學(xué)習(xí)到了很多知識,通過實驗以及大作業(yè)使我更深入的理解了理論知識,使我收獲了很多。通過對這門課程的學(xué)習(xí),我已經(jīng)理解了一些計算機(jī)系統(tǒng)的知識,但在后續(xù)的學(xué)習(xí)中仍需不斷深入理解,才能不斷提升自己對計算機(jī)系統(tǒng)的理解,提升自己的能力。
附件
列出所有的中間產(chǎn)物的文件名,并予以說明起作用。
(1)hello.c:源代碼
(2)hello.i:預(yù)處理后的文本文件
(3)hello.s:編譯后的匯編文件
(4)hello.o:匯編后的可重定位目標(biāo)執(zhí)行文件
(5)hello:鏈接之后的可執(zhí)行文件
(6)hello_elf.txt:用readelf讀取hello.o得到的ELF格式信息
(7)hello1_elf.txt:用readelf讀取hello得到的ELF格式信息
(8)hello.txt:hello反匯編的結(jié)果
(9)hello_o.txt:hello.o反匯編的結(jié)果
參考文獻(xiàn)
[1] Randal E.Bryant, David R.O'Hallaron.深入理解計算機(jī)系統(tǒng)[M]. 北京:機(jī)械工業(yè)出版社,2016.
[2] http://t.csdnimg.cn/jJrbB
[3] http://t.csdnimg.cn/jozdZ
[4] http://t.csdnimg.cn/DfSvX
[5] 袁春風(fēng). 計算機(jī)系統(tǒng)基礎(chǔ). 北京:機(jī)械工業(yè)出版社,2014
[6] https://www.cnblogs.com/pianist/p/3315801.html
柚子快報激活碼778899分享:HITICS大作業(yè)——程序人生
參考文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。