用Hello's P2P實例一文帶你徹底看懂linux(預(yù)處理-編譯-鏈接+進(jìn)程/存儲/IO管理)全過程

linux.png

摘 要

關(guān)鍵詞:LINUX;預(yù)處理;編譯;鏈接;進(jìn)程管理;存儲管理;IO管理

本文的主要內(nèi)容是介紹了在linux環(huán)境下hello程序從預(yù)處理到編譯再到鏈接,最后執(zhí)行的全過程以及進(jìn)程管理,存儲管理及IO管理的實現(xiàn)方式。本文結(jié)合hello程序的生成詳細(xì)地講述了預(yù)處理、編譯、匯編、進(jìn)程管理、存儲管理、IO管理的概念、作用、命令等。本文的目的是幫助程序員了解在C語言的“外衣”下,程序是如何從產(chǎn)生、預(yù)處理、編譯、匯編,到最后被執(zhí)行的。對于深入了解操作系統(tǒng)和計算機(jī)編譯原理具有重要意義。

第1章 概述

1.1 Hello簡介

根據(jù)Hello的自白,利用計算機(jī)系統(tǒng)的術(shù)語,簡述Hello的P2P,020的整個過程。

P2P: Hello.c經(jīng)過cpp的預(yù)處理,ccl的編譯、as的匯編、ld的鏈接最終成為可執(zhí)行目標(biāo)程序Hello,在shell中鍵入啟動命令后,shell通過fork產(chǎn)生子進(jìn)程, hello便從program變成了process

020: shell通過execve映射虛擬內(nèi)存,進(jìn)入程序入口后載入物理內(nèi)存,然后進(jìn)入 main函數(shù)執(zhí)行目標(biāo)代碼,CPU為運行的hello分配時間片執(zhí)行邏輯控制流,當(dāng)結(jié)束后,shell父進(jìn)程負(fù)責(zé)回收hello進(jìn)程

1.2 環(huán)境與工具

列出你為編寫本論文,折騰Hello的整個過程中,使用的工具

軟件環(huán)境:Visual studio Community2017, Windows10 64位, Vmware 14;Ubuntu 16.04 LTS 64位;

硬件環(huán)境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

開發(fā)與調(diào)試工具:vim,gcc,as,ld,edb,readelf,HexEdit
GDB/OBJDUMP;DDD/EDB等

1.3 中間結(jié)果

列出你為編寫本論文,生成的中間結(jié)果文件的名字,文件的作用等。

文件名稱 文件屬性
hello.i 預(yù)處理得到的文本文件
hello.s 編譯后的匯編文件
hello.o 匯編后的可重定位目標(biāo)執(zhí)行
hello 鏈接之后的可執(zhí)行目標(biāo)文件
hello.objdmp hello.o的反匯編代碼
hello.elf hello.o的ELF格式
hello1.elf hello的 ELF格式
hello.objdmp hello的反匯編代碼
hello.txt 分析重定位過程時的可執(zhí)行文件hello反匯編代碼

1.4 本章小結(jié)

本章介紹根據(jù)Hello的自白,利用計算機(jī)系統(tǒng)的術(shù)語,簡述Hello的P2P,020的整個過程。列出使用的軟硬件環(huán)境和開發(fā)與調(diào)試工具。列出了生成的中間結(jié)果文件的名字,文件的作用等

第2章 預(yù)處理

2.1 預(yù)處理的概念與作用

2.11 概念:預(yù)處理一般是指在程序源代碼被翻譯為目標(biāo)代碼的過程中,生成二進(jìn)制代碼之前的過程。由預(yù)處理器對程序源代碼文本進(jìn)行處理,把源代碼分割或處理成為特定的單位,得到的結(jié)果再由編譯器核心進(jìn)一步編譯。這個過程并不對程序的源代碼進(jìn)行解析。

2.12 作用
1: 宏定義。宏定義是用一個標(biāo)識符來表示一個字符串,這個字符串可以是常量、變量或表達(dá)式。在宏調(diào)用中將用該字符串代換宏名。
2:文件包含。文件包含是預(yù)處理的一個重要功能,它可用來把多個源文件連接成一個源文件進(jìn)行編譯,結(jié)果將生成一個目標(biāo)文件。
3:條件編譯。條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標(biāo)程序較短,從而減少了內(nèi)存的開銷并提高了程序的效率。

2.2 在Ubuntu下預(yù)處理的命令

預(yù)處理命令:
gcc –E hello.c > hello.i

圖2.2 在Ubuntu下預(yù)處理的命令

圖2.2 在Ubuntu下預(yù)處理的命令

2.3 Hello的預(yù)處理結(jié)果解析

在這里插入圖片描述

圖2.3 hello.i文件

用文本編輯器打開hello.i,main函數(shù)的預(yù)處理解析結(jié)果如上圖。
在main函數(shù)前出現(xiàn)的是stdio.h unistd.h stdlib.h頭文件。
.i程序中是沒有#define的,并使用了大量的#ifdef #ifndef的語句。
預(yù)處理指令會對條件值進(jìn)行判斷來決定是否執(zhí)行包含其中的邏輯。

2.4 本章小結(jié)

本章介紹了預(yù)處理的概念與作用、命令,并展示了Hello的預(yù)處理結(jié)果解析。

第3章 編譯

3.1 編譯的概念與作用

  1. 編譯的概念:利用編譯程序從源語言編寫的源程序產(chǎn)生目標(biāo)程序的過程,用編譯程序產(chǎn)生目標(biāo)程序。 編譯程序把一個源程序翻譯成目標(biāo)程序的工作過程分為五個階段:詞法分析;語法分析;語義檢查和中間代碼生成;代碼優(yōu)化;目標(biāo)代碼生成。
  2. 編譯的作用:把高級語言變成計算機(jī)可以識別的2進(jìn)制語言,詞法分析、語法分析、語義檢查和中間代碼生成、代碼優(yōu)化、目標(biāo)代碼生成。

3.2 在Ubuntu下編譯的命令

gcc -S hello.i -o hello.s
在這里插入圖片描述

圖3.2 在Ubuntu下編譯的命令

3.3 Hello的編譯結(jié)果解析

在linux用文本編輯器打開hello.s查看編譯結(jié)果

