HIT-ICS2022大作业-程序人生-Hello’s P2P
計算機科學與技術學院
2021年5月
摘 ?要
本文通過一個簡單的小程序hello.c 從產生到死亡的一生,來介紹 Linux 系統下的程序從代碼到運行再到最后終止過程的底層實現進行了分析,描述了與之相關的計算機組成與操作系統的相關 內容。過 gcc、objdump、gdb、edb 等工具對一段程序代碼預處理、編譯、匯編、 鏈接與反匯編的過程進行分析與比較,并且通過 shell 及其他 Linux 內置程序對進 程運行過程進行了分析。研究內容對理解底層程序與操作系統的實現原理具有一定指導作用。
關鍵詞:鏈接,進程管理,計算機組成原理…
?????????????????????????
目 ?錄
第1章 概述
1.1 Hello簡介
1.2 環境與工具
1.3 中間結果
1.4 本章小結
第2章 預處理
2.1 預處理的概念與作用
2.2在Ubuntu下預處理的命令
2.3 Hello的預處理結果解析
2.4 本章小結
第3章 編譯
3.1 編譯的概念與作用
3.2 在Ubuntu下編譯的命令
3.3 Hello的編譯結果解析
3.4 本章小結
第4章 匯編
4.1 匯編的概念與作用
4.2 在Ubuntu下匯編的命令
4.3 可重定位目標elf格式
4.4 Hello.o的結果解析
4.5 本章小結
第5章 鏈接
5.1 鏈接的概念與作用
5.2 在Ubuntu下鏈接的命令
5.3 可執行目標文件hello的格式
5.4 hello的虛擬地址空間
5.5 鏈接的重定位過程分析
5.6 hello的執行流程
5.7 Hello的動態鏈接分析
5.8 本章小結
第6章 hello進程管理
6.1 進程的概念與作用
6.2 簡述殼Shell-bash的作用與處理流程
6.3 Hello的fork進程創建過程
6.4 Hello的execve過程
6.5 Hello的進程執行
6.6 hello的異常與信號處理
6.7本章小結
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
7.2 Intel邏輯地址到線性地址的變換-段式管理
7.3 Hello的線性地址到物理地址的變換-頁式管理
7.4 TLB與四級頁表支持下的VA到PA的變換
7.5 三級Cache支持下的物理內存訪問
7.6 hello進程fork時的內存映射
7.7 hello進程execve時的內存映射
7.8 缺頁故障與缺頁中斷處理
7.9動態存儲分配管理
7.10本章小結
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
8.2 簡述Unix IO接口及其函數
8.3 printf的實現分析
8.4 getchar的實現分析
8.5本章小結
結論
附件
參考文獻
第1章 概述
1.1 Hello簡介
已知一個hello.c 文件,由預處理器cpp尋找以#開頭的命令,然后修改這個程序變成hello.i文件;編譯器cll將預處理文件變成可讀的匯編語言程序hello.s。
然后匯編器 cs 將 hello.s 變成機器碼,將其打包成可重定位的目標文件 hello.o。hello.o不能直接打開但是可以使用 objdump 來反匯編查看。
再使用鏈接器ld合并,獲得執行目標文件 hello。在 Linux 系統中內置命令行解釋器 shell 加載運行 hello 程序,經過 fork?創建一個新的進程,hello 完成了 p2p 過程。
Shell 使用 execve 來加載運行 hello,為其分配虛擬內存空間,構建虛擬內存映射,MMU 組織各級頁表與 cache給予 hello 想要的所有信息,CPU 給予hello時間片并控制邏輯流。最后,由shell 回收 hello 的進程, 內核清除與 hello 相關的所有內容,完成 O2O 的過程。
1.2 環境與工具
X64 CPU;2GHz;2G RAM;256GHD Disk
Ubuntu16.04 LTS VirtualBox 11
vim,gcc,as,ld,edb,readelf,objdump
Visual Studio Code
1.3 中間結果
列出你為編寫本論文,生成的中間結果文件的名字,文件的作用等。
| 文件名 | 文件作用 |
| hello.c | 源程序 |
| hello.i | 預處理文件 |
| hello.s | 編譯生成的匯編文件 |
| hello.o | 可重定位目標文件 |
| hello.elf | hello.o的ELF格式 |
| objhello.txt | hello的反匯編代碼文本文件 |
| Rehello.s | hello.o的反匯編代碼文件 |
| hello | hello.o鏈接后得到的可執行文件 |
1.4 本章小結
本章簡述了hello程序的運行過程和結束過程,并且說明了使用的環境和工具。
最后對研究過程中中間產生的文件作用進行說明。
(第1章0.5分)
第2章 預處理
2.1?預處理的概念與作用
概念:
程序設計領域中,預處理一般是指在程序源代碼被翻譯為目標代碼的過程中,
生成二進制代碼之前的過程。
作用:
1: 宏定義:用一個標識符來表示一個字符串,這個字符串可以是常量、變量或表達式。在宏調用中將用該字符串代換宏名。
2:文件包含:它可用來把多個源文件連接成一個源文件進行編譯,結果將生成一個目標文件。
3:條件編譯:允許只編譯源程序中滿足條件的程序段,使生成的目標程序較短,從而減少了內存的開銷并提高了程序的效率。
2.2在Ubuntu下預處理的命令
重用的預處理命令有gcc -E hello.c -o hello.i 或 cpp hello.c > hello.i 。
應截圖,展示預處理過程!
圖1-預處理命令產生.i文件
圖2-.i文件部分截圖
2.3 Hello的預處理結果解析
對hello.c預處理結果產生hello.i文件。查看hello.i文件可以看出在原有代碼的基礎上將#開頭的內容進行引入了,如函數聲明、結構體定義、定義宏變量等。
如果頭文件的內容中仍然有#開頭的語句則遞歸地進行展開,指導沒有含#的語句。
除此外的代碼部分仍然是可讀的c程序。
2.4 本章小結
本章就hello.c的編譯,先引入預處理的概念,再在虛擬機上實際操作得到hello.i文件并進行分析。
(第2章0.5分)
第3章 編譯
3.1 編譯的概念與作用
概念:編譯是指編譯器將文本文件 hello.i 翻譯成文本文件 hello.s 的過
程,也就是編譯器對預處理器的輸出進行編譯,生成匯編語言的代碼(.s文件)。
作用:把源程序(高級語言)翻譯成機器能讀懂執行的語言。
除了基本功能之外,編譯器一般還具備語法檢查、調試措施、修改手段、覆蓋處理、目標程序優化等便利功能。
3.2 在Ubuntu下編譯的命令
編譯命令:gcc -S hello.i -o hello.s
圖3-編譯產生hello.s文件
3.3 Hello的編譯結果解析
圖4-編譯結果截圖
3.3.1 數據
hello.c中一共有int型,字符串型,數組三種數據類型。
圖5-i的定義
圖6-i的使用
圖7-匯編代碼中的i
可以看到i是個沒有初始化的整型變量,在for中用作計數變量。編譯器沒有在定義時專門聲明其的值,而是在循環時將i存在-4(%rbp)中,可以看出i占據4個字節。
圖8-局部變量argc
argc是從shell輸入的參數個數,是main函數的第一個參數,通常用%rdi保存。
各種程序中出現的常數,在匯編中直接以$xx形式出現。
圖9-c程序中出現的字符串
圖10-匯編中兩個字符串的聲明
由于在Linux下漢字使用utf-8編碼,所以在匯編中看到的是\xxx的形式。下面的%s表示用戶在shell輸入的兩個參數(argv[1]和argv[2])
圖11-在使用argv時計算地址后取出數組值
argv是main的第二個參數,是char指針的數組。保存在寄存器%rsi中。
在訪問數組元素時,由起始地址argv依次+8或8的倍數來訪問數組元素。在圖11中上兩個(%rax),%rax分別是argv[1]和argv[2]的取值的過程。
3.3.2 賦值
hello.c中的賦值操作只有對循環變量i賦初值i=0。
圖12-對存在寄存器中的i進行賦初值0
要注意的是,i在定義時沒有賦初值,而是在for中使用時給賦了初值,這里匯編代碼中只在后面的操作中出現了賦值。
3.3.3 算數操作
hello.c中只有一處運算操作,就是在for中每次的i++。用addl給存在-4(%rbp)中的i加1,然后再檢查循環條件。
圖13-for中的i++
3.3.4 關系操作
C 語言中關系操作的種類有關系操作:== 、!= 、> 、 < 、 >= 、 <=。
hello.c中有兩處關系操作,分別是argc!=3和i<8.
????
圖14-匯編中兩個位置的關系操作
匯編中cmp的功能是在兩個參數相減后改變標志寄存器存的值,不保存計算結果,然后其他指令再利用標志寄存器的值來得知比較的結果。
3.3.5 數組/指針/結構操作
hello.c中涉及到數組和指針的操作就是對char指針數組argv[]進行訪問。還是看到圖11:先在首地址基礎上加上數據元素的大小得到數組中某一位存放的位置,再用(%rax)取存放在這里的指針指向位置的值。
3.3.6 控制轉移
C 語言中的控制轉移就是,if,switch,while,case,continue,break等
語句。
hello.c中的控制轉移有兩處:
圖15-匯編中判斷argc是否和4相等
圖16-匯編中判斷i是否小于等于7
由于這里i是整數,所以i小于8和小于等于7等價。如果滿足條件就跳轉到.L4位置。
3.3.7 函數操作
hello.c中總共有5個函數:main(),printf(),exit(),sleep(),getchar()和atoi()。
即主函數。是程序最開始執行的函數。開始被存放到.text節,標記為函數。有兩個參數int argc和字符指針argv[],在shell中輸入,分別存在%rdi和%rsi中。
圖17-main函數
圖18-c中的printf函數
printf在hello.c中一共調用了兩次。參數是字符串,函數的作用也是輸出字符串。
圖19-hello.s中出現的puts
第一個printf由于參數只有一個字符串,在hello.s中被優化修改成了puts。
圖20-hello.s中出現的printf
第二個printf有三個參數,分別是輸出的字符串,argv[1]和argv[2].
圖21-hello.s中的exit
在hello.c中,如果輸入的參數個數不是剛好3個則調用exit函數終結程序。調用后返回參數并結束程序返回操作系統。通常exit(0)表示程序正常退出,exit(1)或exit(-1)表示程序異常退出。這里exit的參數是1,表示程序異常退出。
圖22-hello.s中的sleep
sleep函數的作用是將程序掛起一段時間,參數就是掛起的時間。這里的參數是atoi的返回值。
圖23-hello.s中的getchar
getchar沒有參數,在循環結束后直接用call調用getchar。
圖24-hello.s中的atoi
atoi函數的作用是將字符串轉成整型。這里的參數是argv[3].
3.3.8 類型轉換
程序中的類型轉換是用函數atoi()完成的,作用是將字符串轉換為int型。這里把argv[3]轉成整型后作為參數傳給sleep了。
3.4 本章小結
本章主要介紹了編譯器是如何處理C語言中的各種各樣的數據類型和操作的。 分析hello.c和hello.s來認識兩者前后處理的結果關系。
(第3章2分)
第4章 匯編
4.1 匯編的概念與作用
概念:匯編大多是指匯編語言,匯編程序。把匯編語言翻譯成機器語言的過程稱為匯編。
作用:將.s 匯編程序翻譯成機器語言指令, 把這些指令打包成可重定位目標程序的
格式,并將結果保存在.o 目標文件中,.o 文件是一個二進制文件。它包含程序的指令編碼,使得機器能夠識別。
4.2 在Ubuntu下匯編的命令
命令:as hello.s -o hello.o
圖25-匯編得到hello.o
4.3 可重定位目標elf格式
分析hello.o的ELF格式,用readelf等列出其各節的基本信息,特別是重定位項目分析。
用readelf -a hello.o > hello.elf?先得到hello.o文件的ELF格式。
圖26-得到hello.elf
圖27-ELF頭的意義作用
再打開hello.elf:
圖28-hello.o的ELF頭
圖29中上面的8條重定位信息分別是對.L0(第一個printf中的字符串)、puts函數、exit函數、.L1(第二個printf中的字符串)、printf函數、atoi函數、sleep函數、getchar函數進行重定位聲明。
圖29-重定位節
(4)符號表 symtab:符號表中包含用于重定位的信息,符號名稱、符號是全
局還是局部,并標識了符號的對應類型。
圖30-符號表部分
4.4 Hello.o的結果解析
objdump -d -r hello.o ?分析hello.o的反匯編,并請與第3章的 hello.s進行對照分析。
圖31-對hello.o進行反匯編生成Rehello.s
通過和hello.s進行對比,可以找到以下區別:
圖32-hello.s和Rehello.s的對比
圖33-hello.s中使用全局變量.LC0
圖34-Rehello.s中對應位置
在hello.s中直接在賦值時調用了全局變量.LC0,在Rehello.s中.LC0的值被存放在.rodata節中,且是絕對地址的應用。這是因為匯編器在匯編時會對每一個全局符號的引用產生一個重定位條目。這些重定位條目所指定的符號會在后面鏈接器中被重定位,計算出真正的地址并生成最終的可執行文件。
5.其他:由機器語言反匯編得到的Rehello.s與hello.s在其他地方基本相同,可以看出匯編語言和機器碼有著一一對應的映射關系。
4.5 本章小結
本章介紹了匯編的過程,通過指令由.s到.o文件。 還可以在ELF 頭中查看這個文件的基本信息,通過查看這張表可以得到段基址、文件節頭數、符號表等等多樣的信息。然后對hello.o進行反匯編與原先的 hello.s 對比,分析了匯編語言與機器碼的關系。
(第4章1分)
第5章 鏈接
5.1 鏈接的概念與作用
概念:鏈接是一個技術,允許從多個目標文件創建程序。鏈接可以在程序生命的不同時間發生:編譯時、加載時或運行時。
作用:由于可以把多個小的模塊鏈接創建成一個程序,所以可以獨立地分開編譯修改這些模塊,最后只需要簡單地重新編譯、鏈接應用而不用編譯別的沒有修改的部分。
5.2 在Ubuntu下鏈接的命令
鏈接命令:
ld -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc?/usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o?hello
圖35-鏈接產生hello
5.3 可執行目標文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
圖36-readelf下各個節頭部分信息
ELF頭中包含了程序節表大小、節表數量、數據存儲方式、文件類型、程序入口點等與文件整體框架有關的信息。
節頭表圖36中列舉了所有節頭以及其名稱、大小、類型、開始地址、偏移量等信息。
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進程的虛擬地址空間各段信息,并與5.3對照分析說明。 ??
圖37-edb中Data Dump查看hello的虛擬地址空間
在edb中Memory Regions找到hello最開始的起始地址,并在Dump中打開,可以看到開頭就是ELF頭如圖37.接下來根據5.3找其他節:
圖38-.interp節
從圖38可以看出.interp節存的是Linux動態共享庫的路徑。
圖39-.dynstr節
從圖39可以看出.dynstr節存的是動態符號表。
圖40-.rodata節
從圖40可以看出.rodata節存的是要輸出的字符串。
圖41-Memory Regions
其他節也可以使用這個方法到相應位置去查看虛擬地址空間內容。
5.5 鏈接的重定位過程分析
使用objdump -d -r hello > objhello.txt:
圖42-反匯編得到objhello.txt
圖43-Rehello.s與objhello.txt的對比
可以看到大體上并沒有什么不同,不同的地方在于objhello.txt中把地址由相對偏移變為了CPU可直接訪問的虛擬地址(如圖43)。重定位的過程就是鏈接器把hello.o中的偏移量加上程序在虛擬內存中的起始地址就得到了objhello.txt中的一個個地址。
還有就是objhello.txt在開頭部分多了.init,.fini,.plt.got節。其中.init 節是程序初始化需要執行的代碼,.fini 是程序正常終止時需要執行的代碼,.plt 和.plt.got節分別是動態鏈接中的過程鏈接表和全局偏移量表。
鏈接的過程如下:
(1)在使用 ld 命令鏈接的時候,指定了動態鏈接器為 64 的/lib64/ldlinux-x86-64.so.2,crt1.o、crti.o、crtn.o 中主要定義了程序入口_start、初始化函數_init,_start 程序調用 hello.c 中的 main 函數,libc.so 是動態鏈接共享庫,其中定義了 hello.c 中用到的 printf、sleep、getchar、exit 函數和_start 中調用的__libc_csu_init,__libc_csu_fini,__libc_start_main。鏈接器將上述函數加入。
(2)鏈接器解析重定條目時發現對外部函數調用的類型為R_X86_64_PLT32 的重定位,此時動態鏈接庫中的函數已經加入到了 PLT 中,.text與.plt 節相對距離已經確定,鏈接器計算相對距離,將對動態鏈接庫中函數的調用值改為 PLT 中相應函數與下條指令的相對地址,指向對應函數。對于此類重定位鏈接器為其構造.plt 與.got.plt。
(3)鏈接器解析重定條目時發現兩個類型為 R_X86_64_PC32 的對.rodata 的重定位(printf 中的兩個字符串),.rodata 與.text 節之間的相對距離確定,因此鏈接器直接修改 call 之后的值為目標地址與下一條指令的地址之差,使其指向相應的字符串。
5.6 hello的執行流程
| 程序名 | 程序地址 |
| ld-2.31.so!_dl_start | 0x00007f49f61a4df0 |
| ld-2.31.so!_dl_init | 0x00007f49f61b4c10 |
| hello!_start | 0x4010f0 |
| libc-2.31.so!__libc_start_main | 0x00007fede557efc0 |
| libc-2.31.so!__cxa_atexit | 0x00007fede55a1e10 |
| ld-2.31.so!_dl_fixup | 0x00007f49f5fe0bb0 |
| hello!__libc_csu_init | 0x401270 |
| hello!_init | 0x401000 |
| hello!register_tm_clones | 0x401160 |
| libc-2.31.so!_setjmp | 0x00007fede559dcb0 |
| libc-2.31.so!__sigsetjmp | 0x00007fede559dbe0 |
| hello!main | 0x4011d6 |
通過edb逐步調試得到的程序調用順序和地址如上。
5.7 Hello的動態鏈接分析
???通過先前得知.got.plt節的開始地址為0x404000.在dl_init前后到相應位置查看內容的變化。
圖44-dl_init之前的.got.plt節
圖45-dl_init之后的.got.plt節
可以看到在dl_init之后出現了兩個以7f開頭的兩個地址,這就是GOT[1]和GOT[2]。從5.6的調試經驗可知這些是動態鏈接器的首地址。通過查看對應位置內存發現是動態鏈接函數。
圖46-動態鏈接函數
5.8 本章小結
在本章中主要介紹了鏈接的概念與作用、hello 的 ELF 格式,分析了 hello 的
虛擬地址空間、重定位過程、執行流程、動態鏈接過程。
鏈接可以在程序的編譯、加載或運行時進行。經過鏈接,ELF 可重定位的目標文件會變成可執行的目標文件,鏈接器會將靜態庫代碼寫入程序中,以及寫入動態庫調用的相關信息,并將地址進行重定位,從而便于后續尋址。靜態庫會直接寫入代碼,動態鏈接會涉及到共享庫的尋址。
鏈接后,程序便能作為進程在虛擬內存中直接運行。
(第5章1分)
第6章 hello進程管理
6.1 進程的概念與作用
概念:進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括文本區域、數據區域和堆棧。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲著活動過程調用的指令和本地變量。
作用:進程提供給應用程序兩個關鍵抽象:
(1)邏輯控制流 (Logical control flow)
·每個程序似乎獨占地使用 CPU
·通過 OS 內核的上下文切換機制提供
(2)私有地址空間 (Private address space)
·每個程序似乎獨占地使用內存系統
·OS 內核的虛擬內存機制提供
6.2 簡述殼Shell-bash的作用與處理流程
作用:Shell 是一個用 C 語言編寫的程序。是操作系統(內核)與用戶之間的交互的橋梁,充當命令解釋器的作用,將用戶輸入的命令翻譯給系統執行。
處理流程:
1、從終端或控制臺讀入用戶輸入的命令;
2、將輸入字符串切分獲得所有的參數;
3、如果是內置命令則立即執行;
4、否則調用相應的程序為其分配子進程并運行;
5、shell 應該接受鍵盤輸入信號,并對這些信號進行相應處理;
6、判斷程序的執行狀態是前臺還是后臺。若是前臺就等待進程結束;否則
直接將進程放入后臺執行,繼續等待用戶的下一次輸入。
6.3 Hello的fork進程創建過程
圖47-在終端運行hello
由于hello不是內置命令,所以shell會fork一個子進程來運行相應名稱的程序也就是hello。在用法和結果上和直接運行原程序沒有任何區別。
當shell調用fork創建子進程來運行hello時,子進程會得到與父進程用戶級虛擬地址空間相同但是獨立存在的一份副本,其中包括代碼、數據段、堆、共享庫和用戶棧等。子進程還會獲得與父進程任何打開文件描述符相同的副本,所以子進程可以讀寫父進程中的任何文件。此外父進程和子進程的PID是不同的。
6.4 Hello的execve過程
在 fork 出一個子進程之后,要執行 execve 函數在當前進程的上下文中加載并運行一個新程序。
execve 會調用駐留在內存中啟動加載器來執行 hello 程序。加載器會刪除子進程現有的虛擬內存段,并創建一組新的代碼、數據、堆和棧段。
然后新的棧和堆段會被初始化為零,通過將虛擬地址空間中的頁映射到可執行文件頁大小的片上,將新代碼和數據段初始化為可執行文件中的內容。
最后加載器設置 PC 指向_start 地址,再由_start 最終調用 hello 中的 main 函數。除了一些頭部信息,在這個加載過程中沒有任何從磁盤到內存的復制操作。一直到 CPU 引用了一個被映射的虛擬頁時才會進行復制。這時候操作系統會利用它的頁面調度機制自動將頁面從磁盤傳送到內存。
6.5 Hello的進程執行
hello在剛開始運行時,內核保存一個它的上下文。進程會在用戶狀態下運行,若沒有異常或中斷信號,hello將一直正常地運行。除非hello程序被搶占順序執行,則會進行上下文切換。
查看hello的原代碼可以知道hello中是有sleep函數的。hello進程在調用sleep后會由用戶狀態轉換為內核狀態(核心態)。內核處理休眠請求主動釋放當前進程,將hello進程從運行隊列中移到等待隊列,再讓計時器開始計時,然后內核再進行上下文切換把當前進程的控制權交給其他進程。當計時器達到指定秒數也就是sleep的參數時,會發送一個中斷信號中斷當前正在進行的進程并再進行上下文切換,恢復hello進程在休眠前的上下文信息,把控制權交還給hello進程,hello回到用戶狀態。
在hello程序的最后調用了一個getchar函數。當調用getchar時hello會進入內核狀態。內核中的陷阱處理程序請求來自鍵盤緩存區的DMA傳輸,并執行上下文轉換,把控制權交給其他進程。當收到了緩存區的信息并傳輸到內存后,會發送一個中斷信號中止其他進程,內核再進行上下文切換換回hello進程。
最后hello運行到return,進程終止。
6.6 hello的異常與信號處理
hello在正常運行時結果如圖47.
圖48-亂按鍵盤的情況
可以看到并不會發生什么,雖然能輸入東西但程序還是照常執行。因為輸入的內容會被getchar函數讀走并被shell當作一個命令執行,因為是亂輸的所以什么也不會發生。
圖49-輸入了ctrl-c或ctrlz
當輸入了ctrl-c程序會中止。輸入了ctrl-z后hello會被掛起,用jobs命令可以看到被掛起的hello。
在pstree中查看bash下的進程可以找到hello。
圖50-進程樹中被掛起的hello
這時用fg1把hello調到前臺繼續運行,此時程序正常結束。
圖51-恢復運行的hello
繼續ctrl-z掛起hello,這時候可以在ps中看到掛起的hello的pid,此時用kill -9 pid來嘗試殺死hello進程。
圖52-kill殺死進程hello
6.7本章小結
本章介紹了 shell 的原理運行機制還有程序進程的相關概念。shell 中執行程序先是通過fork 函數及 execve 創建子進程,然后在子進程中執行程序。子進程有與父進程相同卻又獨立的環境,“好像”與其他進程并發執行,擁有著各自的時間片,在各種信號機制以及內核的管理下進行上下文切換,執行各自的指令。
異常分為中斷、陷阱、故障和終止四類。操作系統提供了信號這一機制來實現對異常的反饋。當遇到異常時,程序能通過各種信號來調用信號處理子程序,處理各種情況。
(第6章1分)
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
概念:
邏輯地址:指由程式產生的和段相關的偏移地址部分,分為兩個部分,一部分為段基址,另一部分為段偏移量。
線性地址:非負整數并連續的地址的有序集合,稱為線性地址空間。
虛擬地址:由邏輯地址翻譯得到的線性地址就叫虛擬地址。
物理地址:計算機系統的主存被組織成一個由 M 個連續的字節大小的單元組 成的數組,其每個字節都被給予一個唯一的地址,這個地址稱為物理地址。物理地址用于內存芯片級的單元尋址,與處理器和 CPU 連接的地址總線相對應。
7.2 Intel邏輯地址到線性地址的變換-段式管理
段寄存器(16位):用于存放段選擇符的寄存器。
圖53-段寄存器中的段選擇符示意
從邏輯地址到線性地址的變換,首先根據段選擇符的TI部分判斷需要用到的段選擇符表是全局描述符表還是局部描述符表,然后根據段選擇符的高13位索引到對于的描述符表中找到對應偏移量的段描述符,從中取出32位的段基址地址,把32位的段基址地址與32位的段內偏移量相加就得到了32位的線性地址。
圖54-邏輯地址變換為線性地址圖示
7.3 Hello的線性地址到物理地址的變換-頁式管理
頁表是一個頁表條目的數組,將虛擬頁地址映射到物理頁地址。
圖55-虛擬頁和物理頁的映射關系
圖56-虛擬頁表到物理頁表關系
hello的線性地址到物理地址首先要查詢頁表。線性地址分為虛擬頁號部分和虛擬頁偏移量部分,查詢虛擬頁表根據的就是虛擬頁號,先看其有效位是否為1,否則就發生缺頁;然后在頁表中指向的物理頁號和虛擬頁偏移量一起組成物理地址。
圖57-線性地址到物理地址圖示
7.4 TLB與四級頁表支持下的VA到PA的變換
在 Corei7 中 48 位虛擬地址分為 36 位的虛擬頁號以及 12 位的頁內偏移。
四級頁表中包含了一個地址字段,它里面保存了 40 位的物理頁號(PPN),
這就要求物理頁的大小要向 4kb 對齊
四級頁表每個表中均含有 512 個條目,故計算四級頁表對應區域如下:
第四級頁表:每個條目對應 4kb 區域,共 512 個條目;
第三級頁表:每個條目對應 4kb*512=2MB 區域,共 512 個條目;
第二級頁表:每個條目對應 2MB*512 = 1GB 區域,共 512 個條目;
第一級頁表:每個頁表對應 1GB*512 = 512GB 區域,共 512 個條目。
圖58-從VA到PA的變換流程
如圖58,VA中的36位VPN包括TLBT和TLBI,根據TLBI索引到對應的TLB組,并結合TLBT找到對應的行并判斷TLB是否命中。若命中則取出其中的物理頁號PPN,否則轉到四級頁表索引。
將該PPN與VA的VPO部分組合就得到了虛擬地址對應的物理地址。
(以下格式自行編排,編輯時刪除)
7.5 三級Cache支持下的物理內存訪問
圖59-三級cache下物理內存訪問
MMU 發送物理地址 PA 給 L1 緩存,L1 緩存從物理地址中抽取出緩存偏移
CO、緩存組索引 CI 以及緩存標記 CT。高速緩存根據 CI 找到緩存中的一組,并通過 CT 判斷是否已經緩存地址對應的數據,若緩存命中,則根據偏移量直接從緩存中讀取數據并返回;若緩存不命中,則繼續從 L2、L3 緩存中查詢,過程與在L1中類似。若仍未命中,則從主存中讀取數據。
7.6 hello進程fork時的內存映射
fork一個hello進程時會為這個新進程提供私有的虛擬地址空間。
圖60-寫時復制
7.7 hello進程execve時的內存映射
execve在當前進程中加載并運行hello:
圖61-execve時的內存區域示意
(以下格式自行編排,編輯時刪除)
7.8 缺頁故障與缺頁中斷處理
缺頁故障是一種常見的故障,當指令引用一個虛擬地址,在 MMU 中查找頁
表時發現與該地址相對應的物理地址不在內存中,因此必須從磁盤中取出的時候就會發生故障。 即緩存不命中。
缺頁中斷處理:
7.9動態存儲分配管理
圖62-動態內存分配器
malloc等申請內存的函數就是使用了動態內存分配器來獲得虛擬內存。動態內存分配器維護一個進程的虛擬內存區域“堆”。內核則維護著指向堆頂的變量brk。推與其他內存內容的關系可以見圖61.
分配器將堆視為一組不同大小的塊的結合來維護。每個塊就是一個連續的虛擬內存片,要么是已分配的,要么是空閑的。
已分配的塊顯式地保留為供應用程序使用??臻e塊可用來分配??臻e保持空閑,直到它顯式地被應用所分配。一個已分配的塊保持已分配狀態,直到它被釋放,這種釋放要么是應用程序顯式執行的,要么是內存分配器自身隱式執行的。malloc就會根據要申請的內存空間大小,找到空閑塊并分配給應用程序。
分配器有兩種類型策略:顯式分配器和隱式分配器,兩種風格都要求應用顯式地分配塊。不同在于由哪個實體來負責釋放:前者要求應用顯式地釋放任何已分配的塊,后者如果檢測到已分配塊不再被程序所使用,就釋放這個塊。malloc就是顯式分配器。
7.10本章小結
本章通過分析hello的內存映射和存儲管理,主要分析了虛擬內存的原理和內存映射的工作機制。虛擬內存是對主存的一個抽象,訪存時地址需要從邏輯地址翻譯到虛擬地址 并進一步翻譯成物理地址。 ?
操作系統通過地址的頁式管理來實現對磁盤的緩存、內存管理、內存保護等功
能。
虛擬內存為便捷的加載、進程管理提供了可能。程序運行過程中往往涉及動態
內存分配,動態內存分配通過動態內存分配器完成。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
設備的模型化:文件
設備管理:unix io接口
所有的 I/O 設備(例如網絡、磁盤和終端)都被模型化為文件,而所有的輸入和輸出都被當做對相應文件的讀和寫來執行。
8.2 簡述Unix IO接口及其函數
(以下格式自行編排,編輯時刪除)
UNIX I/O:將設備映射為文件,允許 linux 內核引出一個簡單、低級的應用接口,稱為 Unix I/O,使得所有的輸入和輸出都能以一種統一且一致的方式來執行。
Unix I/O 接口提供了以下函數供應用程序調用:
關閉文件:int close(int fd)。
(2)讀文件:ssize_t read(int fd, void *buf, size_t n);
寫文件:ssize_t write(int fd, const void *buf, size_t n)。
8.3 printf的實現分析
圖63-printf函數的聲明
其中形參中的...意為可變形參。當傳遞參數個數不確定時就可以用到這種寫法。
(char*)(&fmt) + 4) 表示的是...中的第一個參數的地址。
vsprintf()是另一個函數。
圖64-vsprintf的定義
可以看出vsprintf返回的是要打印出來的字符串的長度。
回到printf,看到write(buf,i)。write是系統函數。在 Linux 下,write 函數的第一個參數為 fd,也就是描述符,而 1 代表的就是標準輸出。查看 write 函數的匯編實現可以發現,它首先給寄存器傳遞了幾個參數,然后執行 int INT_VECTOR_SYS_CALL,代表通過系統調用 syscall,syscall 將寄存器中的字節通過總線復制到顯卡的顯存中。顯示芯片按照刷新頻率逐行讀取 vram,并通過信號線向液晶顯示器傳輸每一個點(RGB 分量)。由此 write 函數顯示一個已格式化的字符串。vsprintf的作用就是格式化。
8.4 getchar的實現分析
異步異常-鍵盤中斷的處理:當用戶按鍵時,鍵盤接口會得到一個代表該按鍵的鍵盤掃描碼并產生一個中斷請求。中斷請求搶占當前進程運行鍵盤中斷子程序,鍵盤中斷子程序先從鍵盤接口取得該按鍵的掃描碼,再將該按鍵掃描碼轉換成 ASCII 碼,保存到系統的鍵盤緩沖區之中。
圖65-getchar函數
可以看到getchar調用了系統函數read()。read 讀取存儲在鍵盤緩沖區中的 ASCII 碼直到讀到回車符然后返回整個字串,然后getchar再對這個串進行封裝。最后的效果就是讀取字符串的第一個字符并返回。
8.5本章小結
本章簡述了Linux的IO設備管理方法策略已經產生的一些系統函數的應用。在 Linux 中,I/O 的實現是通過 Unix I/O 函數來執行的。它把所有的 I/O 設備模型化為文件并提供統一的接口以供使用,這使得所有的輸入輸出都能以一種統一且一致的方式來執行。
(第8章1分)
結論
用計算機系統的語言,逐條總結hello所經歷的過程。
你對計算機系統的設計與實現的深切感悟,你的創新理念,如新的設計與實現方法。
hello,也就是一個可執行文件的代表。
首先有一個hello.c,也就是hello的C語言源碼。
想要運行源碼,先是編譯器對hello.c進行預處理得到hello.i,再進行編譯得到hello.s,最后匯編器進行匯編得到可重定位目標文件hello.o。
鏈接器對hello.o進行連接得到可執行目標文件hello,這時候hello已經可以被操作系統加載執行。
當bash要執行hello時,會先fork一個新的子進程,hello就在這個子進程中execve。execve會清空當前進程的數據并加載hello,把rip指向hello的程序入口,把控制權交給hello。
雖然說是控制權交給hello,但是系統中還是有許多其他進程也是在并行運行的。hello在運行過程中由于系統調用或計時器中斷會導致上下文切換,別的進程會搶占hello進程。
hello在運行過程中如果鍵盤輸入了什么或是收到來自其他進程的信號,此時hello會調用信號處理程序來處理這些信號,可能會忽略,也可能會中止進程。
hello中調用的printf和getchar在實現時就調用了Unix I/O的函數write和read,這兩個函數的實現就需要借助系統調用。
hello中的訪存操作需要從邏輯地址開始,變成線性地址再轉成物理地址。訪問物理地址時可能內容以及被緩存至高速緩存也就是命中,或者也可能不命中要倒主存中得到數據,再或者是數據位于磁盤中等待被交換到主存。
計算機系統真是太奇妙了!
(結論0分,缺失 -1分,根據內容酌情加分)
附件
列出所有的中間產物的文件名,并予以說明起作用。
| 文件名 | 文件作用 |
| hello.c | 源程序 |
| hello.i | 預處理文件 |
| hello.s | 編譯生成的匯編文件 |
| hello.o | 可重定位目標文件 |
| hello.elf | hello.o的ELF格式 |
| objhello.txt | hello的反匯編代碼文本文件 |
| Rehello.s | hello.o的反匯編代碼文件 |
| hello | hello.o鏈接后得到的可執行文件 |
(附件0分,缺失 -1分)
參考文獻
[1] ?林來興.?空間控制技術[M].?北京:中國宇航出版社,1992:25-42.
[2] ?辛希孟.?信息技術與信息服務國際研討會論文集:A集[C].?北京:中國科學出版社,1999.
[3] ?趙耀東. 新時代的工業工程師[M/OL]. 臺北:天下文化出版社,1998?[1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] ?諶穎. 空間交會控制理論與方法研究[D]. 哈爾濱:哈爾濱工業大學,1992:8-13.
[5]??KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6]??CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL].?Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/?collection/anatmorp.
[7] CSDN網站
[8] ?CSAPP 《深入理解計算機系統》
(參考文獻0分,缺失 -1分)
總結
以上是生活随笔為你收集整理的HIT-ICS2022大作业-程序人生-Hello’s P2P的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python学习之路day05(迭代器和
- 下一篇: 运筹帷幄之中,决胜千里之外——运筹学1-