1. 程式人生 > >HITICS || 2018大作業 程式人生 Hello's P2P

HITICS || 2018大作業 程式人生 Hello's P2P

 

  本文通過分析一個hello.c的完整的生命週期,從它開始被編譯,到被彙編、連結、在程序中執行,講解了Linux計算機系統執行一個程式的完整過程。

  關鍵詞:作業系統,程序,程式的生命週期

 

 

1章 概述- 4 -

1.1 Hello簡介 - 4 -

1.2 環境與工具 - 4 -

1.3 中間結果 - 4 -

1.4 本章小結 - 4 -

2章 預處理- 5 -

2.1 預處理的概念與作用 - 5 -

2.2Ubuntu下預處理的命令 - 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 -

6hello程序管理- 10 -

6.1 程序的概念與作用 - 10 -

6.2 簡述殼Shell-bash的作用與處理流程 - 10 -

6.3 Hellofork程序建立過程 - 10 -

6.4 Helloexecve過程 - 10 -

6.5 Hello的程序執行 - 10 -

6.6 hello的異常與訊號處理 - 10 -

6.7本章小結

- 10 -

7hello的儲存管理- 11 -

7.1 hello的儲存器地址空間 - 11 -

7.2 Intel邏輯地址到線性地址的變換-段式管理 - 11 -

7.3 Hello的線性地址到實體地址的變換-頁式管理 - 11 -

7.4 TLB與四級頁表支援下的VAPA的變換 - 11 -

7.5 三級Cache支援下的實體記憶體訪問 - 11 -

7.6 hello程序fork時的記憶體對映 - 11 -

7.7 hello程序execve時的記憶體對映 - 11 -

7.8 缺頁故障與缺頁中斷處理 - 11 -

7.9動態儲存分配管理 - 11 -

7.10本章小結 - 12 -

8helloIO管理- 13 -

8.1 LinuxIO裝置管理方法 - 13 -

8.2 簡述Unix IO介面及其函式 - 13 -

8.3 printf的實現分析 - 13 -

8.4 getchar的實現分析 - 13 -

8.5本章小結 - 13 -

結論- 14 -

附件- 15 -

參考文獻- 16 -

 

1章 概述

1.1 Hello簡介

P2Phello.c源程式文字先通過編譯器的預處理轉換為修改了的源程式文字hello.i,再經過編譯變為組合語言文字hello.s,再經過彙編器變為hello.o可重定位目標程式(二進位制),最後通過連結器連結和重定位,形成可執行目標程式。然後系統在shell中執行程式,用fork產生子程序。

020:在開始時程式不佔用系統資源,隨著虛擬記憶體的訪問程式開始載入實體記憶體,shell通過execve載入並執行hello,程序執行結束後系統回收hello程序。

1.2 環境與工具

硬體環境:

X64 CPU2GHz2G RAM256GHD Disk 以上

軟體環境:

Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64

開發與除錯工具:

Visual Studio 2010 64位以上;GDB/OBJDUMPDDD/EDB

1.3 中間結果

hello.i —— 修改了的源程式(文字)

hello.s —— 彙編程式(文字)

hello.o —— 可重定位目標程式(二進位制)

hello —— 可執行目標程式(二進位制)

1.4 本章小結

本章介紹了P2P020的過程,以及進行實驗時的軟硬體環境及開發與除錯工具,以及在本論文中生成的中間結果檔案。

 

2章 預處理

2.1 預處理的概念與作用

預處理是指在編譯之前做的處理。

C語言程式在編譯執行之前,要先對程式進行預處理。預處理主要體現在巨集定義、檔案包含、條件編譯三個方面,預處理命令以符號“#”開頭。預處理會匯入巨集定義與檔案、標頭檔案中的內容,使得程式能完整、正常的執行,預處理生成了hello.i的原始碼文字檔案。

2.2在Ubuntu下預處理的命令

 

通過gcc hello.c -E -o hello.i可以對hello.c進行預處理,得到hello.i

 

2.3 Hello的預處理結果解析

 

gedit開啟我們得到的hello.i可以發現它的前面標頭檔案等等被展開了,變成了很多以#開頭的內容,