數(shù)據(jù)段 作用
.text 已編譯程序的機(jī)器代碼
.rodata 只讀數(shù)據(jù)
.data 已初始化的全局C變量
.bss 未初始化和初始化為0的全局C變量。在目標(biāo)文件中這個節(jié)不占據(jù)實際的空間,僅僅是一個占位符
.symtab 一個符號表,它存放在程序中定義和引用的函數(shù)和全局變量的信息
.rel.text 一個.text節(jié)中位置的列表
.rel.data 被模塊引用或定義的任何全局變量的重定位信息
.debug 一個調(diào)試符號表。只有以-g選項調(diào)用編譯驅(qū)動程序才會得到這張表
.line 原始C源程序中的行號和.text節(jié)中機(jī)器指令之間的映射。只有以-g選項調(diào)用編譯驅(qū)動程序時才會得到這張表
.strtab 一個字符串表,內(nèi)容包括.symtab和.debug節(jié)中的符號表,以及節(jié)頭部中的節(jié)名字。

字符串表是以null結(jié)尾的字符串序列

3.31 數(shù)據(jù)

(1)字符串:


在這里插入圖片描述

圖3.311 字符串 <div>

(2)整數(shù) sleepsecs


在這里插入圖片描述

圖3.312 整數(shù) sleepsecs

3.32 賦值

(1) 全局變量sleepsecs =2


在這里插入圖片描述

圖3.321

(2) 局部變量i =0


在這里插入圖片描述

圖3.322 局部變量i =0

3.33 類型轉(zhuǎn)換

隱式類型轉(zhuǎn)換的是:int sleepsecs=2.5,將浮點數(shù)類型的2.5轉(zhuǎn)換為int類型

3.34 算術(shù)操作

在這里插入圖片描述

圖3.340 算術(shù)操作符號

(1) 相加操作

addq    $16, %rax
addq    $8, %rax
在這里插入圖片描述

(2) 相減操作

subq    $32, %rsp

在這里插入圖片描述

3.35 控制轉(zhuǎn)移
在這里插入圖片描述

圖3.340 指令助記符

(1)比較i<10是否成立,若成立繼續(xù)循環(huán),否則退出循環(huán)


在這里插入圖片描述

圖3.341 i<10對應(yīng)的匯編代碼

3.36 函數(shù)操作

a) int main(int argc, char *argv[])

(1)參數(shù)傳遞:從內(nèi)核中獲取命令行參數(shù)和環(huán)境變量地址

(2)函數(shù)調(diào)用:內(nèi)核執(zhí)行程序時調(diào)用特殊的啟動例程,執(zhí)行main函數(shù)

(3)函數(shù)返回:當(dāng)命令行參數(shù)數(shù)量不為3時輸出提示信息并調(diào)用exit(1)退出main函數(shù);當(dāng)命令行參數(shù)數(shù)量為3執(zhí)行循環(huán)和getchar函數(shù)后return 0的方式退出函數(shù)。

argc: 傳給main()的命令行參數(shù)個數(shù)
argv: 命令行參數(shù)字符型指針數(shù)組的首地址

b) exit()

(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)

