1. 程式人生 > >程式人生 HELLO P2P

程式人生 HELLO P2P

計算機系統

大作業

題 目 程式人生-Hello’s P2P
專 業 計算機系
學   號 1170301005
班   級 1703010
學 生 白鎮北    
指 導 教 師

電腦科學與技術學院
2018年12月
摘 要
摘要是論文內容的高度概括,應具有獨立性和自含性,即不閱讀論文的全文,就能獲得必要的資訊。摘要應包括本論文的目的、主要內容、方法、成果及其理論與實際意義。摘要中不宜使用公式、結構式、圖表和非公知公用的符號與術語,不標註引用文獻編號,同時避免將摘要寫成目錄式的內容介紹。

關鍵詞:關鍵詞1;關鍵詞2;……;

(摘要0分,缺失-1分,根據內容精彩稱都酌情加分0-1分)

目 錄

第1章 概述 - 4 -
1.1 HELLO簡介 - 4 -
1.2 環境與工具 - 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 可執行目標檔案HELLO的格式 - 8 -
5.4 HELLO的虛擬地址空間 - 8 -
5.5 連結的重定位過程分析 - 8 -
5.6 HELLO的執行流程 - 8 -
5.7 HELLO的動態連結分析 - 8 -
5.8 本章小結 - 9 -
第6章 HELLO程序管理 - 10 -
6.1 程序的概念與作用 - 10 -
6.2 簡述殼SHELL-BASH的作用與處理流程 - 10 -
6.3 HELLO的FORK程序建立過程 - 10 -
6.4 HELLO的EXECVE過程 - 10 -
6.5 HELLO的程序執行 - 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動態儲存分配管理 - 11 -
7.10本章小結 - 12 -
第8章 HELLO的IO管理 - 13 -
8.1 LINUX的IO裝置管理方法 - 13 -
8.2 簡述UNIX IO介面及其函式 - 13 -
8.3 PRINTF的實現分析 - 13 -
8.4 GETCHAR的實現分析 - 13 -
8.5本章小結 - 13 -
結論 - 14 -
附件 - 15 -
參考文獻 - 16 -

第1章 概述
1.1 Hello簡介
根據Hello的自白,利用計算機系統的術語,簡述Hello的P2P,020的整個過程。
首先hello.c通過I/O裝置如鍵盤等經過匯流排存入主存。
GCC編譯器驅動程式讀取源程式檔案hello.c,經過預處理(變成hello.i)、編譯(變成hello.s)、彙編(變成hello.o),終於成為機器可以理解的二進位制程式碼了。
然後再通過連結,變成(可執行的二進位制目標程式hello)、由shell程式將字元讀入暫存器,放入到記憶體裡面去,再呼叫fork函式建立一個新執行的子程序,然後子程序通過execve系統呼叫啟動載入器。載入器使用mmap函式建立新的記憶體區和新的程式碼、資料、堆和棧段。通過將虛擬地址空間中的頁對映到可執行檔案的頁大小的片(chunk), 新的程式碼和資料段被初始化為可執行檔案的內容。
最後,載入器跳轉到_start地址,它最終會呼叫應用程式的main 函式。然後程式從記憶體讀取指令位元組,再執行階段算術/邏輯單元要麼執行指令指明的操作,計算記憶體引用的有效地址要麼增加或者減少棧指標。最後變成一個Process執行在記憶體中。
1.2 環境與工具
列出你為編寫本論文,折騰Hello的整個過程中,使用的軟硬體環境,以及開發與除錯工具。
軟體環境:Ubuntu16.04.1
硬體環境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
開發與除錯工具:vim,gcc,as,ld,edb,readelf,HexEdit

1.3 中間結果
列出你為編寫本論文,生成的中間結果檔案的名字,檔案的作用等。
hello.i 預處理過的文字檔案
hello.s 編譯過的組合語言檔案
hello.o 彙編過的可重定位目標執行檔案
hello 連結過的可執行目標檔案
hello2.c 測試程式程式碼
hello2 測試程式
helloo.objdmp Hello.o的反彙編程式碼
helloo.elf Hello.o的ELF格式儲存
hello.objdmp Hello的反彙編程式碼
hello.elf Hellode ELF格式儲存

1.4 本章小結
本章主要介紹了hello 的P2P、020過程,列出了本次實驗的基本實驗資訊:環境、中間結果。
(第1章0.5分)

第2章 預處理
2.1 預處理的概念與作用
概念:預處理是依據預處理指令,對源程式進行修改,包含其引用的部分使其成為字尾.i的文字檔案。其中預處理指令是以#號開頭的程式碼行。#號必須是該行除了任何空白字元外的第一個字元。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。
作用
1.巨集定義
巨集定義又稱為巨集代換、巨集替換。預處理(預編譯)工作也叫做巨集展開,將巨集名替換為字串,且在對相關命令或語句的含義和功能作具體分析之前將所有的巨集等價代換。是一種轉義方式。
2.檔案包含
檔案包含處理是指在一個原始檔中,通過檔案包含命令將另一個原始檔的內容全部包含在此檔案中。這種檔案包含處理在程式開發中會給我們的模組化程式設計帶來很大的好處,通過檔案包含的方法把程式中的各個功能模組聯絡起來是模組化程式設計中的一種非常有利的手段。在原始檔進行預處理時,連同被引用進來的檔案一同新增到文字中
3.條件編譯
程式設計師可以通過定義不同的巨集來決定編譯程式對哪些程式碼進行處理。條件編譯指令將決定哪些程式碼被編譯,而哪些不被編譯的。可以根據表示式的值或者某個特定的巨集是否被定義來確定編譯條件。

2.2在Ubuntu下預處理的命令
命令:cpp hello.c > hello.i

圖2.1 使用cpp命令生成hello.i檔案
2.3 Hello的預處理結果解析

圖2.2 hello.i檔案區域性
可以看出,相比於原來精簡的hello.c檔案,hello.i被拓展為具有相當內容(3118行)的檔案,其中直到3102行才出現main函式的蹤影,而佔據前3000多行的,是hello.c中引用的<stdio.h> <unistd.h> <stdlib.h>檔案的展開。在預處理過程中,前處理器(cpp)識別到如 #include(包含一個原始碼檔案)的預處理指令,到預設的環境變數下尋找stdio.h,開啟/usr/include/stdio.h 發現其中依然使用了#define語句,再對此遞迴展開

