程序人生P2P
HITICS程序人生-Hello’s P2P From Program to Process
摘 要
俺是Hello,額是每一個程序猿¤的初戀(羞羞……)
卻在短短幾分鐘后慘遭每個菜鳥的無情拋棄(嗚嗚……),他們很快喜歡上sum、sort、matrix、PR、AI、IOT、BD、MIS……,從不回頭。
只有我自己知道,我的出身有多么高貴,我的一生多么坎坷!
多年以后,那些真正懂我的大佬(也是曾經的菜鳥一枚),才恍然感悟我的偉大!
……………………想當年:俺才是第一個玩 P2P的: From Program to Process
懵懵懂懂的你笨笨磕磕的將我一字一鍵敲進電腦存成hello.c(Program),無意識中將我預處理、編譯、匯編、鏈接,歷經艱辛-神秘-高貴-欣喜,我-Hello一個完美的生命誕生了。
你造嗎?在殼(Bash)里,偉大的OS(進程管理)為我fork(Process),為我execve,為我mmap,分我時間片,讓我得以在Hardware(CPU/RAM/IO)上馳騁(取指譯碼執(zhí)行/流水線等);
你造嗎?OS(存儲管理)與MMU為VA到PA操碎了心;TLB、4級頁表、3級Cache,Pagefile等等各顯神通為我加速;IO管理與信號處理使盡了渾身解數(shù),軟硬結合,才使我能在鍵盤、主板、顯卡、屏幕間游刃有余, 雖然我在臺上的表演只是一瞬間、演技看起來很Low、效果很慘白。
感謝 OS!感謝 Bash!在我完美謝幕后為我收尸。 我赤條條來去無牽掛!
我朝 CS(計算機系統(tǒng)-Editor+Cpp+Compiler+AS+LD + OS + CPU/RAM/IO等)揮一揮手,不帶走一片云彩! 想想俺也是 O2O: From Zero-0 to Zero-0。
歷史長河中一個個菜鳥與我擦肩而過,只有CS知道我的生、我的死,我的坎坷,“只有 CS 知道……我曾經……來…………過……”
關鍵詞:關鍵詞1;關鍵詞2;……;
(摘要0分,缺失-1分,根據(jù)內容精彩稱都酌情加分0-1分)
目 錄
第1章 概述 - 4 -
1.1 Hello簡介 - 4 -
1.2 環(huán)境與工具 - 4 -
1.3 中間結果 - 4 -
1.4 本章小結 - 4 -
第2章 預處理 - 5 -
2.1 預處理的概念與作用 - 5 -
2.2在Ubuntu下預處理的命令 - 5 -
2.3 Hello的預處理結果解析 - 5 -
2.4 本章小結 - 5 -
第3章 編譯 - 6 -
3.1 編譯的概念與作用 - 6 -
3.2 在Ubuntu下編譯的命令 - 6 -
3.3 Hello的編譯結果解析 - 6 -
3.4 本章小結 - 6 -
第4章 匯編 - 7 -
4.1 匯編的概念與作用 - 7 -
4.2 在Ubuntu下匯編的命令 - 7 -
4.3 可重定位目標elf格式 - 7 -
4.4 Hello.o的結果解析 - 7 -
4.5 本章小結 - 7 -
第5章 鏈接 - 8 -
5.1 鏈接的概念與作用 - 8 -
5.2 在Ubuntu下鏈接的命令 - 8 -
5.3 可執(zhí)行目標文件hello的格式 - 8 -
5.4 hello的虛擬地址空間 - 8 -
5.5 鏈接的重定位過程分析 - 8 -
5.6 hello的執(zhí)行流程 - 8 -
5.7 Hello的動態(tài)鏈接分析 - 8 -
5.8 本章小結 - 9 -
第6章 hello進程管理 - 10 -
6.1 進程的概念與作用 - 10 -
6.2 簡述殼Shell-bash的作用與處理流程 - 10 -
6.3 Hello的fork進程創(chuàng)建過程 - 10 -
6.4 Hello的execve過程 - 10 -
6.5 Hello的進程執(zhí)行 - 10 -
6.6 hello的異常與信號處理 - 10 -
6.7本章小結 - 10 -
第7章 hello的存儲管理 - 11 -
7.1 hello的存儲器地址空間 - 11 -
7.2 Intel邏輯地址到線性地址的變換-段式管理 - 11 -
7.3 Hello的線性地址到物理地址的變換-頁式管理 - 11 -
7.4 TLB與四級頁表支持下的VA到PA的變換 - 11 -
7.5 三級Cache支持下的物理內存訪問 - 11 -
7.6 hello進程fork時的內存映射 - 11 -
7.7 hello進程execve時的內存映射 - 11 -
7.8 缺頁故障與缺頁中斷處理 - 11 -
7.9動態(tài)存儲分配管理 - 11 -
7.10本章小結 - 12 -
第8章 hello的IO管理 - 13 -
8.1 Linux的IO設備管理方法 - 13 -
8.2 簡述Unix IO接口及其函數(shù) - 13 -
8.3 printf的實現(xiàn)分析 - 13 -
8.4 getchar的實現(xiàn)分析 - 13 -
8.5本章小結 - 13 -
結論 - 14 -
附件 - 15 -
參考文獻 - 16 -
第1章 概述
1.1 Hello簡介
P2P:
在Editor中鍵入代碼得到 hello.c
linux,cpp預處理、ccl編譯、as匯編、ld鏈接>>可執(zhí)行目標程序hello,在shell中鍵入啟動命令,fork產生子進程,變?yōu)镻rocess
020:
用shell ,execve,映射虛擬內存,進入程序入口后程序開始載入物理內 存,進入main函數(shù)執(zhí)行目標代碼,CPU為運行的hello分配時間片執(zhí)行邏輯控制流。當程序運行結束后,shell父進程負責回收hello進程,內核刪除相關數(shù)據(jù)結構
1.2 環(huán)境與工具
列出你為編寫本論文,折騰Hello的整個過程中,使用的軟硬件環(huán)境,以及開發(fā)與調試工具。
軟件環(huán)境 Windows7 64 位以上;VirtualBox/Vmware 11 以上;Ubuntu 16.04 LTS 64 位/ 優(yōu)麒麟 64 位
硬件環(huán)境 X64 CPU;2GHz;2G RAM;256GHD Disk 以上
開發(fā)與調試工具 vim,gcc,as,ld,edb,readelf,HexEdit
1.3 中間結果
列出你為編寫本論文,生成的中間結果文件的名字,文件的作用等。
hello.i 預處理之后文本文件
hello.s 編譯之后的匯編文件
hello.o 匯編之后的可重定位目標執(zhí)行
Hello 鏈接之后的可執(zhí)行目標文件
hello2.c 測試程序代碼
hello2 測試程序
Helloo.objdmp Hello.o的反匯編代碼
helloo.elf Hello.o的ELF格式
hello.objdmp Hello的反匯編代碼
hello.elf Hello的ELF格式
tmp.txt 存放臨時數(shù)據(jù)
1.4 本章小結
Hello的P2P,020,實驗環(huán)境,中間結果
(第1章0.5分)
第2章 預處理
2.1 預處理的概念與作用
程序設計語言的預處理的概念:在編譯之前進行的處理。
C語言的預處理主要有三個方面的內容:
1.宏定義;
2.文件包含;
3.條件編譯。 預處理命令以符號“#”開頭。
預處理階段:預處理器(cpp)根據(jù)字符#開頭的命令,修改原始的c程序。得到另 一個c程序,通常以.i 作為文件擴展名
概念:預處理器 cpp 根據(jù)以字符#開頭的命令(宏定義、條件編譯),修改原始的C程序,將引用的所有庫展開合并成為一個完整的文本文件。
主要功能如下:
1、將源文件中用#include形式聲明的文件復制到新的程序中。比如hello.c第6-8行中的#include等命令告訴預處理器讀取系統(tǒng)頭文件 stdio.h unistd.h stdlib.h 的內容,并把它直接插入到程序文本中。
2、用實際值替換用#define 定義的字符串
3、根據(jù)#if 后面的條件決定需要編譯的代碼
2.2在Ubuntu下預處理的命令
2.3 Hello的預處理結果解析
先是stdio.h unistd.h stdlib.h的依次展開
3099行開始是hello.c
2.4 本章小結
預處理的定義與作用、并結合預處理之后的程序對預處理結果進行了解析。
(第2章0.5分)
第3章 編譯
3.1 編譯的概念與作用
編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s,它包含一個匯編語言程序。
編譯器將文本文件hello.i翻譯成文本文件hello.s,它包含一個匯編語言程序。
這個過程稱為編譯,同時也是編譯的作用。
編譯器的構建流程主要分為3個步驟:
1.詞法分析器,用于將字符串轉化成內部的表示結構。
2.語法分析器,將詞法分析得到的標記流(token)生成一棵語法樹。
目標代碼的生成,將語法樹轉化成目標代碼。
3.2 在Ubuntu下編譯的命令
3.3 Hello的編譯結果解析
3.3.1匯編指令
.file 聲明源文件
.text 以下是代碼段
.section .rodata 以下是rodata節(jié)
.globl 聲明一個全局變量
.type 用來指定是函數(shù)類型或是對象類型
.size 聲明大小
.long.string 聲明一個long、string類型
.align 聲明對指令或者數(shù)據(jù)的存放地址進行對齊的方式
字符串
1.“Usage: Hello 學號 姓名!\n”,第一個printf傳入的輸出格式化參數(shù),字符串被編碼成UTF-8格式,一個漢字在utf-8編碼中占三個字節(jié),一個\代表一個字節(jié)。
2.“Hello %s %s\n”,第二個printf傳入的輸出格式化參數(shù),其中后兩個字符串都聲明在了.rodata只讀數(shù)據(jù)節(jié)。
3.3.1整數(shù)
int i:編譯器將局部變量存儲在寄存器或者棧空間中,在hello.s中編譯器將i存儲在棧上空間-4(%rbp)中,可以看出i占據(jù)了棧中的4B
int argc:作為第一個參數(shù)傳入
立即數(shù):其他整形數(shù)據(jù)的出現(xiàn)都是以立即數(shù)的形式出現(xiàn)的,直接硬編碼在匯編代碼中
數(shù)組
char argv[]:main函數(shù)執(zhí)行時輸入的命令行,argv作為存放char指針的數(shù)組同時是第二個參數(shù)傳入。argv 單個元素char大小為 8B,argv 指針指向已經分配好的、一片存放著字符指針的連續(xù)空間,起始地址為argv,main函數(shù)中訪問數(shù)組元素argv[1],argv[2]時,按照起始地址argv大小8B計算數(shù)據(jù)地址取數(shù)據(jù),在hello.s中,使用兩次(%rax)(兩次rax分別為argv[1]和argv[2]的地址)取出其值。
3.3.3賦值
i=0:整型數(shù)據(jù)的賦值使用 mov 指令完成
3.3.4算數(shù)
i++,對計數(shù)器i自增,使用程序指令addl,后綴1代表操作數(shù)是一個4B大小的數(shù)據(jù)
leaq .LC0(%rip),%rdi,使用了加載有效地址指令leaq計算LC0的段地址%rip+.LC0 并傳遞給%rdi。
3.3.5compare
argc!=4:判斷argc不等于3。hello.s中使用cmpl $4,-20(%rbp),計算argc-4然后設置條件碼,為下一步je利用條件碼進行跳轉作準備。
i<10:判斷i小于8。hello.s 中使用cmpl $7,-4(%rbp),計算i-7然后設置條件碼,為下一步jle利用條件碼進行跳轉做準備。
3.3.6控制
if (argv!=4):當argv不等于4的時候執(zhí)行程序段中的代碼。if 判斷編譯器使用跳轉指令實現(xiàn),首先cmpl比較argv和4,使用je判斷標志位,如果為0,說明argv==4, 則不執(zhí)行if中的代碼直接跳轉到.L2,否則順序執(zhí)行下一條語句,即執(zhí)行if中的代碼
for(i=0;i<10;i++):使用計數(shù)變量i循環(huán)8次。先無條件跳轉到位于循環(huán)體.L4之后的比較代碼,使用cmpl比較,如果 i<=7,則跳入.L4 for 循環(huán)體執(zhí)行,否則循環(huán)結束,順序執(zhí)行for之后的邏輯。
3.4 本章小結
hello.c和hello.s的映射關系,將C語言結構為低級匯編語言
(第3章2分)
第4章 匯編
4.1 匯編的概念與作用
匯編器(as)將.s匯編程序翻譯成機器語言指令,把這些指令打包成可重定位目標程序的格式,并將結果保存在.o目標文件中,.o文件是一個二進制文件,它包含程序的指令編碼。
4.2 在Ubuntu下匯編的命令
4.3 可重定位目標elf格式
1.ELF Header
以16B的序列Magic開始,Magic描述了生成該文件的系統(tǒng)的字的大小和字節(jié)順序,ELF頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息,其中包括ELF頭的大小、目標文件的類型、機器類型、字節(jié)頭部表的文件偏移,以及節(jié)頭部表中條目的大小和數(shù)量等信息。
2.Section Headers
節(jié)頭部表,包含文件中出現(xiàn)的各個節(jié)的語義,包括節(jié)的類型、位置和大小等信息。
3.重定位節(jié).rela.text ,一個.text節(jié)中位置的列表,包含.text節(jié)中需要進行重定位的信息,當鏈接器把這個目標文件和其他文件組合時,需要修改這些位置。8條重定位信息分別是對.L0、puts函數(shù)、exit函數(shù)、.L1、 printf 函數(shù)、atoi、sleep函數(shù)、getchar 函數(shù)進行重定位聲明。
4.4 Hello.o的結果解析
objdump和.s區(qū)別
1.分支轉移:反匯編代碼跳轉指令的操作數(shù)使用的不是段名稱而是地址,因為段名稱只是在匯編語言中便于編寫的助記符,所以在匯編成機器語言之后不存在,而是確定的地址。
2.函數(shù)調用:在.s文件中,函數(shù)調用之后是函數(shù)名稱,而反匯編程序中,call的目標地址是當前下一條指令。因為hello.c中調用的函數(shù)都是共享庫中的函數(shù),最終需要通過動態(tài)鏈接器才能確定函數(shù)的運行時執(zhí)行地址
3.全局變量訪問:在.s文件中,訪問rodata使用段名稱+%rip,在反匯編代碼中 0+%rip,因為rodata中數(shù)據(jù)地址在運行時確定,所以訪問也需要重定位。所以在匯編成為機器語言時,將操作數(shù)設置為全0并添加重定位條目。
4.5 本章小結
從 hello.s到hello.o匯編,查看hello.o的elf格式,使用 objdump得到反匯編代碼,與hello.s進行比較
(第4章1分)
第5章 鏈接
5.1 鏈接的概念與作用
鏈接是將各種代碼和數(shù)據(jù)片段收集并組合成一個單一文件的過程,這個文件可被加載到內存并執(zhí)行。鏈接可以執(zhí)行于編譯時,也就是在源代碼被編譯成機器代碼時;也可以執(zhí)行于加載時,也就是在程序被加載器加載到內存并執(zhí)行時;甚至于運行時,也就是由應用程序來執(zhí)行。鏈接是由叫做鏈接器的程序執(zhí)行的。鏈接器使得分離編譯成為可能。
5.2 在Ubuntu下鏈接的命令
5.3 可執(zhí)行目標文件hello的格式
5.4 hello的虛擬地址空間
在0x400000~0x401000段中,程序被載入,自虛擬地址0x400000開始,自0x400fff 結束,這之間每個節(jié)的排列即開始結束同Section Headers的Address中聲明。
查看 ELF 格式文件中的Program Headers,程序頭表在執(zhí)行的時候被使用,告訴鏈接器運行時加載的內容并提供動態(tài)鏈接的信息,提供各段在虛擬地址空間和物理地址空間的大小、位置、標志、訪問權限和對齊方面的信息。
1.PHDR保存程序頭表。
2.INTERP指定在程序已經從可執(zhí)行文件映射到內存之后,必須調用的解釋器(如動態(tài)鏈接器)。
3.LOAD表示一個需要從二進制文件映射到虛擬地址空間的段。其中保存了常量數(shù)據(jù)、程序的目標代碼等。
4.DYNAMIC保存由動態(tài)鏈接器使用的信息。
5.NOTE保存輔助信息。
6.GNU_STACK權限標志,標志棧是否是可執(zhí)行的。
7.GNU_RELRO指定在重定位結束之后那些內存區(qū)域是需要設置只讀。
5.5 鏈接的重定位過程分析
objdump -d -r hello 分析hello與hello.o的不同,說明鏈接的過程。
結合hello.o的重定位項目,分析hello中對其怎么重定位的。
5.6 hello的執(zhí)行流程
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
-libc-2.27.so!__cxa_atexit
-libc-2.27.so!__libc_csu_ini
hello!_init
libc-2.27.so!_setjmp
-libc-2.27.so!_sigsetjmp
–libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
*hello!printf@plt
*hello!sleep@plt
*hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave
-ld-2.27.so!_dl_fixup
–ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
5.7 Hello的動態(tài)鏈接分析
分析hello程序的動態(tài)鏈接項目,通過edb調試,分析在dl_init前后,這些項目的內容變化。要截圖標識說明。
5.8 本章小結
(以下格式自行編排,編輯時刪除)
(第5章1分)
第6章 hello進程管理
6.1 進程的概念與作用
進程是一個執(zhí)行中的程序的實例,每一個進程都有它自己的地址空間,一般 情況下,包括文本區(qū)域、數(shù)據(jù)區(qū)域和堆棧。文本區(qū)域存儲處理器執(zhí)行的代碼;數(shù)據(jù)區(qū)域存儲變量和進程執(zhí)行期間使用的動態(tài)分配的內存;堆棧區(qū)域存儲區(qū)著活動 過程調用的指令和本地變量。進程為用戶提供了以下假象:我們的程序好像是系統(tǒng)中當前運行的唯一程序一樣,我們的程序好像是獨占的使用處理器和內存,處理器好像是無間斷的執(zhí)行我們程序中的指令,我們程序中的代碼和數(shù)據(jù)好像是系統(tǒng)內存中唯一的對象。
6.2 簡述殼Shell-bash的作用與處理流程
Shell 的作用:Shell 是一個用 C 語言編寫的程序,他是用戶使用 Linux 的 橋梁。 Shell 是指一種應用程序,Shell 應用程序提供了一個界面,用戶通過這個 界面訪問 操作系統(tǒng)內核的服務。
1.從終端讀入輸入的命令。
2.將輸入字符串切分獲得所有的參數(shù)
3.如果是內置命令則立即執(zhí)行
4.否則調用相應的程序為其分配子進程并運行
5.shell 應該接受鍵盤輸入信號,并對這些信號進行相應處理
6.3 Hello的fork進程創(chuàng)建過程
運行的終端程序對輸入的命令行進行解析,因為hello不是一個內置的shell命令,解析之后終端程序判斷./hello的語義為執(zhí)行當前目錄下的可執(zhí)行目標文件 hello,之后終端程序首先會調用fork函數(shù)創(chuàng)建一個新的運行的子進程,新創(chuàng)建的子進程幾乎但不完全與父進程相同,子進程得到與父進程用戶級虛擬地址空間相同的(但是獨立的)一份副本,這就意味著,當父進程調用fork時,子進程可以讀寫父進程中打開的任何文件。父進程與子進程之間最大的區(qū)別在于它們擁有不同的PID。父進程與子進程是并發(fā)運行的獨立進程,內核能夠以任意方式交替執(zhí)行它們的邏輯控制流的指令。在子進程執(zhí)行期間,父進程默認選項是顯示等待子進程的完成。
6.4 Hello的execve過程
fork之后,子進程調用execve函數(shù)(傳入命令行參數(shù))在當前進程的上下文中加載并運行一個新程序即hello程序,execve調用駐留在內存中的被稱為啟動加載器的操作系統(tǒng)代碼來執(zhí)行hello程序,加載器刪除子進程現(xiàn)有的虛擬內存段,并創(chuàng)建一組新的代碼、數(shù)據(jù)、堆和棧段。新的棧和堆段被初始化為零,通過將虛擬地址空間中的頁映射到可執(zhí)行文件的頁大小的片,新的代碼和數(shù)據(jù)段被初始化 為可執(zhí)行文件中的內容。最后加載器設置PC指向_start地址,_start最終調用hello中的main函數(shù)。除了一些頭部信息,在加載過程中沒有任何從磁盤到內存的數(shù)據(jù) 復制。直到CPU引用一個被映射的虛擬頁時才會進行復制,這時,操作系統(tǒng)利用 它的頁面調度機制自動將頁面從磁盤傳送到內存。
6.5 Hello的進程執(zhí)行
上下文信息:內核重新啟動一個被搶占的進程所需要的狀態(tài),它由通用寄存器、浮點寄存器、程序計數(shù)器、用戶棧、狀態(tài)寄存器、內核棧和各種內核數(shù)據(jù)結構等對象的值構成。
時間片:一個進程執(zhí)行它的控制流的一部分的每一時間段叫做時間片。用戶模式和內核模式:處理器通常使用一個寄存器提供兩種模式的區(qū)分,該寄存器描述了進程當前享有的特權,當沒有設置模式位時,進程就處于用戶模式中,用戶模式的進程不允許執(zhí)行特權指令,也不允許直接引用地址空間中內核區(qū) 內的 代碼和數(shù)據(jù);設置模式位時,進程處于內核模式,該進程可以執(zhí)行指令集中的任何命令,并且可以訪問系統(tǒng)中的任何內存位置。
hello初始運行在用戶模式,在hello進程調用sleep之后內核模式,內核處理休眠請求主動釋放當前進程,并將hello進程從運行隊列中移出加入等待隊列,定時器開始計時,內核進行上下文切換將當前進程的控制權交給其他進程,當定時器到時時發(fā)送一個中斷信號,此時進入內核狀態(tài)執(zhí)行中斷處理,將hello進程從等待隊列中移出重新加入到運行隊列,成為就緒狀態(tài),hello進程繼續(xù)進行自己的控制邏輯流。循環(huán)8次。當hello調用getchar,實際落腳到執(zhí)行輸入流是stdin的系統(tǒng)調用read,hello之前運行在用戶模式,在進行read調用之后陷入內核,內核中的陷阱處理程序請求來自鍵盤緩沖區(qū)的DMA傳輸,并且安排在完成從鍵盤緩沖區(qū)到內存的數(shù)據(jù)傳輸后,中斷處理器。此時進入內核模式,內核執(zhí)行上下文切換,切換到其他進程。當完成鍵盤緩沖區(qū)到內存的數(shù)據(jù)傳輸時,引發(fā)一個中斷信號,此時內核從其他進程進行上下文切換回hello進程。。
6.6 hello的異常與信號處理
(以下格式自行編排,編輯時刪除)
hello執(zhí)行過程中會出現(xiàn)哪幾類異常,會產生哪些信號,又怎么處理的。
程序運行過程中可以按鍵盤,如不停亂按,包括回車,Ctrl-Z,Ctrl-C等,Ctrl-z后可以運行ps jobs pstree fg kill 等命令,請分別給出各命令及運行結截屏,說明異常與信號的處理。
6.7本章小結
(以下格式自行編排,編輯時刪除)
(第6章1分)
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
邏輯地址:程序代碼經過編譯后出現(xiàn)在匯編程序中地址,由選擇符(在實模式下是描述符,在保護模式下是用來選擇描述符的選擇符)和偏移量(偏移部分)組成。
線性地址:邏輯地址經過段機制后轉化為線性地址,為描述符:偏移量的組合 形式。分頁機制中線性地址作為輸入。
虛擬地址:線性地址。
物理地址:CPU 通過地址總線的尋址,找到真實的物理內存對應地址。CPU 對內存的訪問是通過連接著CPU和北橋芯片的前端總線來完成的。在前端總線上 傳輸?shù)膬却娴刂范际俏锢韮却娴刂贰?br /> 7.2 Intel邏輯地址到線性地址的變換-段式管理
邏輯地址=段選擇符+偏移量
每個段首地址存放在自己的段描述符中,所有段描述符存放在一個描述附表中(GDT和LDT),想找到某個段的描述符必須經過段選擇符。段選擇符由3個部分組成,從右向左依次是RPL、TI、index。當TI=0時,表示段描述符在GDT中,當TI=1時表示段描述符在LDT中。將描述符表看成是一個數(shù)組,每個元素都存放一個段描述符,那index就表示某個段描述符在數(shù)組中的索引。假設有一個段的段選擇符TI=0,index=8。我們可以知道這個段的描述符是在GDT數(shù)組中,并且他的在數(shù)組中的索引是8。假設GDT的起始位置是0x00020000,而一個段描述符的大小是8個字節(jié),可以計算出段描述符所在的地址:0x00020000+8index,從而我們就可以找到我們想要的段描述符,從而獲取某個段的首地址,然后再將從段描述符中獲取到的首地址與邏輯地址的偏移量相加就得到了線性地址。
7.3 Hello的線性地址到物理地址的變換-頁式管理
由邏輯地址得到的線性地址一共 32 位。前 10 位是頁目錄索引,中間 10 位是頁表索引,最后 12 位是業(yè)內偏移量
由 CR3 寄存器得到「頁目錄基地址」,再得到「頁目錄項」
由「頁目錄項」得到「頁表基地址」
由「頁表基地址」得到「頁表項」,最后得到物理地址。
7.4 TLB與四級頁表支持下的VA到PA的變換
在Intel Core i7環(huán)境下研究VA到PA的地址翻譯問題。
前提如下:虛擬地址空間48位,物理地址空間52位,頁表大小4KB,4級頁表。TLB4路16組相聯(lián)。CR3 指向第一級頁表的起始位置。
解析前提條件:由一個頁表大小 4KB,一個PTE條目8B,共512個條目,使用9位二進制索引,一共4個頁表共使用36位二進制索引,所以VPN共36位,因為 VA 48 位,所以VPO 12位;因為TLB共16組,所以TLBI需4位,因為VPN 36 位,所以TLBT 32位。CPU 產生虛擬地址VA,VA傳送給MMU,MMU使用前 36位VPN作為TLBT(前32位)+TLBI(后 4 位)向TLB中匹配,如果命中,則 得到PPN(40bit)與VPO(12bit)組合成 PA(52bit)。
如果TLB中沒有命中,MMU向頁表中查詢,CR3確定第一級頁表的起始地址,VPN1(9bit)確定在第一級頁表中的偏移量,查詢出 PTE,如果在物理內存中且權限符合,確定第二級頁表的起始地址,以此類推,最終在第四級頁表中查詢到PPN,與VPO 組合成 PA,并且向TLB中添加條目。
如果查詢PTE的時候發(fā)現(xiàn)不在物理內存中,則引發(fā)缺頁故障。如果發(fā)現(xiàn)權限不夠,則引發(fā)段錯誤。
7.5 三級Cache支持下的物理內存訪問
L1 Cache 8路64組相聯(lián),塊大小64B。解析前提條件:因為共64組,所以需要6bit CI進行組尋址,共有8 路,塊大小為64B所以需要6bit CO表示數(shù)據(jù)偏移位置。因為VA共 2bit,所以CT共40bit。 使用CI進行組索引,每組8路,對8路的塊分別匹配CT(前40位)如果匹配成功 且塊的valid 標志位為1,則命中(hit),根據(jù)數(shù)據(jù)偏移量 CO(后六位)取出數(shù)據(jù)返回。如果沒有匹配成功或者匹配成功但是標志位是1,則不命中,向下一級緩存中查詢數(shù)據(jù)(L2 Cache->L3 Cache->主存)。查詢到數(shù)據(jù)之后,一種簡單的放置策略如下:如果映射到的組內有空閑塊,則直接放置,否則組內都是有效塊, 產生沖突,則采用最近最少使用策略 LFU 進行替換。
7.6 hello進程fork時的內存映射
當fork函數(shù)被shell進程調用,內核為新進程創(chuàng)建各種數(shù)據(jù)結構,并分配給它一個唯一的 PID,為了給這個新進程創(chuàng)建虛擬內存,它創(chuàng)建了當前進程的mm_struct、區(qū)域結構和頁表的原樣副本。它將這兩個進程的每個頁面都標記為只讀,并將兩個進程中的每個區(qū)域結構都標記為私有的寫時復制。
7.7 hello進程execve時的內存映射
1.刪除已存在的用戶區(qū)域,刪除當前進程虛擬地址的用戶部分中的已存 在 的區(qū)域結構。
2.映射私有區(qū)域,為新程序的代碼、數(shù)據(jù)、bss 和棧區(qū)域創(chuàng)建新的區(qū)域結 構, 所有這些新的區(qū)域都是私有的、寫時復制的。代碼和數(shù)據(jù)區(qū)域被映射 hello 文件中 的.text 和.data 區(qū),bss 區(qū)域是請求二進制零的,映射到匿名 文件,其大小包含 在 hello 中,棧和堆地址也是請求二進制零的,初始長度為零。
3.映射共享區(qū)域, hello 程序與共享對象 libc.so 鏈接,libc.so 是動態(tài)鏈 接 到這個程序中的,然后再映射到用戶虛擬地址空間中的共享區(qū)域內。
4.設置程序計數(shù)器(PC),execve 做的最后一件事情就是設置當前進程 上 下文的程序計數(shù)器,使之指向代碼區(qū)域的入口點。
7.8 缺頁故障與缺頁中斷處理
缺頁故障是一種常見的故障,當指令引用一個虛擬地址,在MMU中查找頁表時發(fā)現(xiàn)與該地址相對應的物理地址不在內存中,因此必須從磁盤中取出的時候 就 會發(fā)生故障。
缺頁中斷處理:缺頁處理程序是系統(tǒng)內核中的代碼,選擇一個犧牲頁面,如果這個犧牲頁面被修改過,那么就將它交換出去,換入新的頁面并更新頁表。當缺頁處理程序返回時,CPU重新啟動引起缺頁的指令,這條指令再次發(fā)送VA到MMU,這次MMU就能正常翻譯VA 了。
7.9動態(tài)存儲分配管理
(以下格式自行編排,編輯時刪除)
Printf會調用malloc,請簡述動態(tài)內存管理的基本方法與策略。
7.10本章小結
hello的存儲器地址空間、intel的段式管理、hello的頁式管理,以intel Core7在指定環(huán)境下介紹了VA到PA的變換、物理內存訪問,hello進程fork時的內存映射、execve時的內存映射、缺頁故障與缺頁中處理、動態(tài)存儲分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
設備的模型化:所有的IO設備都被模型化為文件,而所有的輸入和輸出都被當做對相應文件的讀和寫來執(zhí)行,這種將設備優(yōu)雅地映射為文件的方式,允許Linux內核引出一個簡單低級的應用接口,稱為Unix I/O。
8.2 簡述Unix IO接口及其函數(shù)
Unix I/O 接口統(tǒng)一操作:
1)打開文件。一個應用程序通過要求內核打開相應的文件,來宣告它想要訪問一個I/O設備,內核返回一個小的非負整數(shù),叫做描述符,它在后續(xù)對此文件的所有操作中標識這個文件,內核記錄有關這個打開 件的所有信 息。
2)Shell 創(chuàng)建的每個進程都有三個打開的文件:標準輸入,標準輸出,標準錯誤。
3)改變當前的文件位置:對于每個打開的文件,內核保持著一個文件位置k,初始為0,這個文件位置是從文件開頭起始的字節(jié)偏移量,應用程序能夠通過執(zhí)行 seek,顯式地將改變當前文件位置 k。
4)讀寫文件:一個讀操作就是從文件復制n>0個字節(jié)到內存,從當前文件位置k 開始,然后將k增加到k+n,給定一個大小為m字節(jié)的而文件,當k>=m時,觸發(fā)EOF。類似一個寫操作從內存中復制n>0個字 到一個文件,從當前文件位置k開始,然后更新k。
5)關閉文件,內核釋放文件打開時創(chuàng)建的數(shù)據(jù)結構,并將這個描述符恢復到可用的描述符池中去。
Unix I/O 函數(shù):
1)int open(char filename,int flags,mode_t mode),進程通過調用open函數(shù)來打開一個存在的文件或是創(chuàng)建一個新文件。open函數(shù)將filename轉換為一個文件描述符,并且返回描述符數(shù)字,返回的描述符總是在進程中當前沒有打開的最小描述符,flags參數(shù)指明了進程打算如何訪問這個文件,mode 參數(shù)指定了新文件的訪問權限位。
2)int close(fd),fd是需要關閉的文件的描述符,close返回操作結果。
3)ssize_t read(int fd,void *buf,size_t n),read函數(shù)從描述符為fd的當前文件位置賦值最多n個字節(jié)到內存位置buf。返回值-1表示一個錯誤,0表 示EOF,否則返回值表示的是實際傳送的字節(jié)數(shù)量。
4)ssize_t wirte(int fd,const void *buf,size_t n),write函數(shù)從內存位置buf 復制至多n個字節(jié)到描述符為fd的當前文件位置。
8.3 printf的實現(xiàn)分析
int printf(const char fmt, …)?
{?
int i;?
char buf[256];?
???? va_list arg = (va_list)((char)(&fmt) + 4);?
???? i = vsprintf(buf, fmt, arg);?
???? write(buf, i);?
???? return i;?
}?
指針va_list
函數(shù)va_start()、va_arg()、va_end();
va_list args; //聲明args,用于存儲了所有的val1,val2,……
va_start(args, fmt);//得到第一個可變參數(shù)的地址
va_end(args);//清空args的指針
https://www.cnblogs.com/pianist/p/3315801.html
從vsprintf生成顯示信息,到write系統(tǒng)函數(shù),到陷阱-系統(tǒng)調用 int 0x80或syscall.
字符顯示驅動子程序:從ASCII到字模庫到顯示vram(存儲每一個點的RGB顏色信息)。
顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點(RGB分量)。
8.4 getchar的實現(xiàn)分析
異步異常-鍵盤中斷的處理:當用戶按鍵時,鍵盤接口會得到一個代表該按鍵 的鍵盤掃描碼,同時產生一個中斷請求,中斷請求搶占當前進程運行鍵盤中斷子 程序,鍵盤中斷子程序先從鍵盤接口取得該按鍵的掃描碼,然后將該按鍵掃描碼 轉換成ASCII碼,保存到系統(tǒng)的鍵盤緩沖區(qū)之中。getchar函數(shù)落實到底層調用了系統(tǒng)函數(shù)read,通過系統(tǒng)調用read讀取存儲在鍵盤緩沖區(qū)中的ASCII碼直到讀到回車符然后返回整個字串,getchar進行封裝,大體邏輯是讀取字符串的第一個字符然后返回。
8.5本章小結
IO 設備管理方法、Unix IO 接口及其函數(shù)、printf 函數(shù)和 getchar 函數(shù)。
(第8章1分)
結論
1.編寫 建立hello.c
2.預處理 hello.c預處理為hello.i
3.編譯 將hello.i編譯成為匯編文件hello.s
4.匯編 將hello.s匯編為可重定位目標文件hello.o
5.鏈接 將hello.o與可重定位目標文件和動態(tài)鏈接庫鏈接成為可執(zhí)行目標程序 hello
6.運行 命令./hello 1180300328 zhangyifei
7.創(chuàng)建子進程 shell進程調用fork為其創(chuàng)建子進程
8.運行程序 shell調用execve,execve調用啟動加載器,加映射虛擬內存,進入程序入口后程序載入物理內存,然后進入main函數(shù)。
9.執(zhí)行指令 CPU為其分配時間片,在一個時間片中,hello享有CPU資源,順序執(zhí)行自己的控制邏輯流
10.訪問內存 MMU將程序中使用的虛擬內存地址通過頁表映射成物理地址。
11.動態(tài)申請內存 printf調用malloc向動態(tài)內存分配器申請堆中的內存。
12.信號 如果運行途中鍵入ctr-c ctr-z則調用shell的信號處理函數(shù)分別停止、掛起。
13.結束 shell父進程回收子進程,內核刪除為這個進程創(chuàng)建的所有數(shù)據(jù)結構。
用計算機系統(tǒng)的語言,逐條總結hello所經歷的過程。
你對計算機系統(tǒng)的設計與實現(xiàn)的深切感悟,你的創(chuàng)新理念,如新的設計與實現(xiàn)方法。
(結論0分,缺失 -1分,根據(jù)內容酌情加分)
附件
hello 鏈接之后的可執(zhí)行目標文件
helloo.elf Hello.o的ELF格式
hello.i 預處理之后文本文件
hello.o 匯編之后的可重定位目標執(zhí)行
hello.objdmp Hello的反匯編代碼
hello.s 編譯之后的匯編文件
hello.elf Hello的ELF格式
helloo.objdmp Hello.o的反匯編代碼
(附件0分,缺失 -1分)
參考文獻
為完成本次大作業(yè)你翻閱的書籍與網站等
[1]ELF構造 https://www.cs.stevens.edu/~jschauma/631/elf.html
[2]虛擬地址、邏輯地址、線性地址、物理地址
https://blog.csdn.net/rabbit_in_android/article/details/49976101
[3]printf源代碼的分析 https://blog.csdn.net/smallfish0315/article/details/46812081
[4]進程的睡眠、掛起和阻塞:https://www.zhihu.com/question/42962803
(參考文獻0分,缺失 -1分)
總結
- 上一篇: 下次激活策略10_陈金凌:年流量1-10
- 下一篇: 盘点软件测试中那些必不可少的“用例集”