(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()

(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1

c) printf()

在這里插入圖片描述

圖3.363 printf匯編代碼

(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1

d) sleep()


在這里插入圖片描述

圖3.364 sleep匯編代碼

(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1

e) getchar()


在這里插入圖片描述

圖3.365 getchar匯編代碼

(1)參數(shù)傳遞:getchar()函數(shù)無參數(shù)
(2)函數(shù)傳遞:main函數(shù)通過call指令調(diào)用getchar()
(3)函數(shù)返回:返回值類型為int,如果成功返回用戶輸入的ASCII碼,出錯返回-1

3.37關(guān)系操作

(1)argc!=3


在這里插入圖片描述

圖3.371 !=匯編代碼

(2)i<10


在這里插入圖片描述

圖3.371 <匯編代碼

3.4 本章小結(jié)

本章介紹了編譯的概念與作用,展示了編譯命令的使用,對編譯結(jié)果解析,并說明編譯器處理C語言的各個數(shù)據(jù)類型以及各類操作的過程。

第4章 匯編

4.1 匯編的概念與作用

  1. 概念:把匯編語言翻譯成機(jī)器語言的過程稱為匯編。在匯編語言中,用助記符代替操作碼,用地址符號或標(biāo)號代替地址碼。通過用符號代替機(jī)器語言的二進(jìn)制碼,可以把機(jī)器語言變成匯編語言。
  2. 作用:將匯編語言翻譯成機(jī)器語言。

編譯 VS 匯編
編譯:將高級語言程序變成計算機(jī)能識別的二進(jìn)制語言
匯編:將匯編語言翻譯成機(jī)器語言

4.2 在Ubuntu下匯編的命令

匯編的命令:as hello.s -o hello.o

在這里插入圖片描述

圖4.2 在Ubuntu下匯編的命令

4.3 可重定位目標(biāo)elf格式

分析hello.o的ELF格式,用readelf等列出其各節(jié)的基本信息,特別是重定位項目分析。

(1) ELF頭


在這里插入圖片描述

圖4.311 ELF頭

ELF頭包括一個16字節(jié)的序列、ELF頭的大小、目標(biāo)文件的類型(如可重定位、可執(zhí)行或共享的)、機(jī)器類型(如x86-64)、
節(jié)頭部表(section header table)的文件偏移,以及節(jié)頭部表中條目的大小和數(shù)量。
其結(jié)構(gòu)體表示:

define EI_NIDENT 16

typedef struct{
  unsigned char e_ident[EI_NIDENT];
  Elf32_Half e_type;
  Elf32_Half e_machine;
  Elf32_Word e_version;
  Elf32_Addr e_entry;
  Elf32_Off e_phoff;
  Elf32_Off e_shoff;
  Elf32_Word e_flags;
  Elf32_Half e_ehsize;
  Elf32_Half e_phentsize;
  Elf32_Half e_phnum;
  Elf32_Half e_shentsize;
  Elf32_Half e_shnum;
  Elf32_Half e_shstrndx;
}Elf32_Ehdr;

數(shù)據(jù)格式:
在這里插入圖片描述

圖4.312 數(shù)據(jù)格式

(2) 節(jié)頭部表:文件中出現(xiàn)的各個節(jié)的語義,包括節(jié)的類型、位置和大小


在這里插入圖片描述

圖4.32 節(jié)頭部表

根據(jù)節(jié)頭部表可知,當(dāng)號=1,符號在.text;當(dāng)號=3,符號在.data,以此類推。
三個特殊偽節(jié):
ABS:不該被重定位的符號,如main()函數(shù)。
UND:其它文件中定義,本文件中引用的符號,如swap()函數(shù)。
COM:還未分配位置的未初始化數(shù)據(jù)目標(biāo),如buf2,它最終放在.bss。
(3) 重定位節(jié)

(a)普通重定位由以下數(shù)據(jù)結(jié)構(gòu)定義:

typedef struct
{
Elf32_Addr r_offset; //指定需要重定位的項的位置
Elf32_Word r_info; //提供了符號表中的一個位置,包括重定位類型信息。
r_info == int symbol:24,type:8;
} Elf32_Rel;

(b)在ELF定義了32種不同的重定位類型,其中最基本的兩種是:

R X86_ 64 PC32。 重定位一個使用32位PC相對地址的引用。一個PC相對地址就是距程序計數(shù)器(PC)的當(dāng)前運行時值的偏移量。當(dāng)CPU執(zhí)行一條使用PC相對尋址的指令時,它就將在指令中編碼的32位值加上PC的當(dāng)前運行時值,得到有效地址(如call指令的目標(biāo)),PC值通常是下一條指令在內(nèi)存中的地址。
R X86_ 64 _32。 重定位一個使用32位絕對地址的引用。通過絕對尋址,CPU直接使用在指令中編碼的32位值作為有效地址,不需要進(jìn)一步修改。

(c)代碼重定位條目放在.rel.text中。已經(jīng)初始化數(shù)據(jù)的重定位條目放在.rel.data中。
main.c源文件引用了一個全局sleepsecs符號。
sleepsecs的重定位類型為相對重定位
并且由圖4.33(1)可以得到:sleepsecs的r_offset : 000000000060重定位的字節(jié)處, 由圖4.33(2)可以得到:sleepsecs的大小為4個字節(jié)
計算sleepsecs的重定位后的地址:Result = S-P+A
A代表加數(shù)值,S是符號表中保存的符號的值,P代表重定位的位置偏移量


在這里插入圖片描述

圖4.331重定位節(jié)

在這里插入圖片描述

圖4.332

(4) 符號表:存放著程序中定義和引用函數(shù)和全局變量的信息,不包含局部變量的條目


在這里插入圖片描述

圖4.34 符號表

Value:在對應(yīng)節(jié)的偏移。
Size:目標(biāo)大小。
Type:是數(shù)據(jù)或函數(shù)。
Bind:本地或全局。
Vis:預(yù)留。
Ndx:符號所在的節(jié),其實是節(jié)頭部表中條目的索引。
Name:符號名,為空的為鏈接器內(nèi)部使用的本地符號,可以忽略。

4.4 Hello.o的結(jié)果解析

用命令行得到,比較hello.objdump與hello.o,進(jìn)行對照分析


在這里插入圖片描述

圖4.40 命令行

(1) hello.objdump記錄了文件格式和.text代碼段:
而hello.s中除了記錄了文件格式和.text代碼段還包括.type .size .align以及.rodata


在這里插入圖片描述

圖4.41 hello.objdump與hello.s文件內(nèi)容對比

(2) 分支轉(zhuǎn)移:

hello.objdump跳轉(zhuǎn)中地址為已確定的實際指令地址;
hello.s跳轉(zhuǎn)中地址為助記符如.L2,通過使用例如.L2等的助記符進(jìn)行跳轉(zhuǎn)。


在這里插入圖片描述

圖4.42 hello.objdump與hello.s分支轉(zhuǎn)移對比
(3)函數(shù)調(diào)用
在.s文件中,call的地址是函數(shù)名稱,如puts@PLT,
而在反匯編程序中,call的目標(biāo)地址是指令,如callq 21 <main+0x21>。因為hello.c中調(diào)用的函數(shù)都是共享庫中的函數(shù),共享庫函數(shù)調(diào)用需要通過鏈接時重定位才能確定地址


在這里插入圖片描述

圖4.43 hello.objdump與hello.s函數(shù)puts調(diào)用對比
(4)全局變量訪問
hello.objdump使用0+%rip訪問全局變量sleepsecs,如lea 0x0(%rip),%rdi。hello.s使用段名稱+%rip訪問全局變量sleepsecs,如leaq .LC0(%rip), %rdi
在這里插入圖片描述

圖4.44 hello.objdump與hello.s全局變量sleepsecs訪問對比

4.5 本章小結(jié)

本章介紹了匯編的概念與作用,在linux下進(jìn)行匯編的指令,可重定位目標(biāo)文件elf的格式,將hello.o的結(jié)果解析與hello.s進(jìn)行對照分析,分析了機(jī)器語言的構(gòu)成以及與匯編語言的映射關(guān)系。

第5章 鏈接

5.1 鏈接的概念與作用

鏈接的概念:Linux 鏈接分兩種,一種被稱為硬鏈接(Hard Link),另一種被稱為符號鏈接(Symbolic Link)。默認(rèn)情況下,ln 命令產(chǎn)生硬鏈接。
(1)硬連接指通過索引節(jié)點來進(jìn)行連接。在 Linux 的文件系統(tǒng)中,保存在磁盤分區(qū)中的文件不管是什么類型都給它分配一個編號,稱為索引節(jié)點號(Inode Index)。在 Linux 中,多個文件名指向同一索引節(jié)點是存在的。
(2)軟連接。軟鏈接文件是一個特殊的文件。在符號連接中,文件實際上是一個文本文件,其中包含的有另一文件的位置信息。
作用:鏈接操作給系統(tǒng)中已有的某個文件指定另外一個可用于訪問它的名稱。我們可以為這個新的文件名指定不同的訪問權(quán)限。鏈用戶可以利用鏈接直接進(jìn)入被鏈接的目錄。即使刪除這個鏈接,也不會破壞原來的目錄。硬連接的作用是允許一個文件擁有多個有效路徑名,用戶就可以建立硬連接到重要文件,以防止“誤刪”的功能。

5.2 在Ubuntu下鏈接的命令

使用ld的鏈接命令,應(yīng)截圖,展示匯編過程! 注意不只連接hello.o文件
鏈接的命令:ld -o OUTPUT /lib/crt0.o hello.o –lc
鏈接的命令行: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 在Ubuntu下鏈接的命令

5.3 可執(zhí)行目標(biāo)文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用命令行readelf -a hello > hello1.elf生成hello1.elf文件
節(jié)頭表中包含了各段的起始地址,大小等信息。

在這里插入圖片描述

圖5.31 節(jié)頭表


在這里插入圖片描述

圖5.32 節(jié)頭表

5.4 hello的虛擬地址空間

使用edb加載hello,查看本進(jìn)程的虛擬地址空間各段信息,并與5.3對照分析說明。

(1) 分析程序頭部表。

PHDR:程序頭表
INTERP:程序執(zhí)行前需要調(diào)用的解釋器
LOAD:程序目標(biāo)代碼和常量信息
DYNAMIC:動態(tài)鏈接器所使用的信息
NOTE::輔助信息
GNU_EH_FRAME:保存異常信息
GNU_STACK:使用系統(tǒng)棧所需要的權(quán)限信息
GNU_RELRO:保存在重定位之后只讀信息的位置
VirtAddr:本段首字節(jié)的虛擬地址
PhysAddr指出本段首字節(jié)的物理地址
pFileSiz指出本段在文件中所占的字節(jié)數(shù),可以為0
MemSiz指出本段在存儲器中所占字節(jié)數(shù),可以為0
Flags指出存取權(quán)限,Align指出對齊方式


在這里插入圖片描述

圖5.41 程序頭部表

(2) 在edb查看hello的虛擬地址空間的各段信息


在這里插入圖片描述

圖5.42 hello的虛擬地址空間

(3) 程序頭與Datadump的映射關(guān)系:例如PHDR對應(yīng)的虛擬內(nèi)存地址是0x400000—— 0x4001c0


在這里插入圖片描述

圖5.43 程序頭與Datadump的映射關(guān)系

5.5 鏈接的重定位過程分析

通過命令行objdump –d –r hello > hello.txt得到反匯編文件hello.txt。

(1) hello的反匯編結(jié)果與hello.o的反匯編結(jié)果相比,hello.txt多了以下節(jié)頭表:

_init 程序初始化代碼
gmon_start call_gmon_start函數(shù)初始化
gmon profiling system,程序通過gprof可以輸出函數(shù)調(diào)用等信息
_dl_relocate_static_pie 靜態(tài)庫鏈接
.plt 動態(tài)鏈接-過程鏈接表
Puts(等函數(shù))@plt 動態(tài)鏈接各個函數(shù)
_start 編譯器為可執(zhí)行文件加上了一個啟動例程
__libc_csu_init 程序調(diào)用libc庫用來對程序進(jìn)行初始化的函數(shù),一般先于main函數(shù)執(zhí)行
_fini 當(dāng)程序正常終止時需要執(zhí)行的代碼

(2) 函數(shù)個數(shù):在使用ld命令鏈接的時候,指定了動態(tài)鏈接器為64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定義了程序入口_start、初始化函數(shù)_init,_start程序調(diào)用hello.c中的main函數(shù),libc.so是動態(tài)鏈接共享庫,鏈接器加入了以下函數(shù)printf、sleep、getchar、exit函數(shù)和_start中調(diào)用的__libc_csu_init,__libc_csu_fini,__libc_start_main。

函數(shù)調(diào)用:鏈接器解析重定條目時發(fā)現(xiàn)對外部函數(shù)調(diào)用的類型為R_X86_64_PLT32的重定位,此時動態(tài)鏈接庫中的函數(shù)已經(jīng)加入到了PLT中,.text與.plt節(jié)相對距離已經(jīng)確定,鏈接器計算相對距離,將對動態(tài)鏈接庫中函數(shù)的調(diào)用值改為PLT中相應(yīng)函數(shù)與下條指令的相對地址,指向?qū)?yīng)函數(shù)。
rodata引用:鏈接器解析重定條目時發(fā)現(xiàn)兩個類型為R_X86_64_PC32的對.rodata的重定位(printf中的兩個字符串),.rodata與.text節(jié)之間的相對距離確定,因此鏈接器直接修改call之后的值為目標(biāo)地址與下一條指令的地址之差,指向相應(yīng)的字符串。