2.4 本章小結
本章的主要內容是前處理器對hello.c檔案的預處理過程,可以看出,預處理階段主要是完成對源程式的替換工作。經過替換後,會生成一個沒有巨集定義、沒有條件編譯指令、沒有特殊符號的輸出檔案.i,該輸出檔案中只有常量如數字、字元等,或者是變數的定義,以及C語言的關鍵字如if、else等。其中用處最廣泛的當屬檔案包含功能。通過這個功能,我們能在程式設計時方便地引用自己乃至前人預先完成的模組化程式,大大提高了開發效率。

(第2章0.5分)

第3章 編譯
3.1 編譯的概念與作用
概念:編譯,對於預處理後的hello.i檔案在確認所有指令都符合語法規則之後,將其翻譯成等價的中間程式碼或者是彙編程式碼。其中.s字尾的為組合語言程式。
作用:通過編譯,使預處理後的hello.i檔案變成了可以彙編為機器指令的組合語言,編譯階段所有做的工作就是通過詞法分析和語法分析,生成一棵語法樹再轉換為目的碼。編譯階段會對程式碼進行優化處理,不僅涉及到編譯技術本身,還涉及到機器的硬體環境。

注意:這兒的編譯是指從 .i 到 .s 即預處理後的檔案到生成組合語言程式

3.2 在Ubuntu下編譯的命令
gcc -S hello.i -o hello.s

圖3.1 使用gcc生成hello.s檔案
3.3 Hello的編譯結果解析
首先,宣告彙編指令及其含義:
指令 含義
.file 宣告原始檔
.text 標誌程式碼段
.section .rodata 標誌rodata節
.globl 宣告全域性變數
.type 指定是函式型別或是物件型別
.size 宣告大小
.long、.string 宣告long、string型別
.align 宣告對指令或者資料的存放地址進行對齊的方式
3.3.1 資料
hello.s中用到的C資料型別有:整數、字串、陣列。

1)字串
可知程式中的字串有:
“Usage: Hello 1170301005 白鎮北!\n”,這是第一個printf傳入的輸出格式化引數,在hello.s中宣告如下圖:

圖3.2 hello.s中宣告在.LC0和.LC1段中的字串

可以發現字串中數字沒有變化,而漢字被編碼成UTF-8格式,一個漢字在utf-8編碼中佔三個位元組(一個\為一個位元組)。
“Hello %s %s\n”,這是第二個printf傳入的輸出格式化引數,在hello.s中也宣告如圖3.2。

2)整數
可知程式中涉及的整數有:
int sleepsecs:sleepsecs在C程式中被宣告為全域性變數,且已經被賦值,編譯器處理時在.data節宣告該變數,.data節存放已經初始化的全域性和靜態C變數。如下圖:

圖3.3 hello.s中sleepsecs的宣告

可以看處,編譯器最先在.text程式碼段中就進行了宣告:globl sleepsecs。然後又在.data程式碼段中,設定其對齊方式為4、型別為object(物件)、大小(size)為4位元組、設定為long型別其值為2(從這可以看出,編譯器在這一步就已經進行了優化(整型數舍入),且舍入規則為向零舍入)
其餘資料
int i:編譯器將區域性變數儲存在暫存器或者棧空間中,在hello.s中編譯器將i儲存在棧上空間-4(%rbp)中(可以看出在linux下long和int一樣佔4B)。
int argc:作為第一個引數傳入。
立即數:其他整形資料的出現都是以立即數的形式出現的,即直接編碼在彙編程式碼中。

3)陣列
可知程式中涉及的陣列是:
char argv[] main,記錄函式執行時輸入的命令列,argv作為存放char指標的陣列同時是也第二是個引數傳入。
Argv中沒個元素char
大小為8B,argv指標指向已經分配好的的連續空間(若為連結串列則不一定連續)。argv作為陣列名,也是陣列的首地址。在main函式內訪問陣列元素argv[1],argv[2]時,按照起始地址argv大小8B計算資料地址取資料,在hello.s中,使用兩次(%rax)取出其值。如下圖

圖3.4 依照地址取出陣列值

3.3.2 賦值
程式中有關賦值操作的有:
int sleepsecs=2.5 :由sleepsecs是全域性變數,所以直接在.data節中將sleepsecs宣告為值2的long型別資料(如圖3.3)
i=0:整型資料的賦值使用mov指令完成,不同的指令(b、w、l、q)分別對應不同的資料大小:8b (1B)、16b (2B)、32b (4B)、64b (8B)
由於i是int型別,大小為4B,所以使用movl進行賦值,如下圖:

圖3.5 hello.s中對i的賦值

3.3.3 型別轉換
程式中有關隱式型別轉換的有:
int sleepsecs=2.5,將浮點數型別的2.5轉換為int型別。
當在double或float向int進行型別轉換的時候,程式改變數值和位模式的原則是:值會向零舍入。例如1.999將被轉換成1,-1.999將被轉換成-1。進一步來講,可能會產生值溢位的情況,與Intel相容的微處理器指定位模式[10…000]為整數不確定值,一個浮點數到整數的轉換,如果不能為該浮點數找到一個合適的整數近似值,就會產生一個整數不確定值。
浮點數預設型別為double,所以上述強制轉化是double強制轉化為int型別。遵從向零舍入的原則,將2.5舍入為2。

3.3.4算數操作
程式中有關的算數操作有:
i++,令計數器i自增。彙編中使用leaq .LC1(%rip),%rdi,使用了載入有效地址指令leaq計算LC1的段地址%rip+.LC1並傳遞給%rdi。

3.3.5 關係操作
程式中有關的關係運算有:
argc!=3:判斷argc不等於3。hello.s中使用cmpl $3,-20(%rbp),計算argc-3然後設定條件碼,為下一步je利用條件碼進行跳轉作準備。
i<10:判斷i小於10。hello.s中使用cmpl $9,-4(%rbp),計算i-9然後設定條件碼,為下一步jle利用條件碼進行跳轉做準備。
3.3.6 控制轉移
程式中有關控制轉移的有:
if (argv!=3):當argv不等於3的時候執行程式段中的程式碼。如下圖:

圖3.6 if語句的編譯