拉到最下面發現hello.i的最後是我們熟悉的C語言程式,而.i的檔案一共有3k多行

 

2.4 本章小結

本章簡述了在編譯前進行的預處理的過程,hello.c檔案經過預處理生成了hello.i檔案,hello.i檔案中對巨集定義、檔案包含、條件編譯進行了展開。

 

 

3章 編譯

 

3.1 編譯的概念與作用

 

編譯是指從 .i .s 即預處理後的檔案到生成組合語言程式的過程,生成一個hello.s的組合語言源程式檔案。

 

編譯的作用是將高階語言轉變為更易於計算機讀懂的組合語言,同時它還可以進行語法檢查、程式優化。

 

3.2 在Ubuntu下編譯的命令

 

 

 

通過gcc hello.i -S -o hello.s可以對.i檔案進行彙編,得到hello.s

 

 

 

3.3 Hello的編譯結果解析

 

我們得到的hello.s就是編譯後得到的組合語言源程式檔案,通過gedit檢視它,發現它的頭部聲明瞭全域性變數和它們存放的節段,接下來是將源程式的命令彙編得到的程式碼,接下來對它們進行分析。

 

3.3.1 全域性變數

 

hello.c中有一個全域性變數sleepsecs,它被定義成int型,但在編譯器編譯的過程中將它優化為了long型,這裡編譯器進行了隱式的型別轉換,我們給它賦值為2.5,檢視sleepsecs的值時發現sleepsecs = 2,它被存放在.rotate節中。

 

 

 

3.3.2 區域性變數

 

通過觀察這裡,我們發現在.L2中聲明瞭一個區域性變數i,將其儲存在-4(%rbp)中,可以得知在處理區域性變數時,編譯到當前位置才去申請這樣一個記憶體空間的。

 

 

 

 

 

3.3.3 賦值

 

對於全域性變數的賦值直接在處理時在.data節宣告,sleepsecs為值為2long型變數。

 

 

 

對於區域性變數的賦值使用movx語句完成,在棧或暫存器內分配空間,由於i4個位元組的int型資料,因此採用movl

 

 

 

3.3.4 型別轉換

 

hello.c中定義全域性變數sleepsecs時將其定義為int型,但在檢視時發現它的型別是long,由於我們在程式中並沒有型別轉換這個操作,因此可以發現編譯器會進行隱式的型別轉換,以對程式起到優化的作用。

 

 

 

3.3.5 算術操作

 

組合語言中加減乘除四則運算是通過語句來實現的:

 

加法 x = x + y  ->  addq y, x

 

 

 

減法 x = x - y   ->  subq y, x

 

 

 

以及在hello中沒有體現的乘除

 

乘法  x = x * y  ->  imulq y, x

 

除法  x = x / y  ->  divq y, x

 

3.3.6 關係操作

 

關係操作就是比較兩個變數的大小情況,通過cmpl來執行,在cmpl中比較兩個數的大小,用後一個數減去前一個數得到結果的情況來設定標誌位,接下來可以通過設定的標誌位進行跳轉等操作。

 

 

 

3.3.7 控制轉移之條件語句

 

通過cmpl進行比較,根據比較的結果通過jx進行跳轉,跳轉方式可以通過檢視跳轉表得到

 

     

 

3.3.8 控制轉移之迴圈語句

 

這裡就是一個迴圈語句的開始,可以發現我們的迴圈條件是i < 10,在這裡被優化為了i <= 9,每次將計數器的值與9進行比較,若小於等於則跳轉到迴圈內部.L4執行

 

 

 

迴圈內部語句如下,在每次執行.L4結束後都將-4(%rbp)1,因此它是i,起到一個計數器的作用

 

 

 

3.3.9 函式操作

 

·引數傳遞:在函式的引數傳遞中使用不同的暫存器來儲存第x個引數

 

·函式呼叫:使用call語句來實現函式的呼叫

 

  

 

·函式返回:函式的返回值儲存在%rax中,將需要返回的變數值存在%rax中,在進行函式的操作之後ret即可返回%rax中的值

 

3.4 本章小結

 

本章簡述了在預處理之後編譯器進行編譯的過程,通過.i檔案生成.s檔案,得到組合語言原始碼文字,通過分析文字中的每一部分了解了C語言中的不同操作在組合語言中的實現方法。

 

 