(3) 重定位過程。hello反匯編文件中對應(yīng)全局變量已通過重定位絕對引用被替換為固定地址。

5.6 hello的執(zhí)行流程

(以下格式自行編排,編輯時刪除)
使用edb執(zhí)行hello,說明從加載hello到_start,到call main,以及程序終止的所有過程。請列出其調(diào)用與跳轉(zhuǎn)的各個子程序名或程序地址。
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
libc-2.27.so!__cxa_atexit
libc-2.27.so!__libc_csu_init
hello!_init
libc-2.27.so!_setjmp
libc-2.27.so!_sigsetjmp
libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!sleep@plt
hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave
ld-2.27.so!_dl_fixup
ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit

5.7 Hello的動態(tài)鏈接分析

(1)編譯器無法確定動態(tài)鏈接庫中的函數(shù)地址,因為動態(tài)鏈接庫中的函數(shù)在程序執(zhí)行的時候才會確定地址。GNU編譯系統(tǒng)采用延遲綁定技術(shù)來解決動態(tài)庫函數(shù)模塊調(diào)用的問題。

(2)延遲綁定通過全局偏移量表(GOT)和過程鏈接表(PLT)實現(xiàn)。

(a)PLT是一個數(shù)組,其中每個條目是16字節(jié)代碼。每個庫函數(shù)都有自己的PLT條目,PLT[0]是一個特殊的條目,跳轉(zhuǎn)到動態(tài)鏈接器中。從PLT[2]開始的條目調(diào)用用戶代碼調(diào)用的函數(shù)。

(b)GOT同樣是一個數(shù)組,每個條目是8字節(jié)的地址,和PLT聯(lián)合使用時,GOT[2]是動態(tài)鏈接在ld-linux.so模塊的入口點,其余條目對應(yīng)于被調(diào)用的函數(shù),在運行時被解析。每個條目都有匹配的PLT條目。

(3)延遲綁定的實現(xiàn)步驟如下:
a.建立一個 GOT.PLT 表,用來放全局函數(shù)的實際地址
b.對每一個全局函數(shù),鏈接器生成一個與之相對應(yīng)的函數(shù),如 puts@plt。
c.所有的puts都換成對 puts@plt。

(4)下面分析在dl_init調(diào)用前后,項目的內(nèi)容的變化
a)dl_init調(diào)用前


在這里插入圖片描述

圖5.71 dl_init調(diào)用前GOT條目
b)dl_init調(diào)用后, GOT條目初始時指向其PLT條目的第二條指令的地址


在這里插入圖片描述

圖5.72 dl_init調(diào)用后GOT條目

5.8 本章小結(jié)

本章介紹了鏈接的概念作用,分析hello的ELF格式和虛擬地址空間,通過實例分析了hello的動態(tài)鏈接、執(zhí)行流程、重定位過程、加載以及運行時函數(shù)調(diào)用順序,深入理解鏈接和重定位的過程。

第6章 hello的進(jìn)程管理

6.1 進(jìn)程的概念與作用

進(jìn)程的概念:進(jìn)程是正在運行的程序的實例,是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。進(jìn)程是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進(jìn)程既是基本的分配單元,也是基本的執(zhí)行單元。
進(jìn)程的作用:進(jìn)程提供兩個假象,程序獨占地使用處理器和程序在獨占地使用系統(tǒng)內(nèi)存。

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