對於if判斷,首先cmpl比較argv和3,設定條件碼,使用je判斷ZF標誌位,如果為0,說明argv-3=0 argv==3,則不執行if中的程式碼直接跳轉到.L2,否則順序執行下一條語句,即執行if中的程式碼。
for(i=0;i<10;i++):使用計數變數i迴圈10次。如下圖:

圖3.7 for迴圈的編譯
編譯器的編譯邏輯是,首先無條件跳轉到位於迴圈體.L4之後的比較程式碼,使用cmpl進行比較,如果i<=9,則跳入.L4 for迴圈體執行,否則說明迴圈結束,順序執行for之後的邏輯。
可以看出,同樣是跳轉,if大多是跳轉到後方程式碼段,而迴圈往往是跳轉到前方程式碼段
3.3.7 函式操作
程式中有關函式操作的有:
1)main函式:
傳遞控制,main函式因為被呼叫call才能執行(被系統啟動函式__libc_start_main呼叫),call指令將下一條指令的地址dest壓棧,然後跳轉到main函式。
傳遞資料,外部呼叫過程向main函式傳遞引數argc和argv,分別使用%rdi和%rsi儲存,函式正常出口為return 0,將%eax設定0返回。
分配和釋放記憶體,使用%rbp記錄棧幀的底,函式分配棧幀空間在%rbp之上,程式結束時,呼叫leave指令,leave相當於mov %rbp,%rsp,pop %rbp,恢復棧空間為呼叫之前的狀態,然後ret返回,ret相當pop IP,將下一條要執行指令的地址設定為dest。
2)printf函式:
傳遞資料:第一次printf將%rdi設定為“Usage: Hello 學號 姓名!\n”字串的首地址。第二次printf設定%rdi為“Hello %s %s\n”的首地址,設定%rsi為argv[1],%rdx為argv[2]。
控制傳遞:第一次printf因為只有一個字串引數,所以call [email protected];第二次printf使用call [email protected]
3)exit函式:
傳遞資料:將%edi設定為1。
控制傳遞:call [email protected]
4)sleep函式:
傳遞資料:將%edi設定為sleepsecs。
控制傳遞:call [email protected]
5)getchar函式:
控制傳遞:call [email protected]

此部分是重點,說明編譯器是怎麼處理C語言的各個資料型別以及各類操作的。應分3.3.1~ 3.3.x等按照型別和操作進行分析,只要hello.s中出現的屬於大作業PPT中P4給出的參考C資料與操作,都應解析。

3.4 本章小結

編譯是對程式語言的一次“降維”,在降維之後的組合語言變得比原來較長,同時操作也更加基本。
(第3章2分)

第4章 彙編
4.1 彙編的概念與作用

概念:彙編器(as) 將hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程式(relocatable object program) 的格式,並將結果儲存在目標檔案hello.o 中。

作用:彙編器是將彙編程式碼轉變成機器可以執行的命令,每一個彙編語句幾乎都對應一條機器指令。彙編相對於編譯過程比較簡單,根據彙編指令和機器指令的對照表一一翻譯即可。
注意:這兒的彙編是指從 .s 到 .o 即編譯後的檔案到生成機器語言二進位制程式的過程。

4.2 在Ubuntu下彙編的命令
as hello.s -o hello.o

圖4.1 使用指令as生成hello.o檔案
4.3 可重定位目標elf格式
使用指令readelf -a hello.o > helloo.elf 指令即可獲得hello.o檔案的ELF格式。

圖4.2 使用指令readelf生成helloo.elf檔案
ELF檔案主要分為以下三個部分:
1)ELF Header:以16B的序列Magic開始,Magic描述了生成該檔案的系統的字的大小和位元組順序,ELF頭剩下的部分包含幫助連結器語法分析和解釋目標檔案的資訊,其中包括ELF頭的大小、目標檔案的型別、機器型別、位元組頭部表(section header table)的檔案偏移,以及節頭部表中條目的大小和數量等資訊。

圖4.3 elf Header
2) Section Headers:節頭部表,包含了檔案中出現的各個節的語義,包括節的型別、位置和大小等資訊。

圖4.4 Section Headers

3)重定位節.rela.text ,一個.text節中位置的列表,包含.text節中需要進行重定位的資訊,當連結器把這個目標檔案和其他檔案組合時,需要修改這些位置。如下圖:

圖4.5 重定向節.rela.text
中8條重定位資訊分別是對.L0(第一個printf中的字串)、puts函式、exit函式、.L1(第二個printf中的字串)、printf函式、sleepsecs、sleep函式、getchar函式進行重定位宣告。
4.4 Hello.o的結果解析
objdump -d -r hello.o 分析hello.o的反彙編,並請與第3章的 hello.s進行對照分析。
使用objdump -d -r hello.o > helloo.objdump即可得到helloo.objdump檔案
說明機器語言的構成,與組合語言的對映關係。特別是機器語言中的運算元與組合語言不一致,特別是分支轉移函式呼叫等。

機器語言指的是二進位制的機器指令集合,而機器指令是由操作碼和運算元構成的。組合語言的主體是彙編指令。彙編指令和機器指令的差別在於指令的表示方法上,彙編指令是機器指令便於記憶的書寫格式。
在對比兩個檔案後,可以發現彙編器在彙編hello.s時:
為每條語句加上了具體的地址,全域性變數和常量都被安排到了具體的地址裡面。運算元在hello.s裡面都是十進位制,在到hello.o裡面的機器級程式時都是十六進位制。跳轉語句jx&jxx原來對應的符號都變成了相對偏移地址。函式呼叫時原來的函式名字也被替換成了函式的相對偏移地址。

4.5 本章小結
彙編器將組合語言轉化成機器語言,機器語言是用二進位制程式碼表示的計算機能直接識別和執行的一種機器指令的集合。它是計算機的設計者通過計算機的硬體結構賦予計算機的操作功能。機器語言具有靈活、直接執行和速度快等特點。 不同型號的計算機其機器語言是不相通的,按著一種計算機的機器指令編制的程式,不能在另一種計算機上執行。
一條指令就是機器語言的一個語句,它是一組有意義的二進位制程式碼,指令的基本格式如,操作碼欄位和地址碼欄位,其中操作碼指明瞭指令的操作性質及功能,地址碼則給出了運算元或運算元的地址。
(第4章1分)

