哈工大计算机系统大作业——hello P2P
計算機系統
大作業
題 ????目??程序人生-Hello’s P2P ?
專 ??????業 ?????????計算學部????????
學 ??號 ???????
班 ??級?????? ? ?
學 ??????生 ????????????? ??
指 導 教 師????????????劉宏偉??????
計算機科學與技術學院
2021年5月
摘 ?要
Hello World程序幾乎是每個程序員第一次接觸編程學習到的內容。hello.c程序歷經預處理、編譯、匯編、鏈接四個環節,最終生成可執行文件hello。而后,shell為hello創建子進程,可執行文件被加載到內存中執行。在hello執行的過程中,硬件和操作系統分工協同,共同完成程序的執行。
本文通過對hello程序執行的各個環節的探究,展示了計算機系統的各個組成部分是如何分工合作,共同完成hello的P2P過程。
關鍵詞:計算機系統;P2P;預處理;編譯;進程????????????????????????????
目 ?錄
第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簡介
在Unix系統中,GCC驅動程序讀取源文件hello.c,經過預處理,編譯,匯編和鏈接四個階段,最終生成一個可執行目標文件hello,并保存在磁盤上。
為了執行hello,操作系統為hello分配了虛擬內存,shell為hello創建了一個新的子進程,并在這個進程中調用execve函數,加載器將hello從磁盤中加載到內存,并將PC值設置為程序的入口。
然后,CPU從內存中讀取指令并執行,操作系統調度程序進行上下文切換。四級頁表和三級cache分工協作,共同完成地址的翻譯和數據的讀取。
程序執行完畢后,由父進程shell將子進程回收,hello的生命周期結束了。
1.2 環境與工具
1.2.1 硬件環境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
Windows10 64位以上;VirtualBox 11以上;Ubuntu 16.04 LTS 64位
1.2.2 軟件環境
Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc
1.2.3 開發工具
Ubuntu,gcc,edb
1.3 中間結果
| 中間文件 | 作用 |
| hello.i | 經過預處理的源程序,用于編譯器的翻譯 |
| hello.s | 匯編程序,用于匯編 |
| hello.o | 可重定位目標文件,用于鏈接 |
| hello | 可執行目標文件,可以加載到內存中執行 |
| elf.txt | hello.o對應的elf文本文件 |
| dump1.txt | hello.o 的反匯編文本文件 |
| elf2.txt | hello對應的elf文本文件 |
| dump2.txt | hello 的反匯編文本文件 |
1.4 本章小結
本章介紹了hello的P2P流程,給出了開發中用到的工具,同時介紹了中間文件的名稱和作用。
第2章 預處理
2.1?預處理的概念與作用
2.1.1 預處理的概念
在hello程序中,預處理器(cpp)根據以字符#開頭的命令,修改原始的C程序。比如 hello . c中第1行的?#include<stdio. h> 命令告訴預處理器讀取系統頭文件stdio . h 的內容,并把它直接插入程序文本中。結果就得到了另一個 C 程序。預處理后的程序通常是以.i作為文件擴展名。
2.2.2 預處理的作用
預處理器(cpp)將頭文件的內容插入到程序文本中,方便編譯器進行下一步的編譯。
2.2在Ubuntu下預處理的命令
Linux系統中對hello.c進行預處理的命令是gcc -E hello.c -o hello.i,執行過程如下圖所示:
????
圖2-1:linux下預處理命令執行過程
2.3 Hello的預處理結果解析
???????????????
圖2-2:hello.i文件部分截圖
對比hello.c和hello.i的文件大小,可以看到,文件從1kb增長到了64kb。在hello.i中,源代碼被放到了文件的最后,文件前面插入了系統頭文件stdio.h unistd.h和stdlib.h的源代碼,說明頭文件的內容被展開并插入到程序中。
2.4 本章小結
本章介紹了預處理的概念和作用,并對預處理生成的hello.i文件進行初步分析。
第3章 編譯
3.1 編譯的概念與作用
3.1.1 編譯的概念
編譯是指編譯器將文本文件hello.i翻譯成文本文件hello.s,即將預處理后的文件轉換成匯編語言程序
3.3.2 編譯的作用
編譯將高級語言指令翻譯為統一的低級機器語言指令,便于機器處理。在生成匯編語言的過程中,同時對代碼進行優化,發現和報告錯誤。
3.2 在Ubuntu下編譯的命令
Linux下編譯的命令為gcc -s hello.i -o hello.s
???????
圖3-1:編譯過程展示
3.3 Hello的編譯結果解析
下面將分類討論編譯器如何處理各種數據類型和操作:
3.3.1 對數據的處理
編譯器將局部變量保存在寄存器或棧中,并用棧指針+偏移量的形式進行所以進行索引。hello.c程序有兩個局部變量,分別是參數個數argc和循環索引i,分別保存在相對棧指針%rsp偏移量為20和4的內存空間中。
在程序中,指針就是一個地址值,同樣保存在棧或者寄存器中。
??????????????
????????????????
圖3-2 對數據的處理
同時,編譯器將常量表示為立即數的形式,無全局變量和靜態變量
3.3.2 對賦值運算的處理
一種常見的賦值操作是使用mov指令,如圖3-2所示
?????????????????
圖3-2 hello.s中的mov指令
3.3.3 對類型轉換的處理
源代碼中有顯式類型轉換atoi函數將字符串轉換為常量,通過調用函數實現
???????????????
圖3-3 atoi函數調用
3.3.4 對算術操作的處理
使用算術操作指令進行算數操作
????????????????
圖3-4 add指令
3.3.5 對關系操作和控制轉移的處理
編譯器通常使用cmp指令和條件跳轉共同完成控制轉移,例如原程序中的for(i=0;i<8;i++)語句被轉換為下面的指令
??????????????
圖3-5 cmp指令+條件跳轉
3.3.6 對函數的處理
編譯器使用call指令調用函數,call后接函數名稱。
函數調用參數傳遞規則:X86-64中,過程調用傳遞參數規則為第1~6個參數依次儲存在%rdi、%rsi、%rdx、%rcx、%r8、%r9寄存器中,剩下的參數通過棧傳遞。
?????????????
圖3-6 函數調用
3.4 本章小結
本章介紹了編譯的概念和作用,并分析了hello.s文件,可以看到,源文件中的不同代碼段被翻譯為了不同的匯編指令。
第4章 匯編
4.1 匯編的概念與作用
4.1.1 匯編的概念
驅動程序運行匯編器as,將匯編語言文件hello.s翻譯成可重定位目標文件hello.o的過程
4.1.2 匯編的作用
將文本文件翻譯為機器可讀懂的二進制文件,以便于執行鏈接
4.2 在Ubuntu下匯編的命令
Linux下的匯編指令:as hello.s -o hello.o
????????????
圖4-1 匯編過程展示
4.3 可重定位目標elf格式
下面分析hello.o的ELF格式,并用readelf等列出其各節的基本信息。
readelf指令:readelf -a hello.o > ./elf.txt
4.3.1 ELF頭
ELF頭以一個16字節的序列開始,這個序列描述了生成該文件的系統的字的大小和字節順序。ELF頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息。其中包括ELF頭的大小、目標文件的類型、機器類型、節頭部表的文件偏移,以及節頭部表中條目的大小和數量。
??????
圖4-2 hello.o的ELF頭
4.3.2 節頭部表
節頭部表描述不同節的位置和大小信息,其中目標文件的每個節都有一個固定大小的條目。如圖所示,hello.o共有14個條目,代表了14個不同的節。
???????????????
圖4-3 hello.o的節頭部表
4.3.3 符號表
.symtab節中包含的符號表,符號表存放在程序中定義和引用的全局變量的信息。如圖4-4所示,在符號表中可以看到函數定義或引用的sleep,getchar等符號
???????????
圖4-4 hello.o的符號表
4.3.4 .rela.text節
?.rela.text包含代碼的重定位條目。offset?是需要被修改的引用的節偏移。symbol?標識被修改引用應該指向的符號;?type告知鏈接器如何修改新的引用。addend?是一個有符號常數,一些類型的重定位要使用它對被修改引用的值做偏移調整。
圖4-4 hello.o的.rela.text節
4.4 Hello.o的結果解析
使用objdump -d -r hello.o指令,得到hello.o的反匯編??
????????
圖4-5 hello.o反匯編過程
將hello.o的反匯編與hello.s進行對照分析,可以看出其明顯差異如下:
4.4.1 操作數的差異
匯編語言使用10進制操作數,而機器語言使用十六進制操作數
4.4.1 分支轉移的差異
hello.s使用代碼的標號進行直接跳轉,如圖4-6所示,控制直接跳轉到.L2處
????????????????
圖4-6 hello.s的跳轉指令
而hello.o的反匯編中使用相對地址進行跳轉,如圖所示
????????????????
圖4-7 hello.o的反匯編中的跳轉
4.4.3?函數調用的差異
hello.s中使用call+函數名的方式調用函數,如圖4-8所示
???????????????????
圖4-8 hello.s的函數調用
而hello.o的反匯編中使用call+函數地址調用函數(由于此時尚未進行重定位,所以對函數的調用采用了重定位條目的形式)
??????????????
圖4-8 hello.o的反匯編中的跳轉
4.5 本章小結
本章介紹了將文本文件進行匯編生成可執行目標文件的過程。同時對生成的ELF文件進行了分析,可以看到,ELF文件由ELF頭,節頭部表以及夾在二者之間的若干節構成。此外還將匯編后的文件與匯編前的文件進行對比,并觀察到二者在分支轉移和函數調用等方面差異顯著。
??????????????????第5章 鏈接
5.1 鏈接的概念與作用
5.1.1 鏈接的概念
鏈接是將各種代碼和數據片段收集并組合成為一個單一文件的過程,這個文件可以被加載到內存執行。
5.1.2 鏈接的作用
鏈接將獨立的可重定位目標模塊組織成統一的可執行目標文件。在軟件開發中扮演重要的角色,它使得分離編譯成為可能。我們不用將一個大型的應用程序組織稱為一個巨大的源文件,而是可以把它們分解成更好管理的模塊。
5.2 在Ubuntu下鏈接的命令
5.2.1 Linux下的鏈接命令
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
5.2.2 鏈接過程
如下圖所示:
圖5-1 鏈接過程展示
5.3 可執行目標文件hello的格式
5.3.1 ELF頭
?????????
圖5-2 hello文件的ELF頭
5.3.2 節頭部表
從節頭部表可以得到每個節的位置和大小信息,比如.text節的開始位置是0x4010f0,大小為145個字節,如圖5-3所示
????????
圖5-3 hello文件節頭部表
5.3.3 程序頭部表
程序頭部表描述了可執行文件到內存的映射關系,如圖5-4所示
?????????
圖5-4 hello文件程序頭部表
5.4 hello的虛擬地址空間
5.4.1 虛擬地址空間概述
圖5-5展示了一個x86-64 Linux進程的虛擬地址空間的組織結構。地址空間底部是保留給用戶程序的,包括通常的代碼、數據、堆和棧段。代碼段總是從地址0x400000開始。地址空間頂部保留給內核(操作系統常駐內存的部分)。地址空的這個部分包含內核在代表進程執行指令時(比如當應用程序執行系統調用時)使用的碼、數據和棧。
????????????????????
圖5-5 一個標準linux進程的虛擬內存結構
5.4.1 棧
使用edb查看hello程序,可以看到虛擬空間的棧向下增長,棧指針位于地址00007ffdbdec6030
???????????????????????????
圖5-5 edb中展示的棧結構
5.4.2 數據和代碼段
?????????
圖5-5 edb中展示的數據和代碼段
數據和代碼段從地址0x400000開始。查看可執行文件的節頭部表可知,程序的入口點.init節開始于地址0x401000處,這與上圖顯示的初始地址相吻合。????????????????
5.5 鏈接的重定位過程分析
5.5.1 hello和hello.o的差異
新增節:hello相比于hello.o,新增了init,plt等節,atoi,sleep等函數的反匯編代碼也包含在節中,如圖5-6所示
?????
圖5-6 hello中新增的節
地址:hello中使用虛擬內存地址,如圖5-7所示
???????????????
圖5-7 hello中的虛擬地址
符號引用 :在hello.o函數中,由于沒有進行重定位,所以使用諸如“R_X86_64_PC32 .rodata+0x22”的重定位條目顯示在引用的后面一行上;而在hello中,重定位條目被替換為真實的虛擬內存尋址。
???????????
圖5-8 hello中call指令后接虛擬地址
5.5.2 對hello.o的重定位過程分析
鏈接器使用一種叫做重定位算法的方法來解析hello.o中的引用。在hello.o文件中,匯編器為每個引用產生一個重定位條目,顯示在引用的后面一行上。這些重定位條目告訴鏈接器對該符號的引用要使用哪種具體算法。
???????????
圖5-9 重定位算法展示
5.6 hello的執行流程
5.6.1 程序執行的全過程
使用edb執行hello,程序初始位置位于0x7f632c643100。
1. 從加載到進入main函數的過程
開始時,經過一系列執行,程序首先跳轉到子程序hello!_start,該子程序位于地址0x4010f0處。隨后通過callq *0x2ed2(%rip) 指令跳轉到位于地址0x7f38faffefc0的“Libc-2.31.so!_libc_start_main”子程序,在子程序中,通過call *%rax指令跳轉到main函數,地址為0x401125。
2. 從main函數到程序執行完畢
進入main函數,程序按照源代碼順序依次執行,在執行的過程中分別調用不同的子程序,子程序名稱和地址見下表
5.6.2 各子程序展示
| 子程序名稱 | 地址 |
| Hello!start | 0x4010f0 |
| libc-2.31.so!_libc_start_main | 0x7f38faffefc0 |
| Hello!main | 0x401125 |
| <puts@plt> | 0x401090 |
| <printf@plt> | 0x4010a0 |
| <getchar@plt> | 0x4010b0 |
| <atoi@plt> | Ox4010c0 |
| <exit@plt> | 0x4010d0 |
| <sleep@plt> | 0x4010e0 |
5.7 Hello的動態鏈接分析
下面展示了動態鏈接前后的變化:
首先在elf文件中找到:
???????????????
動態鏈接前:
動態鏈接后:
5.8 本章小結
本章介紹了鏈接的概念和作用,同時分析了hello程序的虛擬地址空間、重定位過程、執行流程、動態鏈接過程。
第6章 hello進程管理
6.1 進程的概念與作用
6.1.1進程的概念
進程的經典定義就是一個執行中程序的實例,是操作系統對一個正在運行程序的抽象。
6.1.2 進程的作用
操作系統通過進程提供一種假象,就好像系統上只有這個程序在運行。程序看上去是獨占地使用處理器、主存和 I / O 設備。處理器看上去就像在不間斷地一條接一條地執行程序中的指令,即該程序的代碼和數據是系統內存中唯一的對象。
6.2 簡述殼Shell-bash的作用與處理流程
6.2.1 shell的作用
Shell是一個交互性的應用級程序,代表用戶運行其他程序
6.2.2 處理流程
shell讀取用戶輸入的命令行,解析該命令行并代表用戶運行程序,包括使用fork函數創建一個新的進程,并在新的進程上下文中運行程序。
6.3 Hello的fork進程創建過程
在命令行輸入“./hello 7203610410 xukun 1”后,shell解析命令行,并發現該命令行的第一個單詞不是shell的內置命令(在這里是./hello),那么shell就會假設這是一個可執行文件的名字,然后加載并運行這個文件。Shell會根據輸入的命令行的剩余部分構造參數列表,并構造最終會傳遞給execve的argv向量。
shell使用fork創建一個新的子進程,子進程得到與父進程用戶級虛擬地址空間相同的(但是獨立的)副本,包括代碼和數據段、堆、共享庫以及用戶棧。子進程還獲得與父進程任何打開文件描述符相同的副本,這就意味著當父進程調用 fork時,子進程可以讀寫父進程中打開的任何文件。父進程和新創建的子進程之間最大的區別在于它們有不同的PID。
6.4 Hello的execve過程
在新創建的子進程中,shell調用execve函數加載并運行可執行目標文件hello,且帶參數列表argv和環境變量列表envp。參數列表在shell解析的過程中創建, argv變量指向一個以null結尾的指針數組,其中每個指針都指向一個參數字符串。按照慣例,argv [0]是可執行目標文件的名字。環境變量的列表是由一個類似的數據結構表示的,envp變量指向一個以null結尾的指針數組,其中每個指針指向一個環境變量字符串,每個串都是形如name = value的名字-值對。
execve函數代碼如下:
??????????
圖6-1 execve函數代碼
6.5 Hello的進程執行
6.5.1 基本概念
上下文:內核為每個進程維持一個上下文,上下文就是內核重新啟動一個被搶占的進程所需的狀態。
調度:在進程執行的某些時刻,內核可以決定搶占當前進程,并重新開始一個先前被搶占了的進程,這種決策就叫做調度,是由內核中稱為調度器的代碼處理的。
上下文切換:在內核調度了一個新的進程運行后,它就搶占當前進程,并使用一種稱為上下文切換的機制來將控制轉移到新的進程。
時間片:一個進程執行它的控制流的一部分的每一時間段叫做時間片
6.5.2 hello的進程執行
本例中有兩個并發的進程: shell進程和 hello 進程。最開始,shell進程在運行,即等待命令行上的輸入。當我們讓它運行hello程序時,shell通過系統調用,來執行我們的請求,系統調用會將控制權傳遞給操作系統。操作系統保存shell進程的上下文,創建一個新的hello進程及其上下文,然后將控制權傳給新的hello進程。在hello進程的執行過程中,在某些時刻,hello程序調用sleep函數顯式請求進程休眠,此時內核執行上下文切換,從用戶模式轉換到內核模式,搶占hello進程。隨后,在其他進程執行一段時間后,內核做出決定將控制返回給hello進程繼續執行。如此反復直到hello進程終止后,操作系統恢復shell進程的上下文,并將控制權傳給它,shell進程會繼續等待下一個命令行輸入。
6.6 hello的異常與信號處理
hello執行過程中會出現異步中斷異常和系統調用,中斷即來自外部I/O設備的信號打斷進程。同時會產生諸如SIGINT,SIGQUIT,SIGTSTP等信號。
6.6.1 執行過程中鍵入Ctrl-z
當在鍵盤中敲入Ctrl-z時,內核會向前臺進程組的每個進程發送SIGTSTP信號,hello進程捕獲信號并停止,如下圖所示。
????????
圖6-2 執行過程中鍵入Ctrl-z
6.6.2 停止后輸入ps/jobs/pstree等命令
ps/jobs/pstree等命令是shell的內置命令,shell執行并實現相應指令的功能。
????????
圖6-3 停止后輸入jobs命令,列出所有的jobs
???????????
圖6-4 停止后輸入pstree命令
??????????????????
圖6-5 停止后輸入ps命令
6.6.3 停止后輸入fg命令
fg指令是shell的內置命令,當在鍵盤中敲入fg時,內核會向前臺進程組的每個進程發送SIGCONT信號,此時被停止的hello進程被喚醒并繼續運行
???????????????????
圖6-5 停止后輸入fg命令
6.6.4 停止后輸入kill命令
kill是shell的內置命令,當在鍵盤中敲入kill時,會顯示地從從進程向目標進程發送指定的信號,如圖所示,是向hello所在進程發送SIGKILL信號殺死該進程。
??????????????
圖6-6 停止后輸入kill命令
6.6.5 執行過程中鍵入Ctrl-c
當在鍵盤中敲入Ctrl-c時,內核會向前臺進程組的每個進程發送SIGINT信號,hello進程捕獲信號并終止,等待回收。
????????
圖6-7 執行過程中鍵入Ctrl-c
6.6.5 執行過程中不停亂按和鍵入回車
亂按的字符會保存在緩沖區內,等待getchar()函數讀取
??????????
圖6-8 執行過程中不停亂按
?????????????
圖6-8 執行過程中鍵入回車
6.7本章小結
本章從進程和上下文的角度分析了hello的執行過程,以及在執行過程中程序對異常的處理流程。
第7章 hello的存儲管理
7.1 hello的存儲器地址空間?
7.1.1 邏輯地址
邏輯地址是用戶編程時使用的地址,分為段地址和偏移地址兩部分。在hello程序中,指的是hello.o中的地址。
7.1.2 線性地址
線性地址(Linear Address)是邏輯地址到物理地址變換之間的中間層。程序代碼會產生邏輯地址,或者說是段中的偏移地址,加上相應段的基地址就生成了一個線性地址。
7.1.3 物理地址
物理地址是指出現在CPU外部地址總線上的尋址物理內存的地址信號,是地址變換的最終結果地址,這里指的是hello保存在主存上的實際地址。
7.1.4 虛擬地址
虛擬地址指的是程序在虛擬內存空間的地址,這里就是hello的虛擬地址。
7.2 Intel邏輯地址到線性地址的變換-段式管理
linux是通過分段機制,將邏輯地址轉化為線性地址。通過數據段寄存器ds,可以找到此進程數據段所在的段描述符,再通過段描述符找到相應的線性地址。
段描述符是描述段的屬性的數據結構,長度一般為8字節。
給定一個完整的邏輯地址段選擇符+段內偏移地址,看段選擇符的T1=0還是1,知道當前要轉換是GDT中的段,還是LDT中的段,再根據相應寄存器,得到其地址和大小。拿出段選擇符中前13位,可以在這個數組中,查找到對應的段描述符,就得到了其基地址。從邏輯地址到線性地址的全部流程如下圖所示:
?????????
圖7-1 從邏輯地址到線性地址的全部流程
7.3 Hello的線性地址到物理地址的變換-頁式管理
線性地址怎么對應到物理地址是通過分頁機制來完成的,具體的說,就是通過頁表查找來對應物理地址。
分頁的基本原理是把線性地址分成固定長度的單元,稱為頁(page)。頁內部連續的線性地址映射到連續的物理地址中。X86每頁為4KB。為了能轉換成物理地址,我們需要給CPU提供當前任務的線性地址轉物理地址的查找表,即頁表(page table),頁表存放在內存中。
在保護模式下,控制寄存器CR0的最高位PG位控制著分頁管理機制是否生效,如果PG=1,分頁機制生效,需通過頁表查找才能把線性地址轉換物理地址。如果PG=0,則分頁機制無效,線性地址就直接作為物理地址。
為了實現每個任務的平坦的虛擬內存和相互隔離,每個任務都有自己的頁目錄表和頁表。
7.4 TLB與四級頁表支持下的VA到PA的變換
core i7支持48位虛擬地址空間和52位物理地址空間,采用四級頁表層次結構。如圖所示,展示了VA到PA的變換。CPU首先生成一個虛擬地址,并將其拆分為36位的VPN部分和12位的VPO部分。
首先查找TLB,MMU從虛擬地址中抽取VPN部分,并將其解讀為標記(TLBT)和索引(TLBL)兩部分,用于對TLB進行組選擇和行匹配。然后檢查TLB,若命中,則直接將緩存的PPN返回,與VPO連接起來,這就形成了物理地址,完成了VA到PA的變換。
若TLB不命中,那么MMU必須從頁表中的PTE取出PPN。此時,36位VPN被劃分成四個9位的片,每個片被用作到一個頁表的偏移量。比如前9位就被用作到一級頁表的偏移量,以此類推。前3層頁表中,每個頁表的PTE都指向下一個頁表的基址。第四層頁表中,每個PTE包含某個物理頁面的PPN。分層順序查找頁表,將得到的PPN與VPO連接起來,形成物理地址,完成了VA到PA的變換。
??????
圖7-2 TLB與四級頁表支持下的VA到PA的變換
7.5 三級Cache支持下的物理內存訪問
當MMU生成物理地址后,將獲得的物理地址拆分為CT(標記位)CI(組索引),CO(塊偏移)。
組索引位是一個到組的索引。第一個組是組0,第二個組是組1,依此類推。組索引位被解釋為一個無符號整數、它告訴我們這個字必須存儲在哪個組中。一旦我們知道了這個字必須放在哪個組中,A中的個標記位就告訴我們這個組中的哪一行包含這個字(如果有的話)。當且僅當設置了有效位并且該行的標記位與地址 A 中的様記位相匹配時,組中的這一行才包含這個字。一旦我們在由組索引標識的組中定位了由標號所標識的行,那么塊偏移位給出了在B個字節的數據塊中的字偏移。
整個流程分為三步:組選擇,行匹配,字抽取。
?????????????????
圖7-3 Intel core i7 高速緩存層次結構
???????????????????
圖7-4 三級Cache支持下的物理內存訪問
7.6 hello進程fork時的內存映射
7.6.1 私有對象的內存映射
私有對象使用一種叫做寫時復制的巧妙技術被映射到虛擬內存中。對于每個映射私有對象的進程,相應私有區域的頁表條目都被標記位只讀,并且區域結構被標記位私有的寫時復制。
7.6.2 hello進程fork時的內存映射
當shell調用fork函數為hello創建新的進程時,內核為新進程創建各種數據結構,并分配給它一個唯一的PID。為了給這個新進程創建虛擬內存,它創建了當前進程的 mm_struct、區域結構和頁表的原樣副本。它將兩個進程中的每個頁面都標記為只讀,并將兩個進程中的每個區域結構都標記為私有的寫時復制。
當fork在新進程中返回時,新進程現在的虛擬內存剛好和調用fork時存在的虛擬內存相同。當這兩個進程中的任一個后來進行寫操作時,寫時復制機制就會創建新頁面,因此,也就為每個進程保持了私有地址空間的抽象概念。
7.7 hello進程execve時的內存映射
當在新的進程中調用execve函數時,在當前進程中加載并運行包含在可執行目標文件hello中的程序,用hello程序有效地替代了當前程序。加載并運行hello需要以下幾個步驟:
1. 刪除已存在的用戶區域。刪除當前進程虛擬地址的用戶部分中的已存在的區域結構。
2. 映射私有區域。為新程序的代碼、數據、 bss和棧區域創建新的區域結構。所有這些新的區域都是私有的、寫時復制的。代碼和數據區域被映射為hello文件中的.text和.data 區。bss區域是請求二進制零的,映射到匿名文件,其大小包含在hello中。棧和堆區域也是請求二進制零的,初始長度為零。下圖概括了私有區域的不同映射。
3. 映射共享區域。hello程序與共享對象鏈接,比如標準C庫1ibc. So,那么這些對象都是動態鏈接到這個程序的,然后再映射到用戶虛擬地址空間中的共享區域內。
4. 設置程序計數器(PC)。 execve做的最后一件事情就是設置當前進程上下文中的程序計數器,使之指向代碼區域的入口點。下一次調度這個進程時,它將從這個入口點開始執行。 Linux將根據需要換入代碼和數據頁面。
7.8 缺頁故障與缺頁中斷處理
7.8.1 缺頁故障
在虛擬內存的習慣說法中,DRAM緩存不命中稱為缺頁。
7.8.2 缺頁中斷處理
處理缺頁要求硬件和操作系統內核協作完成。當發生缺頁故障時,MMU觸發了一次異常,傳遞CPU中的控制到操作系統中的缺頁異常處理程序,處理程序隨后執行下面的步驟:
1. 判斷虛擬地址A是否合法。
處理程序首先判斷A是否在某個區域結構定義的區域內。為了回答這個問題,缺頁處理程序搜索區域結構的鏈表,把A和每個區域結構中的vm_start和vm_end 做比較。如果這個指令是不合法的,那么缺頁處理程序就觸發一個段錯誤,從而終止這個進程。這個情況在下圖中標識為“1”。
因為一個進程可以創建任意數量的新虛擬內存區域,所以順序搜索區域結構的鏈表花銷可能會很大。因此在實際中,Linux使用某些我們沒有顯示出來的字段, Linux在鏈表中構建了一棵樹,并在這棵樹上進行査找。
2. 判斷試圖進行的內存訪問是否合法
處理程序需要判斷進程是否有讀、寫或者執行這個區域內頁面的權限。例如,這個缺頁可能是由一條試圖對這個代碼段里的只讀頁面進行寫操作的存儲指令造成的。如果試圖進行的訪問是不合法的,那么缺頁處理程序會觸發一個保護異常,從而終止這個進程。這種情況在圖9-28中標識為2。
3. 最終處理
此刻,內核知道了這個缺頁是由于對合法的虛擬地址進行合法的操作造成的。它是這樣來處理這個缺頁的:選擇一個犧牲頁面,如果這個犧牲頁面被修改過,那么就將它交換出去,換入新的頁面并更新頁表。當缺頁處理程序返回時,CPU 重新啟動引起缺頁的指令,這條指令將再次發送A到MMU。這次,MMU就能正常地翻譯A,而不會再產生缺頁中斷了。
7.9動態存儲分配管理
7.9.1 動態內存管理的基本方法和策略
使用動態內存分配器是一種常見的動態內存管理方法。分配器按照釋放方式不同分為顯式分配器和隱式分配器。
動態內存分配器維護著一個進程的虛擬內存區域,稱為堆(heap)。堆是一個請求二進制零的區域,它緊接在未初始化的數據區域后開始,并向上生長(向更高的地址)。對于每個進程,內核維護著一個變量brk,它指向堆頂。
分配器將堆視為一組不同大小的塊的集合來維護。每個塊就是一個連續的虛擬內存片,要么是已分配的,要么是空閑的。已分配的塊顯式地保留為供應用程序使用。空閑塊可用來分配。空閑塊保持空閑,直到它顯式地被應用所分配。一個已分配的塊保持已分配狀態,直到它被釋放,這種釋放要么是應用程序顯式執行的,要么是內存分配器自身隱式執行的。
7.9.2 malloc和free函數
在hello程序中,printf中使用malloc函數,程序可以通過調用malloc函數分配塊。
7.10本章小結
本章講解了hello的存儲器空間,并給出了?TLB與四級頁表支持下的VA到PA的變換和三級Cache支持下的物理內存訪問過程的介紹。
第8章 hello的IO管理
8.1 Linux的IO設備管理方法
(以下格式自行編排,編輯時刪除)
設備的模型化:文件
設備管理:unix io接口
8.2 簡述Unix IO接口及其函數
(以下格式自行編排,編輯時刪除)
8.3 printf的實現分析
(以下格式自行編排,編輯時刪除)
[轉]printf 函數實現的深入剖析 - Pianistx - 博客園
從vsprintf生成顯示信息,到write系統函數,到陷阱-系統調用 int 0x80或syscall等.
字符顯示驅動子程序:從ASCII到字模庫到顯示vram(存儲每一個點的RGB顏色信息)。
顯示芯片按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點(RGB分量)。
8.4 getchar的實現分析
(以下格式自行編排,編輯時刪除)
異步異常-鍵盤中斷的處理:鍵盤中斷處理子程序。接受按鍵掃描碼轉成ascii碼,保存到系統的鍵盤緩沖區。
getchar等調用read系統函數,通過系統調用讀取按鍵ascii碼,直到接受到回車鍵才返回。
8.5本章小結
(以下格式自行編排,編輯時刪除)
(第8章1分)
結論
逐條總結hello所經歷的過程:
1. ?hello.c經過預處理生成hello.i文件
2. ?hello.i經過編譯生成hello.s匯編文件
3. ?hello.s經過匯編生成可重定位目標文件hello.o
4. ?hello.o經過鏈接生成可執行目標文件hello.out
5. ?shell進程調用fork函數生成子進程
6. ?在子進程內調用execve函數執行hello
7. ?CPU執行指令,在屏幕上顯示結果
8. ?操作系統調度進程,進行上下文切換
9. ?在TLB、四級頁表和三級cache的合作下完成hello程序與內存的交互
在計算機硬件和操作系統分工合作下,完成了hello程序的執行,每一步執行都顯示出計算機設計師們的奇思妙想和科學技術的飛速發展。
附件
| 中間文件 | 作用 |
| hello.i | 經過預處理的源程序,用于編譯器的翻譯 |
| hello.s | 匯編程序,用于匯編 |
| hello.o | 可重定位目標文件,用于鏈接 |
| hello | 可執行目標文件,可以加載到內存中執行 |
| elf.txt | hello.o對應的elf文本文件 |
| dump1.txt | hello.o 的反匯編文本文件 |
| elf2.txt | hello對應的elf文本文件 |
| dump2.txt | hello 的反匯編文本文件 |
參考文獻
為完成本次大作業你翻閱的書籍與網站等
[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.
(參考文獻0分,缺失 -1分)
總結
以上是生活随笔為你收集整理的哈工大计算机系统大作业——hello P2P的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FPGA Verilog 串口无限多字节
- 下一篇: QT Opencv 图像处理 视频处理