寫給大忙人看的教程,一文形象生動地讓你理解linux

Linux的知識範圍廣,一篇博客不可能全部講解。因此,選取了最重要的內容,包括預處理、編譯、鏈接,進程管理。結合實例講解,專門寫給大忙人,有圖有知識點!

預處理

預處理的概念與作用

概念:預處理一般是指在程序源代碼被翻譯爲目標代碼的過程中,生成二進制代碼之前的過程。由預處理器對程序源代碼文本進行處理,把源代碼分割或處理成爲特定的單位,得到的結果再由編譯器核心進一步編譯。這個過程並不對程序的源代碼進行解析。

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

在Linux下預處理的命令

預處理命令:
gcc –E hello.c > hello.i
圖2.2 在Linux下預處理的命令

圖2.2 在Linux下預處理的命令

Hello的預處理結果解析

在這裏插入圖片描述

圖2.3 hello.i文件

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

編譯

概念與作用

  1. 編譯的概念:利用編譯程序從源語言編寫的源程序產生目標程序的過程,用編譯程序產生目標程序。 編譯程序把一個源程序翻譯成目標程序的工作過程分爲五個階段:詞法分析;語法分析;語義檢查和中間代碼生成;代碼優化;目標代碼生成。
  2. 編譯的作用:把高級語言變成計算機可以識別的2進制語言,詞法分析、語法分析、語義檢查和中間代碼生成、代碼優化、目標代碼生成。

在Linux下編譯的命令

gcc -S hello.i -o hello.s

在這裏插入圖片描述

圖3.2 在Linux下編譯的命令

Hello的編譯結果解析

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

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

字符串表是以null結尾的字符串序列

3.31 數據

(1)字符串:
在這裏插入圖片描述

圖3.311 字符串

(2)整數 sleepsecs
在這裏插入圖片描述

圖3.312 整數 sleepsecs

3.32 賦值

(1) 全局變量sleepsecs =2
在這裏插入圖片描述

圖3.321

(2) 局部變量i =0
在這裏插入圖片描述

圖3.322 局部變量i =0

3.33 類型轉換

隱式類型轉換的是:int sleepsecs=2.5,將浮點數類型的2.5轉換爲int類型

3.34 算術操作
在這裏插入圖片描述

圖3.340 算術操作符號

3.35 控制轉移
在這裏插入圖片描述

圖3.340 指令助記符

3.36 函數操作

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

(1)參數傳遞:從內核中獲取命令行參數和環境變量地址

(2)函數調用:內核執行程序時調用特殊的啓動例程,執行main函數

(3)函數返回:當命令行參數數量不爲3時輸出提示信息並調用exit(1)退出main函數;當命令行參數數量爲3執行循環和getchar函數後return 0的方式退出函數。

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

b) exit()

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

(2)函數傳遞:main函數通過call指令調用getchar()

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

3.37關係操作

(1)argc!=3
在這裏插入圖片描述

圖3.371 !=彙編代碼

(2)i<10
在這裏插入圖片描述

圖3.371 <彙編代碼

彙編

概念與作用

  1. 概念:把彙編語言翻譯成機器語言的過程稱爲彙編。在彙編語言中,用助記符代替操作碼,用地址符號或標號代替地址碼。通過用符號代替機器語言的二進制碼,可以把機器語言變成彙編語言。
  2. 作用:將彙編語言翻譯成機器語言。

編譯 VS 彙編
編譯:將高級語言程序變成計算機能識別的二進制語言
彙編:將彙編語言翻譯成機器語言

在Linux下彙編的命令

彙編的命令:as hello.s -o hello.o
在這裏插入圖片描述

圖4.2 在Linux下彙編的命令

4.3 可重定位目標elf格式

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

(1) ELF頭
在這裏插入圖片描述

圖4.311 ELF頭

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

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

數據格式:在這裏插入圖片描述

圖4.312 數據格式

(2) 節頭部表:文件中出現的各個節的語義,包括節的類型、位置和大小
在這裏插入圖片描述

圖4.32 節頭部表

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

(a)普通重定位由以下數據結構定義:

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相對地址就是距程序計數器(PC)的當前運行時值的偏移量。當CPU執行一條使用PC相對尋址的指令時,它就將在指令中編碼的32位值加上PC的當前運行時值,得到有效地址(如call指令的目標),PC值通常是下一條指令在內存中的地址。
R X86_ 64 _32。 重定位一個使用32位絕對地址的引用。通過絕對尋址,CPU直接使用在指令中編碼的32位值作爲有效地址,不需要進一步修改。

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