第5章 連結
5.1 連結的概念與作用
概念:連結是將各種程式碼和資料片段收集並組合成一個單一檔案的過程,這個檔案可被載入到記憶體並執行。合併相同的“節”
作用:將不能直接執行的目的碼變成可執行程式。其中最重要的操作就是將函式庫中相應的程式碼組合到目標檔案中。
注意:這兒的連結是指從 hello.o 到hello生成過程。
5.2 在Ubuntu下連結的命令
使用ld的鏈 接命令,應截圖,展示彙編過程! 注意不只連線hello.o檔案
Ld -o hello -dynamic-linker /lin/ld-linux-x86-64-so,2/usr/lib/x86_64-linux-gnu/crt
1.o /usr/lib/x8

圖5.1 使用ld命令連結生成可執行程式hello
5.3 可執行目標檔案hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本資訊,包括各段的起始地址,大小等資訊。
使用readelf -a hello > hello.elf 命令生成hello程式的ELF格式檔案。
在ELF格式檔案中,Section Headers對hello中的節資訊進行了宣告,其中包括大小Size以及在程式中的偏移量Offset,因此根據Section Headers中的資訊我們就可以用HexEdit定位各個節所佔的區間(起始位置,大小)。其中Address是程式被載入到虛擬地址的起始地址。

圖5.2 hello ELF格式中的Section Headers Table
5.4 hello的虛擬地址空間
使用edb載入hello,檢視本程序的虛擬地址空間各段資訊,並與5.3對照分析說明。

圖5.3 hello ELF格式中的Section Headers Table
如圖5.3,檢視ELF格式檔案中的Program Headers,程式頭表在執行的時候被使用,它告訴連結器執行時載入的內容並提供動態連結的資訊。每一個表項提供了各段在虛擬地址空間和實體地址空間的大小、位置、標誌、訪問許可權和對齊方面的資訊。可以看出,程式包含8個段:

PHDR儲存程式頭表。
INTERP指定在程式已經從可執行檔案對映到記憶體之後,必須呼叫的直譯器(如動態連結器)。
LOAD表示一個需要從二進位制檔案對映到虛擬地址空間的段。其中儲存了常量資料(如字串)、程式的目的碼等。
DYNAMIC儲存了由動態連結器使用的資訊。
NOTE儲存輔助資訊。
GNU_STACK:許可權標誌,標誌棧是否是可執行的。
GNU_RELRO:指定在重定位結束之後那些記憶體區域是需要設定只讀。
5.5 連結的重定位過程分析
使用objdump -d -r hello > hello.objdump 獲得hello的反彙編程式碼。
與hello.o反彙編文字helloo.objdump相比,在hello.objdump中多了許多節:

.interp 儲存ld.so的路徑
.note.ABI-tag Linux下特有的section
.hash 符號的雜湊表
.gnu.hash GNU拓展的符號的雜湊表
.dynsym 執行時/動態符號表
.dynstr 存放.dynsym節中的符號名稱
.gnu.version 符號版本
.gnu.version_r 符號引用版本
.rela.dyn 執行時/動態重定位表
.rela.plt .plt節的重定位條目
.init 程式初始化需要執行的程式碼
.plt 動態連結-過程連結表
.fini 當程式正常終止時需要執行的程式碼
.eh_frame contains exception unwinding and source language information.
.dynamic 存放被ld.so使用的動態連結資訊
.got 動態連結-全域性偏移量表-存放變數
.got.plt 動態連結-全域性偏移量表-存放函式
.data 初始化了的資料
.comment 一串包含編譯器的NULL-terminated字串
5.6 hello的執行流程
通過使用objdump檢視反彙編程式碼,以及使用gdb單步執行,可以找出.text節中main函式前後執行的函式名稱。在main函式之前執行的程式有:_start、[email protected]、__libc_csu_init、_init、frame_dummy、register_tm_clones。在main函式之後執行的程式有:exit、cxa_thread_atexit_impl、fini。
使用edb執行hello,說明從載入hello到_start,到call main,以及程式終止的所有過程。請列出其呼叫與跳轉的各個子程式名或程式地址。
5.7 Hello的動態連結分析
動態連結的基本思想是把程式按照模組拆分成各個相對獨立部分,在程式執行時才將它們連結在一起形成一個完整的程式,而不是像靜態連結一樣把所有程式模組都連結成一個單獨的可執行檔案。雖然動態連結把連結過程推遲到了程式執行時,但是在形成可執行檔案時(注意形成可執行檔案和執行程式是兩個概念),還是需要用到動態連結庫。比如我們在形成可執行程式時,發現引用了一個外部的函式,此時會檢查動態連結庫,發現這個函式名是一個動態連結符號,此時可執行程式就不對這個符號進行重定位,而把這個過程留到裝載時再進行。
分析hello程式的動態連結專案,通過edb除錯,分析在dl_init前後,這些專案的內容變化。要截圖示識說明。
5.8 本章小結
本章討論了連結過程中對程式的處理。Linux系統使用可執行可連結格式,即ELF,具有.text,.rodata等節,並且通過特定的結構組織。
經過連結,ELF可重定位的目標檔案變成可執行的目標檔案,連結器會將靜態庫程式碼寫入程式中,以及動態庫呼叫的相關資訊,並且將地址進行重定位,從而保證定址的正確進行。靜態庫直接寫入程式碼即可,而動態連結過程相對複雜一些,涉及共享庫的定址。
連結後,程式便能夠在作為程序通過虛擬記憶體機制直接執行。
(第5章1分)

