計算機系統
大作業
題 目 程式人生-Hello’s P2P
專 業 計算機
學 号
班 級
學 生
指 導 教 師 劉宏偉
計算機科學與技術學院
2021年6月
摘 要
本文就hello.c檔案為研究對象,通過分析從hello.c到可執行檔案hello,再到運作該可執行檔案到該檔案結束運作的一系列過程,了解計算機系統的相應知識。
關鍵詞:計算機系統;程式一生;
目 錄
第1章 概述................................................................................... - 4 -
1.1 Hello簡介............................................................................ - 4 -
1.2 環境與工具........................................................................... - 4 -
1.3 中間結果............................................................................... - 4 -
1.4 本章小結............................................................................... - 4 -
第2章 預處理............................................................................... - 6 -
2.1 預處理的概念與作用........................................................... - 6 -
2.2在Ubuntu下預處理的指令................................................ - 6 -
2.3 Hello的預處理結果解析.................................................... - 6 -
2.4 本章小結............................................................................... - 7 -
第3章 編譯................................................................................... - 8 -
3.1 編譯的概念與作用............................................................... - 8 -
3.2 在Ubuntu下編譯的指令.................................................... - 8 -
3.3 Hello的編譯結果解析........................................................ - 8 -
3.4 本章小結............................................................................. - 11 -
第4章 彙編................................................................................. - 12 -
4.1 彙編的概念與作用............................................................. - 12 -
4.2 在Ubuntu下彙編的指令.................................................. - 12 -
4.3 可重定位目标elf格式...................................................... - 12 -
4.4 Hello.o的結果解析........................................................... - 14 -
4.5 本章小結............................................................................. - 14 -
第5章 連結................................................................................. - 15 -
5.1 連結的概念與作用............................................................. - 15 -
5.2 在Ubuntu下連結的指令.................................................. - 15 -
5.3 可執行目标檔案hello的格式......................................... - 15 -
5.4 hello的虛拟位址空間....................................................... - 18 -
5.5 連結的重定位過程分析..................................................... - 20 -
5.6 hello的執行流程............................................................... - 21 -
5.7 Hello的動态連結分析...................................................... - 22 -
5.8 本章小結............................................................................. - 23 -
第6章 hello程序管理.......................................................... - 24 -
6.1 程序的概念與作用............................................................. - 24 -
6.2 簡述殼Shell-bash的作用與處理流程........................... - 24 -
6.3 Hello的fork程序建立過程............................................ - 24 -
6.4 Hello的execve過程........................................................ - 25 -
6.5 Hello的程序執行.............................................................. - 25 -
6.6 hello的異常與信号處理................................................... - 26 -
6.7本章小結.............................................................................. - 27 -
第7章 hello的存儲管理...................................................... - 28 -
7.1 hello的存儲器位址空間................................................... - 28 -
7.2 Intel邏輯位址到線性位址的變換-段式管理.................. - 28 -
7.3 Hello的線性位址到實體位址的變換-頁式管理............. - 28 -
7.4 TLB與四級頁表支援下的VA到PA的變換................... - 29 -
7.5 三級Cache支援下的實體記憶體通路................................ - 31 -
7.6 hello程序fork時的記憶體映射......................................... - 32 -
7.7 hello程序execve時的記憶體映射..................................... - 32 -
7.8 缺頁故障與缺頁中斷處理................................................. - 33 -
7.9動态存儲配置設定管理.............................................................. - 36 -
7.10本章小結............................................................................ - 37 -
第8章 hello的IO管理....................................................... - 38 -
8.1 Linux的IO裝置管理方法................................................. - 38 -
8.2 簡述Unix IO接口及其函數.............................................. - 39 -
8.3 printf的實作分析.............................................................. - 39 -
8.4 getchar的實作分析.......................................................... - 39 -
8.5本章小結.............................................................................. - 40 -
結論............................................................................................... - 40 -
附件............................................................................................... - 41 -
參考文獻....................................................................................... - 42 -
第1章 概述
1.1 Hello簡介
P2P:用C語言寫出hello.c檔案(Program),經過預處理器(ccp),編譯器(ccl),彙編器(as)的預處理編譯彙編分别生成.i,.s,.o 檔案,最後經過連結器連結可執行檔案。在Bash中,程序管理(OS)利用fork生成子程式(Process)。
020:在輸入hello的運作指令後,shell利用fork生成子程序,再利用execve加載。經曆訪存,記憶體配置設定等程序結束後被回收。
1.2 環境與工具
軟體:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/優麒麟 64位。
硬體:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
開發及調試工具:Codeblocks gcc cpp edb
1.3 中間結果
檔案名字 | 描述 |
hello.i | 修改了的源程式 (文本) |
hello.s | 彙程式設計式 (文本) |
hello.o | 可重定位 目标程式 (二進制) |
hello.txt | hello.o的ELF格式 |
objdump.txt | hello.o的反彙編檔案 |
hello | 可執行目标檔案 |
helloelf.txt | hello的ELF格式 |
diff.txt | 分析hello與hello.o的不同的文本檔案。 |
1.4 本章小結
通過第一章的描述,使我們了解了在整個研究過程中,hello的From Program to Process與From Zero-0 to Zero-0的基本過程。還了解了本次研究所需要的硬體軟體與開發調試工具和本次研究的中間結果,讓我們對本次研究有了基本的了解。
第2章 預處理
2.1 預處理的概念與作用
2.1.1預處理的概念
預處理階段。預處理器(CPP)根據以字元 #開頭的指令,修改原始的 C 程式。比如 hello.c 中第 1 行的#include < stdio.h> 指令告訴預處理器讀取系統頭檔案 stdio.h 的内容,并把它直接插入程式文本中。結果就得到了另一個 C 程式,通常是以 .i 作為檔案擴充名。
2.1.2預處理的作用
擴充源代碼,插人所有用include 指令指定的檔案,并擴充所有聲明指定 的宏。
2.2在Ubuntu下預處理的指令
預處理指令:cpp hello.c>hello.i 生成.i檔案。
2.3 Hello的預處理結果解析
.i檔案:
生成了三千餘行的文本檔案,将所有用include 指令指定的檔案與所有聲明指定的宏插入。
例如在.i檔案中插入的stdio.h頭檔案中的一部分。
該部分在stdio.h頭檔案中也存在。
檔案最後的部分與.c檔案相同。
2.4 本章小結
本章就預處理操作,使我了解了預處理的概念與作用,預處理的指令與預處理結果。
第3章 編譯
3.1 編譯的概念與作用
3.1.1編譯的概念
編譯器(ccl)将文本檔案 hello.i 翻譯成文本檔案 hello.s,它包含一個彙編語言程式。
3.1.2編譯的作用
編譯是非常有用的,因為它為不同進階語言的不同編譯器提供了通用的輸出語言。例如,C 編譯器和 Fortran 編譯器産生的輸出檔案用的都是一樣的彙編語言。
3.2 在Ubuntu下編譯的指令
gcc -S hello.c -o hello.s 生成.s檔案
3.3 Hello的編譯結果解析
3.3.1 編譯結果
.s檔案:
3.3.2 資料
常量:
字元串常量存儲在.text段中
例如代碼中的printf中的兩個字元串。
變量:
局部變量存儲在堆棧段。
例如代碼中的int i 存儲在棧的-4(%rbp)中,大小為4個位元組。
例如代碼中的argc存儲在edi中
初始化的全局變量存儲在資料段(.data段)。
例如代碼中的int sleepsecs=2.5。
可以看到sleepsecs在.data段中。
指派:
mov 例如給i指派為0。
3.3.3 類型轉換
隐式:
例如代碼中的int sleepsecs=2.5隐式類型轉換。
3.3.4算術操作
例如hello中i+1表示為:
3.3.5關系操作
例如在比較i<10時:
比較i與9
例如比較argc!=3時:
比較3與argc
3.3.6數組操作
例如在通路數組argv[]時:
利用rax先後讀取argv[1]與argv[2]
3.3.7控制轉移
if(argc!=3) 若條件成立,則跳轉到L2。
for(i=0;i<10;i++) 若條件不成立,則跳轉到L4,否則i+1。
3.3.8函數操作
main函數:傳遞參數argc與argv[]存儲在edi與rsi中。
printf函數:
printf("Usage: Hello 學号 姓名!\n");傳遞參數為列印的字元串的首位址。利用call調用函數。
printf("Hello %s %s\n",argv[1],argv[2]); 傳遞參數為列印的字元串的首位址。利用call調用函數。
getchar函數:
直接利用call調用。
exit函數:
利用call調用,傳遞參數為edi為1.
Sleep函數:
傳遞參數eax作為sleep的參數。利用call調用
3.4 本章小結
本章就編譯操作的分析,使我了解了編譯的概念與作用,編譯的指令與Hello的編譯結果。
第4章 彙編
4.1 彙編的概念與作用
彙編器(as)将hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目标程式(relocatable object program)的格式,并将結果儲存在目标檔案 hello.o 中。hello.o 檔案是一個二進制檔案,它包含的17個位元組是函數 main 的指令編碼。如果我們在文本編輯器中打開 hello.o 檔案,将看到一堆亂碼。
4.2 在Ubuntu下彙編的指令
as hello.s -o hello.o 産生.o檔案
4.3 可重定位目标elf格式
Readelf -a hello.o>hello.txt生成elf格式檔案
ELF 頭(ELF header):以一個 16 位元組的序列開始,這個序列描述了生成該檔案 的系統的字的大小和位元組順序。ELF 頭剩下的部分包含幫助連結器文法分析和解釋目标檔案的資訊。其中包括 ELF 頭的大小、目标檔案的類型(如可重定位、可執行或者共享的)、機器類型(如 X86-64) 節頭部表(section header table)的檔案偏移,以及節頭部表中條目的大小和數量。
節頭部表:不同節的位置和大小是由節頭部表描述的,其中目标檔案中每個節都有一個固定大小的條目(entry)。
重定位節:連結器将所有相同類型的節合并為同一類型的新的聚合節。例如,來自所有輸人子產品的.data 節被全部合并成一個節,這個節成為輸出的可執行目标檔案的.data節。然後,連結器将運作時記憶體位址賦給新的聚合節,賦給輸入子產品定義的每個節,以及賦給輸人子產品定義的每個符号。當這一步完成時,程式中的每條指令和全局變量都有唯一的運作時記憶體位址了。
offset 是需要被修改的引用的節偏移。symDol 辨別被修改引用應該指向的符号。type 告知連結器如何修改新的引用。addend是一個有符号常數,一些類型的重定位要使用它對被修改引用的值做偏移調整。
符号表:在編譯時,編譯器向彙編器輸出每個全局符号,或者是強(strong)或者是弱(weak), 彙編器把這個資訊隐含地編碼在可重定位目标檔案的符号表裡。函數和已初始化的全局 變量是強符号,未初始化的全局變量是弱符号。
4.4 Hello.o的結果解析
objdump -d -r hello.o>objdump.txt
機器語言由機器指令集構成,能夠直接被機器執行。在不同的裝置中,彙編語言對應着不同的機器語言指令集 ,通過彙編過程轉換成機器指令。 普遍地說,特定的彙編語言和特定的機器語言指令集是一一對應的,不同平台之間不可直接移植。簡單來說,彙編語言是機器語言(二進制指令)的文本形式,與指令是一一對應的關系 。在機器語言中,調用函數時并沒有像彙編語言一樣使用函數的目标位址,而是調用目标函數相對位址的下一條位址。在彙編語言中程式對不同分支進行了分段,以便通路這些分支,而機器語言調用了明确的位址。
4.5 本章小結
本章就對彙編過程的分析,使我們了解了彙編的概念與作用,可重定位目标elf格式及機器語言與彙編語言的關系。
第5章 連結
5.1 連結的概念與作用
連結(linking)是将各種代碼和資料片段收集并組合成為一個單一檔案的過程,這個檔案可被加載(複制)到記憶體并執行。
5.2 在Ubuntu下連結的指令
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.3 可執行目标檔案hello的格式
readelf -a hello > helloelf.txt
ELF 頭(ELF header):ELF 頭描述檔案的總體格式。它還包括程式的入口點(entry point), 也就是當程式運作時要執行的第一條指令的位址。.text .rodata 和.data 節與可重定位目标檔案中的節是相似的。
節頭部表:不同節的位置和大小是由節頭部表描述的,其中目标檔案中每個節都有一個固定大小的條目(entry)。
重定位節:連結器将所有相同類型的節合并為同一類型的新的聚合節。例如,來自所有輸人子產品的.data 節被全部合并成一個節,這個節成為輸出的可執行目标檔案的.data節。然後,連結器将運作時記憶體位址賦給新的聚合節,賦給輸入子產品定義的每個節,以及賦給輸人子產品定義的每個符号。當這一步完成時,程式中的每條指令和全局變量都有唯一的運作時記憶體位址了。
offset 是需要被修改的引用的節偏移。symDol 辨別被修改引用應該指向的符号。type 告知連結器如何修改新的引用。addend是一個有符号常數,一些類型的重定位要使用它對被修改引用的值做偏移調整。
符号表:在編譯時,編譯器向彙編器輸出每個全局符号,或者是強(strong)或者是弱(weak), 彙編器把這個資訊隐含地編碼在可重定位目标檔案的符号表裡。函數和已初始化的全局 變量是強符号,未初始化的全局變量是弱符号。
動态符号表:
動态節:存儲着動态連結器使用的資訊。
程式頭部表:ELF 可執行檔案被設計得很容易加載到記憶體,可執行檔案的連續的片(chunk)被映射到連續的記憶體段。程式頭部表(program header table)描述了這種映射關系。
5.4 hello的虛拟位址空間
從程式頭部表,我們會看到根據可執行目标檔案的内容初始化兩個記憶體段。第 1 行和第 2 行告訴我們第一個段(代碼段)有讀/執行通路權限,開始于記憶體位址 0x400000 處,總共的記憶體大小是 0x76c 位元組,并且被初始化為可執行目标檔案的頭 0x76c 個位元組,其中包括 ELF 頭、程式頭部表以及 .init .text 和 .rodata 節。
PHDR段:開始于記憶體位址 0x400040 處,總共的記憶體大小是 0x1c0 位元組,
與5.3對比可以明顯看出這部分存儲着程式頭部表。
INTERP段:開始于記憶體位址 0x400200 處,總共的記憶體大小是 0x1c 位元組
可以明顯看出這部分存儲解釋器。
NOTE: 開始于記憶體位址 0x40021c 處,總共的記憶體大小是 0x20 位元組
儲存輔助資訊。
第 3 行和第 4 行告訴我們第二個段(資料段)有讀/寫通路權限,開始于記憶體位址 0x600e50 處,總的記憶體大小為 0x1f8 位元組,并用從目标檔案中偏移 0xe50處開始的 .data節中的 0x1f8 個位元組初始化。
DYNAMIC段:開始于記憶體位址 0x600e50 處,總共的記憶體大小是 0x1a0 位元組
與5.3對比可以明顯看出這部分存動态節的資料。
5.5 連結的重定位過程分析
Objdump -d -r hello>diff.txt生成分析hello與hello.o的不同的文本檔案。
連結器的兩個主要任務是符号解析和重定位,符号解析将目标檔案中的每個全局符号都綁定到一個唯一的定義,而重定位确定每個符号的最終記憶體位址,并修改對那些目标的引用。
hello增加了.init節與.plt節與.fini節:
Hello還在增加的三個節中加入了hello所需要的_init, puts, printf, _libc_start_main, getchar, sleep, exit, .plt.got,_start, _libc_cus_init, libc_csu_fini, _fini 函數。
hello的位址較hello.s的位址發生了偏移。
重定位由兩步組成:
重定位節和符号定義。在這一步中,連結器将所有相同類型的節合并為同一類型的新的聚合節。例如,來自所有輸人子產品的.data 節被全部合并成一個節,這個節成為輸出的可執行目标檔案的.data 節。然後,連結器将運作時記憶體位址賦給新的聚合節,賦給輸入子產品定義的每個節,以及賦給輸人子產品定義的每個符号。當這一步完成時,程式中的每條指令和全局變量都有唯一的運作時記憶體位址了。
重定位節中的符号引用。在這一步中,連結器修改代碼節和資料節中對每個符号的引用,使得它們指向正确的運作時位址。
重定位舉例:
從hello的符号表中,變量sleepsecs的ADDR(symbol) = ADDR(sleepsecs) = 0x600904;
從hello.o的重定位節中擷取sleepsec的重定位資訊type= R_X86_64_PC32,offset=0x5c, addend=-4。
在可執行檔案的反彙編檔案中得到ADDR(s) = ADDR(.text) = 0x4004fa。
refaddr = ADDR(s) + offset = 0x4004fa + 0x5c=0x400556
*refptr = (unsigned) (ADDR(symbol) + addend - refaddr) = (unsigned) (0x600904 – 0x4-0x400556) = (unsigned) (0x2003aa) 小端存儲
在可執行檔案的反彙編檔案中對計算結果進行驗證:發現計算正确
5.6 hello的執行流程
_init 0x0000000000400488
_start () 0x000000000040052a
__libc_start_main 0x000000000040052a
__libc_csu_init () 0x0000000000400624
_init () 0x0000000000400488
main () 0x00000000004005b2
_IO_puts 0x00000000004004b5
__GI_exit 0x00000000004004e5
_fini () 0x000000000040063c
_init 0x0000000000400488
_start 0x000000000040052a
__libc_csu_init 0x0000000000400624
_init 0x0000000000400488
main 0x00000000004005b2
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
__sleep 0x00000000004004f5
__printf 0x00000000004004b5
getchar() 0x00000000004004d5
__GI_exit 0x00000000004004e5
_fini () 0x000000000040063c
5.7 Hello的動态連結分析
共享庫是一個目标子產品,在運作或加載時,可以加載到任意的記憶體位址,并和一個在記憶體中的程式連結起來。這個過程稱為動态連結。
假設程式調用一個由共享庫定義的函數。編譯器沒有辦法預測這個函數的運作時位址,因為定義它的共享子產品在運作時可以加載到任意位置。正常的方法是為該引用生成一條重定位記錄,然後動态連結器在程式加載的時候再解析它。不過,這種方法并不是 PIC,因為它需要連結器修改調用子產品的代碼段,GNU 編譯系統使用了一種很有趣的技術來解決這個問題,稱為延遲綁定(lazy binding) 将過程位址的綁定推遲到第一次調用該過程時。延遲綁定是通過兩個資料結構之間簡潔但又有些複雜的互動來實作的,這兩個資料結構是:GOT 和過程連結表(Procedure Linkage Table, PLT),如果一個目标子產品調用定義在共享庫中的任何函數,那麼它就有自己的 GOT 和 PLT。GOT 是資料段的一部分,而PLT 是代碼段的一部分。
可以看到.got大小為0x10,位址為0x600ff0。
可以看到.got.plt大小為0x40,位址為0x601000。
在運作dl_start前.got與.got.plt的存儲如下:
在運作dl_start後.got與.got.plt的存儲如下:
可以很明顯的發現這些段發生了變化。
5.8 本章小結
本章就連結的概念與作用,在Ubuntu下連結的指令,可執行目标檔案hello的格式,hello的虛拟位址空間,連結的重定位過程分析,hello的執行流程,Hello的動态連結分析對連結進行了分析。
第6章 hello程序管理
6.1 程序的概念與作用
在現代系統上運作一個程式時,我們會得到一個假象,就好像我們的程式是系統中目前運作的唯一的程式一樣。我們的程式好像是獨占地使用處理器和記憶體。處理器就好像是無間斷地一條接一條地執行我們程式中的指令。最後,我們程式中的代碼和資料好像是系統記憶體中唯一的對象。這些假象都是通過程序的概念提供給我們的。
程序的經典定義就是一個執行中程式的執行個體。系統中的每個程式都運作在某個程序的上下文(context)中。上下文是由程式正确運作所需的狀态組成的。這個狀态包括存放在記憶體中的程式的代碼和資料,它的棧、通用目的寄存器的内容、程式計數器、環境變量以及打開檔案描述符的集合。每次使用者通過向 shell 輸人一個可執行目标檔案的名字,運作程式時,shell 就會建立一個新的程序,然後在這個新程序的上下文中運作這個可執行目标檔案。應用程式也能夠建立新程序,并且在這個新程序的上下文中運作它們自己的代碼或其他應用程式。
6.2 簡述殼Shell-bash的作用與處理流程
Shell是一個互動型的應用級程式,它代表使用者運作其他程式。最早的 shell 是 sh 程式,後面出現了一些變種,比如 csh,tcsh,ksh 和 bash,shell 執行一系列的讀/求值(read/evaluate)步驟,然後終止。讀步驟讀取來自使用者的一個指令行。求值步驟解析指令行,并代表使用者運作程式。
6.3 Hello的fork程序建立過程
父程序通過調用 fork 函數建立一個新的運作的子程序。新建立的子程序幾乎但不完全與父程序相同。子程序得到與父程序使用者級虛拟位址空間相同的(但是獨立的)一份副本,包括代碼和資料段、堆、共享庫以及使用者棧。子程序還獲得與父程序任何打開檔案描述符相同的副本,這就意味着當父程序調用 fork 時,子程序可以讀寫父程序中打開的任何檔案。父程序和新建立的子程序之間最大的差別在于它們有不同的 PID。
fork 函數隻被調用一次,卻會傳回兩次:一次是在調用程序(父程序)中,一次是在新建立的子程序中。在父程序中,fork 傳回子程序的PID在子程序中,fork 傳回 0。因為子程序的 PID 總是為非零,傳回值就提供一個明确的方法來分辨程式是在父程序還是在子程序中執行。
hello建立程序
觀察hello程序
6.4 Hello的execve過程
execve 函數在目前程序的上下文中加載并運作一個新程式。execve 函數加載并運作可執行目标檔案 filename, 且帶參數清單 argv 和環境變量清單 envp隻有當出現錯誤時,例如找不到 filename才會傳回到調用程式。是以,與 fork —次調用傳回兩次不同,execve調用一次并從不傳回。
6.5 Hello的程序執行
多個流并發地執行的一般現象被稱為并發(concurrency)。一個程序和其他程序輪流運作的概念稱為多任務(multitasking)。一個程序執行它的控制流的一部分的每一時間段叫做時間片是以,多任務也叫做時間分片(time slicing)。
為了使作業系統核心提供一個無懈可擊的程序抽象,處理器必須提供一種機制,限制一個應用可以執行的指令以及它可以通路的位址空間範圍。
處理器通常是用某個控制寄存器中的一個模式位(mode bit)來提供這種功能的,該寄存器描述了程序目前享有的特權。當設定了模式位時,程序就運作在核心模式中(有時叫做超級使用者模式)。 一個運作在核心模式的程序可以執行指令集中的任何指令,并且可以通路系統中的任何記憶體位置。
沒有設定模式位時,程序就運作在使用者模式中。使用者模式中的程序不允許執行特權指令(privileged instruction), 比如停止處理器、改變模式位,或者發起一個 I/O 操作。也不允許使用者模式中的程序直接引用位址空間中核心區内的代碼和資料。任何這樣的嘗試都會導緻緻命的保護故障。反之,使用者程式必須通過系統調用接口間接地通路核心代碼和資料。
運作應用程式代碼的程序初始時是在使用者模式中的。程序從使用者模式變為核心模式的唯一方法是通過諸如中斷、故障或者陷人系統調用這樣的異常。當異常發生時,控制傳遞到異常處理程式,處理器将模式從使用者模式變為核心模式。處理程式運作在核心模式中,當它傳回到應用程式代碼時,處理器就把模式從核心模式改回到使用者模式。
作業系統核心使用一種稱為上下文切換(context switch)的較高層形式的異常控制流來實作多任務。核心為每個程序維持一個上下文(context)。上下文就是核心重新啟動一個被搶占的程序所需的狀态。它由一些對象的值組成,這些對象包括通用目的寄存器、浮點寄存器、程式計數器、使用者棧、狀态寄存器、核心棧和各種核心資料結構,比如描述位址空間的頁表、包含有關目前程序資訊的程序表,以及包含程序已打開檔案的資訊的檔案表。
在程序執行的某些時刻,核心可以決定搶占目前程序,并重新開始一個先前被搶占了的程序。這種決策就叫做排程(scheduling), 是由核心中稱為排程器(scheduler)的代碼處理的。當核心選擇一個新的程序運作時,我們說核心排程了這個程序。在核心排程了一個新的程序運作後,它就搶占目前程序,并使用一種稱為上下文切換的機制來将控制轉移到新的程序,上下文切換 1)儲存目前程序的上下文,2)恢複某個先前被搶占的程序被儲存的上下文,3)将控制傳遞給這個新恢複的程序。
當核心代表使用者執行系統調用時,可能會發生上下文切換。如果系統調用因為等待某個事件發生而阻塞,那麼核心可以讓目前程序休眠,切換到另一個程序。比如,hello中的 sleep 系統調用,它顯式地請求讓調用程序休眠。一般而言,即使系統調用沒有阻塞,核心也可以決定執行上下文切換,而不是将控制傳回給調用程序。中斷也可能引發上下文切換。比如,所有的系統都有某種産生周期性定時器中斷的機制,通常為每 1 毫秒或每 10 毫秒。每次發生定時器中斷時,核心就能判定目前程序已經運作了足夠長的時間,并切換到一個新的程序。
6.6 hello的異常與信号處理
正常運作:
可以看到程式結束後無該程序,說明上述程序被回收。
在鍵盤上輸入 Ctrl+C 會導緻核心發送一個 SIGINT 信号到前台程序組中的每個程序。結果是終止前台作業。
可以看到Ctrl+C後無該程序,說明上述程序被回收。
輸入 Ctrl+Z 會發送一個 SIGTSTP 信号到前台程序組中的每個程序。結果是停止(挂起)前台作業。
可以看到Ctrl+Z後有程序,說明上述程序未被回收。
發現為停止(挂起)前台作業
可繼續運作該程序
也可終止該程序
不停亂按鍵盤
程序忽略該信号
6.7本章小結
本文就hello程序管理,使我了解了程序的概念與作用,殼Shell-bash的作用與處理流程,Hello的fork程序建立過程,Hello的execve過程,Hello的程序執行,hello的異常與信号處理。
第7章 hello的存儲管理
7.1 hello的存儲器位址空間
實體位址:計算機系統的主存被組織成一個由 M 個連續的位元組大小的單元組成的數組。每位元組都有一個唯一的實體位址。第一個位元組的位址為 0, 接下來的位元組位址為 1,再下一個為 2, 依此類推。給定這種簡單的結構,CPU 通路記憶體的最自然的方式就是使用實體位址。我們把這種方式稱為實體尋址。
邏輯位址:這存儲單元的位址就可以用段基址(段位址)和段内偏移量(偏移位址)來表示,段基址确定它所在的段居于整個存儲空間的位置,偏移量确定它在段内的位置,這種位址表示方式稱為邏輯位址,通常表示為段位址:偏移位址的形式。
線性位址:是邏輯位址到實體位址變換之間的中間層。在分段部件中邏輯位址是段中的偏移位址,然後加上基位址就是線性位址。
虛拟位址:虛拟位址是程式運作在保護模式下,這樣程式通路存儲器所使用的邏輯位址稱為虛拟位址。
7.2 Intel邏輯位址到線性位址的變換-段式管理
段式管理(segmentation),是指把一個程式分成若幹個段(segment)進行存儲,每個段都是一個邏輯實體(logical entity),程式員需要知道并使用它。它的産生是與程式的子產品化直接有關的。段式管理是通過段表進行的,它包括段号或段名、段起點、裝入位、段的長度等。此外還需要主存占用區域表、主存可用區域表。
7.3 Hello的線性位址到實體位址的變換-頁式管理
同任何緩存一樣,虛拟記憶體系統必須有某種方法來判定一個虛拟頁是否緩存在DRAM 中的某個地方。如果是,系統還必須确定這個虛拟頁存放在哪個實體頁中。如果不命中,系統必須判斷這個虛拟頁存放在磁盤的哪個位置,在實體記憶體中選擇一個犧牲頁,并将虛拟頁從磁盤複制到 DRAM 中,替換這個犧牲頁。
這些功能是由軟硬體聯合提供的,包括作業系統軟體、MMU(記憶體管理單元)中的位址翻譯硬體和一個存放在實體記憶體中叫做頁表(page table)的資料結構,頁表将虛拟頁映射到實體頁。每次位址翻譯硬體将一個虛拟位址轉換為實體位址時,都會讀取頁表。操作 系統負責維護頁表的内容,以及在磁盤與 DRAM 之間來回傳送頁。
7.4 TLB與四級頁表支援下的VA到PA的變換
TLB 是一個小的、虛拟尋址的緩存,其中每一行都儲存着一個由單個 PTE 組成的塊。TLB 通常有高度的相聯度。如圖 9-15 所示,用于組選擇和行比對的索引和标記字段是從虛拟位址中的虛拟頁号中提取出來的。如果 TLB 有:T=2t個組,那麼 TLB 索引(TLBI)是由VPN 的 t 個最低位組成的,而 TLB 标記(TLBT)是由 VPN 中剩餘的位組成的。
圖 9-16a 展示了當 TLB 命中時(通常情況)所包括的步驟。這裡的關鍵點是,所有的位址翻譯步驟都是在晶片上的 MMU 中執行的,是以非常快。
•第 1 步:CPU 産生一個虛拟位址。
•第 2 步和第 3 步:MMU 從 TLB 中取出相應的 PTE
•第 4 步:MMU 将這個虛拟位址翻譯成一個實體位址,并且将它發送到高速緩存/主存。
•第 5 步:高速緩存/主存将所請求的資料字傳回給 CPU
當 TLB 不命中時,MMU 必須從 L1 緩存中取出相應的 PTE, 如圖 9-16b 所示。新取出的 PTE 存放在 TLB 中,可能會覆寫一個已經存在的條目。
用來壓縮頁表的常用方法是使用層次結構的頁表。用一個具體的示例是最容易了解這個思想的。假設 32 位虛拟位址空間被分為 4KB 的頁,而每個頁表條目都是 4 位元組。還假設在這一時刻,虛拟位址空間有如下形式:記憶體的前 2K 個頁面配置設定給了代碼和資料,接下來的 6K 個頁面還未配置設定,再接下來的 1023 個頁面也未配置設定,接下來的 1 個頁面配置設定給了使用者棧。圖 9-17 展示了我們如何為這個虛拟位址空間構造一個兩級的頁表層次結構。
一級頁表中的每個 PTE 負責映射虛拟位址空間中一個 4MB 的片(chunk),這裡每一片都是由 1024 個連續的頁面組成的。比如,PTE0 映射第一片,PTE1 映射接下來的一片,以此類推。假設位址空間是 4GB,1024 個 PTE 已經足夠覆寫整個空間了。
如果片 i 中的每個頁面都未被配置設定,那麼一級 PTEi 就為空。例如,圖 9-17 中,片 2到7是未被配置設定的。然而,如果在片 i 中至少有一個頁是配置設定了的,那麼一級PTEi就指向一個二級頁表的基址。例如,在圖 9-17 中,片 0、1 和 8 的所有或者部分已被配置設定,是以它們的一級 PTE 就指向二級頁表。
二級頁表中的每個 PTE 都負責映射一個 4KB 的虛拟記憶體頁面,就像我們檢視隻有一級的頁表一樣。注意,使用 4 位元組的 PTE。每個一級和二級頁表都是 4KB 位元組,這剛好和一個頁面的大小是一樣的。
圖 9-25 描述了使用4級頁表層次結構的位址翻譯。虛拟位址被劃分成為4個 VPN 和1 個 VPO。每個 VPNi 都是一個到第 i 級頁表的索引,其中1<=i<=4。 第j級頁表中的每個 PTE,1<=j<=3,都指向第 j+1 級的某個頁表的基址。第4級頁表中的每個 PTE 包含某個實體頁面的 PPN,或者一個磁盤塊的位址。為了構造實體位址,在能夠确定 PPN之前,MMU 必須通路4個 PTE。對于隻有一級的頁表結構,PPO 和 VPO 是相同的。
7.5 三級Cache支援下的實體記憶體通路
位于處理器晶片上的 L1 高速緩存的容量可以達到數萬位元組,通路速度幾乎和通路寄存器檔案一樣快。一個容量為數十萬到數百萬位元組的更大的 L2 高速緩存通過一條特殊的總線連接配接到處理器。程序通路 L2 高速緩存的時間要比通路 L1 高速緩存的時間長 5 倍,但是這仍然比通路主存的時間快 5到10 倍。L1 和 L2 高速緩存是用一種叫做靜态随機通路存儲器(SRAM)的硬體技術實作的。比較新的、處理能力更強大的系統甚至有三級高速緩存:LI,L2 和L3系統可以獲得一個很大的存儲器,同時通路速度也很快,原因是利用了高速緩存的局部性原理,即程式具有通路局部區域裡的資料和代碼的趨勢。通過讓高速緩存裡存放可能經常通路的資料,大部分的記憶體操作都能在快速的高速緩存中完成。
在處理器和一個較大較慢的裝置(例如主存)之間插入一個更小更快的儲存設備(例如高速緩存)的想法已經成為一個普遍的觀念。實際上,每個計算機系統中的儲存設備都被組織成了一個存儲器層次結構,如圖 1-9 所示。在這個層次結構中,從上至下,裝置的通路速度越來越慢、容量越來越大,并且每位元組的造價也越來越便宜。寄存器檔案在層次結構中位于最頂部,也就是第 0 級或記為 L0,這裡我們展示的是三層高速緩存 L1 到 L3,占據存儲器層次結構的第 1 層到第 3 層。主存在第 4 層,以此類推。
存儲器層次結構的主要思想是上一層的存儲器作為低一層存儲器的高速緩存。是以,寄存器檔案就是 L1 的高速緩存,L1 是 L2 的高速緩存,L2 是 L3 的高速緩存,L3 是主存的高速緩存,而主存又是磁盤的高速緩存。在某些具有分布式檔案系統的網絡系統中,本地磁盤就是存儲在其他系統中磁盤上的資料的高速緩存。
7.6 hello程序fork時的記憶體映射
當 fork 函數被目前程序調用時,核心為新程序建立各種資料結構,并配置設定給它一個唯一的 PID為了給這個新程序建立虛拟記憶體,它建立了目前程序的構和頁表的原樣副本。它将兩個程序中的每個頁面都标記為隻讀,并将兩個程序中的每個區域結構都标記為私有的寫時複制。當 fork 在新程序中傳回時,新程序現在的虛拟記憶體剛好和調用 fork 時存在的虛拟記憶體相同。當這兩個程序中的任一個後來進行寫操作時,寫時複制機制就會建立新頁面,是以,也就為每個程序保持了私有位址空間的抽象概念。
7.7 hello程序execve時的記憶體映射
要運作可執行目标檔案hello, 我們可以在 Linux shell 的指令行中輸入它的名字
./hello 1190201619 惠羿
因為 hello 不是一個内置的 shell 指令,是以 shell 會認為 hello 是一個可執行目标檔案,通過調用某個駐留在存儲器中稱為加載器(loader)的作業系統代碼來運作它。任何Linux 程式都可以通過調用execve函數來調用加載器。加載器将可執行目标檔案中的代碼和資料從磁盤複制到記憶體中,然後通過跳轉到程式的第一條指令或入口點來運作該程式。這個将程式複制到記憶體并運作的過程叫做加載。
虛拟記憶體和記憶體映射在将程式加載到記憶體的過程中也扮演着關鍵的角色。我們就能夠了解 execve 函數實際上是如何加載和執行程式的。假設運作在目前程序中的程式執行了如下的 execve 調用:
execve(“hello”,NULL,NULL);
函數在目前程序中加載并運作包含在可執行目标檔案hello中的程式,用 hello程式有效地替代了目前程式。加載并運作 hello 需要以下幾個步驟:
删除已存在的使用者區域。删除目前程序虛拟位址的使用者部分中的已存在的區域結構。
映射私有區域。為新程式的代碼、資料、bss 和棧區域建立新的區域結構。所有這些新的區域都是私有的、寫時複制的。代碼和資料區域被映射為 hello 檔案中的.text和.data 區。bss 區域是請求二進制零的,映射到匿名檔案,其大小包含在hello中。棧和堆區域也是請求二進制零的,初始長度為零。圖 9-31 概括了私有區域的不同映射。
映射共享區域。如果 hello 程式與共享對象(或目标)連結,比如标準 C 庫 libc.so。那麼這些對象都是動态連結到這個程式的,然後再映射到使用者虛拟位址空間中的共享區域内。
設定程式計數器(PC)。execve做的最後一件事情就是設定目前程序上下文中的程式計數器,使之指向代碼區域的人口點。下一次排程這個程序時,它将從這個入口點開始執行。Linux 将根據需要換入代碼和資料頁面。
7.8 缺頁故障與缺頁中斷處理
在有些情況中,被引用的存儲器位置實際上是存儲在磁盤存儲器上的。此時,硬體會産生一個缺頁(page fault)異常信号。同其他異常一樣,這個異常會導緻處理器調用作業系統的異常處理程式代碼。然後這段代碼會發起一個從磁盤到主存的傳送操作。一旦完成,作業系統會傳回到原來的程式,而導緻缺頁的指令會被重新執行。這次,存儲器引用将成功,雖然可能會導緻高速緩存不命中。讓硬體調用作業系統例程,然後作業系統例程又會将控制傳回給硬體,這就使得硬體和系統軟體在處理缺頁時能協同工作。因為通路磁盤需要數百萬個時鐘周期,0S 缺頁中斷處理程式執行的處理所需的幾百個時鐘周期對性能的影響可以忽略不計。
從處理器的角度來看,将用暫停來處理短時間的高速緩存不命中和用異常處理來處理長時間的缺頁結合起來,能夠顧及到存儲器通路時由于存儲器層次結構引起的所有不可預測性。
故障由錯誤情況引起,它可能能夠被故障處理程式修正。當故障發生時,處理器将控制轉移給故障處理程式。如果處理程式能夠修正這個錯誤情況,它就将控制傳回到引起故障的指令,進而重新執行它。否則,處理程式傳回到核心中的 abort 例程,abort 例程會終止引起故障的應用程式。圖 8-7 概述了一個故障的處理。
一個經典的故障示例是缺頁異常,當指令引用一個虛拟位址,而與該位址相對應的實體頁面不在記憶體中,是以必須從磁盤中取出時,就會發生故障。就像我們将在第 9 章中看到的那樣,一個頁面就是虛拟記憶體的一個連續的塊(典型的是 4KB)缺頁處理程式從磁盤加載适當的頁面,然後将控制傳回給引起故障的指令。當指令再次執行時,相應的實體頁面已經駐留在記憶體中了,指令就可以沒有故障地運作完成了。
在虛拟記憶體的習慣說法中,DRAM 緩存不命中稱為缺頁(page fault), 圖 9-6 展示了在缺頁之前我們的示例頁表的狀态。CPU 引用了 VP 3 中的一個字,VP 3 并未緩存在DRAM 中。位址翻譯硬體從記憶體中讀取 PTE 3, 從有效位推斷出 VP 3 未被緩存,并且觸發一個缺頁異常。缺頁異常調用核心中的缺頁異常處理程式,該程式會選擇一個犧牲頁,在此例中就是存放在 PP 3 中的 VP 4。如果 VP 4 已經被修改了,那麼核心就會将它複制回磁盤。無論哪種情況,核心都會修改 VP 4 的頁表條目,反映出 VP 4 不再緩存在主存中這一事實。
接下來,核心從磁盤複制 VP 3 到記憶體中的 PP 3, 更新 PTE 3,随後傳回。當異常處理程式傳回時,它會重新啟動導緻缺頁的指令,該指令會把導緻缺頁的虛拟位址重發送到位址翻譯硬體。但是現在,VP 3 已經緩存在主存中了,那麼頁命中也能由位址翻譯硬體正常處理了。圖 9-7 展示了在缺頁之後我們的示例頁表的狀态。
假設 MMU 在試圖翻譯某個虛拟位址 A 時,觸發了一個缺頁。這個異常導緻控制轉移到核心的缺頁處理程式,處理程式随後就執行下面的步驟:
1) 虛拟位址 A 是合法的嗎?換句話說,A 在某個區域結構定義的區域内嗎?為了回答這個問題,缺頁處理程式搜尋區域結構的連結清單,把 A 和每個區域結構中的 vm_start 和_end 做比較。如果這個指令是不合法的,那麼缺頁處理程式就觸發一個段錯誤,進而終止這個程序。這個情況在圖 9-28 中辨別為 “1”。因為一個程序可以建立任意數量的新虛拟記憶體區域是以順序搜尋區域結構的連結清單花銷可能會很大。是以在實際中,Linux 使用某些我們沒有顯示出來的字段,Linux 在連結清單中建構了一棵樹,并在這棵樹上進行査找。
2) 試圖進行的記憶體通路是否合法?換句話說,程序是否有讀、寫或者執行這個區域内頁面的權限?例如,這個缺頁是不是由一條試圖對這個代碼段裡的隻讀頁面進行寫操作的存儲指令造成的?這個缺頁是不是因為一個運作在使用者模式中的程序試圖從核心虛拟記憶體中讀取字造成的?如果試圖進行的通路是不合法的,那麼缺頁處理程式會觸發一個保護異常,進而終止這個程序。這種情況在圖 9-28 中辨別為 “2”。
3) 此刻,核心知道了這個缺頁是由于對合法的虛拟位址進行合法的操作造成的。它是這樣來處理這個缺頁的:選擇一個犧牲頁面,如果這個犧牲頁面被修改過,那麼就将它交換出去,換入新的頁面并更新頁表。當缺頁處理程式傳回時,CPU 重新啟動引起缺頁的指令,這條指令将再次發送 A 到 MMU。這次,MMU 就能正常地翻譯 A而不會再産生缺頁中斷了。
7.9動态存儲配置設定管理
雖然可以使用低級的 mmap 和 munmap 函數來建立和删除虛拟記憶體的區域,但是 C 程式員還是會覺得當運作時需要額外虛拟記憶體時,用動态記憶體配置設定器更友善,也有更好的可移植性。
動态記憶體配置設定器維護着一個程序的虛拟記憶體區域,稱為堆(heap)(見圖 9-33)。系統之間細節不同,但是不失通用性,假設堆是一個請求二進制零的區域,它緊接在未初始化的資料區域後開始,并向上生長(向更高的位址)。 對于每個程序,核心維護着一個變量 brk,它指向堆的頂部。配置設定器将堆視為一組不同大小的塊(block)的集合來維護。每個塊就是一個連續的虛拟記憶體片(chunk),要麼是已配置設定的,要麼是空閑的。已配置設定的塊顯式地保留為供應用程式使用。空閑塊可用來配置設定。空閑塊保持空閑,直到它顯式地被應用所配置設定。一個已配置設定的塊保持已配置設定狀态,直到它被釋放,這種釋放要麼是應用程式顯式執行的,要麼是記憶體配置設定器自身隐式執行的。
配置設定器有兩種基本風格。兩種風格都要求應用顯式地配置設定塊。它們的不同之處在于由哪個實體來負責釋放已配置設定的塊。
顯式配置設定器(explicit allocator), 要求應用顯式地釋放任何已配置設定的塊。例如,C 标準庫提供一種叫做 malloc 程式包的顯式配置設定器。C 程式通過調用 malloc 函數來配置設定一個塊,并通過調用 free 函數來釋放一個塊。
隐式配置設定器(implicit allocator), 另一方面,要求配置設定器檢測一個已配置設定塊何時不再被程式所使用,那麼就釋放這個塊。隐式配置設定器也叫做垃圾收集器, 而自動釋放未使用的已配置設定的塊的過程叫做垃級收集例如,諸如 Lisp, ML 以及 Java 之類的進階語言就依賴垃圾收集來釋放已配置設定的塊。
7.10本章小結
本章就hello的存儲管理,使我了解了hello的存儲器位址空間,Intel邏輯位址到線性位址的變換-段式管理,Hello的線性位址到實體位址的變換-頁式管理,TLB與四級頁表支援下的VA到PA的變換,缺頁故障與缺頁中斷處理,hello程序fork時的記憶體映射,hello程序execve時的記憶體映射,缺頁故障與缺頁中斷處理,動态存儲配置設定管理。
第8章 hello的IO管理
8.1 Linux的IO裝置管理方法
裝置的模型化:檔案
一個 Linux 檔案就是一個 m 個位元組的序列,所有的 I/O 裝置(例如網絡、磁盤和終端)都被模型化為檔案,而所有的輸入和輸出都被當作對相應檔案的讀和寫來執行。
裝置管理:unix io接口
這種将裝置優雅地映射為檔案的方式,允許 Linux 核心引出一個簡單、低級的應用接口,稱為 Unix I/O, 這使得所有的輸人和輸出都能以一種統一且一緻的方式來執行:
打開檔案。一個應用程式通過要求核心打開相應的檔案,來宣告它想要通路一個I/O 裝置。核心傳回一個小的非負整數,叫做描述符,它在後續對此檔案的所有操作中辨別這個檔案。核心記錄有關這個打開檔案的所有資訊。應用程式隻需記住這個描述符。Linux shell 建立的每個程序開始時都有三個打開的檔案:标準輸入(描述符為 0)、标準輸出(描述符為 1)和标準錯誤(描述符為 2)。頭檔案< unistd.h> 定義了常量 STDIN_FILENO,STDOUT_FILENO 和 STDERR_FILENO,它們可用來代替顯式的描述符值。
改變目前的檔案位置。對于每個打開的檔案,核心保持着一個檔案位置 k,初始為0, 這個檔案位置是從檔案開頭起始的位元組偏移量。應用程式能夠通過執行 seek 操作,顯式地設定檔案的目前位置為是k。
讀寫檔案。一個讀操作就是從檔案複制n>0 個位元組到記憶體,從目前檔案位置k開始,然後将k增加到 k+n。給定一個大小為 m 位元組的檔案,當 時執行讀操作會觸發一個稱為 end-of-file(EOF)的條件,應用程式能檢測到這個條件。在檔案結尾處并沒有明确的 “EOF 符号”。
類似地,寫操作就是從記憶體複制 n>0 個位元組到一個檔案,從目前檔案位置k開始,然後更新k。
關閉檔案。當應用完成了對檔案的通路之後,它就通知核心關閉這個檔案。作為響應,核心釋放檔案打開時建立的資料結構,并将這個描述符恢複到可用的描述符池中。無論一個程序因為何種原因終止時,核心都會關閉所有打開的檔案并釋放它們的記憶體資源。
8.2 簡述Unix IO接口及其函數
程序是通過調用 open 函數來打開一個已存在的檔案或者建立一個新檔案的:
open函數将filename轉換為一個檔案描述符,并且傳回描述符數字。傳回的描述符總是在程序中目前沒有打開的最小描述符。flags參數指明了程序打算如何通路這個檔案。mode 參數指定了新檔案的通路權限位。
程序通過調用 close 函數關閉一個打開的檔案。
關閉一個已關閉的描述符會出錯。
應用程式是通過分别調用 read 和 write 函數來執行輸入和輸出的。
read 函數從描述符為 fd 的目前檔案位置複制最多 n 個位元組到記憶體位置 buf。傳回值-1,表示一個錯誤,而傳回值 0 表示 EOF。否則,傳回值表示的是實際傳送的位元組數量。
write 函數從記憶體位置 buf 複制至多 n 個位元組到描述符 fd 的目前檔案位置。
8.3 printf的實作分析
從vsprintf生成顯示資訊,到write系統函數,到陷阱-系統調用 int 0x80或syscall.
字元顯示驅動子程式:從ASCII到字模庫到顯示vram(存儲每一個點的RGB顔色資訊)。
顯示晶片按照重新整理頻率逐行讀取vram,并通過信号線向液晶顯示器傳輸每一個點(RGB分量)。
8.4 getchar的實作分析
異步異常-鍵盤中斷的處理:鍵盤中斷處理子程式。接受按鍵掃描碼轉成ascii碼,儲存到系統的鍵盤緩沖區。
getchar等調用read系統函數,通過系統調用讀取按鍵ascii碼,直到接受到Enter鍵才傳回。
8.5本章小結
本章就hello的IO管理,使我了解了Linux的IO裝置管理方法,Unix IO接口及其函數,printf的實作,getchar的實作。
結論
Hello的一生:
- 源代碼:用C語言編寫hello.c的代碼。
- 預處理:預處理器将hello.c處理成hello.i。
- 編譯:編譯器将hello.i 翻譯成 hello.s。
- 彙編:彙編器将hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目标程式的格式,并将結果儲存在目标檔案 hello.o 中。
- 連結器的兩個主要任務是符号解析和重定位,符号解析将目标檔案中的每個全局符号都綁定到一個唯一的定義,而重定位确定每個符号的最終記憶體位址,并修改對那些目标的引用。
- 輸入指令:在shell中輸入./hello 1190201619 惠羿 shell識别并發送信号
- 建立子程序:程序管理shell通過fork()建立子程序。
- 運作:程序管理 shell利用execve加載程序。
- 執行:程序管理配置設定mmap,時間片。
- 訪存:MMU将CPU給出的虛拟位址翻譯為實體位址,再根據實體位址去記憶體中尋找資料。
- 動态申請記憶體:通過調用malloc,從堆中申請記憶體。
- 結束:shell父程序回收子程序。
這就是hello的一生,雖然它的一生在機器中很短暫,但它的一生基本上囊括了計算機系統的所有知識。雖然hello的一生看起來簡單,但對hello的一生的研究對我們了解計算機系統有着重要的作用。
附件
檔案名字 | 描述 |
hello.i | 修改了的源程式 (文本) |
hello.s | 彙程式設計式 (文本) |
hello.o | 可重定位 目标程式 (二進制) |
hello.txt | hello.o的ELF格式 |
objdump.txt | hello.o的反彙編檔案 |
hello | 可執行目标檔案 |
helloelf.txt | hello的ELF格式 |
diff.txt | 分析hello與hello.o的不同的文本檔案。 |
參考文獻
[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.