圖4.331重定位節

在這裏插入圖片描述

圖4.332

(4) 符號表:存放着程序中定義和引用函數和全局變量的信息,不包含局部變量的條目
在這裏插入圖片描述

圖4.34 符號表

Value:在對應節的偏移。
Size:目標大小。
Type:是數據或函數。
Bind:本地或全局。
Vis:預留。
Ndx:符號所在的節,其實是節頭部表中條目的索引。
Name:符號名,爲空的爲鏈接器內部使用的本地符號,可以忽略。

4.4 Hello.o的結果解析

用命令行得到,比較hello.objdump與hello.o,進行對照分析
在這裏插入圖片描述

圖4.40 命令行

(1) hello.objdump記錄了文件格式和.text代碼段:
而hello.s中除了記錄了文件格式和.text代碼段還包括.type .size .align以及.rodata
在這裏插入圖片描述

圖4.41 hello.objdump與hello.s文件內容對比

(2) 分支轉移:

hello.objdump跳轉中地址爲已確定的實際指令地址;
hello.s跳轉中地址爲助記符如.L2,通過使用例如.L2等的助記符進行跳轉。
在這裏插入圖片描述
圖4.42 hello.objdump與hello.s分支轉移對比
(3)函數調用
在.s文件中,call的地址是函數名稱,如puts@PLT,
而在反彙編程序中,call的目標地址是指令,如callq 21 <main+0x21>。因爲hello.c中調用的函數都是共享庫中的函數,共享庫函數調用需要通過鏈接時重定位才能確定地址
在這裏插入圖片描述
圖4.43 hello.objdump與hello.s函數puts調用對比
(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訪問對比

鏈接

5.1 鏈接的概念與作用

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

5.2 在Linux下鏈接的命令

使用ld的鏈接命令,應截圖,展示彙編過程! 注意不只連接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 在Linux下鏈接的命令

5.3 可執行目標文件hello的格式

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

在這裏插入圖片描述
圖5.31 節頭表
在這裏插入圖片描述
圖5.32 節頭表

5.4 hello的虛擬地址空間

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

(1) 分析程序頭部表。

PHDR:程序頭表
INTERP:程序執行前需要調用的解釋器
LOAD:程序目標代碼和常量信息
DYNAMIC:動態鏈接器所使用的信息
NOTE::輔助信息
GNU_EH_FRAME:保存異常信息
GNU_STACK:使用系統棧所需要的權限信息
GNU_RELRO:保存在重定位之後只讀信息的位置
VirtAddr:本段首字節的虛擬地址
PhysAddr指出本段首字節的物理地址
pFileSiz指出本段在文件中所佔的字節數,可以爲0
MemSiz指出本段在存儲器中所佔字節數,可以爲0
Flags指出存取權限,Align指出對齊方式
在這裏插入圖片描述
圖5.41 程序頭部表

(2) 在edb查看hello的虛擬地址空間的各段信息
在這裏插入圖片描述
圖5.42 hello的虛擬地址空間

(3) 程序頭與Datadump的映射關係:例如PHDR對應的虛擬內存地址是0x400000—— 0x4001c0
在這裏插入圖片描述
圖5.43 程序頭與Datadump的映射關係

5.5 鏈接的重定位過程分析

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

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

_init 程序初始化代碼
gmon_start call_gmon_start函數初始化
gmon profiling system,程序通過gprof可以輸出函數調用等信息
_dl_relocate_static_pie 靜態庫鏈接
.plt 動態鏈接-過程鏈接表
Puts(等函數)@plt 動態鏈接各個函數
_start 編譯器爲可執行文件加上了一個啓動例程
__libc_csu_init 程序調用libc庫用來對程序進行初始化的函數,一般先於main函數執行
_fini 當程序正常終止時需要執行的代碼

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

函數調用:鏈接器解析重定條目時發現對外部函數調用的類型爲R_X86_64_PLT32的重定位,此時動態鏈接庫中的函數已經加入到了PLT中,.text與.plt節相對距離已經確定,鏈接器計算相對距離,將對動態鏈接庫中函數的調用值改爲PLT中相應函數與下條指令的相對地址,指向對應函數。
rodata引用:鏈接器解析重定條目時發現兩個類型爲R_X86_64_PC32的對.rodata的重定位(printf中的兩個字符串),.rodata與.text節之間的相對距離確定,因此鏈接器直接修改call之後的值爲目標地址與下一條指令的地址之差,指向相應的字符串。

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

5.6 hello的執行流程

(以下格式自行編排,編輯時刪除)
使用edb執行hello,說明從加載hello到_start,到call main,以及程序終止的所有過程。請列出其調用與跳轉的各個子程序名或程序地址。
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的動態鏈接分析

(1)編譯器無法確定動態鏈接庫中的函數地址,因爲動態鏈接庫中的函數在程序執行的時候纔會確定地址。GNU編譯系統採用延遲綁定技術來解決動態庫函數模塊調用的問題。

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

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

(b)GOT同樣是一個數組,每個條目是8字節的地址,和PLT聯合使用時,GOT[2]是動態鏈接在ld-linux.so模塊的入口點,其餘條目對應於被調用的函數,在運行時被解析。每個條目都有匹配的PLT條目。

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

(4)下面分析在dl_init調用前後,項目的內容的變化
a)dl_init調用前
在這裏插入圖片描述
圖5.71 dl_init調用前GOT條目
b)dl_init調用後, GOT條目初始時指向其PLT條目的第二條指令的地址
在這裏插入圖片描述
圖5.72 dl_init調用後GOT條目