第6章 hello程序管理
6.1 程序的概念與作用
程序是一個執行中的程式的例項,每一個程序都有它自己的地址空間,一般情況下,包括文字區域、資料區域、和堆疊。文字區域儲存處理器執行的程式碼;資料區域儲存變數和程序執行期間使用的動態分配的記憶體;堆疊區域儲存區著活動過程呼叫的指令和本地變數。
程序為使用者提供了以下假象:我們的程式好像是系統中當前執行的唯一程式一樣,我們的程式好像是獨佔的使用處理器和記憶體,處理器好像是無間斷的執行我們程式中的指令,我們程式中的程式碼和資料好像是系統記憶體中唯一的物件。
6.2 簡述殼Shell-bash的作用與處理流程
Linux實質上是一個作業系統核心,一般使用者不能直接使用核心,而是通過外殼程式,也就是所謂的shell來與核心進行溝通。外殼程式可以保證作業系統的安全性,抵禦使用者的一些不正確操作。Linux的外殼程式稱作shell(命令列直譯器),它能夠將命令翻譯給核心、將核心處理結果翻譯給使用者。一般我們使用的shell為bash。在解釋命令的時候,bash不會直接參與解釋,而是建立新程序進行命令的解釋,bash只用等待結果即可,這樣能保證bash程序的安全。
首先 shell檢查命令是否是內部命令,若不是再檢查是否是一個應用程式(這裡的應用程式可以是Linux 本身的實用程式,如ls 和rm;也可以是購買的商業程式,如xv;或者是自由軟體,如emacs)。然後shell在搜尋路徑裡尋找這些應用程式(搜尋路徑就是一個能找到可執行程式的目錄列表)。如果輸入的命令不是一個內部命令且在路徑裡沒有找到這個可執行檔案,將會顯示一條錯誤資訊。如果能找到命令,該內部命令或應用程式分解後將被系統呼叫並傳給Linux 核心。
6.3 Hello的fork程序建立過程
一個程序,包括程式碼、資料和分配給程序的資源。fork函式通過系統呼叫建立一個與原來程序幾乎完全相同的程序,也就是兩個程序可以做完全相同的事,但如果初始引數或者傳入的變數不同,兩個程序也可以做不同的事。一個程序呼叫fork函式後,系統先給新的程序分配資源,例如儲存資料和程式碼的空間。然後把原來的程序的所有值都複製到新的新程序中,只有少數值與原來的程序的值不同。相當於克隆了一個自己。在fork函式執行完畢後,如果建立新程序成功,則出現兩個程序,一個是子程序,一個是父程序。在子程序中,fork函式返回0,在父程序中,fork返回新建立子程序的程序ID。我們可以通過fork返回的值來判斷當前程序是子程序還是父程序。
6.4 Hello的execve過程
建立程序後,在子程序中通過判斷pid即fork()函式的返回值,判斷處於子程序,則會通過execve函式在當前程序的上下文中載入並執行一個新程式。execve載入並執行可執行目標檔案,且帶引數列表argv和環境變數列表envp。只有當出現錯誤時,execve才會返回到呼叫程式。
在execve載入了可執行程式之後,它呼叫啟動程式碼。啟動程式碼設定棧,並將控制傳遞給新程式的主函式,即可執行程式的main函式。此時使用者棧已經包含了命令列引數與環境變數,進入main函式後便開始逐步執行程式。
6.5 Hello的程序執行
邏輯控制流:一系列程式計數器PC的值的序列叫做邏輯控制流,程序是輪流使用處理器的,在同一個處理器核心中,每個程序執行它的流的一部分後被搶佔(暫時掛起),然後輪到其他程序。
時間片:一個程序執行它的控制流的一部分的每一時間段叫做時間片。
使用者模式和核心模式:處理器通常使用一個暫存器提供兩種模式的區分,該暫存器描述了程序當前享有的特權,當沒有設定模式位時,程序就處於使用者模式中,使用者模式的程序不允許執行特權指令,也不允許直接引用地址空間中核心區內的程式碼和資料;設定模式位時,程序處於核心模式,該程序可以執行指令集中的任何命令,並且可以訪問系統中的任何記憶體位置。

   上下文資訊:上下文就是核心重新啟動一個被搶佔的程序所需要的狀態,它由通用暫存器、浮點暫存器、程式計數器、使用者棧、狀態暫存器、核心棧和各種核心資料結構等物件的值構成。
   簡單看hello sleep程序排程的過程:當呼叫sleep之前,如果hello程式不被搶佔則順序執行,假如發生被搶佔的情況,則進行上下文切換,上下文切換是由核心中排程器完成的,當核心排程新的程序執行後,它就會搶佔當前程序,並進行1)儲存以前程序的上下文2)恢復新恢復程序被儲存的上下文,3)將控制傳遞給這個新恢復的程序 ,來完成上下文切換。
  hello初始執行在使用者模式,在hello程序呼叫sleep之後陷入核心模式,核心處理休眠請求主動釋放當前程序,並將hello程序從執行佇列中移出加入等待佇列,定時器開始計時,核心進行上下文切換將當前程序的控制權交給其他程序,當定時器到時時(2.5secs)傳送一箇中斷訊號,此時進入核心狀態執行中斷處理,將hello程序從等待佇列中移出重新加入到執行佇列,成為就緒狀態,hello程序就可以繼續進行自己的控制邏輯流了。

當hello呼叫getchar的時候,實際落腳到執行輸入流是stdin的系統呼叫read,hello之前執行在使用者模式,在進行read呼叫之後陷入核心,核心中的陷阱處理程式請求來自鍵盤緩衝區的DMA傳輸,並且安排在完成從鍵盤緩衝區到記憶體的資料傳輸後,中斷處理器。此時進入核心模式,核心執行上下文切換,切換到其他程序。當完成鍵盤緩衝區到記憶體的資料傳輸時,引發一箇中斷訊號,此時核心從其他程序進行上下文切換回hello程序。
結合程序上下文資訊、程序時間片,闡述程序排程的過程,使用者態與核心態轉換等等。
6.6 hello的異常與訊號處理
hello執行過程中會出現哪幾類異常,會產生哪些訊號,又怎麼處理的。
hello執行過程中可能出現四類異常:中斷、陷阱、故障和終止。
中斷是來自I/O裝置的訊號,非同步發生,中斷處理程式對其進行處理,返回後繼續執行呼叫前待執行的下一條程式碼,就像沒有發生過中斷。
陷阱是有意的異常,是執行一條指令的結果,呼叫後也會返回到下一條指令,用來呼叫核心的服務進行操作。幫助程式從使用者模式切換到核心模式。
故障是由錯誤情況引起的,它可能能夠被故障處理程式修正。如果修正成功,則將控制返回到引起故障的指令,否則將終止程式。
終止是不可恢復的致命錯誤造成的結果,通常是一些硬體的錯誤,處理程式會將控制返回給一個abort例程,該例程會終止這個應用程式。
hello執行過程中,可能會遇到各種異常,訊號則是一種通知使用者異常傳送的機制。例如較為底層的硬體異常以及較高層的軟體事件,比如Ctrl-Z和Ctrl-C,分別觸發SIGCHLD和SIGINT訊號。
收到訊號後進程會呼叫相應的訊號處理程式對其進行處理。

圖6.1 正常執行+Ctrl-Z