Shell-bash的作用:shell和其他軟件一樣都是和內(nèi)核打交道,直接服務(wù)于用戶。但和其他軟件不同,shell主要用來管理文件和運行程序。
處理流程:shell對命令行的處理流程
(1)讀取輸入的命令行。
(2)解析引用并分割命令行為各個單詞,其中重定向所在的單詞會被保存下來,直到擴(kuò)展步驟(5)結(jié)束后才進(jìn)行相關(guān)處理。
(3)檢查命令行結(jié)構(gòu)。
(4)對第一個單詞進(jìn)行別名擴(kuò)展。
(5)進(jìn)行各種擴(kuò)展。擴(kuò)展順序為:大括號擴(kuò)展;波浪號擴(kuò)展;參數(shù)、變量和命令替換、算術(shù)擴(kuò)展;單詞拆分;文件名擴(kuò)展。
(6)引號去除。
(7)搜索和執(zhí)行命令。
(8)返回退出狀態(tài)碼。

6.3 Hello的fork進(jìn)程創(chuàng)建過程

普通的系統(tǒng)調(diào)用,調(diào)用一次就返回一次,而fork()調(diào)用一次,會返回兩次,一次是父進(jìn)程,另一個是子進(jìn)程,互不干擾,調(diào)用的先后順序由操作系統(tǒng)的調(diào)度算法決定。子進(jìn)程永遠(yuǎn)返回0,父進(jìn)程則返回子進(jìn)程的ID。

fork的進(jìn)程圖為:


在這里插入圖片描述

圖6.3 fork的進(jìn)程圖

6.4 Hello的execve過程

execve 函數(shù)加載并運行可執(zhí)行目標(biāo)文件 filename, 且?guī)?shù)列表 argv 和環(huán)境變量列表 envp 。只有當(dāng)出現(xiàn)錯誤時,例如找不到 filename, execve 才會返回到調(diào)用程序。所以,與 fork 一次調(diào)用返回兩次不同, execve 調(diào)用一次并從不返回。

6.5 Hello的進(jìn)程執(zhí)行

結(jié)合進(jìn)程上下文信息、進(jìn)程時間片,闡述進(jìn)程調(diào)度的過程,用戶態(tài)與核心態(tài)轉(zhuǎn)換等等。

(1)上下文及上下文切換:進(jìn)程的物理實體(代碼和數(shù)據(jù)等)和支持進(jìn)程運行的環(huán)境。系統(tǒng)通過處理器調(diào)度讓處理器輪流執(zhí)行多個進(jìn)程,實現(xiàn)不同進(jìn)程中指令交替執(zhí)行的機(jī)制稱為進(jìn)程的上下文切換。

(2)進(jìn)程時間片:連續(xù)執(zhí)行同一個進(jìn)程的時間段稱為時間片

(3)用戶態(tài)與核心態(tài)轉(zhuǎn)換:處理器通過某個控制寄存器中的一個模式位來提供限制一個應(yīng)用可以執(zhí)行的指令以及它可以訪問的地址空間范圍的功能。當(dāng)設(shè)置了模式位時,進(jìn)程就運行在內(nèi)核模式中。沒有設(shè)置模式位時,進(jìn)程就運行在用戶模式中。

(4)Hello進(jìn)程調(diào)度的過程以及用戶態(tài)與核心態(tài)的轉(zhuǎn)換
調(diào)度是在進(jìn)程執(zhí)行的某些時刻,內(nèi)核可以決定搶占當(dāng)前進(jìn)程并重新開始一個先前被搶占了的進(jìn)程的決策。在切換的第一部分中,內(nèi)核代表進(jìn)程A在內(nèi)核模式下執(zhí)行指令。然后在某一時刻,shell加載可執(zhí)行目標(biāo)文件hello。在上下文切換之后,內(nèi)核代表進(jìn)程hello在用戶模式下執(zhí)行指令。之后進(jìn)程hello在用戶模式下運行,直到磁盤發(fā)出一個中斷信號,執(zhí)行一個從進(jìn)程hello到進(jìn)程A的上下文切換,將控制返回給進(jìn)程A,進(jìn)程A繼續(xù)運行,直到下一次異常發(fā)生。


在這里插入圖片描述

圖6.5 Hello進(jìn)程調(diào)度的過程以及用戶態(tài)與核心態(tài)的轉(zhuǎn)換

6.6 hello的異常與信號處理

hello執(zhí)行過程中會出現(xiàn)哪幾類異常,會產(chǎn)生哪些信號,又怎么處理的。
程序運行過程中可以按鍵盤,如不停亂按,包括回車,Ctrl-Z,Ctrl-C等,Ctrl-z后可以運行ps jobs pstree fg kill 等命令,請分別給出各命令及運行結(jié)截屏,說明異常與信號的處理。
Hello執(zhí)行過程出現(xiàn)的異常為:中斷、故障
會產(chǎn)生的信號為:SIGSTP 來自終端的停止信號,SIGINT 來自鍵盤的中斷

(1) 正常終止


在這里插入圖片描述

圖6.61 正常終止

(2) Ctrl + C
當(dāng)按下ctrl-c之后,shell父進(jìn)程收到SIGINT信號,信號處理程序結(jié)束hello,并回收hello進(jìn)程。


在這里插入圖片描述

圖6.62 按Ctrl + C時

(3) Ctrl + Z
當(dāng)按下ctrl-z之后,
(a)shell父進(jìn)程收到SIGSTP信號,
(b)信號處理程序打印并將hello進(jìn)程掛起,
(c)通過ps命令看到hello進(jìn)程沒有被回收,
通過jobs命令看到hello進(jìn)程的號為1,
通過pstree命令可以看出:之后調(diào)用fg 1將其調(diào)到前臺,執(zhí)行相應(yīng)命令行


在這里插入圖片描述

圖6.63 按Ctrl + Z時

(4) 中途亂按
中途亂按不導(dǎo)致異常和產(chǎn)生信號


在這里插入圖片描述

圖6.64 中途亂按時

6.7 本章小結(jié)

本章首先介紹了進(jìn)程的概念與作用,并簡述殼Shell-bash的作用與處理流程,講解了Hello的fork進(jìn)程創(chuàng)建過程和execve過程,以及Hello的進(jìn)程是如何執(zhí)行的,如何處理hello的異常與產(chǎn)生的信號

第7章 hello的存儲管理

7.1 hello的存儲器地址空間

(1) 邏輯地址:是指由程式產(chǎn)生的和段相關(guān)的偏移地址部分。
在反匯編hello得到的調(diào)用puts函數(shù)的指令是call 21<main + 0x21>,邏輯地址是[puts的代碼的段標(biāo)識符:21<main + 0x21>]