4章 彙編

 

4.1 彙編的概念與作用

 

彙編是指將組合語言翻譯成機器語言的過程。

 

作用:用匯編語言寫的程式,機器不能直接識別,要將.s生成一個機器語言二進位制的.o檔案,才能使得機器能夠識別。

 

4.2 在Ubuntu下彙編的命令

 

 

 

通過as hello.s -o hello.o進行彙編,得到hello.o

 

 

 

4.3 可重定位目標elf格式

 

通過readelf -a hello.o語句檢視hello.oelf格式

 

4.3.1 ELF Header

 

 

 

ELF Header中描述了elf檔案總的資訊

 

4.3.2 Section Headers

 

 

 

這部分描述了hello.o中出現的各個節的型別、位置、所佔空間大小等資訊

 

4.3.3 .rela.text

 

 

 

這部分描述了.text節中需要重定位的資訊,這些資訊在生成可執行檔案時就會被重定位,通過檢視可以發現在hello.o中需要被重定位的有.radata, puts, exit, printf, sleepsecs, sleep, getchar

 

4.4 Hello.o的結果解析

 

通過objdump -d -r hello.o得到hello.o的反彙編,與 hello.s進行對照分析,發現hello.o的比hello.s在左側多了一些十六進位制的數字,這些就是機器碼,其餘的具體內容與hello.s並無巨大差異,只是多了一些跳轉的位置。

 

4.4.1 分支轉移

 

在組合語言中,分支轉移的跳轉位置都是用.L3, .L4來表示的,但在機器語言中它們被具體的跳轉地址所替代。

 

   

 

4.4.2 函式呼叫

 

在組合語言中,函式的呼叫都是用函式名來跳轉的的,但在機器語言中它們被具體的跳轉地址所替代。

 

  

 

4.5 本章小結

 

本章簡述了將hello.s生成hello.o,從彙編程式碼生成機器程式碼的過程,比較了彙編程式碼與機器程式碼的異同。

 

 

5 連結

 

5.1 連結的概念與作用

 

連結是將各種程式碼和資料片段收集並組合成為一個單一檔案的過程,這個檔案可被載入到記憶體並執行。

 

連結使得分離編譯成為可能,能夠將一個大型的應用程式分解成為更小、更好管理的模組,可以獨立地修改和編譯這些模組。當我們改變這些模組中的一個時,只需簡單地重新編譯它,並重新連結應用,而不必重新編譯其他檔案。

 

5.2 在Ubuntu下連結的命令

 

 

 

5.3 可執行目標檔案hello的格式

 

通過readelf檢視hello的資訊

 

5.3.1 ELF Header

 

 

 

這部分描述了elf檔案總的資訊

 

5.3.2 Section Headers

 

 

 

