柚子快報(bào)激活碼778899分享:ubuntu HIT
柚子快報(bào)激活碼778899分享:ubuntu HIT
摘? 要
本文主要介紹了從源程序hello.c到可執(zhí)行目標(biāo)程序hello的變化過(guò)程,詳細(xì)闡述了預(yù)處理、編譯、匯編、鏈接等階段。在每一階段中,通過(guò)各種調(diào)試工具對(duì)該階段所得文件的內(nèi)容加以分析,并與前一階段所得文件加以對(duì)比。同時(shí),對(duì)hello的進(jìn)程管理、存儲(chǔ)管理和IO管理進(jìn)行了系統(tǒng)解釋與分析。對(duì)于把握計(jì)算機(jī)系統(tǒng)的整體框架結(jié)構(gòu)大有益處。
關(guān)鍵詞:預(yù)處理;編譯;匯編;鏈接;進(jìn)程管理;存儲(chǔ)管理;IO管理???????????????????????????
目? 錄
第1章 概述............................................................................................................. - 4 -
1.1 Hello簡(jiǎn)介...................................................................................................... - 4 -
1.2 環(huán)境與工具..................................................................................................... - 4 -
1.3 中間結(jié)果......................................................................................................... - 4 -
1.4 本章小結(jié)......................................................................................................... - 4 -
第2章 預(yù)處理......................................................................................................... - 5 -
2.1 預(yù)處理的概念與作用..................................................................................... - 5 -
2.2在Ubuntu下預(yù)處理的命令.......................................................................... - 5 -
2.3 Hello的預(yù)處理結(jié)果解析.............................................................................. - 5 -
2.4 本章小結(jié)......................................................................................................... - 5 -
第3章 編譯............................................................................................................. - 6 -
3.1 編譯的概念與作用......................................................................................... - 6 -
3.2 在Ubuntu下編譯的命令............................................................................. - 6 -
3.3 Hello的編譯結(jié)果解析.................................................................................. - 6 -
3.4 本章小結(jié)......................................................................................................... - 6 -
第4章 匯編............................................................................................................. - 7 -
4.1 匯編的概念與作用......................................................................................... - 7 -
4.2 在Ubuntu下匯編的命令............................................................................. - 7 -
4.3 可重定位目標(biāo)elf格式................................................................................. - 7 -
4.4 Hello.o的結(jié)果解析...................................................................................... - 7 -
4.5 本章小結(jié)......................................................................................................... - 7 -
第5章 鏈接............................................................................................................. - 8 -
5.1 鏈接的概念與作用......................................................................................... - 8 -
5.2 在Ubuntu下鏈接的命令............................................................................. - 8 -
5.3 可執(zhí)行目標(biāo)文件hello的格式.................................................................... - 8 -
5.4 hello的虛擬地址空間.................................................................................. - 8 -
5.5 鏈接的重定位過(guò)程分析................................................................................. - 8 -
5.6 hello的執(zhí)行流程.......................................................................................... - 8 -
5.7 Hello的動(dòng)態(tài)鏈接分析.................................................................................. - 8 -
5.8 本章小結(jié)......................................................................................................... - 9 -
第6章 hello進(jìn)程管理................................................................................... - 10 -
6.1 進(jìn)程的概念與作用....................................................................................... - 10 -
6.2 簡(jiǎn)述殼Shell-bash的作用與處理流程..................................................... - 10 -
6.3 Hello的fork進(jìn)程創(chuàng)建過(guò)程..................................................................... - 10 -
6.4 Hello的execve過(guò)程................................................................................. - 10 -
6.5 Hello的進(jìn)程執(zhí)行........................................................................................ - 10 -
6.6 hello的異常與信號(hào)處理............................................................................ - 10 -
6.7本章小結(jié)....................................................................................................... - 10 -
第7章 hello的存儲(chǔ)管理................................................................................ - 11 -
7.1 hello的存儲(chǔ)器地址空間............................................................................ - 11 -
7.2 Intel邏輯地址到線性地址的變換-段式管理............................................ - 11 -
7.3 Hello的線性地址到物理地址的變換-頁(yè)式管理....................................... - 11 -
7.4 TLB與四級(jí)頁(yè)表支持下的VA到PA的變換............................................. - 11 -
7.5 三級(jí)Cache支持下的物理內(nèi)存訪問(wèn).......................................................... - 11 -
7.6 hello進(jìn)程fork時(shí)的內(nèi)存映射.................................................................. - 11 -
7.7 hello進(jìn)程execve時(shí)的內(nèi)存映射.............................................................. - 11 -
7.8 缺頁(yè)故障與缺頁(yè)中斷處理........................................................................... - 11 -
7.9動(dòng)態(tài)存儲(chǔ)分配管理....................................................................................... - 11 -
7.10本章小結(jié)..................................................................................................... - 12 -
第8章 hello的IO管理................................................................................. - 13 -
8.1 Linux的IO設(shè)備管理方法.......................................................................... - 13 -
8.2 簡(jiǎn)述Unix IO接口及其函數(shù)....................................................................... - 13 -
8.3 printf的實(shí)現(xiàn)分析........................................................................................ - 13 -
8.4 getchar的實(shí)現(xiàn)分析.................................................................................... - 13 -
8.5本章小結(jié)....................................................................................................... - 13 -
結(jié)論......................................................................................................................... - 14 -
附件......................................................................................................................... - 15 -
參考文獻(xiàn)................................................................................................................. - 16 -
第1章 概述
1.1 Hello簡(jiǎn)介
根據(jù)Hello的自白,利用計(jì)算機(jī)系統(tǒng)的術(shù)語(yǔ),簡(jiǎn)述Hello的P2P,020的整個(gè)過(guò)程。
(1)P2P(From Program to Process):
源程序(文本)為hello.c。在預(yù)處理階段預(yù)處理器(cpp)根據(jù)以字符#開(kāi)頭的命令,修改原始的C程序,得到修改了的源程序(文本)hello.i;在編譯階段編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s,它包括一個(gè)匯編語(yǔ)言程序;在匯編階段,匯編器(as)將hello.s翻譯為機(jī)器語(yǔ)言指令,把這些指令打包為可重定位目標(biāo)程序的格式,并將結(jié)果保存在目標(biāo)文件hello.o中(hello.o是一個(gè)二進(jìn)制文件);在鏈接階段,鏈接器(ld)將單獨(dú)的預(yù)編譯好了的目標(biāo)文件合并到hello.o中,結(jié)果得到hello文件;在shell中輸入相關(guān)命令,調(diào)用fork函數(shù)創(chuàng)建新的子進(jìn)程。
(2)020(From Zero-0 to Zero-0):
在shell中輸入相關(guān)命令,調(diào)用fork函數(shù)創(chuàng)建新的子進(jìn)程,通過(guò)execve函數(shù)加載并運(yùn)行程序,映射到相應(yīng)的虛擬內(nèi)存空間,加載需要的物理內(nèi)存,開(kāi)始運(yùn)行hello,CPU為其分配時(shí)間片等。當(dāng)hello運(yùn)行完畢,父進(jìn)程回收hello進(jìn)程,內(nèi)核清除相關(guān)信息。
1.2 環(huán)境與工具
列出你為編寫(xiě)本論文,折騰Hello的整個(gè)過(guò)程中,使用的軟硬件環(huán)境,以及開(kāi)發(fā)與調(diào)試工具。
硬件環(huán)境:x64 CPU;3.20GHz;16G RAM
軟件環(huán)境:Windows 11 64位;Ubuntu 20.04
開(kāi)發(fā)與調(diào)試工具:edb,gcc,vim,objdump,readelf
1.3 中間結(jié)果
列出你為編寫(xiě)本論文,生成的中間結(jié)果文件的名字,文件的作用等。
表1 中間結(jié)果文件
文件名字文件作用hello.ihello.c預(yù)處理后得到的文本文件hello.shello.i編譯后得到的文本文件hello.ohello.s匯編后得到的二進(jìn)制文件hello_elf.txthello.o的ELF格式文件hello_obj.txthello.o的反匯編文件hellohello.o鏈接得到的可執(zhí)行目標(biāo)文件hello_elf2.elfhello的ELF格式文件hello_obj2.txthello的反匯編文件
?
1.4 本章小結(jié)
本章主要介紹了hello的P2P和020的整個(gè)過(guò)程、軟硬件環(huán)境、開(kāi)發(fā)工具以及相應(yīng)的中間結(jié)果,有助于梳理清楚大致的脈絡(luò)框架。
第2章 預(yù)處理
2.1 預(yù)處理的概念與作用
(1)預(yù)處理概念:
預(yù)處理器(cpp)根據(jù)以字符#開(kāi)頭的命令,修改原始的C程序。結(jié)果得到另一個(gè)C程序,通常是以.i作為文件擴(kuò)展名。
(2)預(yù)處理的作用:
①處理源文件:搜索指定的文件,并將內(nèi)容包含進(jìn)來(lái)。比如hello.c中#include
②處理?xiàng)l件編譯:根據(jù)條件有選擇性的保留或者放棄源文件中的內(nèi)容。常見(jiàn)的條件包含#if、#ifdef、#ifndef指令開(kāi)始,以#endif結(jié)束。用#undef指令可對(duì)用#define定義的標(biāo)識(shí)符取消定義。
③進(jìn)行宏替換:對(duì)于宏定義,將代碼中所有使用宏定義的地方用符號(hào)表示的實(shí)際值替換該符號(hào)。
④進(jìn)行特殊控制:如進(jìn)行行控制——改變預(yù)定義宏“__LINE__”的值,如果后面的字符串存在,則改變“__FILE__”的值;發(fā)送拋錯(cuò)指令——在預(yù)處理期間發(fā)出一個(gè)診斷信息,停止轉(zhuǎn)換;發(fā)送雜注指令——傳遞額外信息,實(shí)現(xiàn)某些控制。
⑤處理特殊符號(hào):識(shí)別特殊符號(hào),進(jìn)行部分替換。
⑥將程序中的所有注釋刪去。
2.2在Ubuntu下預(yù)處理的命令
預(yù)處理命令:cpp hello.c > hello.i
圖2.2-1 預(yù)處理命令截圖
圖2.2-2 hello.i內(nèi)容部分截圖
2.3 Hello的預(yù)處理結(jié)果解析
hello.c只有24行,而經(jīng)過(guò)預(yù)處理得到的hello.i有3061行。文件開(kāi)頭是預(yù)處理器生成的一些相關(guān)信息,之后是對(duì)頭文件stdio.h、unistd.h、stdlib.h中的內(nèi)容展開(kāi),最后是刪去所有注釋的源代碼部分。同時(shí),除了以上3個(gè)文件外,hello.i中還出現(xiàn)了其他文件,說(shuō)明這3個(gè)文件內(nèi)容中也包括了其他頭文件。
圖2.3-1 預(yù)處理器生成的相關(guān)信息截圖
圖2.3-2 包含的庫(kù)文件截圖
圖2.3-3 源代碼部分截圖
2.4 本章小結(jié)
本章主要介紹了預(yù)處理的概念與作用,以及在Ubuntu下實(shí)現(xiàn)了預(yù)處理過(guò)程,并對(duì)預(yù)處理得到的hello.i文件進(jìn)行分析。
第3章 編譯
3.1 編譯的概念與作用
(1)編譯的概念:
編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s,它包含一個(gè)匯編語(yǔ)言程序。
(2)編譯的作用:
①詞法分析:對(duì)由字符組成的單詞進(jìn)行處理,從左至右逐個(gè)字符地對(duì)源程序進(jìn)行掃描,產(chǎn)生一個(gè)個(gè)的單詞符號(hào),把作為字符串的源程序改造成為單詞符號(hào)串的中間程序;
②語(yǔ)法分析:以單詞符號(hào)作為輸入,分析單詞符號(hào)串是否形成符合語(yǔ)法規(guī)則的語(yǔ)法單位,是否構(gòu)成一個(gè)符合要求的程序,按該語(yǔ)言使用的語(yǔ)法規(guī)則分析檢查每條語(yǔ)句是否有正確的邏輯結(jié)構(gòu);
③語(yǔ)義檢查和中間代碼生成:使編譯程序的結(jié)構(gòu)在邏輯上更為簡(jiǎn)單明確,特別是使目標(biāo)代碼的優(yōu)化比較容易實(shí)現(xiàn)中間代碼;
④代碼優(yōu)化:對(duì)程序進(jìn)行多種等價(jià)變換,使得從變換后的程序出發(fā),能生成更有效的目標(biāo)代碼;
⑤目標(biāo)代碼生成:把語(yǔ)法分析后或優(yōu)化后的中間代碼變換成目標(biāo)代碼。
3.2 在Ubuntu下編譯的命令
編譯命令:gcc -S hello.i -o hello.s
圖3.2-1編譯命令截圖
圖3.2-2 hello.s內(nèi)容部分截圖
3.3 Hello的編譯結(jié)果解析
3.3.1數(shù)據(jù)
①常量以立即數(shù)的形式出現(xiàn)
源代碼if(argc!=4)中的4:
圖3.3.1-1 常量例1截圖
源代碼exit(1)中的1:
圖3.3.1-2 常量例2截圖
源代碼for(i=0;i<8;i++)中i<8被優(yōu)化為i<=7,立即數(shù)7:
圖3.3.1-3 常量例3截圖
②局部變量
源代碼中的i:
初始值為0:
圖3.3.1-4 i初始值截圖
每次循環(huán)i=i+1,并與7進(jìn)行比較:
圖3.3.1-5 i循環(huán)截圖
源代碼中的argc:
與4比較:
圖3.3.1-6 argc與4比較截圖
③表達(dá)式
源代碼argc!=4,argc與4比較:
圖3.3.1-7 表達(dá)式例1截圖
源代碼i=0,賦初值:
圖3.3.1-8 表達(dá)式例2截圖
源代碼i++,在每次循環(huán)+1:
圖3.3.1-9 表達(dá)式例3截圖
源代碼i<8,由于已被優(yōu)化為7,所以與7進(jìn)行比較:
圖3.3.1-10 表達(dá)式例4截圖
3.3.2賦值
源代碼i=0,為i賦初值,通過(guò)movl指令實(shí)現(xiàn):
圖3.3.2-1 賦值例1截圖
源代碼i++,在每次循環(huán)i賦值為i+1,通過(guò)addl指令實(shí)現(xiàn):
圖3.3.2-2 賦值例2截圖
3.3.3算術(shù)操作
源代碼i++,通過(guò)addl指令實(shí)現(xiàn):
圖3.3.3-1 算術(shù)操作截圖
3.3.4關(guān)系操作
源代碼argc!=4,將argc與4比較,通過(guò)cmpl指令實(shí)現(xiàn):
圖3.3.4-1 關(guān)系操作例1截圖
源代碼i<8,將i與8比較,由于i<8已被優(yōu)化為i<=7,所以實(shí)際將i與7比較,通過(guò)cmpl指令實(shí)現(xiàn):
圖3.3.4-2 關(guān)系操作例2截圖
3.3.5數(shù)組操作
源代碼printf("Hello %s %s\n",argv[1],argv[2])。先在%rax中存儲(chǔ)-32(%rbp),再將%rax加上16,最后將%rax指向的數(shù)據(jù)傳遞給%rdx;先在%rax中存儲(chǔ)-32(%rbp),再將%rax加上8,將%rax指向的數(shù)據(jù)存儲(chǔ)在%rax中,將%rax的值傳遞給%rsi;也就是argv[1]和argv[2]的值存儲(chǔ)在%rsi和%rdx中,最后調(diào)用printf函數(shù):
圖3.3.5-1 數(shù)組操作例1截圖
源代碼sleep(atoi(argv[3])),將argv[3]存儲(chǔ)在%rdi中:
圖3.3.5-2 數(shù)組操作例2截圖
3.3.6控制轉(zhuǎn)移
①if語(yǔ)句:
源代碼if(argc!=4){
????????????? printf("用法: Hello 學(xué)號(hào) 姓名 秒數(shù)!\n");
????????????? exit(1);
?????? ?? }
通過(guò)cmpl指令和je指令,將agrc與4作比較,如果argc=4則跳轉(zhuǎn)至.L2部分執(zhí)行,否則繼續(xù)執(zhí)行下面的語(yǔ)句,直到最后退出:
圖3.3.6-1 if語(yǔ)句截圖
②for循環(huán):
源代碼for(i=0;i<8;i++){
????????????? printf("Hello %s %s\n",argv[1],argv[2]);
????????????? sleep(atoi(argv[3]));
?????? ?? }
循環(huán)中的內(nèi)容和+1操作在.L3部分執(zhí)行,判斷在.L4部分執(zhí)行,通過(guò)cmpl指令和jle指令,如果i<=7,則跳轉(zhuǎn)到.L4部分繼續(xù)執(zhí)行:
圖3.3.6-2 for循環(huán)截圖
3.3.7函數(shù)操作
①main函數(shù):
傳入?yún)?shù)argc和*argv[],返回0:
圖3.3.7-1 main函數(shù)截圖
②printf函數(shù):
源代碼printf("用法: Hello 學(xué)號(hào) 姓名 秒數(shù)!\n")調(diào)用時(shí)傳入字符串的首地址,通過(guò)call指令轉(zhuǎn)移到指定程序:
圖3.3.7-2 printf函數(shù)例1截圖
源代碼printf("Hello %s %s\n",argv[1],argv[2])調(diào)用時(shí)傳入?yún)?shù)argv[1]和argv[2],通過(guò)call指令轉(zhuǎn)移到指定程序:
圖3.3.7-3 printf函數(shù)例2截圖
③exit函數(shù):
通過(guò)%edi傳遞參數(shù)1,通過(guò)call指令轉(zhuǎn)移到指定位置:
圖3.3.7-4 exit函數(shù)截圖
④sleep函數(shù):
通過(guò)%rdi傳遞參數(shù),通過(guò)call指令轉(zhuǎn)移到指定位置:
圖3.3.7-5 sleep函數(shù)截圖
3.4 本章小結(jié)
本章主要介紹了編譯的概念與作用,在Ubuntu下編譯的命令,同時(shí)對(duì)hello的編譯結(jié)果進(jìn)行解析,詳細(xì)分析了編譯器如何處理C語(yǔ)言中的數(shù)據(jù)、賦值、算術(shù)運(yùn)算、關(guān)系操作、數(shù)組操作、控制轉(zhuǎn)移和函數(shù)操作。有助于理解C語(yǔ)言在匯編語(yǔ)言中的表現(xiàn)形式。
第4章 匯編
4.1 匯編的概念與作用
(1)匯編的概念:
匯編器(as)將hello.s翻譯成機(jī)器語(yǔ)言指令,把這些指令打包成一種叫做可重定位目標(biāo)程序的格式,并將結(jié)果保存在目標(biāo)文件hello.o中。(hello.o文件是一個(gè)二進(jìn)制文件)
(2)匯編的作用;
將匯編代碼轉(zhuǎn)換為二進(jìn)制文件,也就是機(jī)器語(yǔ)言指令,并將指令打包。匯編指令包括數(shù)據(jù)傳輸指令、算術(shù)和邏輯指令、分支和循環(huán)指令、過(guò)程調(diào)用指令、處理器控制指令,通過(guò)這些指令完成匯編的過(guò)程。
4.2 在Ubuntu下匯編的命令
匯編命令:as hello.s -o hello.o
圖4.2-1 匯編命令截圖
4.3 可重定位目標(biāo)elf格式
分析hello.o的ELF格式,用readelf等列出其各節(jié)的基本信息,特別是重定位項(xiàng)目分析。
4.3.1readelf命令
命令:readelf -a helo.o > hello_elf.txt(導(dǎo)出elf文件至hello_elf.txt)
圖4.3.1 readelf命令截圖
4.3.2ELF頭
ELF頭以一個(gè)16字節(jié)的序列開(kāi)始,這個(gè)序列描述了生成該文件的系統(tǒng)的字的大小和字節(jié)順序。ELF頭剩下的部分包含幫助鏈接器語(yǔ)法分析和解釋目標(biāo)文件的信息。其中包括ELF頭的大小、目標(biāo)文件的類(lèi)型、機(jī)器類(lèi)型、節(jié)頭部表的文件偏移,以及節(jié)頭部表中條目的大小和數(shù)量。
圖4.3.2-1 ELF頭截圖
4.3.3節(jié)頭
不同節(jié)的位置和大小是由節(jié)頭部表描述的,其中目標(biāo)文件中每個(gè)節(jié)都有一個(gè)固定大小的條目。
圖4.3.3-1 節(jié)頭截圖
4.3.4重定位節(jié)
重定位節(jié)中包含了在代碼中使用的一些外部變量等信息,在鏈接的時(shí)候需要根據(jù)重定位節(jié)的信息對(duì)這些變量符號(hào)進(jìn)行修改。鏈接的時(shí)候鏈接器會(huì)根據(jù)重定位節(jié)的信息對(duì)外部變量符號(hào)決定選擇何種方式計(jì)算正確的地址,通過(guò)偏移量等信息計(jì)算出正確的地址。
圖4.3.4-1 重定位節(jié)截圖
.rodata中的內(nèi)容、puts、exit、printf、atoi、sleep、getchar都需要通過(guò)重定位計(jì)算正確的地址。且只出現(xiàn)了R_X86_64_PC_32和R_X86_64_PLT32兩種重定位類(lèi)型。
4.3.5符號(hào)表
.symtab存放在程序中定義和引用的函數(shù)和全局變量的信息。
圖4.3.5-1 符號(hào)表截圖
main、puts、exit、printf、atoi、sleep、getchar等函數(shù)都在.symtab中出現(xiàn)。
4.4 Hello.o的結(jié)果解析
objdump -d -r hello.o? 分析hello.o的反匯編,并請(qǐng)與第3章的 hello.s進(jìn)行對(duì)照分析。
說(shuō)明機(jī)器語(yǔ)言的構(gòu)成,與匯編語(yǔ)言的映射關(guān)系。特別是機(jī)器語(yǔ)言中的操作數(shù)與匯編語(yǔ)言不一致,特別是分支轉(zhuǎn)移函數(shù)調(diào)用等。
4.4.1objdump命令
命令:objdump -d -r hello.o > hello_obj.txt(將反匯編文件導(dǎo)出至hello_obj.txt)
圖4.4.1-1 objdump命令截圖
圖4.4.1-2 反匯編代碼截圖
4.4.2機(jī)器語(yǔ)言與匯編語(yǔ)言對(duì)比
①操作數(shù):
hello.s中的操作數(shù)是十進(jìn)制數(shù),如下圖中的$16;而hello.o反匯編中的操作數(shù)是十六進(jìn)制數(shù),如下圖中的$0x10。
圖4.4.2-1 hello.o的反匯編中操作數(shù)截圖
圖4.4.2-2 hello.s中操作數(shù)截圖
②分支轉(zhuǎn)移:
hello.s中跳轉(zhuǎn)時(shí)顯示的是段的名字,如下圖中的.L2;而hello.o由于已經(jīng)進(jìn)行重定位,因此跳轉(zhuǎn)時(shí)顯示的是跳轉(zhuǎn)的目的地址(與main函數(shù)的相對(duì)偏移地址),如下圖中的2f。
圖4.4.2-3 hello.o的反匯編中分支轉(zhuǎn)移截圖
圖4.4.2-4 hello.s中分支轉(zhuǎn)移截圖
③函數(shù)調(diào)用:
hello.s中call指令后顯示的是函數(shù)名稱(chēng),如下圖的exit;而hello.o的反匯編中call指令后顯示的是與main函數(shù)的相對(duì)偏移地址,如下圖的2f。
圖4.4.2-5 hello.o的反匯編中函數(shù)調(diào)用截圖
圖4.4.2-6 hello.s中函數(shù)調(diào)用截圖
4.5 本章小結(jié)
本章主要介紹了匯編的概念與作用,在Ubuntu下匯編的命令,對(duì)hello.o的ELF格式進(jìn)行分析,包括ELF頭、節(jié)頭、重定位節(jié)、符號(hào)表等內(nèi)容,對(duì)hello.o的結(jié)果進(jìn)行解析,對(duì)比分析了hello.o反匯編的結(jié)果與hello.s的區(qū)別,著重分析了操作數(shù)、分支轉(zhuǎn)移、函數(shù)調(diào)用的區(qū)別。有助于理解ELF格式以及匯編語(yǔ)言和機(jī)器語(yǔ)言的區(qū)別。
第5章 鏈接
5.1 鏈接的概念與作用
(1)鏈接的概念:
鏈接是指將各種代碼和數(shù)據(jù)片段收集并組合成為一個(gè)單一文件的過(guò)程,這個(gè)文件可被加載(復(fù)制)到內(nèi)存并執(zhí)行。
(2)鏈接的作用:
使得分離編譯成為可能。不用將一個(gè)大型的應(yīng)用程序組織為一個(gè)巨大的源文件,而是可以把它分解為更小、更好管理的模塊,可以獨(dú)立地修改和編譯這些模塊。當(dāng)改變這些模塊中的一個(gè)時(shí),只需簡(jiǎn)單地重新編譯它,并重新鏈接應(yīng)用,而不必重新編譯其他文件。
5.2 在Ubuntu下鏈接的命令
鏈接命令:ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello
圖5.2-1 鏈接命令截圖
5.3 可執(zhí)行目標(biāo)文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.3.1readelf命令
命令:readelf -a hello >hello_elf2.elf
圖5.3.1-1 readelf命令截圖
5.3.2ELF頭
內(nèi)容與4.3.2中介紹類(lèi)似。
圖5.3.2-1 ELF頭截圖
5.3.3節(jié)頭
描述了各個(gè)節(jié)的大小、偏移量和其他屬性。鏈接器鏈接時(shí),會(huì)將各個(gè)文件的相同段合并為一個(gè)大段,并且根據(jù)這個(gè)大段的大小以及偏移量重新設(shè)置各個(gè)符號(hào)的地址。
圖5.3.3-1 節(jié)頭截圖
5.3.4重定位節(jié)
重定位節(jié)內(nèi)容變?yōu)樾枰獎(jiǎng)討B(tài)鏈接調(diào)用的函數(shù),同時(shí)重定位類(lèi)型發(fā)生改變。
5.3.4-1 重定位節(jié)截圖
5.3.5符號(hào)表
存放在程序中定義和引用的函數(shù)和全局變量的信息,連接后符號(hào)表?xiàng)l目增加。
圖5.3.5-1 符號(hào)表截圖
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進(jìn)程的虛擬地址空間各段信息,并與5.3對(duì)照分析說(shuō)明。??
使用edb加載hello。
圖5.4-1 edb加載hello截圖
從Data Dump窗口可以看到hello的虛擬地址空間分配情況。
圖5.4-2 Data Dump窗口截圖
②.init段起始地址:0x401000
圖5.4-3 .init段起始地址截圖
③.text段起始地址:0x4010f0
圖5.4-4 .text段起始地址截圖
以此類(lèi)推,節(jié)頭中的各段在edb中均能對(duì)應(yīng)找到,說(shuō)明節(jié)頭表中存儲(chǔ)各段的起始地址與各段的虛擬地址之間存在對(duì)應(yīng)關(guān)系。
5.5 鏈接的重定位過(guò)程分析
objdump -d -r hello 分析hello與hello.o的不同,說(shuō)明鏈接的過(guò)程。
結(jié)合hello.o的重定位項(xiàng)目,分析hello中對(duì)其怎么重定位的。
5.5.1objdump命令
命令:objdump -d -r hello > hello_obj2.txt(將文件導(dǎo)出至hello_obj2.txt)
圖5.5.1-1 objdump命令截圖
5.5.2hello與hello.o對(duì)比及重定位過(guò)程
①hello比hello.o增加了一些節(jié),例如.init、.plt、.plt.sec等。
圖5.5.2-1 .init截圖
圖5.5.2-2 .plt截圖
圖5.5.2-3 .plt.sec截圖
②hello比hello中增加了一些函數(shù),例如_init等。
圖5.5.2-4 _init函數(shù)截圖
③hello刪去了hello.o中的重定位條目,hello.o中跳轉(zhuǎn)的目的地址和函數(shù)地址都是與main函數(shù)的相對(duì)偏移地址,hello中跳轉(zhuǎn)的目的地址和函數(shù)地址都是虛擬內(nèi)存地址。
圖5.5.3-4 hello.o中跳轉(zhuǎn)截圖
圖5.5.3-5 hello中跳轉(zhuǎn)截圖
5.5.3鏈接過(guò)程
主要分兩步:
①符號(hào)解析:目標(biāo)文件定義和引用符號(hào),每個(gè)符號(hào)對(duì)應(yīng)于一個(gè)函數(shù)、一個(gè)局部變量或一個(gè)靜態(tài)變量。符號(hào)解析的目的是將每個(gè)符號(hào)引用正好和一個(gè)符號(hào)定義關(guān)聯(lián)起來(lái)。
②重定位:編譯器和匯編器生成從地址0開(kāi)始的代碼和數(shù)據(jù)節(jié)。鏈接器通過(guò)把每個(gè)符號(hào)定義與一個(gè)內(nèi)存位置關(guān)聯(lián)起來(lái),從而重定位這些節(jié),然后修改所有對(duì)這些符號(hào)的引用,使得它們指向這個(gè)內(nèi)存位置。鏈接器使用匯編器產(chǎn)生的重定位條目的詳細(xì)指令,不加甄別地執(zhí)行這樣的重定位。
5.6 hello的執(zhí)行流程
使用edb執(zhí)行hello,說(shuō)明從加載hello到_start,到call main,以及程序終止的所有過(guò)程。請(qǐng)列出其調(diào)用與跳轉(zhuǎn)的各個(gè)子程序名或程序地址。
5.6.1執(zhí)行過(guò)程
使用edb執(zhí)行hello截圖:
圖5.6.1-1 edb執(zhí)行hello截圖
①?gòu)募虞dhello到_start:
程序先調(diào)用_init函數(shù),之后是puts、printf等庫(kù)函數(shù),最后調(diào)用_start函數(shù)。
②從_start到call main:
程序先調(diào)用__libc_csu_init等函數(shù),完成初始化工作,隨后調(diào)用main函數(shù)。
③從main函數(shù)到程序終止:
程序執(zhí)行main函數(shù)調(diào)用main函數(shù)用到的一些函數(shù),main函數(shù)執(zhí)行完畢之后調(diào)用__libc_csu_fini、_fini完成資源釋放和清理的工作。
5.6.2子程序名或程序地址
0000000000401000 <_init>
0000000000401020 <.plt>
0000000000401090
00000000004010a0
00000000004010b0
00000000004010c0
00000000004010d0
00000000004010e0
00000000004010f0 <_start>
0000000000401120 <_dl_relocate_static_pie>
0000000000401125
00000000004011c0 <__libc_csu_init>
0000000000401230 <__libc_csu_fini>
0000000000401238 <_fini>
5.7 Hello的動(dòng)態(tài)鏈接分析
分析hello程序的動(dòng)態(tài)鏈接項(xiàng)目,通過(guò)edb調(diào)試,分析在dl_init前后,這些項(xiàng)目的內(nèi)容變化。要截圖標(biāo)識(shí)說(shuō)明。
程序調(diào)用由共享庫(kù)定義的函數(shù),編譯器沒(méi)有辦法預(yù)測(cè)這個(gè)函數(shù)的運(yùn)行時(shí)地址,因?yàn)槎x它的共享模塊在運(yùn)行時(shí)可以加載到任意位置。GNU編譯系統(tǒng)使用延遲綁定將過(guò)程地址的綁定過(guò)程推遲到第一次調(diào)用該過(guò)程時(shí)。通過(guò)兩個(gè)數(shù)據(jù)結(jié)構(gòu)——GOT和PLT協(xié)作在運(yùn)行時(shí)解析函數(shù)的地址。
.got.plt的首地址為0x404000,通過(guò)edb觀察其在_init前后的內(nèi)容變化。
圖5.7-1 .got.plt在_init前的內(nèi)容截圖
圖5.7-2 .got.plt在_init后的內(nèi)容截圖
5.8 本章小結(jié)
本章主要介紹了鏈接的概念與作用,在Ubuntu下鏈接的命令,可執(zhí)行目標(biāo)文件hello的格式,包括ELF頭、節(jié)頭、重定位節(jié)、符號(hào)表,通過(guò)對(duì)比hello與hello.o進(jìn)行鏈接的過(guò)程分析和重定位過(guò)程分析,對(duì)hello的執(zhí)行流程和動(dòng)態(tài)鏈接進(jìn)行分析。
第6章 hello進(jìn)程管理
6.1 進(jìn)程的概念與作用
(1)進(jìn)程的概念:
進(jìn)程的經(jīng)典定義就是一個(gè)執(zhí)行中程序的實(shí)例。
(2)進(jìn)程的作用:
每次用戶向shell輸入一個(gè)可執(zhí)行目標(biāo)文件的名字,運(yùn)行程序時(shí),shell就會(huì)創(chuàng)建一個(gè)新的進(jìn)程,然后在這個(gè)新進(jìn)程的上下文中運(yùn)行這個(gè)可執(zhí)行目標(biāo)文件。應(yīng)用程序也能夠創(chuàng)建新進(jìn)程,并且在這個(gè)新進(jìn)程的上下文運(yùn)行它們自己的代碼或其他應(yīng)用程序。進(jìn)程提供給應(yīng)用程序兩個(gè)關(guān)鍵抽象:
①一個(gè)獨(dú)立的邏輯流,它提供一個(gè)假象,好像我們的程序獨(dú)占地使用處理器。
②一個(gè)私有的地址空間,它提供一個(gè)假象,好像我們的程序獨(dú)占地使用內(nèi)存系統(tǒng)。
6.2 簡(jiǎn)述殼Shell-bash的作用與處理流程
(1)殼shell-bash的作用:
bash是變種、缺省的Linux shell,是信號(hào)處理的代表,負(fù)責(zé)各進(jìn)程創(chuàng)建與程序加載運(yùn)行及前后臺(tái)控制、作業(yè)調(diào)用、信號(hào)發(fā)送與管理等;是一個(gè)交互型應(yīng)用級(jí)程序,代表用戶運(yùn)行其他程序;執(zhí)行一系列的讀/求值等步驟;讀步驟讀取用戶的命令行,求值步驟解析命令,代表用戶運(yùn)行。
(2)殼shell-bash的處理流程:
①判斷命令是否通過(guò)絕對(duì)路徑執(zhí)行; ②判斷命令是否存在alias別名; ③判斷用戶輸入的是內(nèi)部命令還是外部命令; ④Bash內(nèi)部命令直接執(zhí)行,外部命令檢測(cè)是否存在緩存; ⑤通過(guò)PATH路徑查找命令,有執(zhí)行,無(wú)報(bào)錯(cuò);
6.3 Hello的fork進(jìn)程創(chuàng)建過(guò)程
父進(jìn)程通過(guò)調(diào)用fork函數(shù)創(chuàng)建一個(gè)新的運(yùn)行的子進(jìn)程;子進(jìn)程返回0,父進(jìn)程返回子進(jìn)程的PID;新創(chuàng)建的子進(jìn)程幾乎但不完全與父進(jìn)程相同:子進(jìn)程得到與父進(jìn)程虛擬地址空間相同的(但是獨(dú)立的)一份副本(代碼、數(shù)據(jù)段、堆、共享庫(kù)以及用戶棧);子進(jìn)程獲得與父進(jìn)程任何打開(kāi)文件描述符相同的副本,子進(jìn)程有不同于父進(jìn)程的PID;fork函數(shù):被調(diào)用一次,卻返回兩次。
6.4 Hello的execve過(guò)程
execve函數(shù)在當(dāng)前進(jìn)程的上下文中加載并運(yùn)行一個(gè)新程序。execve函數(shù)加載并運(yùn)行可執(zhí)行目標(biāo)文件,且?guī)?shù)列表和環(huán)境變量列表。只有當(dāng)出現(xiàn)錯(cuò)誤時(shí),execve才會(huì)返回到調(diào)用程序。所以,與fork依次調(diào)用返回兩次不同,execve調(diào)用一次并從不返回。在execve加載了可執(zhí)行目標(biāo)文件之后,其調(diào)用啟動(dòng)代碼。啟動(dòng)代碼設(shè)置棧,并將控制傳遞給新程序的主函數(shù)。
6.5 Hello的進(jìn)程執(zhí)行
結(jié)合進(jìn)程上下文信息、進(jìn)程時(shí)間片,闡述進(jìn)程調(diào)度的過(guò)程,用戶態(tài)與核心態(tài)轉(zhuǎn)換等等。
(1)進(jìn)程上下文信息:
系統(tǒng)中的每個(gè)程序都運(yùn)行在某個(gè)進(jìn)程的上下文中。上下文是由程序正確運(yùn)行所需的狀態(tài)組成的。這個(gè)狀態(tài)包括存放在內(nèi)存中的程序的代碼和數(shù)據(jù),它的棧、通用目的寄存器的內(nèi)容、程序計(jì)數(shù)器、環(huán)境變量以及打開(kāi)文件描述符的集合。
(2)進(jìn)程時(shí)間片:
一個(gè)進(jìn)程執(zhí)行它的控制流的每一部分的每一時(shí)間段叫做時(shí)間片。
(3)進(jìn)程調(diào)度的過(guò)程:
在進(jìn)程執(zhí)行的某些時(shí)刻,內(nèi)核可以決定搶占當(dāng)前進(jìn)程,并重新開(kāi)始一個(gè)先前被搶占了的進(jìn)程。這種決策就叫做調(diào)度,是由內(nèi)核中被稱(chēng)為調(diào)度器的代碼處理的。當(dāng)內(nèi)核選擇一個(gè)新的進(jìn)程運(yùn)行時(shí),我們說(shuō)內(nèi)核調(diào)度了這個(gè)進(jìn)程。在內(nèi)核調(diào)度了一個(gè)新的進(jìn)程運(yùn)行后,他就搶占當(dāng)前進(jìn)程,并使用一種稱(chēng)為上下文切換的機(jī)制來(lái)將控制轉(zhuǎn)移到新的進(jìn)程,上下文切換①保存當(dāng)前進(jìn)程的上下文;②恢復(fù)某個(gè)先前進(jìn)程被搶占的進(jìn)程被保存的上下文;③將控制傳遞給這個(gè)新恢復(fù)的進(jìn)程。
(4)用戶態(tài)與核心態(tài)轉(zhuǎn)換:
運(yùn)行應(yīng)用程序代碼的進(jìn)程初始時(shí)是在用戶模式中的。進(jìn)程從用戶模式變?yōu)閮?nèi)核模式的唯一方法是通過(guò)諸如中斷、故障或者陷入系統(tǒng)調(diào)用這樣的異常。當(dāng)異常發(fā)生時(shí),控制傳遞到異常處理程序,處理器將模式從用戶模式變?yōu)閮?nèi)核模式。處理程序運(yùn)行在內(nèi)核模式中,當(dāng)它返回到應(yīng)用程序代碼時(shí),處理器就把模式從內(nèi)核模式改回到用戶模式。
6.6 hello的異常與信號(hào)處理
?hello執(zhí)行過(guò)程中會(huì)出現(xiàn)哪幾類(lèi)異常,會(huì)產(chǎn)生哪些信號(hào),又怎么處理的。
?程序運(yùn)行過(guò)程中可以按鍵盤(pán),如不停亂按,包括回車(chē),Ctrl-Z,Ctrl-C等,Ctrl-z后可以運(yùn)行ps? jobs? pstree? fg? kill 等命令,請(qǐng)分別給出各命令及運(yùn)行結(jié)截屏,說(shuō)明異常與信號(hào)的處理。
6.6.1異常
①中斷(異步異常):處理器外部I/O設(shè)備引起;
②陷阱(同步異常):有意的,執(zhí)行指令的結(jié)果;
③故障(同步異常):不是有意的,但可能被修復(fù);
④終止(同步異常):非故意,不可恢復(fù)的致命錯(cuò)誤造成。
6.6.2信號(hào)
①中斷:信號(hào)SIGTSTP,默認(rèn)行為是停止直到下一個(gè)SIGCONT;
②終止:信號(hào)SIGINT,默認(rèn)行為是終止。
6.6.3處理方式
中斷:
圖6.6.3-1 中斷處理方式
陷阱:
圖6.6.3-2 陷阱處理方式
故障:
圖6.6.3-3 故障處理方式
終止:
圖6.6.3-4 終止處理方式
6.6.4命令、運(yùn)行結(jié)果、異常與信號(hào)的處理
①正常運(yùn)行:程序每隔3秒輸出一次,共輸出8次。
圖6.6.4-1 正常運(yùn)行結(jié)果截圖
②不停亂按:將屏幕輸入緩存到緩沖區(qū),不影響當(dāng)前進(jìn)程的執(zhí)行。
圖6.6.4-2 不停亂按結(jié)果截圖
③回車(chē):不影響當(dāng)前進(jìn)程的執(zhí)行。
圖6.6.4-3 回車(chē)結(jié)果截圖
④Ctrl-Z后運(yùn)行ps命令:Ctrl-Z掛起前臺(tái)作業(yè),ps顯示進(jìn)程的詳細(xì)信息。
圖6.6.4-4 Ctrl-Z后運(yùn)行ps命令截圖
⑤Ctrl-Z后運(yùn)行jobs命令:Ctrl-Z掛起前臺(tái)作業(yè),jobs顯示任務(wù)列表和任務(wù)狀態(tài)。
圖6.6.4-5 Ctrl-Z后運(yùn)行jobs命令截圖
⑥Ctrl-Z后運(yùn)行pstree命令:Ctrl-Z掛起前臺(tái)作業(yè),pstree以樹(shù)狀結(jié)構(gòu)顯示進(jìn)程之間的關(guān)系。
圖6.6.4-6 Ctrl-Z后運(yùn)行pstree命令1
圖6.6.4-7 Ctrl-Z后運(yùn)行pstree命令2
⑦Ctrl-Z后運(yùn)行fg命令:Ctrl-Z掛起前臺(tái)作業(yè),fg %n使第n個(gè)任務(wù)在前臺(tái)運(yùn)行,此處是第一個(gè)任務(wù)。
圖6.6.4-8 Ctrl-Z后運(yùn)行fg命令截圖
⑧Ctrl-Z后運(yùn)行kill命令:Ctrl-Z掛起前臺(tái)作業(yè),kill殺死進(jìn)程。通過(guò)kill -9 8993給進(jìn)程8993發(fā)送SIGKILL信號(hào)。
圖6.6.4-9 Ctrl-Z后運(yùn)行kill命令截圖
⑨Ctrl-C:發(fā)送SIGINT信號(hào),結(jié)束hello。在ps中沒(méi)有其相關(guān)信息。
圖6.6.4-10 Ctrl-C結(jié)果截圖
6.7本章小結(jié)
本章主要介紹了進(jìn)程的概念與作用、殼Shell-bash的作用與處理流程、fork進(jìn)程創(chuàng)建過(guò)程、execve過(guò)程、進(jìn)程執(zhí)行和異常與信號(hào)處理等內(nèi)容。有助于通過(guò)hello理解進(jìn)程、異常、信號(hào)等概念以及其在實(shí)踐中的體現(xiàn)與作用。
第7章 hello的存儲(chǔ)管理
7.1 hello的存儲(chǔ)器地址空間
結(jié)合hello說(shuō)明邏輯地址、線性地址、虛擬地址、物理地址的概念。
(1)邏輯地址:
由程序產(chǎn)生的與段相關(guān)的偏移地址部分。如hello.o中跳轉(zhuǎn)的目的地址和函數(shù)地址都是與main函數(shù)的相對(duì)偏移地址。
(2)線性地址:
段地址+偏移地址=線性地址,線性地址是邏輯地址到物理地址變換的中間結(jié)果。如hello中代碼與數(shù)據(jù)的地址。
(3)虛擬地址:
虛擬內(nèi)存是對(duì)整個(gè)內(nèi)存的抽象描述,是相對(duì)于物理內(nèi)存來(lái)講的。虛擬內(nèi)存也就是線性地址空間。如hello中的地址。
(4)物理地址:
用于內(nèi)存芯片級(jí)的單元尋址,與處理器和CPU連接的地址總線相對(duì)應(yīng)。在hello運(yùn)行中,需要根據(jù)虛擬地址通過(guò)地址翻譯得到物理地址,并通過(guò)物理地址訪問(wèn)其在內(nèi)存中的位置。
7.2 Intel邏輯地址到線性地址的變換-段式管理
(1)段寄存器:
圖7.2-1 段寄存器的含義
段寄存器(16位)用于存放段選擇符
CS(代碼段):程序代碼所在段;
SS(棧段):棧區(qū)所在段;
DS(數(shù)據(jù)段):全局靜態(tài)數(shù)據(jù)區(qū)所在段;
其他3個(gè)段寄存器ES、GS和FS可指向任意數(shù)據(jù)段;
(2)段選擇符:
圖7.2-2 段選擇符
①TI=0,選擇全局描述符表(GDT),TI=1,選擇局部描述符表(LDT);
②CS寄存器中的RPL字段表示CPU的當(dāng)前特權(quán)級(jí)。RPL=00,為第0級(jí),位于最高級(jí)的內(nèi)核態(tài),RPL=11,為第3級(jí),位于最低級(jí)的用戶態(tài),第0級(jí)高于第3級(jí);
③高13位-8K個(gè)索引用來(lái)確定當(dāng)前使用的段描述符在描述符表中的位置。
(3)段描述符:
段描述符是一種數(shù)據(jù)結(jié)構(gòu),實(shí)際上就是段表項(xiàng),分兩類(lèi):用戶的代碼段和數(shù)據(jù)段描述符、系統(tǒng)控制段描述符,其中系統(tǒng)控制段描述符又分兩種:特殊系統(tǒng)控制段描述符,包括:局部描述符表(LDT)描述符和任務(wù)狀態(tài)段(TSS)描述符;控制轉(zhuǎn)移類(lèi)描述符,包括:調(diào)用門(mén)描述符、任務(wù)門(mén)描述符、中斷門(mén)描述符和陷阱門(mén)描述符。
(4)描述符表:
描述符表實(shí)際上就是段表,由段描述符(段表項(xiàng))組成。有三種類(lèi)型:
①全局描述符表GDT:只有一個(gè),用來(lái)存放系統(tǒng)內(nèi)每個(gè)任務(wù)都可能訪問(wèn)的描述符,例如,內(nèi)核代碼段、內(nèi)核數(shù)據(jù)段、用戶代碼段、用戶數(shù)據(jù)段以及TSS(任務(wù)狀態(tài)段)等都屬于GDT中描述的段;
②局部描述符表LDT:存放某任務(wù)(即用戶進(jìn)程)專(zhuān)用的描述符;
③中斷描述符表IDT:包含256個(gè)中斷門(mén)、陷阱門(mén)和任務(wù)門(mén)描述符。
(5)Intel處理器的存儲(chǔ)器尋址:
圖7.2-3 Intel處理器的存儲(chǔ)器尋址
(6)段式管理:
根據(jù)段選擇符定位到相應(yīng)的段描述符,根據(jù)段描述符在描述符表中得到相應(yīng)的段基址,加上偏移量,得到線性地址。
7.3 Hello的線性地址到物理地址的變換-頁(yè)式管理
(1)頁(yè)表:
頁(yè)表是一個(gè)頁(yè)表?xiàng)l目的數(shù)組,將虛擬頁(yè)地址映射到物理頁(yè)地址。
(2)頁(yè)命中:
虛擬內(nèi)存中的一個(gè)字存在于物理內(nèi)存中,即(DRAM緩存命中)。 (3)缺頁(yè)及缺頁(yè)處理:
虛擬內(nèi)存中的字不在物理內(nèi)存中(DRAM緩存不命中)。缺頁(yè)導(dǎo)致頁(yè)面出錯(cuò) (缺頁(yè)異常);缺頁(yè)異常處理程序選擇一個(gè)犧牲頁(yè);導(dǎo)致缺頁(yè)的指令重新啟動(dòng): 頁(yè)面命中。
(4)分配一個(gè)新的虛擬頁(yè):
內(nèi)核在磁盤(pán)上分配,并將頁(yè)表指向這個(gè)位置。
7.4 TLB與四級(jí)頁(yè)表支持下的VA到PA的變換
若TLB命中,則MMU從TLB中取出相應(yīng)的PTE,將這個(gè)虛擬地址翻譯為物理地址;若TLB不命中,根據(jù)VPN1在一級(jí)頁(yè)表選擇對(duì)應(yīng)的PTE,該P(yáng)TE包含二級(jí)頁(yè)表的基地址;根據(jù)VPN2在二級(jí)頁(yè)表選擇對(duì)應(yīng)的PTE,該P(yáng)TE包含三級(jí)頁(yè)表的基地址;根據(jù)VPN3在三級(jí)頁(yè)表選擇對(duì)應(yīng)的PTE,該P(yáng)TE包含四級(jí)頁(yè)表的基地址;在四級(jí)頁(yè)表取出對(duì)應(yīng)的PPN,與VPO串聯(lián)起來(lái),就得到相應(yīng)的物理地址。
7.5 三級(jí)Cache支持下的物理內(nèi)存訪問(wèn)
首先訪問(wèn)一級(jí)Cache,尋找該物理內(nèi)存對(duì)應(yīng)的內(nèi)容是否已被緩存且有效,若已被緩存且有效,則緩存命中;否則緩存不命中,則需要訪問(wèn)二級(jí)Cache,重復(fù)上述步驟;若二級(jí)Cache中依然緩存不命中,則需要訪問(wèn)三級(jí)Cache,直到訪問(wèn)主存。將訪問(wèn)到的內(nèi)容分別加載進(jìn)上一層緩存,再進(jìn)行后續(xù)操作。
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。為了給這個(gè)新進(jìn)程創(chuàng)建虛擬內(nèi)存,它創(chuàng)建了當(dāng)前進(jìn)程的mm_struct、區(qū)域結(jié)構(gòu)和頁(yè)表的原樣副本。它將兩個(gè)進(jìn)程中的每個(gè)頁(yè)面都標(biāo)記為只讀,并將兩個(gè)進(jìn)程中的每個(gè)區(qū)域結(jié)構(gòu)都標(biāo)記為私有的寫(xiě)時(shí)復(fù)制。
當(dāng)fork函數(shù)在新進(jìn)程中返回時(shí),新進(jìn)程現(xiàn)在的虛擬內(nèi)存剛好和調(diào)用fork時(shí)存在的虛擬內(nèi)存相同。當(dāng)這兩個(gè)進(jìn)程中的任一個(gè)后來(lái)進(jìn)行寫(xiě)操作時(shí),寫(xiě)時(shí)復(fù)制機(jī)制就會(huì)創(chuàng)建新頁(yè)面,因此,也就為每個(gè)進(jìn)程保持了私有地址空間的抽象概念。
7.7 hello進(jìn)程execve時(shí)的內(nèi)存映射
execve函數(shù)在當(dāng)前進(jìn)程中加載并運(yùn)行包含在可執(zhí)行目標(biāo)文件hello中的程序,用hello程序有效地替代了當(dāng)前程序。加載并運(yùn)行hello需要以下幾個(gè)步驟:
①刪除已存在的用戶區(qū)域。刪除當(dāng)前進(jìn)程虛擬地址的用戶部分中的已存在的區(qū)域結(jié)構(gòu)。
②映射私有區(qū)域。為新程序的代碼、數(shù)據(jù)、bss和棧區(qū)域創(chuàng)建新的區(qū)域結(jié)構(gòu)。所有這些新的區(qū)域都是私有的、寫(xiě)時(shí)復(fù)制的。代碼和數(shù)據(jù)區(qū)域都被映射為hello文件中的.text和.data區(qū).bss區(qū)域是請(qǐng)求二進(jìn)制零的,映射到匿名文件,其大小包含在hello中。棧和堆區(qū)域也是請(qǐng)求二進(jìn)制零的,初始長(zhǎng)度為零。
③映射共享區(qū)域。如果hello程序與共享對(duì)象(或目標(biāo))鏈接,那么這些對(duì)象都是動(dòng)態(tài)鏈接到這個(gè)程序的,然后再映射到用戶虛擬地址空間中的共享區(qū)域內(nèi)。
④設(shè)置程序計(jì)數(shù)器(PC)。execve做的最后一件事情就是設(shè)置當(dāng)前進(jìn)程上下文中的程序計(jì)數(shù)器,使之指向代碼區(qū)域的入口點(diǎn)。
7.8 缺頁(yè)故障與缺頁(yè)中斷處理
DRAM緩存不命中稱(chēng)為缺頁(yè)。CPU引用了VPm中的一個(gè)字,VPm并未緩存在DRAM中。地址翻譯硬件從內(nèi)存中讀取PTEm,從有效位推斷出PTEm未被緩存,并且觸發(fā)一個(gè)缺頁(yè)異常。缺頁(yè)異常調(diào)用內(nèi)核中的缺頁(yè)異常處理程序,該程序會(huì)選擇一個(gè)犧牲頁(yè)(假設(shè)其為VPn)。如果VPn已經(jīng)被修改了,那么內(nèi)核就會(huì)將它復(fù)制回磁盤(pán)。無(wú)論哪種情況,內(nèi)核都會(huì)修改VPn的頁(yè)表?xiàng)l目,反映出VPn已經(jīng)不在緩存在主存中這一事實(shí)。
接下來(lái),內(nèi)核從磁盤(pán)復(fù)制VPm到內(nèi)存中,更新PTEm,隨后返回。當(dāng)異常處理程序返回時(shí),它會(huì)重新啟動(dòng)導(dǎo)致缺頁(yè)的指令,該指令會(huì)把導(dǎo)致缺頁(yè)的虛擬地址重發(fā)送到地址翻譯硬件。
7.9動(dòng)態(tài)存儲(chǔ)分配管理
動(dòng)態(tài)內(nèi)存分配器維護(hù)著一個(gè)進(jìn)程的虛擬內(nèi)存區(qū)域,稱(chēng)為堆。系統(tǒng)之間細(xì)節(jié)不同,但是不失通用性,假設(shè)堆是一個(gè)請(qǐng)求二進(jìn)制零的區(qū)域,它緊接著未初始化的數(shù)據(jù)區(qū)域后開(kāi)始,并向上生長(zhǎng)。對(duì)于每個(gè)進(jìn)程,內(nèi)核維護(hù)著一個(gè)變量brk,它指向堆的頂部。
分配器將堆視為一組大小不同的塊的集合來(lái)維護(hù)。每個(gè)塊就是一個(gè)連續(xù)的虛擬內(nèi)存片,要么是已分配的,要么是空閑的。已分配的塊顯式地保留為供應(yīng)用程序使用??臻e塊可用來(lái)分配。空閑塊保持空閑,直到它顯式地被應(yīng)用所分配。一個(gè)已分配的塊保持已分配狀態(tài),直到它被釋放,這種釋放要么是應(yīng)用程序顯式執(zhí)行的,要么是內(nèi)存分配器自身隱式執(zhí)行的。
分配器有兩種基本風(fēng)格。兩種風(fēng)格都要求應(yīng)用顯式地分配塊。它們的不同之處在于由哪個(gè)實(shí)體來(lái)負(fù)責(zé)釋放已分配的塊。
①顯式分配器,要求應(yīng)用顯式地釋放任何已分配的塊。
②隱式分配器,另一方面,要求分配器檢測(cè)一個(gè)已分配塊何時(shí)不再被程序所使用,那么就釋放這個(gè)塊。隱式分配器也叫做垃圾收集器,而自動(dòng)釋放未使用的已分配的塊的過(guò)程就叫做垃圾收集。
7.10本章小結(jié)
本章主要介紹了存儲(chǔ)器地址空間、Intel邏輯地址到線性地址的變換-段式管理、線性地址到物理地址的變換-頁(yè)式管理、TLB與四級(jí)頁(yè)表支持下的VA到VP的變換、三級(jí)Cache支持下的物理內(nèi)存訪問(wèn),進(jìn)程fork和execve時(shí)的內(nèi)存映射、缺頁(yè)故障與缺頁(yè)中斷處理、動(dòng)態(tài)存儲(chǔ)分配管理等內(nèi)容。有助于理解程序的存儲(chǔ)管理的各種機(jī)制,以及有關(guān)的存儲(chǔ)方式與管理策略。
第8章 hello的IO管理
8.1 Linux的IO設(shè)備管理方法
設(shè)備的模型化:文件。所有的I/O設(shè)備(例如網(wǎng)絡(luò)、磁盤(pán)和終端)都被模型化為文件,而所有的輸入和輸出都被當(dāng)作對(duì)對(duì)應(yīng)文件的讀和寫(xiě)來(lái)執(zhí)行。
設(shè)備管理:unix io接口。將設(shè)備映射為文件的方式,使得Linux內(nèi)核引出一個(gè)簡(jiǎn)單、低級(jí)的應(yīng)用接口,稱(chēng)為I/O接口,這使得所有的輸入和輸出都能以一種統(tǒng)一且一致的方式來(lái)執(zhí)行。
8.2 簡(jiǎn)述Unix IO接口及其函數(shù)
8.2.1Unix IO接口
Unix IO使得所有的輸入和輸出都能以一種統(tǒng)一且一致的方式來(lái)執(zhí)行:
①打開(kāi)文件。一個(gè)應(yīng)用程序通過(guò)要求內(nèi)核打開(kāi)相應(yīng)的文件,來(lái)宣告它想要訪問(wèn)一個(gè)I/O設(shè)備。內(nèi)核返回一個(gè)小的非負(fù)整數(shù),叫做描述符,它在后續(xù)對(duì)此文件的所有操作中標(biāo)識(shí)這個(gè)文件。內(nèi)核記錄有關(guān)這個(gè)打開(kāi)文件的所有信息。應(yīng)用程序只需記住這個(gè)描述符。
②Linux Shell創(chuàng)建的每個(gè)進(jìn)程開(kāi)始時(shí)都有三個(gè)打開(kāi)的對(duì)應(yīng)文件:標(biāo)準(zhǔn)輸入(描述符為0)、標(biāo)準(zhǔn)輸出(描述符為1)和標(biāo)準(zhǔn)錯(cuò)誤(描述符為2)。頭文件
③改變當(dāng)前的文件位置。對(duì)于每個(gè)打開(kāi)的文件,內(nèi)核保持著一個(gè)文件位置k,初始時(shí)為0。這個(gè)文件位置是從文件開(kāi)頭起始的字節(jié)偏移量。應(yīng)用程序能夠通過(guò)執(zhí)行seek操作,顯式地設(shè)置文件的當(dāng)前位置為k。
④讀寫(xiě)文件。一個(gè)讀操作就是從文件復(fù)制n>0個(gè)字節(jié)到內(nèi)存,從當(dāng)前文件位置k開(kāi)始,然后將k增加到k+n。給定一個(gè)大小為m字節(jié)的文件,當(dāng)k>=m時(shí)執(zhí)行讀操作會(huì)觸發(fā)一個(gè)稱(chēng)為end-of-file(EOF)的條件,應(yīng)用程序能檢測(cè)到這個(gè)條件。在文件結(jié)尾處并沒(méi)有明確的“EOF符號(hào)”。類(lèi)似地,寫(xiě)操作就是從內(nèi)存復(fù)制n>0個(gè)字節(jié)到一個(gè)文件,從當(dāng)前文件位置k開(kāi)始,然后更新k。
⑤關(guān)閉文件。當(dāng)應(yīng)用完成了對(duì)文件的訪問(wèn)之后,它就通知內(nèi)核關(guān)閉這個(gè)文件。作為響應(yīng),內(nèi)核釋放文件打開(kāi)時(shí)創(chuàng)建的數(shù)據(jù)結(jié)構(gòu),并將這個(gè)描述符恢復(fù)到可用的描述符池中。無(wú)論一個(gè)進(jìn)程因?yàn)楹畏N原因終止時(shí),內(nèi)核都會(huì)關(guān)閉所有打開(kāi)的文件并釋放它們的內(nèi)存資源。
8.2.2Unix IO函數(shù)
①打開(kāi)文件:int open(char *filename, int flag, mode_t mode);
進(jìn)程通過(guò)調(diào)用open函數(shù)來(lái)打開(kāi)一個(gè)已存在的文件或者創(chuàng)建一個(gè)新文件。open函數(shù)將filename轉(zhuǎn)換為一個(gè)文件描述符,并且返回描述符數(shù)字。返回的描述符總是在進(jìn)程中當(dāng)前沒(méi)有打開(kāi)的最小描述符。flags參數(shù)指明了進(jìn)程打算如何訪問(wèn)這個(gè)文件,也可以是一個(gè)或者更多位掩碼的或,為寫(xiě)提供給一些額外的指示。mode參數(shù)指定了新文件的訪問(wèn)權(quán)限位。
②關(guān)閉文件:int close(int fd);
進(jìn)程通過(guò)調(diào)用close函數(shù)關(guān)閉一個(gè)打開(kāi)的文件。關(guān)閉一個(gè)已關(guān)閉的描述符會(huì)出錯(cuò)。
③讀文件:ssize_t read(int fd, void *buf, size_t n);
應(yīng)用程序通過(guò)調(diào)用read函數(shù)執(zhí)行輸入。read函數(shù)從描述符為fd的當(dāng)前文件位置復(fù)制最多n個(gè)字節(jié)到內(nèi)存位置buf。返回值-1表示一個(gè)錯(cuò)誤,而返回值0表示EOF。否則,返回值表示的是實(shí)際傳送的字節(jié)數(shù)量。
④寫(xiě)文件:ssize_t write(int fd, const void *buf, size_t n);
應(yīng)用程序通過(guò)調(diào)用write函數(shù)執(zhí)行輸出。write函數(shù)從內(nèi)存位置buf復(fù)制至多n個(gè)字節(jié)到描述符fd的當(dāng)前文件位置。
8.3 printf的實(shí)現(xiàn)分析
printf函數(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;
}
printf函數(shù)調(diào)用vsprintf函數(shù)進(jìn)行格式化,接受確定輸出格式的格式字符串fmt,用格式字符串對(duì)個(gè)數(shù)變化的參數(shù)進(jìn)行格式化,產(chǎn)生格式化輸出;調(diào)用write函數(shù)把buf中的i個(gè)元素的值寫(xiě)到終端。syscall函數(shù)不斷地打印出字符,直到遇到:'\0'。
字符顯示驅(qū)動(dòng)子程序:從ASCII到字模庫(kù)到顯示vram(存儲(chǔ)每一個(gè)點(diǎn)的RGB顏色信息)。顯示芯片按照刷新頻率逐行讀取vram,并通過(guò)信號(hào)線向液晶顯示器傳輸每一個(gè)點(diǎn)(RGB分量)。
8.4 getchar的實(shí)現(xiàn)分析
getchar函數(shù)體:
int getchar(void)
{
????? static char buf[BUFSIZE];
????? static char *b=buf;
????? static int n=0;
????? if(n==0)
????? {
???????????? read(0,buf,BUFSIZE);
???????????? b=buf;
????? }
????? return ((--n)>0) ? (unsigned char) *b++ : EOF;
}?
用戶從鍵盤(pán)輸入,鍵盤(pán)接口得到對(duì)應(yīng)的鍵盤(pán)掃描碼,同時(shí)發(fā)送中斷請(qǐng)求,通過(guò)鍵盤(pán)中斷處理子程序,接受按鍵掃描碼轉(zhuǎn)成ascii碼,保存到系統(tǒng)的鍵盤(pán)緩沖區(qū)。getchar等調(diào)用read系統(tǒng)函數(shù),通過(guò)系統(tǒng)調(diào)用讀取按鍵ascii碼,直到接受到回車(chē)鍵才返回。getchar的返回值是用戶輸入字符的ascii碼,若到文件結(jié)尾則返回-1(EOF),且將用戶輸入顯示到屏幕。
8.5本章小結(jié)
本章主要介紹了Linux的IO設(shè)備管理方法、Unix IO接口及其函數(shù),同時(shí)對(duì)printf和getchar的實(shí)現(xiàn)進(jìn)行分析。通過(guò)對(duì)熟悉函數(shù)的分析深入了解函數(shù)實(shí)現(xiàn)與IO的關(guān)系,有助于理解IO管理的概念和相關(guān)函數(shù)。
結(jié)論
(1)用計(jì)算機(jī)系統(tǒng)的語(yǔ)言,逐條總結(jié)hello所經(jīng)歷的過(guò)程。
本文圍繞hello所經(jīng)歷的過(guò)程展開(kāi),其中hello所經(jīng)歷的重要結(jié)點(diǎn)包括:
①編寫(xiě)源程序(文本)hello.c;
②hello.c經(jīng)過(guò)預(yù)處理器(cpp)的預(yù)處理得到修改了的源程序(文本)hello.i;
③hello.i經(jīng)過(guò)編譯器(ccl)的編譯得到匯編程序(文本)hello.s;
④hello.s經(jīng)過(guò)匯編器(as)的匯編得到可重定位目標(biāo)程序(二進(jìn)制)hello.o;
⑤hello.o經(jīng)過(guò)鏈接器(ld)將其與其它目標(biāo)文件合并得到可執(zhí)行目標(biāo)文件hello;
⑥shell調(diào)用fork函數(shù)創(chuàng)建子進(jìn)程;
⑦shell調(diào)用execve函數(shù)加載hello程序,映射到對(duì)應(yīng)的虛擬內(nèi)存;
⑧hello程序執(zhí)行過(guò)程中通過(guò)進(jìn)程管理實(shí)現(xiàn)異常與信號(hào)的處理,存儲(chǔ)管理實(shí)現(xiàn)內(nèi)存訪問(wèn),同時(shí)相應(yīng)的IO設(shè)備配合hello程序?qū)崿F(xiàn)輸入輸出等功能;
⑨程序結(jié)束,父進(jìn)程對(duì)其進(jìn)行回收,內(nèi)核將其從系統(tǒng)中清除。
(2)你對(duì)計(jì)算機(jī)系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)的深切感悟,你的創(chuàng)新理念,如新的設(shè)計(jì)與實(shí)現(xiàn)方法。
計(jì)算機(jī)系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)首先需要深刻理解計(jì)算機(jī)系統(tǒng)的各種概念,包括運(yùn)行機(jī)制、原理等,了解程序執(zhí)行過(guò)程中的基本流程,包括預(yù)處理、編譯、匯編、鏈接等階段以及相應(yīng)的細(xì)節(jié)。在此基礎(chǔ)上,通過(guò)對(duì)進(jìn)程管理、存儲(chǔ)管理、IO管理等加深對(duì)整體框架結(jié)構(gòu)的認(rèn)識(shí)。同時(shí),依據(jù)計(jì)算機(jī)系統(tǒng)的相關(guān)原理,可以在安全性、高效性等方面對(duì)程序做進(jìn)一步優(yōu)化;針對(duì)各種可能存在的安全風(fēng)險(xiǎn)進(jìn)行有效防范。因此,熟練掌握計(jì)算機(jī)系統(tǒng)相關(guān)知識(shí)無(wú)論是對(duì)于加深對(duì)計(jì)算機(jī)的理解,還是編寫(xiě)更加優(yōu)秀的程序都至關(guān)重要。
附件
列出所有的中間產(chǎn)物的文件名,并予以說(shuō)明起作用。
表2 中間結(jié)果文件
文件名作用hello.ihello.c預(yù)處理后得到的文本文件hello.shello.i編譯后得到的文本文件hello.ohello.s匯編后得到的二進(jìn)制文件hello_elf.txthello.o的ELF格式文件hello_obj.txthello.o的反匯編文件hellohello.o鏈接得到的可執(zhí)行目標(biāo)文件hello_elf2.elfhello的ELF格式文件hello_obj2.txthello的反匯編文件
?
參考文獻(xiàn)
[1] 程序預(yù)處理階段,在做什么_預(yù)處理階段主要做的是哪兩件事-CSDN博客
[2] 程序詳細(xì)編譯過(guò)程(預(yù)處理、編譯、匯編、鏈接) - 知乎
[3] 編譯_百度百科
[4] https://wenku.csdn.net/answer/3865203b590443c0b62e8743d9038bd1
[5] Linux(四):什么是Bash、什么是shell?_bash shell-CSDN博客
[6] Linux Bash shell - 知乎
[7] 邏輯地址、物理地址、虛擬地址_虛擬地址 邏輯地址-CSDN博客
[8] https://www.cnblogs.com/pianist/p/3315801.html
[9]《深入理解計(jì)算機(jī)系統(tǒng)》 Randal E.Bryant & David R.O’Hallaron 機(jī)械工業(yè)出版社
柚子快報(bào)激活碼778899分享:ubuntu HIT
精彩鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。