圖6.2 Ctrl-C

圖6.3 回車符

圖6.3 亂輸入

6.7本章小結
本階段通過在hello.out執行過程中執行各種操作,瞭解了與系統相關的若干概念、函式和功能。分析了在程式執行過程中,計算機硬體、軟體和作業系統之間的配合和協作的方式。
(第6章1分)
第7章 hello的儲存管理
7.1 hello的儲存器地址空間
邏輯地址空間的格式為“段地址:偏移地址”,例如“23:8048000”,在真實模式下可以轉換為實體地址:邏輯地址CS:EA = 實體地址CS × 16 + EA。保護模式下以段描述符作為下標,通過在GDT/LDT表獲得段地址,段地址加偏移地址得到線性地址。
線性地址空間是指一個非負整數地址的有序集合,例如{0,1,2,3……}。在採用虛擬記憶體的系統中,CPU從一個有N = 2n個地址的地址空間中生成虛擬地址,這個地址空間稱為虛擬地址空間。
而對應於物理記憶體中M個位元組的地址空間{0, 1, 2, 3, …, M-1}則稱為實體地址空間。
Intel處理器採用段頁式儲存管理,前者將邏輯地址轉換為線性地址從而得到虛擬地址,後者將虛擬地址轉換為實體地址。
以hello程式為例,反彙編可以得到這樣一段彙編程式碼“mov $0x400772,%edi”,其中0x400772其實是邏輯地址的偏移地址,必須加上隱含的DS資料段的基地址才能構成線性空間地址,或者說0x400772是當前任務DS資料段的偏移。
這樣得到的線性地址其實是資料儲存的虛擬地址,還需要經過MMU轉換為實體地址,轉換為其實體記憶體的地址。
7.2 Intel邏輯地址到線性地址的變換-段式管理
Intel處理器從邏輯地址到線性地址的變換通過段式管理,介紹段式管理就必須瞭解段暫存器的相關知識。段暫存器對應著記憶體不同的段,有棧段暫存器(SS)、資料段暫存器(DS)、程式碼段暫存器(CS)和輔助段暫存器(ES/GS/FS)。其大體對應關係如下圖:

圖7.1 段暫存器

段暫存器用於存放段選擇符,通過段選擇符可以得到對應段的首地址。段選擇符分為三個部分,分別是索引、TI(決定使用全域性描述符表還是區域性描述符表)和RPL(CPU的當前特權級)。

圖7.2 段選擇符
這樣,Intel處理器在通過段式管理定址時,首先通過段描述符得到段基址,然後與偏移量結合得到線性地址,從而得到了虛擬地址。至於偏移量,基址暫存器還是變址暫存器有不同的計算方法,後者需要經過乘比例因子等處理。
7.3 Hello的線性地址到實體地址的變換-頁式管理
CPU的頁式記憶體管理單元,負責把一個線性地址,最終翻譯為一個實體地址。從管理和效率的角度出發,線性地址被分為以固定長度為單位的組,稱為頁(page),例如一個32位的機器,線性地址最大可為4G,可以用4KB為一個頁來劃分,這頁,整個線性地址就被劃分為一個tatol_page[2^20]的大陣列,共有2的20個次方個頁。這個大陣列我們稱之為頁目錄。目錄中的每一個目錄項,就是一個地址——對應的頁的地址。另一類“頁”,我們稱之為物理頁,或者是頁框、頁楨的。是分頁單元把所有的實體記憶體也劃分為固定長度的管理單位,它的長度一般與記憶體頁是一一對應的。
7.4 TLB與四級頁表支援下的VA到PA的變換
Core i7 MMU 使用四級的頁表將虛擬地址翻譯成實體地址。36位VPN 被劃分成四個9 位VPN,分別用於一個頁表的偏移量。具體結構如下圖:

圖7.3 i7 頁表翻譯

7.5 三級Cache支援下的實體記憶體訪問
(1)直接對映快取記憶體
直接對映快取記憶體每個組只有一行,當CPU執行一條讀記憶體字w的指令,它會向L1快取記憶體請求這個字。如果L1快取記憶體中有w的一個快取副本,那麼就會得到L1快取記憶體命中,快取記憶體會很快抽取出w,並將它返回給CPU。否則就是快取不命中,當L1快取記憶體向主存請求包含w的塊的一個副本時,CPU必須等待。當被請求塊最終從記憶體到達時,L1快取記憶體將這個塊存放在它的一個快取記憶體行裡,從被儲存的塊中抽取出字w,然後將它返回給CPU。確定是否命中然後抽取的過程分為三步:1)組選擇;2)行匹配;3)字抽取。

圖7.4 直接對映快取記憶體中的組選擇

組選擇即從w的地址中間抽取出s個索引位,將其解釋為一個對應組號的無符號整數,從而找到對應的組;行匹配即對組內的唯一一行進行判斷,當有效位為1且標記位與從地址中抽取出的標記位相同則成功匹配,否則就得到不命中;而字選擇即在行匹配的基礎上通過地址的後幾位得到塊偏移,從而在快取記憶體塊中索引到資料。

圖7.5直接對映快取記憶體中的行匹配