進程管理

6.1 概念與作用

進程的概念:進程是正在運行的程序的實例,是一個具有一定獨立功能的程序關於某個數據集合的一次運行活動。進程是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
進程的作用:進程提供兩個假象,程序獨佔地使用處理器和程序在獨佔地使用系統內存。

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

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

6.3 Hello的fork進程創建過程

普通的系統調用,調用一次就返回一次,而fork()調用一次,會返回兩次,一次是父進程,另一個是子進程,互不干擾,調用的先後順序由操作系統的調度算法決定。子進程永遠返回0,父進程則返回子進程的ID。

fork的進程圖爲:
在這裏插入圖片描述
圖6.3 fork的進程圖

6.4 Hello的execve過程

execve 函數加載並運行可執行目標文件 filename, 且帶參數列表 argv 和環境變量列表 envp 。只有當出現錯誤時,例如找不到 filename, execve 纔會返回到調用程序。所以,與 fork 一次調用返回兩次不同, execve 調用一次並從不返回。

6.5 Hello的進程執行

結合進程上下文信息、進程時間片,闡述進程調度的過程,用戶態與核心態轉換等等。

(1)上下文及上下文切換:進程的物理實體(代碼和數據等)和支持進程運行的環境。系統通過處理器調度讓處理器輪流執行多個進程,實現不同進程中指令交替執行的機制稱爲進程的上下文切換。

(2)進程時間片:連續執行同一個進程的時間段稱爲時間片

(3)用戶態與核心態轉換:處理器通過某個控制寄存器中的一個模式位來提供限制一個應用可以執行的指令以及它可以訪問的地址空間範圍的功能。當設置了模式位時,進程就運行在內核模式中。沒有設置模式位時,進程就運行在用戶模式中。

(4)Hello進程調度的過程以及用戶態與核心態的轉換
調度是在進程執行的某些時刻,內核可以決定搶佔當前進程並重新開始一個先前被搶佔了的進程的決策。在切換的第一部分中,內核代表進程A在內核模式下執行指令。然後在某一時刻,shell加載可執行目標文件hello。在上下文切換之後,內核代表進程hello在用戶模式下執行指令。之後進程hello在用戶模式下運行,直到磁盤發出一箇中斷信號,執行一個從進程hello到進程A的上下文切換,將控制返回給進程A,進程A繼續運行,直到下一次異常發生。
在這裏插入圖片描述
圖6.5 Hello進程調度的過程以及用戶態與核心態的轉換

6.6 hello的異常與信號處理

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

(1) 正常終止
在這裏插入圖片描述
圖6.61 正常終止

(2) Ctrl + C
當按下ctrl-c之後,shell父進程收到SIGINT信號,信號處理程序結束hello,並回收hello進程。
在這裏插入圖片描述
圖6.62 按Ctrl + C時

(3) Ctrl + Z
當按下ctrl-z之後,
(a)shell父進程收到SIGSTP信號,
(b)信號處理程序打印並將hello進程掛起,
©通過ps命令看到hello進程沒有被回收,
通過jobs命令看到hello進程的號爲1,
通過pstree命令可以看出:之後調用fg 1將其調到前臺,執行相應命令行
在這裏插入圖片描述

圖6.63 按Ctrl + Z時

(4) 中途亂按
中途亂按不導致異常和產生信號
在這裏插入圖片描述
圖6.64 中途亂按時

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

1、關注我的原創微信公衆號「程序猿的進階」,主要是IT與競賽

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章