在這里插入圖片描述

圖7.1 puts函數(shù)的地址
(2) 線性地址:是邏輯地址到物理地址變換之間的中間層。程式代碼會產(chǎn)生邏輯地址,或說是段中的偏移地址,加上相應(yīng)段的基地址就生成了一個線性地址。如果啟用了分頁機(jī)制,那么線性地址能再經(jīng)變換以產(chǎn)生一個物理地址。若沒有啟用分頁機(jī)制,那么線性地址直接就是物理地址。
(3)虛擬地址:也叫線性地址,是一個不真實的地址。
(4)物理地址:是指出目前CPU外部地址總線上的尋址物理內(nèi)存的地址信號,是地址變換的最終結(jié)果地址,用于內(nèi)存芯片級的單元尋址,與地址總線相對應(yīng)。

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

在這里插入圖片描述

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

1)基本原理。
在段式存儲管理中,將程序的地址空間劃分為若干個段,在段式存儲管理系統(tǒng)中,為每個段分配一個連續(xù)的分區(qū),而進(jìn)程中的各個段可以不連續(xù)地存放在內(nèi)存的不同分區(qū)中。程序加載時,操作系統(tǒng)為所有段分配其所需內(nèi)存,物理內(nèi)存的管理采用動態(tài)分區(qū)的管理方法。在為某個段分配物理內(nèi)存時,可以采用首先適配法、下次適配法、最佳適配法等方法。在回收某個段所占用的空間時,要注意將收回的空間與其相鄰的空間合并。段式存儲管理也需要硬件支持,實現(xiàn)邏輯地址到物理地址的映射。
2)段式管理的數(shù)據(jù)結(jié)構(gòu)。
為了實現(xiàn)段式管理,操作系統(tǒng)需要進(jìn)程段表、系統(tǒng)段表和空閑段表來實現(xiàn)進(jìn)程的地址空間到物理內(nèi)存空間的映射。
3)段式管理的地址變換。
在段式管理系統(tǒng)中,其邏輯地址由段號和段內(nèi)地址兩部分組成。處理器會查找內(nèi)存中的段表,由段號得到段的首地址,加上段內(nèi)地址,得到實際的物理地址,從而完成邏輯地址到物理地址的映射。

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

(1)頁式存儲管理的基本原理:
1)分頁存儲器將主存劃分成多個大小相等的頁架;
2)程序的邏輯地址分成頁;
3)不同的頁可以放在不同頁架中,不需要連續(xù)
4)頁表用于維系進(jìn)程的主存完整性


在這里插入圖片描述

圖7.31 進(jìn)程頁表
(2)頁式存儲管理的邏輯地址由兩部分組成:


在這里插入圖片描述

圖7.32頁式存儲管理的邏輯地址
(3) 頁式存儲管理的物理地址由兩部分組成:
在這里插入圖片描述

圖7.33 頁式存儲管理的物理地址
(4) 頁式存儲管理的地址轉(zhuǎn)換思路:
在這里插入圖片描述

圖7.34 從邏輯地址映射到物理地址
(5) 頁的共享:頁式存儲管理能夠?qū)崿F(xiàn)多個進(jìn)程共享程序和數(shù)據(jù),包括數(shù)據(jù)共享和程序共享
(6)頁式虛擬存儲管理的基本思想:把進(jìn)程全部頁面裝入虛擬存儲器,執(zhí)行時先把部分頁面裝入實際內(nèi)存,然后根據(jù)執(zhí)行行為,動態(tài)調(diào)入不在主存的頁,同時進(jìn)行必要的頁面調(diào)出
7.4 TLB與四級頁表支持下的VA到PA的變換


在這里插入圖片描述

圖7.4 VA到PA的映射過程
(1) 首先介紹以下VA和PA。VA:virtual address稱為虛擬地址,PA:physical address稱為物理地址。MMU是內(nèi)存管理單元。MMU將VA翻譯成為PA發(fā)到CPU芯片的外部地址引腳上,也就是將VA映射到PA中。MMU將VA映射到PA是以頁為單位的,對于32位的CPU,通常一頁為4k,物理內(nèi)存中的一個物理頁面稱頁為一個頁框。
(2) TLB與四級頁表支持下的VA到PA的變換和TLB與二級頁表支持下的VA到PA的變換的原理相同,為了結(jié)合實例分析,下面介紹二級頁表的變換。如圖7.4,首先將CPU內(nèi)核發(fā)送過來的32位VA[31:0]分成三段,前兩段VA[31:20]和VA[19:12]作為兩次查表的索引,第三段VA[11:0]作為頁內(nèi)的偏移,查表的步驟如下:
a)從協(xié)處理器CP15的寄存器2(TTB寄存器)中取出保存在其中的第一級頁表的基地址PA
b)以TTB中的內(nèi)容為基地址,以VA[31:20]為索引值在一級頁表中查找出一項,一級頁表中保存著第二級頁表的基地址。
c)以VA[19:12]為索引值在第二級頁表中查出一項,第二級頁表中保存著物理頁面的基地址,從這里可以印證一個虛擬內(nèi)存的頁映射到一個物理內(nèi)存的頁框,因為查表是以頁為單位來查的。
d)有了物理頁面的基地址之后,加上VA[11:0]偏移量就可以取出相應(yīng)地址上的數(shù)據(jù)

7.5 三級Cache支持下的物理內(nèi)存訪問

(以下格式自行編排,編輯時刪除)


在這里插入圖片描述

圖7.5 CPU訪問內(nèi)存時的硬件操作順序
(1) 以VA為索引到cache中查找是否緩存了要讀取的數(shù)據(jù),如果cache中已經(jīng)緩存了該數(shù)據(jù)則直接返回給CPU內(nèi)核,如果cache中沒有緩存該數(shù)據(jù),則發(fā)出PA從物理內(nèi)存中讀取數(shù)據(jù)并緩存到cache中,同時返回給CPU內(nèi)核。cache不只是緩存CPU內(nèi)核所需要的數(shù)據(jù),同時緩存相鄰的數(shù)據(jù)。
(2) 高速緩存確定一個請求是否命中,然后抽取出被請求的字的過程,分為三步,(1)組選擇、(2)行匹配、(3)字抽取。
下面是物理內(nèi)存的讀策略和寫策略。
a) 直接映射高速緩存E=1,即每組只有一行。組選擇是通過組索引位標(biāo)識組。高速緩存從w的地址中間抽取出s個組索引位,這些位被解釋為一個對應(yīng)于一個組號的無符號整數(shù),來進(jìn)行組索引。行匹配中,確定了某個組i,接下來需要確定是否有字w的一個副本存儲在組i包含的一個高速緩存行里,因為直接映射高速緩存只有一行,如果有效位為1且標(biāo)志位相同則緩存命中,根據(jù)塊偏移位即可查找到對應(yīng)字的地址并取出;若有效位為1但標(biāo)志位不同則沖突不命中,有效位為0則為冷不命中,此時都需要從存儲器層次結(jié)構(gòu)下一層取出被請求的塊,然后將新的塊存儲在組索引位指示的組中的一個高速緩存行中。
b) 組相聯(lián)高速緩存每個組都會保存多余一個的高速緩存行,組選擇與直接映射高速緩存的組選擇一樣,通過組索引位標(biāo)識組。行匹配時需要找遍組中所有行,找到標(biāo)記位有效位均相同的一行則緩存命中;如果CPU請求的字不在組的任何一行中,則緩存不命中,選擇替換時如果存在空行選擇空行,如果不存在空行則通過替換策略替換其中一行。
c) 全相聯(lián)高速緩存只包含一個組,其行匹配和字選擇與組相聯(lián)高速緩存中一樣
d)寫策略:分為直寫和寫回。