(2)組相聯快取記憶體
組相聯快取記憶體每個組內可以多於一個快取行,總體邏輯類似於直接對映快取記憶體,不同之處在於行匹配時每組有更多的行可以嘗試匹配,遍歷每一行。如果不命中,有空行時也就是冷不命中則直接儲存在空行;如果沒有空行也就是衝突不命中,則替換已有行,通常有LFU(最不常使用)、LRU(最近最少使用)兩者替換策略。
(3)全相聯快取記憶體
全相聯快取記憶體只有一個組,且這個組包含所有的快取記憶體行(即E =
C/B)。對於全相聯快取記憶體,因為只有一個組,組選擇變的十分簡單。地址中不存在索引位,地址只被劃分為一個標記位和一個塊偏移。行匹配和字選擇同組相聯快取記憶體。
寫入資料時,假設我們要寫一個已經快取了的字w,在快取記憶體中更新了它的w的副本之後,有兩種方法來更新w在層次結構中緊接著低一層中的副本。分別是直寫和寫回,在這裡分別介紹:
(1)直寫
立即將w的快取記憶體塊寫回到緊挨著的低一層中。優點是簡單,缺點則是每次寫都會引起匯流排流量。其處理不命中的方法是非寫分配,即避開快取記憶體,直接將這個字寫到低一層去。
(2)寫回
儘可能地推遲更新,只有當替換演算法要驅逐這個更新過的塊時,才把它寫到緊接著的低一層中。優點是能顯著地減少匯流排流量,缺點是增加了複雜性,必須為每個快取記憶體行增加一個額外的修改位,表明是否被修改過。寫回處理不命中的方法是寫分配,載入相應低一層中的塊到快取記憶體中,然後更新這個快取記憶體塊,利用了寫的空間區域性性,但會導致每次不命中都會有一個塊從低一層傳到快取記憶體。
通過這樣的Cache讀寫機制,實現了從CPU暫存器到L1快取記憶體,再到L2快取記憶體,再到L3快取記憶體,再到實體記憶體的訪問,有效的提高了CPU訪問實體記憶體的速度。
7.6 hello程序fork時的記憶體對映
虛擬記憶體和記憶體對映解釋了fork函式如何為每個新程序提供私有的虛擬地址空間。Fork函式為新程序建立虛擬記憶體。建立當前程序的的mm_struct, vm_area_struct和頁表的原樣副本,兩個程序中的每個頁面都標記為只讀,兩個程序中的每個區域結構(vm_area_struct)都標記為私有的寫時複製(COW)。在新程序中返回時,新程序擁有與呼叫fork程序相同的虛擬記憶體,隨後的寫操作通過寫時複製機制建立新頁面。
7.7 hello程序execve時的記憶體對映
execve函式在shell中載入並執行包含在可執行檔案hello中的程式,用hello程式有效地替代了當前程式。載入hello的過程主要步驟如下:
首先刪除已存在的使用者區域,也就是將shell與hello都有的區域結構刪除。然後對映私有區域,即為新程式的程式碼、資料、bss和棧區域建立新的區域結構,均為私有的、寫時複製的。下一步是對映共享區域,將一些動態連結庫對映到hello的虛擬地址空間,最後設定程式計數器,使之指向hello程式的程式碼入口。
經過這個記憶體對映的過程,在下一次排程hello程序時,就能夠從hello的入口點開始執行了。
7.8 缺頁故障與缺頁中斷處理
Linux將虛擬記憶體組織成段的集合。核心為每個程序維護一個單獨的任務結構,這個任務結構的第一個條目指向mm_struct,它描述了虛擬記憶體的當前狀態,其中的pgd欄位又會指向一個區域結構的連結串列,每個區域結構都描述了當前虛擬地址的一個區域,或者稱為一個段。一個具體的區域結構包括vm_start和vm_end等欄位,記錄區域的相關資訊。

圖7.6 Linux虛擬記憶體組織結構
假設MMU在試圖翻譯某個虛擬地址A時,觸發了一個缺頁。這個異常導致控制轉移到核心的缺頁處理程式,處理程式隨後就執行下面的步驟:
首先判斷虛擬地址A是否合法,缺頁處理程式會搜尋區域結構的連結串列,把A和每個區域結構中的vm_start和vm_end做比較。如果指令不合法則觸發段錯誤,從而終止該程序。
然後處理程式會判斷試圖進行的記憶體訪問是否合法,也就是程序是否有讀寫這個區域內頁面的許可權。如果訪問不合法,那麼處理程式會觸發一個保護異常,終止這個程序。
最後,確保了以上兩點的合法性後,根據頁式管理的規則,犧牲一個頁面,並賦值為需要的資料,然後更新頁表並再次觸發MMU的翻譯過程。

圖7.8.2 Linux缺頁處理
7.9動態儲存分配管理
printf函式會呼叫malloc,下面簡述動態記憶體管理的基本方法與策略:
動態記憶體分配器維護著一個程序的虛擬記憶體區域,稱為堆。分配器將堆視為一組不同大小的塊的集合來維護。每個塊就是一個連續的虛擬記憶體片,要麼是已分配的,要麼是空閒的。已分配的塊顯式地保留為供應用程式使用。空閒塊可用來分配。空閒塊保持空閒,直到它顯式地被應用所分配。一個已分配的塊保持已分配狀態,直到它被釋放,這種釋放要麼是應用程式顯式執行的,要麼是記憶體分配器自身隱式執行的。
分配器分為兩種基本風格:顯式分配器、隱式分配器。
顯式分配器:要求應用顯式地釋放任何已分配的塊。
隱式分配器:要求分配器檢測一個已分配塊何時不再使用,那麼就釋放這個塊,自動釋放未使用的已經分配的塊的過程叫做垃圾收集。
帶邊界標籤的隱式空閒連結串列:
1)堆及堆中記憶體塊的組織結構:

在記憶體塊中增加4B的Header和4B的Footer,其中Header用於尋找下一個blcok,Footer用於尋找上一個block。Footer的設計是專門為了合併空閒塊方便的。因為Header和Footer大小已知,所以我們利用Header和Footer中存放的塊大小就可以尋找上下block。
2)隱式連結串列
所謂隱式空閒連結串列,對比於顯式空閒連結串列,代表並不直接對空閒塊進行連結,而是將對記憶體空間中的所有塊組織成一個大連結串列,其中Header和Footer中的block大小間接起到了前驅、後繼指標的作用。
3)空閒塊合併
因為有了Footer,所以我們可以方便的對前面的空閒塊進行合併。合併的情況一共分為四種:前空後不空,前不空後空,前後都空,前後都不空。對於四種情況分別進行空閒塊合併,我們只需要通過改變Header和Footer中的值就可以完成這一操作。
顯示空間連結串列基本原理:
將空閒塊組織成連結串列形式的資料結構。堆可以組織成一個雙向空閒連結串列,在每個空閒塊中,都包含一個pred(前驅)和succ(後繼)指標,如下圖:

使用雙向連結串列而不是隱式空閒連結串列,使首次適配的分配時間從塊總數的線性時間減少到了空閒塊數量的線性時間。
7.10本章小結
程式的實現涉及到從磁碟到主存,從主存到快取記憶體,從快取記憶體再到暫存器的層級儲存。
作業系統將主存抽象為虛擬記憶體,作為磁碟的快取。在程式執行時從磁碟載入到主存,並將其主存的實體地址對映為虛擬地址,這樣,便可以通過虛擬地址對主存進行訪問,從而防止了各個程序之間的衝突與錯誤。作業系統通過MMU將虛擬地址轉換為實體地址,利用TLB和多級頁表提高其訪問速度和記憶體利用率,從而實現對主存的有效訪問。另外,在發生缺頁時,作業系統通過訊號處理程式能夠很好的解決。
CPU對主存的訪問同樣採用快取, 通過三級Cache高效的對資料進行讀寫。
程式執行過程中常常涉及到動態記憶體分配,動態記憶體分配通過動態記憶體分配器完成,能夠對堆空間進行合理的分配與管理,分割與合併。現代使程式記憶體分配器採取了多種策略來提高吞吐量以及記憶體佔用率,從在靈活使用記憶體的基礎上保證了效率。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO裝置管理方法
(以下格式自行編排,編輯時刪除)
裝置的模型化:檔案
裝置管理:unix io介面
首先是裝置的模型化。在裝置模型中,所有的裝置都通過匯流排相連。每一個裝置都是一個檔案。裝置模型展示了匯流排和它們所控制的裝置之間的實際連線。在最底層,Linux 系統中的每個裝置由一個 struct device 代表,而Linux統一裝置模型就是在kobject kset ktype的基礎之上逐層封裝起來的。裝置管理則是通過unix io介面實現的。
8.2 簡述Unix IO介面及其函式
Linux以檔案的方式對I/O裝置進行讀寫,將裝置均對映為檔案。對檔案的操作,核心提供了一種簡單、低階的應用介面,即Unix I/O介面。
Unix I/O介面提供了以下函式供應用程式呼叫:
開啟檔案:int open(char *filename, int flags, mode_t mode);
關閉檔案:int close(int fd);
讀檔案:ssize_t read(int fd, void *buf, size_t n);
寫檔案:ssize_t write(int fd, const void buf, size_t n);
8.3 printf的實現分析
https://www.cnblogs.com/pianist/p/3315801.html
printf函式程式碼如下所示:
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;
}
(char
)(&fmt) + 4) 表示的是…可變引數中的第一個引數的地址。而vsprintf的作用就是格式化。它接受確定輸出格式的格式字串fmt。用格式字串對個數變化的引數進行格式化,產生格式化輸出。接著從vsprintf生成顯示資訊,到write系統函式,直到陷阱系統呼叫 int 0x80或syscall。字元顯示驅動子程式:從ASCII到字模庫到顯示vram(儲存每一個點的RGB顏色資訊)。顯示晶片按照重新整理頻率逐行讀取vram,並通過訊號線向液晶顯示器傳輸每一個點(RGB分量)。
8.4 getchar的實現分析
非同步異常-鍵盤中斷的處理:鍵盤中斷處理子程式。接受按鍵掃描碼轉成ascii碼,儲存到系統的鍵盤緩衝區。
getchar等呼叫read系統函式,通過系統呼叫讀取按鍵ascii碼,直到接受到回車鍵才返回。
8.5本章小結
getchar的實現大體如下:
int getchar(void)
{
char c;
return (read(0,&c,1)==1)?(unsigned char)c:EOF
}
可以看到,getchar函式通過呼叫read函式返回字元。其中read函式的第一個引數是描述符fd,0代表標準輸入。第二個引數輸入內容的指標,這裡也就是字元c的地址,最後一個引數是1,代表讀入一個字元,符號getchar函式讀一個字元的設定。read函式的返回值是讀入的字元數,如果為1說明讀入成功,那麼直接返回字元,否則說明讀到了buf的最後。
read函式同樣通過sys_call中斷來呼叫核心中的系統函式。鍵盤中斷處理子程式會接受按鍵掃描碼並將其轉換為ASCII碼後儲存在緩衝區。然後read函式呼叫的系統函式可以對緩衝區ASCII碼進行讀取,直到接受回車鍵返回。
這樣,getchar函式通過read函式返回字元,實現了讀取一個字元的功能。
(第8章1分)
結論
用計算機系統的語言,逐條總結hello所經歷的過程。
你對計算機系統的設計與實現的深切感悟,你的創新理念,如新的設計與實現方法。
經過研究,可以大體概括hello程式所經歷的過程:
生成階段:預處理→編譯→彙編→連結;
載入階段:shell fork子程序→execve;
執行階段:磁碟讀取、虛擬記憶體對映、CPU執行指令、核心排程、快取載入資料、訊號處理、Unix I/O輸入與輸出;
終止階段:程序終止、shell與核心對其進行回收。
可以發現,一個簡單的hello程式涉及一系列複雜的編譯器、作業系統、硬體實現機制。程式的執行與核心、硬體的多方面協調工作密不可分。簡單的一條指令需要成千上萬條底層步驟,無論是內部處理還是輸入輸出。
硬體系統的設計貫徹了馮諾依曼的構想,又經過數十年的迭代更新變得精巧與複雜。作業系統的設計體現了多方面程式設計思想,從底層出發讓軟體與應用層面能夠排程硬體裝置。抽象與系統的思想在計算機系統的實現過程中得到了深入的體現。
為了提高效能,硬體層在設計與製造工藝角度不斷進階,軟體層則在時空效率上處處考慮。提高執行速度,降低資源佔用,保證系統安全,作業系統為了程式能夠高效的執行在設計上令人驚歎。通過檔案對I/O裝置進行抽象,通過處理器層級關係實現快取從而提高執行速度,命中與缺頁均有不同的策略。通過虛擬內將程序隔離,防止程式直接互相干擾以及影響核心安全。通過訊號對異常進行有效的反饋,應對系統執行中的各類問題。
系統的思想將一切軟硬體裝置組織的恰到好處,互動的過程有一種協調的美感,程式執行的背後是無數二進位制碼在硬體層面的流動,一個簡單的hello world程式背後也充滿著思想。
(結論0分,缺失 -1分,根據內容酌情加分)

附件
列出所有的中間產物的檔名,並予以說明起作用。
hello.i 預處理過的文字檔案
hello.s 編譯過的組合語言檔案
hello.o 彙編過的可重定位目標執行檔案
hello 連結過的可執行目標檔案
hello2.c 測試程式程式碼
hello2 測試程式
helloo.objdmp Hello.o的反彙編程式碼
helloo.elf Hello.o的ELF格式儲存
hello.objdmp Hello的反彙編程式碼
hello.elf Hellode ELF格式儲存

(附件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.
(參考文獻0分,缺失 -1分)