這部分描述了hello中出現的各個節的型別、載入到虛擬記憶體後的地址(Address、節頭表所對應位元組大小(Size)以及這個節的地址偏移量(Offset等資訊

 

5.4 hello的虛擬地址空間

 

使用edb可以檢視本程序虛擬地址的空間,從0x400000開始,到0x400fff結束,這一部分連續存放的是elf中的section headers中的address

 

   

 

5.5 連結的重定位過程分析

 

通過objdump -d -r hello,比較hellohello.o發現hellohello.o要多了很多函式:

 

_init

 

[email protected]

 

[email protected]等等如同[email protected]

 

_start

 

_fini

 

在連結時,_init用來做初始化,_start是程式的入口,它呼叫main

 

libc.so是動態連結共享庫,其中定義了putsgetcharprintfexit等函式

 

連結器將共享庫中的函式加入到程式中。

 

同時我們可以發現,在連結後.text.plt的相對位置確定,因此在跳轉時無需再利用偏移量,可以直接通過:目標地址 = PC + 偏移量,來計算出函式地址來進行跳轉。

 

在重定位過程中由於目標地址 = PC + 偏移量,因此重定位地址 = 目標地址- PC,可以得到重定位的值。

 

5.6 hello的執行流程

 

使用edb執行hello,說明從載入hello_start,到call main,以及程式終止的所有過程。請列出其呼叫與跳轉的各個子程式名或程式地址。

 

執行過程中呼叫的子程式名:

 

_dl_start

 

_dl_init

 

_start 

 

_libc_start_main                      

 

_init      

 

_main

 

_printf

 

_exit

 

_sleep

 

_getchar

 

_dl_runtime_resolve_xsave

 

_dl_fixup

 

_dl_lookup_symbol_x

 

exit

 

5.7 Hello的動態連結分析

 

在動態連結的過程中,程式會生成一個共享庫,它可以載入到任意的記憶體地址,並和一個記憶體中的程式連結起來,它在載入和執行時由動態連結器完成。

 

在處理全域性函式時,對於x函式會生成一個[email protected]函式,這個函式對應重定位後的跳轉地址。

 

_dl_init執行前偏移量中全為0,在它執行後偏移量變為了相應的偏移值,因此可以發現_dl_init操作是載入計算當前記憶體地址的偏移量。

 

 

 

5.8 本章小結

 

本章簡述了連結器的工作,分析了靜態連結和動態連結兩種形式,以及連結器的符號解析和重定位的過程,通過連結和重定位,得到了可執行的二進位制檔案。

 

6 hello程序管理

6.1 程序的概念與作用

程序時計算機程式需要對資料集合進行操作所進行的一次活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。

程序提供給應用程式兩個關鍵抽象,邏輯控制流和私有地址空間:

邏輯控制流程序使得每個程式似乎獨佔地使用CPU,它由通過OS核心的上下文切換機制提供。

私有地址空間使得每個程式似乎獨佔地使用記憶體系統,它由OS核心的虛擬記憶體機制提供。

6.2 簡述殼Shell-bash的作用與處理流程

Shell是使用者和Linux核心之間的介面程式,使用者輸入的命令通過shell解釋,然後傳給Linux核心,然後將核心的處理結果翻譯給使用者。

處理流程:首先shell讀取使用者輸入的命令並進行解析,得到引數列表,然後檢查這條命令是否是核心命令,如果是則直接執行,如果不是則fork子程序,啟動載入器在當前程序中載入並執行程式。

6.3 Hello的fork程序建立過程

父程序通過呼叫fork函式建立一個新的執行的子程序:

pid_t fork(void);

fork子程序時,系統建立一個與父程序幾乎但不完全相同的子程序,子程序得到與父程序使用者級虛擬地址空間相同但獨立的一份副本,包括程式碼、資料段、堆、共享庫以及使用者棧,子程序獲得與父程序任何開啟檔案描述符相同的副本,這就意味著當父程序呼叫fork時,子程序可以讀寫父程序中的內容,但它們有著不同的PID,在父程序中,fork返回子程序的PID,在子程序中,fork返回0

6.4 Hello的execve過程

execve函式載入並執行可執行目標檔案:

int execve(const *filename, const char *argv[], const char *envp[]);

其中filename是可執行目標檔案,argv是引數列表,envp是環境變數列表

它呼叫一次,從不返回,只有出現錯誤時execve才會返回到呼叫程式

Loader刪除子程序現有的虛擬記憶體段,建立一組新的段(棧與堆初始化為0),並將虛擬地址空間中的頁對映到可執行檔案的頁大小的片chunk,新的程式碼與資料段被初始化為可執行檔案的內容,然後跳到_start,新程式啟動後的棧結構如下:

 

6.5 Hello的程序執行

上下文:程序的物理實體(程式碼和資料等)和支援程序執行的環境合稱為程序的上下文;由程序的程式塊、資料塊、執行時的堆和使用者棧(兩者通稱為使用者堆疊)等組成的使用者空間資訊被稱為使用者級上下文,使用者級上下文地址空間和系統級上下文地址空間一起構成了一個程序的整個儲存器映像。

程序時間片:程序時間片即CPU分配給每個程序的時間

Hello程序排程的過程:首先shell通過載入器載入可執行目標檔案hello,作業系統進行上下文切換,切換到hello的程序中,這是我們在使用者態,接下來hello呼叫sleep函式,進入核心態,當sleep的時間到時定時器傳送一箇中斷訊號,通過訊號處理函式處理,通過上下文切換再進入hello程序,回到使用者態。

6.6 hello的異常與訊號處理

6.6.1 正常執行

 

 

6.6.2 ctrl+z

 

ctrl+z向程序傳送了一個SIGTSTP訊號,將當前程序暫時掛起

 

ctrl+z後可執行其他指令:

·ps:通過ps可以發現hello程序還沒有被回收。

 

 

·jobs:可以看到當前掛起的程序

 

·fg:可以使後臺掛起的程序繼續執行

 

 

6.6.3 ctrl+c

 

ctrl+c向程序傳送SIGINT訊號,直接結束當前程序並進行回收,通過ps可以發現hello程序已經不在了

6.7本章小結

本章簡述了程序的概念與作用,以及shell的執行流程,總結了forkexecve的執行過程,以及在上下文切換中使用者態和核心態的切換,探究了在程序執行過程中不同訊號的作用。

 

 

7 hello的儲存管理

 

7.1 hello的儲存器地址空間

 

邏輯地址:指機器語言指令中,用來指定一個運算元或者是一條指令的地址。一個邏輯地址,是由一個段識別符號加上一個指定段內相對地址的偏移量。

 

線性地址(虛擬地址):跟邏輯地址類似,它也是一個不真實的地址,假設邏輯地址是相應的硬體平臺段式管理轉換前地址的話,那麼線性地址則相應了硬體頁式記憶體的轉換前地址。

 

實體地址:用於記憶體晶片級的單元定址,與處理器和CPU連線的地址匯流排相對應,是記憶體單元的真正地址

 

7.2 Intel邏輯地址到線性地址的變換-段式管理

 

一個邏輯地址由兩部分組成,段識別符號和段內偏移量。段識別符號由一個16位長的欄位組成,稱為段選擇符其中前13位是一個索引號後面3位包含一些硬體細節,段選擇符各欄位含義如圖:

 

 

 

通過TI可以判斷這個段描述的是區域性段描述符還是全域性段描述符,然後根據段識別符號的前13索引,在段描述符表中找到一個具體的段描述符,可以得到基地址,然後再將其與段內偏移量結合,即可得到線性地址,如圖:

 

7

 

7.3 Hello的線性地址到實體地址的變換-頁式管理

 

 

 

 

 

CPU中的一個控制暫存器,頁表基址暫存器指向當前頁表,n位的虛擬地址由虛擬頁面偏移(VPO, n位)和虛擬頁號(VPN, n - p位)組成。MMU利用VPN來選擇適當的PTE(頁表條目),將頁表條目中的物理頁號(PPN)與虛擬地址的頁面偏移量(VPO)串聯起來,就得到相應的實體地址。

 

頁面命中時,CPU硬體執行的步驟如下:

 

 

 

1) 處理器生成一個虛擬地址,並將其傳送給MMU

 

2) MMU生成PTE地址,並從快取記憶體/記憶體中請求得到它

 