7.6 hello進(jìn)程fork時的內(nèi)存映射

函數(shù)fork()若成功調(diào)用一次則返回兩個值,子進(jìn)程返回0,父進(jìn)程返回子進(jìn)程ID。內(nèi)核為子進(jìn)程創(chuàng)建各種數(shù)據(jù)結(jié)構(gòu),并分配給它一個唯一的PID,新創(chuàng)建的子進(jìn)程獲得與父進(jìn)程完全相同的虛擬存儲空間中的一個備份這個進(jìn)程的每個頁面都標(biāo)記為只讀。

7.7 hello進(jìn)程execve時的內(nèi)存映射

在這里插入圖片描述

圖7.7進(jìn)程的內(nèi)存映像
execve函數(shù)加載并運行hello需要以下幾個步驟:
1.刪除已存在的用戶區(qū)域
2.映射私有區(qū)域,為新程序創(chuàng)建所有新的區(qū)域結(jié)構(gòu)
3.映射共享區(qū)域
4.設(shè)置當(dāng)前進(jìn)程上下文的程序計數(shù)器

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

(1)缺頁中斷及處理:在主存中查找頁表時相應(yīng)頁表條目有效位為0且物理頁號為NULL,則該頁表條目處于未分配,屬于缺頁中斷。缺頁中斷的異常處理程序為終止
(2)缺頁故障及處理:在主存中查找頁表時相應(yīng)頁表條目有效位為0但是物理頁號指向磁盤,屬于缺頁故障,缺頁故障的異常處理程序是從磁盤裝入相應(yīng)頁到內(nèi)存并更新頁表,再返回到故障指令開始執(zhí)行。

7.9 動態(tài)存儲分配管理

在這里插入圖片描述

圖7.9塊的表示圖
(1)實現(xiàn)動態(tài)內(nèi)存分配要考慮空閑塊組織、放置、分割和合并。
(2)分配器分為兩種:顯式分配器、隱式分配器。顯式分配器:要求應(yīng)用顯式地釋放任何已分配的塊。隱式分配器:要求分配器檢測一個已分配塊何時不再使用,那么就釋放這個塊,自動釋放未使用的已經(jīng)分配的塊的過程叫做垃圾收集。隱式空閑鏈表的優(yōu)點是簡單,缺點是任何操作的開銷,例如放置分配的塊,要求空閑鏈表的搜索與堆中已分配塊和空閑塊的總數(shù)呈線性關(guān)系。
(3)當(dāng)接收到一個內(nèi)存分配請求時,從頭開始遍歷堆,找到一個空閑的滿足大小要求的塊,若有剩余,將剩余部分變成一個新的空閑塊,更新相關(guān)塊的控制信息。調(diào)整起始位置,返回給用戶。釋放內(nèi)存時,僅需把使用情況標(biāo)記為空閑即可。
(4)搜索可以滿足請求的空閑塊時,策略有以下幾種:首次適應(yīng)法、最佳適應(yīng)法、最壞適應(yīng)法和循環(huán)首次適應(yīng)法

7.10 本章小結(jié)

本章結(jié)合hello介紹了邏輯地址、線性地址、虛擬地址、物理地址的概念,對段式管理與頁式管理進(jìn)行比較分析,分析了進(jìn)程 fork 和 execve 時的內(nèi)存映射的內(nèi)容,描述了系統(tǒng)應(yīng)對缺頁異常的方法,最后描述了 malloc 的內(nèi)存分配管理機(jī)制

第8章 hello的IO管理

8.1 Linux的IO設(shè)備管理方法

IO設(shè)備管理方法:一個Linux文件就是一個m字節(jié)的序列:B1,B2,……,Bk,……,Bm-1。所有的I/O設(shè)備(例如網(wǎng)絡(luò)、磁盤和終端)都被模型化為文件,而所有的輸入和輸出都被當(dāng)做對相應(yīng)文件的讀和寫來執(zhí)行。這種將設(shè)備優(yōu)雅地映射為文件的方式,允許Linux內(nèi)核引出一個簡單、低級的應(yīng)用接口,稱為Unix I/O。這使得所有輸入和輸出都能以一種統(tǒng)一且一致的方式來執(zhí)行:a)打開文件b)Linux Shell創(chuàng)建的每個進(jìn)程開始時都有三個打開的文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤。c)改變當(dāng)前文件的位置。d)讀寫文件。e)關(guān)閉文件。

8.2 簡述Unix IO接口及其函數(shù)

(1) 打開和關(guān)閉文件。open函數(shù)的函數(shù)原型是int open(char * path,int flags,mode_t mode) open函數(shù)將filename轉(zhuǎn)換成一個文件描述符,并返回描述符數(shù)字。進(jìn)程是通過調(diào)用open函數(shù)來打開一個已經(jīng)存在的文件或者創(chuàng)建一個新文件。最后進(jìn)程通過調(diào)用close函數(shù)關(guān)閉一個打開的文件。close函數(shù)原型是int close(int fd)
(2)讀和寫文件應(yīng)用程序是通過分別調(diào)用read和write函數(shù)來執(zhí)行輸入和輸出的。read函數(shù)函數(shù)原型是ssize_t read(int fd ,void* buf , size_t n),從描述符為fd的當(dāng)前文件位置復(fù)制最多n個字節(jié)到內(nèi)存位置buf。write函數(shù)函數(shù)原型是ssize_t write(int fd , const void* buf,size_t n),從內(nèi)存位置buf復(fù)制最多n個字節(jié)到描述符為fd的當(dāng)前文件位置。
(3)lseek函數(shù)off_t lseek(int fd, off_t offset , int whence)
應(yīng)用程序通過lseek函數(shù)能夠顯示地修改當(dāng)前文件的位置

