01-計算機系統漫游
編譯過程分為四個階段:預處理、編譯、匯編、鏈接

gcc -E hello.c -o hello.i //預處理
gcc -S hello.i -o hello.s //編譯
gcc -c hello.s -o hello.o //匯編
gcc hello.o -o hello //生成可執行文件
以hello.c為例子:
#include <stdio.h>
#define ANSWER 42
int main() {
int obj = ANSWER;
return 0;
}
預編譯實際上是進行頭文件、宏定義的替換和組織,執行上述預編譯命令可查看其內容(展示部分結果):
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
//...省略
# 216 "/usr/lib/gcc/x86_64-redhat-linux/8/include/stddef.h" 3 4
typedef long unsigned int size_t;
# 34 "/usr/include/stdio.h" 2 3 4
//...省略
typedef __builtin_va_list __gnuc_va_list;
# 37 "/usr/include/stdio.h" 2 3 4
...
# 28 "/usr/include/bits/types.h" 2 3 4
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
//...省略
typedef int __sig_atomic_t;
# 39 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/bits/types/__fpos_t.h" 1 3 4
# 1 "/usr/include/bits/types/__mbstate_t.h" 1 3 4
# 13 "/usr/include/bits/types/__mbstate_t.h" 3 4
typedef struct
{
int __count;
union
{
unsigned int __wch;
char __wchb[4];
} __value;
} __mbstate_t;
# 6 "/usr/include/bits/types/__fpos_t.h" 2 3 4
//...省略
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 864 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 879 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main() {
int obj = 42;
return 0;
}
我們發現預編譯生成的.i文件中已經不存在宏ANSWER了,其值被替換成42。編譯階段主要是對代碼進行詞法分析、語法分析、語義分析、中間代碼優化等等。
然后是通過gcc -S選項進行編譯,編譯生成的.s是匯編程序,結果如下:
.file "hello.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $42, -4(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 8.4.1 20200928 (Red Hat 8.4.1-1)"
.section .note.GNU-stack,"",@progbits
接著就是匯編,將匯編程序轉換為ELF(Executable and Linkable Format)格式的目標.o程序,可通過gcc -c的方式,或直接調用as進行匯編:as -c hello.s -o hello.o。
當拿到.o文件后就可以進行鏈接或者直接生成可執行程序,鏈接的話需要加載鏈接庫,鏈接庫有動態鏈接庫和靜態鏈接庫。以上就是代碼編譯系統的過程。
那為什么要理解一個程序的編譯過程呢?要理解編譯系統的原因:
- 理解編譯系統可以優化程序的性能
- 理解鏈接時出現的錯誤
- 避免安全漏洞
接下來站在全局的角度大致了解硬件架構圖,以便于我們了解程序執行的流程:

其中CPU(Central Processing Unit)中央處理單元包括:PC(Program Count)、寄存器堆(Register file)、ALU(Arithmatic/logic Unit)三部分。
那么執行一個hello程序,計算機內部會發生什么呢?(通常用戶是在shell終端上執行代碼的)



上圖就是程序執行流程,概述如下:
- IO設備鍵盤輸入字符串"./hello",shell程序將輸入字符讀入寄存器,處理器會把
hello字符通過系統總線和內存總線加載至內存(此時還沒有按下回車)。 - 當按下回車執行時,shell就知道我們已經完成命令輸入,然后shell會執行一系列指令加載可執行文件hello。
- 加載的過程實際上是將代碼所需要的數據從磁盤拷貝至內存。拷貝的過程被稱為DMA(Direct Memory Access),DMA技術可將數據不經過處理器,從磁盤直接加載至內存,我們知道CPU時間是非常寶貴的,磁盤讀取本身就很慢,通過DMA技術,拷貝操作不經過處理器,這樣就不會剝奪占用CPU時間,達到更加高效的作用(對計算密集型主機來說特別有幫助)。
- 當可執行文件hello中的代碼和數據被加載至內存中后,處理器就開始執行函數代碼,那么一個程序運行起來就變為了進程。
那么上面說到了shell程序,其實更準確的說法應該是shell進程,進程是正在運行中的程序,當用戶在shell中運行hello進程的時候,其實shell會發生中斷,系統會保存其上下文信息,然后轉而運行hello進程,當hello進程運行完畢后,又恢復shell進程上下文信息。大致形勢如下:

在計算機系統中,每個進程都對應了4GB的虛擬內存地址,操作系統將實際硬件上的物理地址通過內存映射方式,將物理地址映射為連續大小4GB的虛擬內存地址空間,這4GB空間中,由低地址向高地址的3GB空間劃分為用戶空間,然后高地址部分的1GB劃分為內核空間,專門用于保存系統級別數據信息。那么用戶對一個程序的操作實際上是在用戶空間進行的,劃分如下:

關于系統加速,有以下三種定律:
-
阿姆達爾定律


-
古斯塔法森定律

-
孫-倪定律


三種模型關系:

關于并發、并行,首先什么是并發?什么是并行?
并行(parallelise)同時刻(某點),即并行是在某一時刻上有多個任務在執行。
并發(concurrency)同時間(某段),即并發是在某一時段內有多個任務在執行。
如何獲得更高的計算力:
- 線程級并發:增加CPU核心數提高系統行呢哥
- 指令級并行:流水指令集
- 單指令多數據并行:SIMD指令加速

浙公網安備 33010602011771號