计算机系统大作业-程序人生
?
計算機科學與技術學院
2021年5月
摘 ?要
本文介紹并詳細分析了hello程序的完整的一生:預處理,編譯,匯編,鏈接,在shell中運行,在進程中管理,接受I/O管理,終止回收。通過hello的一生,很好地對計算機系統的各部分知識進行了一個較為完整的回顧和梳理。
關鍵詞:hello;計算機系統;完整的一生;????????????????????????????
(摘要0分,缺失-1分,根據內容精彩稱都酌情加分0-1分)
目 ?錄
第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簡介
P2P是指from program to process。在linux系統中,源文件hello.c記過預處理、編譯、匯編、鏈接生成可執行文件hello。在輸入命令(./hello)啟動該程序后,shell為其fork,創建子進程,然后hello就從程序變為了進程。
020是指from zero to zero。hello程序開始運行成為進程后,shell為其execve,映射虛擬內存,調用程序入口時開始載入物理內存,進入main函數執行目標代碼,然后分配時間片,執行邏輯控制流。hello程序運行結束后,父進程回收該子進程,內核刪除進程產生的相關數據和分配的數據結構,恢復程序執行前的狀態,實現020。
1.2 環境與工具
硬件環境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
軟件環境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/優麒麟 64位 以上
開發與調試工具:gcc、edb、readelf、Visual Studio等
1.3 中間結果
hello.i:預處理后的文件,加載了頭文件,進行了宏替換,完成條件編譯。
hello.s:編譯后的匯編文件,已經變為了匯編代碼
hello.o:匯編后的可重定位目標文件,將匯編語言翻譯成機器語言指令,并將指令打包成可重定位目標文件。
hello:?鏈接產生的可執行目標文件。
hello-oobj.txt:存儲了hello.0的反匯編文件。
hello-obj.txt:存儲了hello的反匯編文件。
1.4 本章小結
????本章對實驗內容進行了一個總體的概括,介紹了hello從產生到執行的大致經過,介紹了開發環境以及工具,簡述了寫論文過程中生成的中間文件。
(第1章0.5分)
第2章 預處理
2.1?預處理的概念與作用
(以下格式自行編排,編輯時刪除)
預處理的概念:預處理是在程序源代碼被編譯之前,由預處理器(cpp)根據以字符#開頭的命令,修改原始的C程序。這個過程并不對程序的源代碼語法進行解析,但它會把源代碼分割或處理成為特定的符號為下一步的編譯做準備工作。
預處理的作用:
展開所有宏定義,處理#define x y格式的語句,用y替代x;
加載頭文件,將#include格式的頭文件插入到預編譯指令的位置;
完成條件編譯,處理#if,#ifdef格式的語句;
刪除注釋。
2.2在Ubuntu下預處理的命令
應截圖,展示預處理過程!
gcc -E hello.c -o hello.i
?? 圖2.2.1預處理過程
2.3 Hello的預處理結果解析
?
?
圖2.3.1預處理結果
打開hello.i文件后發現,文件的內容明顯增加,有3000多行,但是仍為可以閱讀的C語言文本文件。預處理器對源文件中的宏進行了宏展開,將系統頭文件中的內容直接插入到了程序文本中,同時也對#define相應的符號進行了替換。
2.4 本章小結
簡單介紹了文件在編譯之前的預處理過程,即.c文件生成.i文本文件的過程,對預處理的含義、具體執行過程和預處理結果進行了解析。
(第2章0.5分)
第3章 編譯
3.1 編譯的概念與作用
編譯的概念:編譯器將文本文件hello.i翻譯成文本文件hello.s,它包含一個匯編語言程序。
編譯的作用:編譯過程是指對預處理生存的.i文件進行一系列詞法分析、語法分析、語義分析,最后優化后生成相應的匯編代碼文件。
3.2 在Ubuntu下編譯的命令
??gcc -S hello.c -o hello.s
?
?????????????????????圖3.2.1編譯過程
3.3 Hello的編譯結果解析
(以下格式自行編排,編輯時刪除)
此部分是重點,說明編譯器是怎么處理C語言的各個數據類型以及各類操作的。應分3.3.1~ 3.3.x等按照類型和操作進行分析,只要hello.s中出現的屬于大作業PPT中P4給出的參考C數據與操作,都應解析。
3.3.1數據
????
?
?
?
???????????????圖3.3.1.1printf中的字符串
???i被存儲在棧中,地址是%rbp-4.
???
?
圖3.3.1.2局部變量i
???
?
圖3.3.1.3參數argc
4).main函數的參數argv數組:數組中的每一個元素都是一個指向字符類型的指針,數組的起始地址存放于棧中-32(%rbp)的位置。分別獲取了argv[2],argv[1],argv[3]的 地址,并將其地址處的內容存儲到相應的寄存器中,用于printf函數和atoi函數的參數傳遞。
?
圖3.3.1.4參數argv
3.3.2操作
????????1).算術操作:每次循環結束后執行i++的操作
????????
?
圖3.3.2.1循環加一操作
????????2).關系操作:
???????????[1].判斷argc是否等于4:
??????????????
?
圖3.3.2.2判斷argc
???
?
圖3.3.2.3判斷for條件
3).控制轉移操作:本程序主要是指if條件分支引起的跳轉以及for循環分支引起的跳轉。它們主要通過關系操作cmpl進行比較設置條件碼,之后根據條件碼進行對應的跳轉。
?
?
圖3.3.2.4跳轉
4).函數調用操作:在函數調用前,設置用于參數傳遞的寄存器的值,之后通過call指令進行函數的調用,本程序主要有printf函數的調用,sleep函數的調用,atoi函數的調用,exit函數的調用,以及getchar函數的調用。
??????????
?
????????????
?
??????????????
????????????
?
?
?????????????????????????圖3.3.2.5函數調用
3.4 本章小結
介紹了編譯的概念和作用。通過hello程序展示了c語言轉換成匯編代碼的過程。介紹了匯編代碼如何實現變量、常量、傳遞參數以及分支和循環。編譯程序所做的工作。并對編譯的過程進行進一步的分析,加深了對c語言的數據與操作,對c語言翻譯成匯編語言的邏輯有進一步的掌握。
(第3章2分)
第4章 匯編
4.1 匯編的概念與作用
匯編的概念:?匯編是通過匯編器,把匯編語言翻譯成機器語言的過程。
匯編的作用:將匯編語言程序翻譯成機器指令,并將這些指令打包成一種叫做可重定位目標程序的格式,并將這個結果保留.o目標文件中。
4.2 在Ubuntu下匯編的命令
gcc -c hello.s -o hello.o
?
圖4.2.1匯編過程
4.3 可重定位目標elf格式
分析hello.o的ELF格式,用readelf等列出其各節的基本信息,特別是重定位項目分析。
ELF Headers:以 16B 的序列 Magic 開始,Magic 描述了生成該文件的系統的字的大小和字節順序,ELF 頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息,其中包括 ELF 頭的大小、目標文件的類型、機器類型、字節頭部表(section header table)的文件偏移,以及節頭部表中條目的大小和數量等信息。
?
圖4.3.1ELF頭信息
?
?
圖4.3.2節頭部表信息
3.查看符號表:symtab:存放程序中定義和引用的函數和全局變量的信息。
Ndx:ABS表示不該被重定位、UND表示未定義(在這個地方被引用,但是在其他地方進行定義)、COM表示未初始化數據(未初始化的全局變量)
Bind:綁定屬性:全局符號、局部符號
Type:符號類型:函數、數據、源文件、節、未知
Value:.o.文件中是偏移量
?
圖4.3.3符號表信息
4.查看重定位條目:描述了需要進行重定位的各種信息,包括需要進行重定位符號的位置、重定位的方式、名字。
?
圖4.3.4重定位條目
4.4 Hello.o的結果解析
將反匯編后的指令存儲在hello-oobj中。
?
?
圖4.4.1匯編指令
對比:
1.代碼左邊多了機器碼;
2.hello.s中的操作數為十進制,hello.o反匯編代碼中的操作數為十六進制;
3.call跳轉指令,在hello.s文件中,直接加上跳轉函數名,在反匯編文件中,加上了跳轉的相對偏移地址,函數在鏈接之后才能確定執行的地址,因此在.rela.text節中為其添加了重定位條目。說明機器語言的構成,與匯編語言的映射關系。特別是機器語言中的操作數與匯編語言不一致,特別是分支轉移函數調用等。
4.5 本章小結
?????通過將hello.s匯編指令轉換成hello.o機器指令,通過readelf查看hello.o的ELF、反匯編的方式查看hello.o反匯編的內容,比較其與hello.s之間的差別,以及學習匯編指令和機器指令之間的映射關系,更深刻地理解了匯編語言到機器語言實現地轉變,和這過程中為鏈接做出的準備。
(第4章1分)
第5章 鏈接
5.1 鏈接的概念與作用
?????鏈接的概念:鏈接是將各種不同文件的代碼和數據部分收集(符號解析和重定位)起來并組合成一個單一文件的過程。
?????鏈接的作用:令源程序節省空間而未編入的常用函數文件進行合并,生成可以正常工作的可執行文件。這令分離編譯成為可能,節省了大量的工作空間。
5.2 在Ubuntu下鏈接的命令
命令:
ld-ohello-dynamic-linker/lib64/ld-linux-x86-64.so.2/usr/lib/x86_64-linux-gnu/crt1.o/usr/lib/x86_64-linux-gnu/crti.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o
?
圖5.2.1鏈接過程
5.3 可執行目標文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
1.查看hello的ELF頭:
?
圖5.3.1ELF頭信息
由此可知hello文件類型為EXEC,即是一個可執行目標文件,文件中共有27個節。
2.查看節頭部表:
?
?
圖5.3.2節頭部表信息
由此可知文件中各個段的基本信息,從Size獲取各個段的大小,從Address可以獲得各個段的起始地址即為程序被載入虛擬地址后各段的起始地址,從offset可以獲得各個段在程序中的偏移量。
3.查看符號表:
?
?
圖5.3.3符號表信息
5.4 hello的虛擬地址空間
使用edb加載hello,查看本進程的虛擬地址空間各段信息,并與5.3對照分析說明。
根據程序頭表查看各個段的信息:??
?
?????????????????????????圖5.4.1起始信息
代碼段從init 401000開始:
?
圖5.4.2代碼段
數據段:
?
圖5.4.3數據段
stack:
?圖5.4.4stack部分信息
?
5.5 鏈接的重定位過程分析
將hello的反匯編文件存儲在hello-obj.txt中:
?
?
?
?
?
?
圖5.5.1反匯編信息(以上這幾張圖是一起的)
對比hello.o:
?1.hello.o中的相對偏移地址到了hello中變成了虛擬內存地址
2. hello中相對hello.o增加了許多的外部鏈接來的函數,比如exit、printf等。
3. hello相對hello.o多了很多的節類似于.init,.plt等
4. hello.o中跳轉以及函數調用的地址在hello中都被更換成了虛擬內存地址
查看hello.o的重定位條目并據此分析hello的重定位過程:
?
圖5.5.2重定位條目
1.重定位節和符號定義鏈接器將所有類型相同的節合并在一起后,這個節就作為可執行目標文件的節。然后鏈接器把運行時的內存地址賦給新的聚合節,賦給輸入模塊定義的每個節,以及賦給輸入模塊定義的每個符號,當這一步完成時,程序中每條指令和全局變量都有唯一運行時的地址。
2,重定位節中的符號引用這一步中,連接器修改代碼節和數據節中對每個符號的引用,使他們指向正確的運行時地址。執行這一步,鏈接器依賴于可重定位目標模塊中稱為的重定位條目的數據結構。
3.重定位條目當編譯器遇到對最終位置未知的目標引用時,它就會生成一個重定位條目。
5.6 hello的執行流程
?
圖5.6.1edb中信息展示
0x401000 <_init>
0x401090 <puts@plt>
0x4010a0 <printf@plt>
0x4010b0 <getchar@plt>
0x4010c0 <atoi@plt>
0x4010d0 <exit@plt>
0x4010e0 <sleep@plt>
0x4010f0 <_start>
0x401120 <_dl_relocate_static_pie>
0x401125 <main>
0x4011c0 <__libc_csu_init>
0x401230 <__libc_csu_fini>
0x401238 <_fini>
5.7 Hello的動態鏈接分析
?
圖5.7.1got和plt的信息
在edb中找到0x403ff0和0x404000的位置
?
圖5.7.2調用init函數前的內容
????一開始地址的字節都為0,調用_init函數之后GOT內容產生變化,指向正確的內存地址,下一次調用跳轉時可以跳轉到正確位置。
?
圖5.7.3調用init函數后內容的變化
5.8 本章小結
本章介紹了鏈接的概念及作用,在Ubuntu下鏈接的命令行,并對hello的elf格式進行了詳細的分析對比,并通過反匯編hello文件,將其與hello.o反匯編文件對比,詳細了解了重定位過程,遍歷了整個hello的執行過程,在最后對hello進行了動態鏈接分析,使得對hello的鏈接過程有了一個深刻的理解和體會。
(第5章1分)
第6章 hello進程管理
6.1 進程的概念與作用
?????進程的概念:一個執行中程序的實例。
?????進程的作用:進程主要為用戶提供了下列兩個假象
(1)一個獨立的邏輯流,提供程序獨占使用處理器的假象。
(2)一個私有的虛擬地址空間,提供程序獨占使用整個系統內存的假象。
每次用戶通過向shell 輸入一個可執行目標文件的名字,運行程序時, shell 就會創建一個新的進程,然后在這個新進程的上下文中運行這個可執行目標文件。應用程序也能夠創建新進程,并且在這個新進程的上下文中運行它們自己的代碼或其他應用程序。
6.2 簡述殼Shell-bash的作用與處理流程
(以下格式自行編排,編輯時刪除)
Shell-bash的作用:shell是一個命令解釋器,它解釋用戶輸入的命令并把它們送到內核,用于用戶和系統的交互。
處理流程:
1.Shell首先從命令行中找出特殊字符(元字符),在將元字符翻譯成間隔符號。元字符將命令行劃分成小塊tokens。Shell中的元字符如下:SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |
2.程序塊tokens被處理,檢查看他們是否是shell中所引用到的關鍵字。
3.當程序塊tokens被確定以后,shell根據aliases文件中的列表來檢查命令的第一個單詞。如果這個單詞出現在aliases表中,執行替換操作并且處理過程回到第一步重新分割程序塊tokens。
4.Shell對~符號進行替換。
5.Shell對所有前面帶有$符號的變量進行替換。
6.Shell將命令行中的內嵌命令表達式替換成命令;他們一般都采用$(command)標記法。
7.Shell計算采用$(expression)標記的算術表達式。
8.Shell將命令字符串重新劃分為新的塊tokens。這次劃分的依據是欄位分割符號,稱為IFS。缺省的IFS變量包含有:SPACE , TAB 和換行符號。
9.Shell執行通配符* ? [ ]的替換。
10.shell把所有從處理的結果中用到的注釋刪除,並且按照下面的順序實行命令的檢查:A. 內建的命令B. shell函數(由用戶自己定義的)C. 可執行的腳本文件(需要尋找文件和PATH路徑)
11.在執行前的最后一步是初始化所有的輸入輸出重定向。
12.最后,執行命令。
6.3 Hello的fork進程創建過程
當在終端中輸入./hello 學號 姓名?時間(比如120L0221 yqb 5)時。shell會通過上述流程處理,首先判斷出它不是內置命令,所以會認為它是一個當前目錄下的可執行文件hello。在加載此進程時shell通過fork創建一個新的子進程。新創建的子進程幾乎但不完全與父進程相同。子進程得到與父進程用戶級虛擬地址空間相同的(但是獨立的)一份副本,包括代碼和數據段、堆、共享庫和用戶棧。子進程還獲得與父進程任何打開文件描述符相同的副本,這就意味著當父進程調用fork時,子進程可以讀寫父進程中打開的任何文件。fork調用一次,返回兩次,子進程與父進程有不同的pid。父進程和子進程是并發運行的獨立進程,內核可以任意方式交替執行他們的邏輯控制流中的指令,所以這會導致我們不能簡單的憑直覺判斷指令執行的順序,應按照拓撲排序序列執行。
6.4 Hello的execve過程
子進程調用execve函數(傳入命令行參數)在當前進程的上下文中加載并運行一個新程序即hello程序。hello子進程通過execve系統調用啟動加載器,加載器刪除子進程所有的虛擬地址段,并創建一組新的代碼、數據、堆段。新的棧和堆段被初始化為0。通過將虛擬地址空間中的頁映射到可執行文件的頁大小的片(chunk),新的代碼和數據段被初始化為可執行文件中的內容。最后加載器跳到_start地址,它最終調用hello的main 函數。除了一些頭部信息,在加載過程中沒有任何從磁盤到內存的數據復制。直到CPU引用一個被映射的虛擬頁時才會進行復制,此時,操作系統利用它的頁面調度機制自動將頁面從磁盤傳送到內存。
6.5 Hello的進程執行
上下文信息:上下文就是內核重新啟動一個被搶占的進程所需要的狀態,它由 通用寄存器、浮點寄存器、程序計數器、用戶棧、狀態寄存器、內核棧和各種內 核數據結構等對象的值構成。
進程時間片:一個進程執行它的控制流的一部分的每一時間段叫做時間片。
調度:在進程執行的某些時刻,內核可以決定搶占當前進程,并重新開始一個先前被強占的進程。這種決策就叫調度(是由內核中的調度器的代碼處理的)。當內核調度一個新的進程的運行的時,內核就會搶占當前進程,通過使用一種上下文切換的較為高層的形式異常控制流將控制轉移到新的進程。具體如下:內核首先保存當前進程的上下文,之后恢復之前被搶占的進程保存的上下文,將控制傳遞給這個恢復的進程。
用戶模式和內核模式:處理器通常使用一個寄存器提供兩種模式的區分,該寄存器描述了進程當前享有的特權,當沒有設置模式位時,進程就處于用戶模式中,用戶模式的進程不允許執行特權指令,也不允許直接引用地址空間中內核區內的代碼和數據;設置模式位時,進程處于內核模式,該進程可以執行指令集中的任何命令,并且可以訪問系統中的任何內存位置。
具體分析hello的進程執行:初始時,運行hello進程,處于用戶模式。調用系統函數sleep后,進入內核模式,執行另一個進程,此時間片停止。倒計時結束后,發送中斷信號,轉回用戶模式,繼續執行hello進程,重復此過程直到結束。
6.6 hello的異常與信號處理
(以下格式自行編排,編輯時刪除)
?hello執行過程中會出現哪幾類異常,會產生哪些信號,又怎么處理的。
?程序運行過程中可以按鍵盤,如不停亂按,包括回車,Ctrl-Z,Ctrl-C等,Ctrl-z后可以運行ps ?jobs ?pstree ?fg ?kill 等命令,請分別給出各命令及運行結截屏,說明異常與信號的處理。
異常種類:中斷、陷阱、故障、終止。
????
可能會產生很多信號,根據信號種類執行此信號的默認行為,如果使用了signal函數則另行討論。
回車:不會產生什么變化,只是會空行而已。
?
圖6.6.1程序執行時按下回車
Ctrl-z:會停止。
?
圖6.6.2程序執行時按下ctrl-z
Ctrl-c:終止進程。
?
圖6.6.3程序執行時按下ctrl-c
Ctrl-z后運行ps:?輸入ctrl-z,內核會發送SIGSTP。SIGSTP默認掛起前臺hello作業,但?hello進程并沒有回收,而是運行在后臺下,通過ps指令可以對其進行查看。
?
圖6.6.4ctrl-z后運行ps
Ctrl-z后運行jobs:
?
圖6.6.5ctrl-z后運行jobs
Ctrl-z后運行pstree:
?
圖6.6.5ctrl-z后運行pstree
Ctrl-z后運行fg:繼續剛才停止的進程。
?
圖6.6.5ctrl-z后運行fg
Ctrl-z后運行kill:
?
圖6.6.5ctrl-z后運行kill
6.7本章小結
本章了解了進程的概念及作用和shell的作用和處理流程,分析了hello進程的執行過程以及fork和execve的作用,了解信號處理和hello進程如何在內核模式和用戶模式中反復跳躍運行的。
????(第6章1分)
第7章 hello的存儲管理
7.1 hello的存儲器地址空間
邏輯地址:是指由程序hello產生的與段相關的偏移地址部分(hello.o)。
線性地址:是邏輯地址到物理地址變換之間的中間層。程序hello的代碼會產生邏輯地址,或者說是(即hello程序)段中的偏移地址,它加上相應段的基地址就生成了一個線性地址。
虛擬地址:也就是線性地址。因為與虛擬內存空間的概念類似,邏輯地址也是與實際物理內存容量無關的,是hello中的虛擬地址。
物理地址:是指出現在CPU外部地址總線上的尋址物理內存的地址信號,是地址變換的最終結果地址。如果啟用了分頁機制,那么hello的線性地址會使用頁目錄和頁表中的項變換成hello的物理地址;如果沒有啟用分頁機制,那么hello的線性地址就直接成為物理地址了。
7.2 Intel邏輯地址到線性地址的變換-段式管理
?一個邏輯地址由兩部份組成,段標識符: 段內偏移量。可以通過段標識符的前13位,直接在段描述符表中找到一個具體的段描述符,Base字段表示包含段的首字節的線性地址,也就是一個段的開始位置的線性地址。
先檢查段選擇符中的TI字段,以決定段描述符保存在哪一個描述符表中;
由于一個段描述符是8字節長,因此她在GDT或LDT內的相對地址是由段選擇符的最高13位的值乘以8得到;
Base + offset,得到要轉換的線性地址。
7.3 Hello的線性地址到物理地址的變換-頁式管理
頁式管理是一種內存空間存儲管理的技術,頁式管理分為靜態頁式管理和動態頁式管理。將各進程的虛擬空間劃分成若干個長度相等的頁(page),頁式管理把內存空間按頁的大小劃分成片或者頁面(page frame),然后把頁式虛擬地址與內存地址建立一一對應頁表,并用相應的硬件地址變換機構,來解決離散地址變換問題。頁式管理采用請求調頁或預調頁技術實現了內外存存儲器的統一管理。
頁表就是一個頁表條目的數組。虛擬地址空間中的每個頁在頁表中一個固定的偏移量處都有一個PTE。PTE包含了一個有效位和一個n位字段,有效位表明了該虛擬頁當前是否被緩存在DRAM中。因為DRAM是全相聯的,所以任意物理頁都可包含任意虛擬頁。
n位的虛擬地址包含兩個部分:一個p位的虛擬頁面偏移(VPO)和一個n-p位的虛擬頁號(VPN)。MMU利用VPN來選擇適當的PTE.將頁表條目中的物理頁號和VPO串聯起來就是相應的物理地址。因為物理和虛擬頁面都是P字節的,所以物理頁面偏移和虛擬頁面偏移是相同的。
7.4 TLB與四級頁表支持下的VA到PA的變換
TLB是MMU中的一個關于PTE的小的緩存,有了TLB后,VPN又分為了TLB標記(TLBT)和TLB索引(TLBI),TLB的機制與全相聯的cache的機制相同,如果TLB有T = 2s個組,那么TLB索引(TLBI)是由VPN的s個最低位組成的,TLB標記(TLBT)是由VPN中剩余的位組成。
引入多級頁表后,VPN被劃分成了多個區域,例如使用k級頁表,VPN被劃分成了k個VPN,每個VPNi都是一個到第i級頁表的索引,第k個VPN中存儲著VPN對應的PPN。
CPU產生一個VA,MMU在根據VPN在TLB中搜索PTE,若命中,MMU取出相應的PTE,根據PTE將VA翻譯成PA;若沒命中,則通過多級頁表查詢PTE是否在頁中,若在頁中,找到對應的PIE,MMU將VA翻譯成PA,若沒有在頁中,則進行缺頁處理
7.5 三級Cache支持下的物理內存訪問
CPU發送一條虛擬地址,隨后MMU按照上述操作獲得了物理地址PA。根據cache大小組數的要求,將PA分為CT(標記位)CS(組號),CO(偏移量)。根據CS尋找到正確的組,比較每一個cacheline是否標記位有效以及CT是否相等。如果命中就直接返回想要的數據,如果不命中,就依次去L2,L3,主存判斷是否命中,當命中時,將數據傳給CPU同時更新各級cache的cacheline(如果cache已滿則要采用換入換出策略)。
7.6 hello進程fork時的內存映射
當fork 函數被shell調用時,內核為hello進程創建各種數據結構,并分配給它一個唯一的PID 。為了給hello進程創建虛擬內存,它創建了hello進程的mm_struct 、區域結構和頁表的原樣副本。它將兩個進程中的每個頁面都標記為只讀,并將兩個進程中的每個區域結構都標記為私有的寫時復制。
當fork 在hello進程中返回時,hello進程現在的虛擬內存剛好和調用fork 時存在的虛擬內存相同。當這兩個進程中的任一個后來進行寫操作時,寫時復制機制就會創建新頁面,因此,也就為每個進程保持了私有地址空間的抽象概念。
7.7 hello進程execve時的內存映射
(以下格式自行編排,編輯時刪除)
1.刪除已存在的用戶區域:刪除當前進程虛擬地址的用戶部分中已存在的區域結構。
2.映射私有區域:為新程序的代碼、數據、bss和棧區域創建新的區域結構。所有這些新的區域都是私有的,寫時復制的。代碼和數據區域被映射為hello文件中的.test和.data區。bss區域是請求二進制0的,映射到匿名文件,其大小包含在a.out當中。棧和堆區域也是請求二進制零的,初始長度為零。
3.映射共享區域hello 程序與共享對象 libc.so 鏈接,libc.s是動態鏈 接到這個程序中的,然后再映射到用戶虛擬地址空間中的共享區域內。
4.設置程序計數器PC:execve做的最后一件事情就是設置當前進程上下文中的程序計數器,指向代碼區域的入口點。
7.8 缺頁故障與缺頁中斷處理
缺頁故障的概念:缺頁故障:當CPU想要讀取虛擬內存中的某個數據,而這一片數據恰好存放在主存當中時,就稱為頁命中。相對的,如果DRAM緩存不命中,則稱之為缺頁。如果CPU嘗試讀取一片內存而這片內存并沒有緩存在主存當中時,就會觸發一個缺頁異常。
缺頁中斷處理:發生缺頁故障時,處出發缺頁異常處理程序,缺頁處理程序確認出物理內存中的犧牲頁,如果這個頁已經被修改了,則把它換到磁盤。缺頁處理程序調入新的頁面,并更新內存中的PTE,缺頁處理程序返回到原來的進程,再次執行導致缺頁的命令。CPU將引起缺頁的虛擬地址重新發送給MMU。因為虛擬頁面已經緩存在物理內存中,所以就會命中。
7.9動態存儲分配管理
動態儲存分配管理使用動態內存分配器來進行。動態內存分配器維護著一個進程的虛擬內存區域,稱為堆。分配器將堆視為一組不同大小的塊的集合來維護。每個塊就是一個連續的虛擬內存片,要么是已分配的,要么是空閑的。已分配的塊顯式地保留為供應用程序使用。空閑塊可以用來分配。空閑塊保持空閑,直到它顯式地被應用所分配。一個已分配的塊保持已分配的狀態,直到它被釋放,這種釋放要么是應用程序顯式執行的,要么是內存分配器自身隱式執行的。動態內存分配主要有兩種基本方法與策略:
帶邊界標簽的隱式空閑鏈表分配器管理:
帶邊界標記的隱式空閑鏈表的每個塊是由一個字的頭部、有效載荷、可能的???????
額外填充以及一個字的尾部組成的。
隱式空閑鏈表:在隱式空閑鏈表中,因為空閑塊是通過頭部中的大小字段隱含地連接著的。分配器可以通過遍歷堆中所有的塊,從而間接地遍歷整個空閑塊的集合。其中,一個設置了已分配的位而大小為零的終止頭部將作為特殊標記的結束塊。
當一個應用請求一個k字節的塊時,分配器搜索空閑鏈表,查找一個足夠大的可以放置所請求塊的空閑塊。分配器有三種放置策略:首次適配、下一次適配合最佳適配。分配完后可以分割空閑塊減少內部碎片。同時分配器在面對釋放一個已分配塊時,可以合并空閑塊,其中便利用隱式空閑鏈表的邊界標記來進行合并。
顯式空間鏈表管理:
顯式空閑鏈表是將空閑塊組織為某種形式的顯式數據結構。因為根據定義,程序不需要一個空閑塊的主體,所以實現這個數據結構的指針可以存放在這些空閑塊的主體里面。如,堆可以組織成一個雙向鏈表,在每個空閑塊中,都包含一個前驅與一個后繼指針。
顯式空閑鏈表:在顯式空閑鏈表中。可以采用后進先出的順序維護鏈表,將最新釋放的塊放置在鏈表的開始處,也可以采用按照地址順序來維護鏈表,其中鏈表中每個塊的地址都小于它的后繼地址,在這種情況下,釋放一個塊需要線性時間的搜索來定位合適的前驅。
7.10本章小結
本章介紹了hello的存儲器地址空間、intel的段式管理、hello的頁式管理,在指定環境下介紹了 VA 到 PA 的變換、物理內存訪問,還介紹 hello 進程 fork 時的內存映射、 execve 時的內存映射、缺頁故障與缺頁中斷處理、動態存儲分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
設備的模型化:文件
設備管理:unix io接口
一個Linux文件就是一個m個字節的序列:B0,B1,B2….Bk,….,Bm-1
所有的I/O設備都被模型化為文件而所有的輸入和輸出都被當作對相應文件的讀和寫來執行。這種將設備優雅地映射為文件的方式,允許Linux 內核引出一個簡單、低級的應用接口,稱為Unix I/O,這使得所有的輸入和輸出都能以一種統一且一致的方式來執行。
8.2 簡述Unix IO接口及其函數
(以下格式自行編排,編輯時刪除)
Unix IO接口:
打開文件。一個應用程序通過要求內核打開相應的文件,來宣告他想要訪問一個I/O設備。內核返回一個小的非負整數,叫做描述符,他在后續對此文件的所有操作中標識這個文件。
Linux shell創建的每個進程開始時都有三個打開的文件:標準輸入(文件描述符0)、標準輸出(描述符為1),標準出錯(描述符為2)。頭文件<unistd.h>定義了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他們可用來代替顯式的描述符值。
函數:
1.open()函數
功能描述:用于打開或創建文件,在打開或創建文件時可以指定文件的屬性及用戶的權限等各種參數。
函數原型:int open(const char *pathname,int flags,int perms)
參數:pathname:被打開的文件名(可包括路徑名如"dev/ttyS0")flags:文件打開方式,
返回值:成功:返回文件描述符;失敗:返回-1
2.close()函數
功能描述:用于關閉一個被打開的的文件
所需頭文件: #include <unistd.h>
函數原型:int close(int fd)
參數:fd文件描述符
函數返回值:0成功,-1出錯
3.read()函數
功能描述: 從文件讀取數據。
所需頭文件: #include <unistd.h>
函數原型:ssize_t read(int fd, void *buf, size_t count);
參數:fd:將要讀取數據的文件描述詞。buf:指緩沖區,即讀取的數據會被放到這個緩沖區中去。count: 表示調用一次read操作,應該讀多少數量的字符。
返回值:返回所讀取的字節數;0(讀到EOF);-1(出錯)。
4.write()函數
功能描述: 向文件寫入數據。
所需頭文件: #include <unistd.h>
函數原型:ssize_t write(int fd, void *buf, size_t count);
返回值:寫入文件的字節數(成功);-1(出錯)
5.lseek()函數
功能描述: 用于在指定的文件描述符中將將文件指針定位到相應位置。
所需頭文件:#include <unistd.h>,#include <sys/types.h>
函數原型:off_t lseek(int fd, off_t offset,int whence);
參數:fd;文件描述符。offset:偏移量,每一個讀寫操作所需要移動的距離,單位是字節,可正可負(向前移,向后移)
返回值:成功:返回當前位移;失敗:返回-1
8.3 printf的實現分析
[轉]printf 函數實現的深入剖析 - Pianistx - 博客園
首先查看printf函數:
?
圖8.3.1printf函數源碼
發現它調用了兩個函數,分別是vsprintf和write。
再看vsprintf函數:
?
圖8.3.2vsprintf函數源碼
vsprintf的作用就是格式化。它接受確定輸出格式的格式字符串fmt。用格式字符串對個數變化的參數進行格式化,產生格式化輸出。
再看write函數:
?????
?
圖8.3.3write函數
write,顧名思義:寫操作,把buf中的i個元素的值寫到終端。
從vsprintf生成顯示信息,到write系統函數,到陷阱-系統調用 int 0x80或syscall等.
字符顯示驅動子程序:從ASCII到字模庫到顯示vram(存儲每一個點的RGB顏色信息)。
顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點(RGB分量)。
8.4 getchar的實現分析
首先查看getchar函數:
?
圖8.4.1getchar函數源碼
通過分析可知,getchar調用了一個read函數,將整個緩沖區都讀到了buf里面,然后返回緩沖區的長度。如果buf長度為0,getchar才會調用read函數,否則是直接將保存的buf中的最前面的元素返回。
異步異常-鍵盤中斷的處理:鍵盤中斷處理子程序。接受按鍵掃描碼轉成ascii碼,保存到系統的鍵盤緩沖區。
getchar等調用read系統函數,通過系統調用讀取按鍵ascii碼,直到接受到回車鍵才返回。
8.5本章小結
本章主要介紹了Linux的IO設備管理方法、Unix IO接口及其函數,分析了printf函數和getchar函數。
(第8章1分)
結論
用計算機系統的語言,逐條總結hello所經歷的過程。
你對計算機系統的設計與實現的深切感悟,你的創新理念,如新的設計與實現方法。
hello所經歷的過程:
1.預處理:將hello.c調用的所有外部的庫拓展到hello.i文件中,宏命令被處理,但總體其實沒什么改變;
2.編譯:將hello.i編譯得到匯編代碼文件hello.s,被編譯為匯編代碼;
3.匯編:將hello.s匯編成為二進制可重定位目標文件hello.o,被編譯為機器代碼;
4.鏈接:將hello.o與可重定位目標文件和動態鏈接庫鏈接成為可執行目標程序hello;
5.運行:shell中運行hello(我使用的是./hello 1111 yqb 1);
6.創建子進程:shell進程調用fork函數創建子進程;
7.運行程序:shell調用execve函數,execve調用啟動加載器,映射虛擬內存,進入程序入口后程序開始載入物理內存,然后進入 main函數;
8.執行指令:CPU為其分配時間片,在一個時間片中,hello享有CPU資源,順序執行自己的控制邏輯流;
9.訪問內存:MMU將程序中使用的虛擬內存地址通過頁表映射成物理地址;
10.動態申請內存:printf會調用malloc向動態內存分配器申請堆中的內存;
11.信號:如果運行途中鍵入Ctrl-C、Ctrl-Z則調用shell的信號處理函數分別停止、掛起;
12.結束:shell父進程回收子進程,內核刪除為這個進程創建的所有數據結構。
我對計算機系統的感悟:
以前使用電腦時從來沒有深入探究過一個程序到底是怎么運行的,只是動動鼠標或者輸入一段命令就可以看到結果,但通過了本學期對計算機系統的學習后,我了解了每天都在使用的計算機卻有如此復雜的體系結構,從最底層的硬件設計,再到指令的運作,再到進程和存儲的邏輯,一個簡單的hello程序卻也需要如此復雜的一套運作流程,著實令我感到驚嘆。
但同時,我認為計算機系統確實有很多知識是晦澀難懂并且非常的抽象的,并不是看看書本就可以理解的,只能是通過查閱大量的資料和詢問老師才有可能弄懂,計算機系統的奧秘還是需要我們細細體會。
(結論0分,缺失 -1分,根據內容酌情加分)
附件
列出所有的中間產物的文件名,并予以說明起作用。
hello.i:預處理后的文件,加載了頭文件,進行了宏替換,完成條件編譯。
hello.s:編譯后的匯編文件,已經變為了匯編代碼
hello.o:匯編后的可重定位目標文件,將匯編語言翻譯成機器語言指令,并將指令打包成可重定位目標文件。
hello:?鏈接產生的可執行目標文件。
hello-oobj.txt:存儲了hello.0的反匯編文件。
hello-obj.txt:存儲了hello的反匯編文件。
(附件0分,缺失 -1分)
參考文獻
? ? ? ? ?(往CSDN上傳圖片是真費勁!)
(參考文獻0分,缺失 -1分)
總結
以上是生活随笔為你收集整理的计算机系统大作业-程序人生的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何手动给Docker容器设置静态IP
- 下一篇: 深究标准IO的缓存