3) 快取記憶體/記憶體向MMU返回PTE(即MMU 使用記憶體中的頁表生成PTE

 

4) MMU構造實體地址,將其傳送給快取記憶體/主存

 

5) 快取記憶體/主存返回所請求的資料字給處理器

 

頁面不命中時,CPU硬體執行的步驟如下:

 

 

 

2) 1) 處理器生成一個虛擬地址,並將其傳送給MMU

 

2) MMU生成PTE地址,並從快取記憶體/記憶體中請求得到它

 

3) 快取記憶體/記憶體向MMU返回PTE(即MMU 使用記憶體中的頁表生成PTE

 

4) PTE中的有效位為零, 因此 MMU 觸發缺頁異常

 

5) 缺頁處理程式確定實體記憶體中犧牲頁 (若頁面被修改,則換出到磁碟)

 

6) 缺頁處理程式調入新的頁面,並更新記憶體中的PTE

 

7) 缺頁處理程式返回到原來程序,再次執行導致缺頁的指令

 

7.4 TLB與四級頁表支援下的VA到PA的變換

 

在這裡要用到翻譯後備緩衝器(TLB),每個VA被分為VPNVPO,每個VPN又分為三段,根據TLB標記(TLBT)和TLB索引(TLBI)到TLB中找對應的虛擬頁號(PPN),找到的PPN+VPO即為實體地址。

 

 

 