8.3 printf的實現(xiàn)分析

(1)首先來看看printf函數(shù)的函數(shù)體

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; 
}

va_list是一個字符指針
(2)printf函數(shù)中調(diào)用了vsprintf函數(shù),
來看看vsprintf(buf, fmt, arg)的代碼

int vsprintf(char *buf, const char *fmt, va_list args) 
   { 
    char* p; 
    char tmp[256]; 
    va_list p_next_arg = args; 
   
    for (p=buf;*fmt;fmt++) { 
    if (*fmt != '%') { 
    *p++ = *fmt; 
    continue; 
    } 
    fmt++; 
    switch (*fmt) { 
    case 'x': 
    itoa(tmp, *((int*)p_next_arg)); 
    strcpy(p, tmp); 
    p_next_arg += 4; 
    p += strlen(tmp); 
    break; 
    case 's': 
    break; 
    default: 
    break; 
    } 
    } 
    return (p - buf); 
   }

vsprintf返回的是要打印出來的字符串的長度
(3)然后看printf中的一句:write(buf, i);printf函數(shù)調(diào)用了write函數(shù),把緩沖區(qū)的元素的值打印。

write: 
     mov eax, _NR_write 
     mov ebx, [esp + 4] 
     mov ecx, [esp + 8] 
     int INT_VECTOR_SYS_CALL

在write () 函數(shù)對應(yīng)的指令序列中,有用于系統(tǒng)調(diào)用的陷阱指令system_call。write通過執(zhí)行syscall指令調(diào)用系統(tǒng)服務(wù),執(zhí)行打印操作。內(nèi)核會通過字符顯示子程序,根據(jù)傳入的ASCII碼到字模庫讀取字符對應(yīng)的點陣,然后通過vram對字符串進(jìn)行輸出。顯示芯片將按照刷新頻率逐行讀取vram,并通過信號線向液晶顯示器傳輸每一個點,最終在終端輸出字符串。

8.4 getchar的實現(xiàn)分析

(1) 首先來看一下getchar函數(shù):getchar由宏實現(xiàn):#define getchar() getc(stdin),從標(biāo)準(zhǔn)輸入里讀取下一個字符,返回類型為int型,為用戶輸入的ASCII碼或EOF。

int getchar(void)
{
    static char buf[BUFSIZ];
    static char* bb=buf;
    static int n=0;
    if(n==0)
    {
        n=read(0,buf,BUFSIZ);
        bb=buf;
    }
    return(--n>=0)?(unsigned char)*bb++:EOF;
}

(2)可以看到n=read(0,buf,BUFSIZ);語句調(diào)用了read函數(shù)。read函數(shù)可以通過sys_call中斷來調(diào)用內(nèi)核中的系統(tǒng)函數(shù)。鍵盤中斷處理子程序會接受按鍵掃描碼并將其轉(zhuǎn)換為ASCII碼后保存在緩沖區(qū),然后對緩沖區(qū)ASCII碼進(jìn)行讀取直到接受回車鍵返回。

8.5本章小結(jié)

本章介紹了Linux的IO設(shè)備管理方法,并簡述了Unix IO接口及其函數(shù),對printf和getchar的實現(xiàn)進(jìn)行分析。

結(jié)論

在linux環(huán)境下hello程序從預(yù)處理到編譯再到鏈接,最后執(zhí)行的全過程以及進(jìn)程管理,存儲管理及IO管理的實現(xiàn)方式。
hello經(jīng)歷的過程如下:
(1)首先通過各種預(yù)處理命令對C程序進(jìn)行處理,由hello.c得到hello.i。
(2)通過編譯由hello.i得到hello.s
(3)通過匯編由hello.s得到hello.o
(4)通過鏈接得到可執(zhí)行目標(biāo)文件hello,然后運行hello,在shell下輸入命令./hello 1170300826 ,shell調(diào)用fork創(chuàng)建子進(jìn)程,然后將構(gòu)造好的參數(shù)列表傳給execve作為參數(shù),啟動加載器并開始執(zhí)行hello
(5)訪問虛擬內(nèi)存,通過虛擬地址在TLB和主存頁表中查找轉(zhuǎn)換為相應(yīng)物理地址,從在虛擬內(nèi)存中讀取hello程序所需要的數(shù)據(jù)
(6)異常處理,對中斷產(chǎn)生信號進(jìn)行處理
(7)回收回收進(jìn)程

附件

列出所有的中間產(chǎn)物的文件名,并予以說明起作用。

文件名稱 文件屬性
hello.i 預(yù)處理得到的文本文件
hello.s 編譯后的匯編文件
hello.o 匯編后的可重定位目標(biāo)執(zhí)行
hello 鏈接之后的可執(zhí)行目標(biāo)文件
hello.objdmp hello.o的反匯編代碼
hello.elf hello.o的ELF格式
hello1.elf hello的 ELF格式
hello.objdmp hello的反匯編代碼
hello.txt 分析重定位過程時的可執(zhí)行文件hello反匯編代碼

參考文獻(xiàn)

為完成本次大作業(yè)你翻閱的書籍與網(wǎng)站等

[1] 蘭德爾E.布萊恩特 大衛(wèi)R.奧哈拉倫. 深入理解計算機(jī)系統(tǒng)(第3版).
機(jī)械工業(yè)出版社. 2018.4.
[2] 袁春風(fēng) 計算機(jī)系統(tǒng)基礎(chǔ) 機(jī)械工業(yè)出版社,2018.
[3] 關(guān)于unix系統(tǒng)接口 普通文件io的小結(jié)
https://www.cnblogs.com/chentest/p/5448483.html
[4] printf 函數(shù)實現(xiàn)的深入剖析
https://www.cnblogs.com/pianist/p/3315801.html
[5] getchar百度百科
https://baike.baidu.com/item/getchar/919709?fr=aladdin
[6] LINUX 邏輯地址、線性地址、物理地址和虛擬地址
https://www.cnblogs.com/zengkefu/p/5452792.html
[7] shell解析命令行的過程以及eval命令
https://www.cnblogs.com/f-ck-need-u/p/7426371.html

如果有收獲?希望來個兩連擊,給更多的人看到這篇文章

1、關(guān)注我的原創(chuàng)微信公眾號「程序猿的進(jìn)階」,主要是IT與競賽

2、創(chuàng)作不易,順便點個贊唄,可以讓更多的人看到這篇文章,激勵一下我這個小白

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容