TLB模式下,如果能直接命中即可直接得到PPN,若發生缺頁則要去頁表裡再進行查詢,VPN被分為了4段,在查詢時通過每一級頁表一級一級往下找,最後找到相應的PPN,加上虛擬頁面偏移量VPO即可得到實體地址。

 

7.5 三級Cache支援下的實體記憶體訪問

 

在三級cache下,將實體地址分成CT(標記)+CI(索引)+CO(偏移量)首先在一級cache下找,若發生不命中miss則到下一級快取即二級cache下找,若不命中則到三級cache下訪問。

 

 

 

7.6 hello程序fork時的記憶體對映

 

fork函式被當前程序呼叫時,核心為新程序建立各種資料結構,並分配給它一個唯一的PID。為了給這個新程序建立虛擬記憶體,它建立了當前程序的mm_struct、區域結構和頁表的原樣副本。它將兩個程序中的每個頁面都標記為只讀,並將兩個程序中的每個區域結構都標記為私有的寫時複製。

 

fork在新程序中返回時,新程序現在的虛擬記憶體剛好和呼叫fork時存在的虛擬記憶體相同,當這兩個程序中的任一個後來進行寫操作時,寫時複製機制就會建立新頁面,因此,也就為每個程序保持了私有地址空間。

 

7.7 hello程序execve時的記憶體對映

 

execve函式在當前程序中載入並執行新程式a.out時:

 

·刪除已存在的使用者區域。刪除當前程序虛擬地址的使用者部分中的已存在的區域結構。

 

·建立新的區域結構,這些新的區域都是私有的、寫時複製的,程式碼和初始化資料對映到.text.data區,.bss和棧堆對映到匿名檔案。

 

·對映共享區域。如果a.out程式與共享物件連結,那麼這些物件都是動態連結到這個程式的,再對映到使用者虛擬地址空間中的共享區域內。

 

·設定程式計數器(PC)。設定當前程序上下文中的程式計數器,使之指向程式碼區域的入口點。

 

 

 

7.8 缺頁故障與缺頁中斷處理

 

當出現缺頁故障時,即DRAM快取不命中,此時呼叫缺頁處理程式,記憶體會確定一個犧牲頁,若頁面被修改,則換出到磁碟,再將新的目標頁替換犧牲頁寫入,缺頁處理程式返回到原來的程序,重啟導致缺頁的指令。

 

7.9動態儲存分配管理

 

7.9.1 隱式空閒連結串列

 

隱式空閒連結串列通過頭部中的大小欄位隱含地連線所有塊

 

 

 

7.9.2 顯式空閒連結串列

 

顯式空閒連結串列在空閒塊中使用指標連線空閒塊。它將空閒塊組織為某種形式的顯式資料結構,只保留空閒塊連結串列,而不是所有塊。在每個空閒塊中,都包含一個前驅(pred)和後繼(succ)指標。

 

 

 

維護顯式空閒連結串列有兩種方式,分別為後進先出(LIFO)和按照地址順序來維護。後進先出的順序維護將新釋放的塊放置在連結串列的開始處,使用LIFO的順序和首次適配的放置策略,分配器會最先檢查最近使用過的塊。按照地址順序來維護連結串列時,連結串列中每個塊的地址都小於它的後繼的地址,按照地址排序的首次適配比LIFO排序的首次適配有更高的記憶體利用率,接近最佳適配的利用率。

 

7.9.3 尋找空閒塊

 

·首次適配 (First fit):從頭開始搜尋空閒連結串列,選擇第一個合適的空閒塊。

 

·下一次適配 (Next fit):從連結串列中上一次查詢結束的地方開始搜尋空閒連結串列,選擇第一個合適的空閒塊。

 

·最佳適配 (Best fit):查詢連結串列,選擇一個最好的空閒塊。

 

7.9.4 分配空閒塊——分割

 

當分配塊比空閒塊小時,我們可以把空閒塊分割成兩部分,並將多餘的空間重新加到空閒連結串列中,以減少內部碎片。

 

7.9.5 空閒塊的合併——帶邊界標記的合併

 

合併後面的空閒塊時,當前塊的頭部指向下一個塊的頭部,可以檢查這個指標以判斷下一個塊是否是空閒的,如果是,就將它的大小簡單地加到當前塊頭部的大小上,使得兩個塊在常數的時間內被合併。

 

合併前面的空閒塊時,在每個塊的結尾處新增一個腳部,它是頭部的一個副本,分配器可以通過檢查它的腳部判斷前一個塊的起始狀態和狀態。

 

 

 

7.10本章小結

 

本章簡述了在計算機中的虛擬記憶體管理,虛擬地址、實體地址、線性地址、邏輯地址的區別以及它們之間的變換模式,以及段式、頁式的管理模式,在瞭解了記憶體對映的基礎上重新認識了共享物件、forkexecve,同時認識了動態記憶體分配的方法與原理。

 

 

8 hello的IO管理

 

8.1 Linux的IO裝置管理方法

 

裝置的模型化:所有IO裝置都被模型化為檔案,所有的輸入和輸出都能被當做相應檔案的讀和寫來執行。

 

裝置管理:Linux核心有一個簡單、低階的介面,成為Unix I/O,是的所有的輸入和輸出都能以一種統一且一致的方式來執行。

 

8.2 簡述Unix IO介面及其函式

 

開啟檔案: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的實現分析

 

printf接受一個格式化的命令,並把指定的匹配的引數格式化輸出

 

printf中呼叫了兩個函式:vsprintwrite

 

觀察vsprintf它接受確定輸出格式的格式字串fmt,將所有引數內容格式化後存入buf,返回格式化陣列的長度

 

觀察write,它將buf中的i個元素的值寫到終端

 

vsprintf生成顯示資訊,到write系統函式,到陷阱-系統呼叫 int 0x80syscall.

 

字元顯示驅動子程式:從ASCII到字模庫到顯示vram(儲存每一個點的RGB顏色資訊)。

 

顯示晶片按照重新整理頻率逐行讀取vram,並通過訊號線向液晶顯示器傳輸每一個點(RGB分量)。

 

8.4 getchar的實現分析

 

getchar呼叫read函式返回字元,read將整個快取區內容讀入buf,然後返回緩衝區長度。然後對buf的長度進行判斷,若buf長度為0,則呼叫read函式,否則直接返回buf前面的元素。

 

非同步異常-鍵盤中斷的處理:鍵盤中斷處理子程式。接受按鍵掃描碼轉成ascii碼,儲存到系統的鍵盤緩衝區。

 

getchar等呼叫read系統函式,通過系統呼叫讀取按鍵ascii碼,直到接受到回車鍵才返回。

 

8.5本章小結

 

本章節簡述了Linux系統下I/O的機制,瞭解了有關開啟、關閉與讀寫檔案的操作,分析了printfgetchar這兩個我們常用的函式的實現過程。

 

 

結論

 

hello首先在計算機內被其他程式進行了一番翻譯,經過預處理變為hello.i,再經過編譯器變為彙編程式hello.s,再經過彙編器變為可重定位的二進位制目標程式hello.o,然後經過連結器生成hello可執行的二進位制目標程式;

 

shell中經過了forkexecve,把hello載入到其中;

 

然後在磁碟中去讀取它,在執行的過程中接受鍵盤的訊號shell對其做出不同的處理,對映虛擬記憶體進行訪問,進行動態記憶體分配;

 

最後程序終止,被shell回收。

 

Hello一路走來很不容易吶,我們在面前只看到了它簡單的執行結果,但其實後面有編譯器彙編器連結器等等在一起工作,使它能完美的呈現在我們面前^ ^

 

寫完大作業對Linux下的各種命令更加熟悉了,學習了一下edb的使用,雖然感覺它不如gdb好用(

 

計算機系統是個很神奇很偉大的東西,它需要我們花更多的時間和精力去深入理解它……光是這學期學的知識還遠遠不夠,以後也要深入理解計算機系統哇

/*

所有圖片沒法複製。。要一個一個慢慢插入qwq

別急啊別急啊窩忙完這波就插圖片順便好好排個版嚶嚶嚶

*/