編譯系統(tǒng)的工作流程
這個(gè)過程雖然是通過一條命令完成的,然而實(shí)際上編譯系統(tǒng)的處理過程卻是非常復(fù) 雜的,大致可以分為四個(gè)階段膏执,分別為預(yù)處理、編譯露久、匯編以及鏈接胧后。
-
預(yù)處理
預(yù)處理器器會根據(jù)以 # 開頭的代碼,來修改原始程序抱环。例如 hello 程序中引入了頭文 件 stdio.h壳快,預(yù)處理器會讀取該頭文件中的內(nèi)容,將其中的內(nèi)容直接插入到源程序中镇草, 結(jié)果就得到了另外一個(gè) C 程序眶痰。即:hello.c 經(jīng)過預(yù)處理器后得到為文本文件 hello.i。
-
編譯
編譯器將 hello.i 文件翻譯成 hello.s 文件梯啤,這一階段包括詞法分析竖伯、語法分析、語義 分析因宇、中間代碼生成以及優(yōu)化等等一系列的中間操作七婴。
-
匯編
匯編器根據(jù)指令集將匯編程序 hello.s 翻譯成機(jī)器指令,并且把這一系列的機(jī)器指令 按照固定的規(guī)則進(jìn)行打包察滑,得到可重定位目標(biāo)文件 hello.o 打厘。此時(shí) hello.o 雖然是一 個(gè)二進(jìn)制的文件,但是還不能執(zhí)行贺辰,還要經(jīng)歷最后一個(gè)階段:鏈接户盯。
-
鏈接
在 hello 這個(gè)程序中,我們調(diào)用了 printf 函數(shù)饲化,這個(gè)函數(shù)是標(biāo)準(zhǔn) C 庫中的一個(gè)函數(shù)莽鸭, 存儲在名為 printf.o 的文件中。鏈接器 (ld)負(fù)責(zé)把 hello.o 和 printf.o 按照一定規(guī)則 進(jìn)行合并吃靠。正是因?yàn)殒溄悠饕獙?hello.o 和 printf.o 的進(jìn)行調(diào)整硫眨,所以 hello.o 才會被 稱之為可重定位目標(biāo)文件。最終經(jīng)過鏈接階段可以得到可執(zhí)行目標(biāo)文件 hello巢块。
硬件架構(gòu)圖
CPU架構(gòu)
中央處理單元(Central Processing Unit , CPU)礁阁,也稱處理器,包含PC ( 程序計(jì)數(shù)器:Program Count )夕冲、寄存器堆(Register file)氮兵、ALU(算數(shù)/邏輯計(jì)算單元:Arithmatic/logic Unit)三個(gè)部分.
內(nèi)存
主存(Main Memory),也稱為內(nèi)存歹鱼、運(yùn)行內(nèi)存泣栈,處理器在執(zhí)行程序時(shí),內(nèi)存主要存放程序指令以及數(shù)據(jù)。從物理上講南片,內(nèi)存是由隨機(jī)動態(tài)存儲器芯片組成掺涛;從邏輯上講,內(nèi)存可以看成一個(gè)從零開始的大數(shù)組疼进,每個(gè)字節(jié)都有相應(yīng)地址.
總線
內(nèi)存和處理器之間通過總線來進(jìn)行數(shù)據(jù)傳遞薪缆。實(shí)際上,總線貫穿了整個(gè)計(jì)算機(jī)系統(tǒng)伞广,它負(fù)責(zé)將信息從一個(gè)部件傳遞到另外一個(gè)部件拣帽。通常總線被設(shè)計(jì)成傳送固定長度的字節(jié)塊嚼锄,也就是字(word)减拭,至于這個(gè)字到底是多少個(gè)字節(jié),各個(gè)系統(tǒng)中是不一樣的区丑,32 位的機(jī)器拧粪,一個(gè)字長是4 個(gè)字節(jié);而64 位的機(jī)器沧侥,一個(gè)字長是8 個(gè)字節(jié).
輸入輸出設(shè)備
-
輸入輸出設(shè)備
除了處理器可霎,內(nèi)存以及總線,計(jì)算機(jī)系統(tǒng)還包含了各種輸入輸出設(shè)備宴杀,例如鍵盤癣朗、鼠標(biāo)、顯示器以及磁盤等等婴氮。每一個(gè)輸入輸出設(shè)備都通過一個(gè)控制器或者適配器與IO 總線相連.
程序執(zhí)行過程
hello.c 經(jīng)過編譯系統(tǒng)得到可執(zhí)行目標(biāo)文件hello斯棒,此時(shí)可執(zhí)行目標(biāo)文件hello 已經(jīng)存放在系統(tǒng)的磁盤上盾致,那么主经,如何運(yùn)行這個(gè)可執(zhí)行文件呢?
-
在linux 系統(tǒng)上運(yùn)行可執(zhí)行程序:打開一個(gè)shell 程序庭惜,然后在shell 中輸入相應(yīng)可執(zhí)行程序的文件名:linux>./hello
(shell 是一個(gè)命令解釋程序罩驻,如果命令行的第一個(gè)單詞不是內(nèi)置的shell 命令,shell就會對這個(gè)文件進(jìn)行加載并運(yùn)行. 此處护赊,shell 加載并且運(yùn)行hello 程序惠遏,屏幕上顯示hello,world 內(nèi)容,hello 程序運(yùn)行結(jié)束并退出骏啰,shell 繼續(xù)等待下一個(gè)命令的輸入.)
程序執(zhí)行流程
- 首先我們通過鍵盤輸入”./hello” 的字符串节吮,shell 程序會將輸入的字符逐一讀入寄存器,處理器會把hello這個(gè)字符串放入內(nèi)存中判耕。
- 當(dāng)我們完成輸入透绩,按下回車鍵時(shí),shell 程序就知道我們已經(jīng)完成了命令的輸入,然后執(zhí)行一系列的指令來來加載可執(zhí)行文件hello帚豪。
- 這些指令將hello 中的數(shù)據(jù)和代碼從磁盤復(fù)制到內(nèi)存碳竟。數(shù)據(jù)就是我們要顯示輸出的”hello , world\n” ,這個(gè)復(fù)制的過程將利用DMA(Direct Memory Access)技術(shù)狸臣,數(shù)據(jù)可以不經(jīng)過處理器莹桅,從磁盤直接到達(dá)內(nèi)存。
- 當(dāng)可執(zhí)行文件hello中的代碼和數(shù)據(jù)被加載到內(nèi)存中烛亦,處理器就開始執(zhí)行main函數(shù)中的代碼诈泼,main 函數(shù)非常簡單,只有一個(gè)打印功能煤禽。
Hello 程序執(zhí)行過程
Cache至關(guān)重要
隨著半導(dǎo)體技術(shù)的發(fā)展厂汗,處理器與內(nèi)存之間的差距還在持續(xù)增大,針對處理器和內(nèi) 存之間的差異呜师,系統(tǒng)設(shè)計(jì)人員在寄存器文件和內(nèi)存之間引入了高速緩存(cache)娶桦, 比較新的,處理能力比較強(qiáng)的處理器汁汗,一般有三級高速緩存衷畦,分別為 L1 cache ,L2 cache 以及 L3 cache知牌。
操作系統(tǒng)
操作系統(tǒng)作用
- 防止硬件被失控的應(yīng)用程序?yàn)E用祈争。
-
操作系統(tǒng)提供統(tǒng)一的機(jī)制來控制這些復(fù)雜的底層硬件。
操作系統(tǒng)抽象概念
- 文件是對IO 設(shè)備的抽象
- 虛擬內(nèi)存是對內(nèi)存和磁盤IO的抽象
-
進(jìn)程是對處理器角寸、內(nèi)存以及IO設(shè)備的抽象
進(jìn)程
現(xiàn)代操作系統(tǒng)中菩混,一個(gè)進(jìn)程實(shí)際上由多個(gè)線程組成,每個(gè)線程都運(yùn)行在進(jìn)程的上下文中扁藕,共享代碼和數(shù)據(jù)沮峡。由于網(wǎng)絡(luò)服務(wù)器對并行處理的需求,線程成為越來越重要的編程模型亿柑。
虛擬內(nèi)存
操作系統(tǒng)為每個(gè)進(jìn)程提供了一個(gè)假象邢疙,就是每個(gè)進(jìn)程都在獨(dú)自占用整個(gè)內(nèi)存空間, 每個(gè)進(jìn)程看到的內(nèi)存都是一樣的望薄,我們稱之為虛擬地址空間1.
下圖為 Linux 的虛擬地址空間疟游,從下往上看,地址是增大的痕支。最下面是 0 地址颁虐。
第一個(gè)區(qū)域是用來存放程序的代碼和數(shù)據(jù)的,這個(gè)區(qū)域的內(nèi)容是從可執(zhí)行目標(biāo)文件中加載而來的卧须,例如我們多次提到的 hello 程序另绩。對所有的進(jìn)程來講瞬痘,代碼都是從固定的地址開始。至于這個(gè)讀寫數(shù)據(jù)區(qū)域放的是什么數(shù)據(jù)呢板熊?例如在 C 語言中框全,全局變量就是存放在這個(gè)區(qū)域.
順著地址增大的方向,繼續(xù)往上看就是堆(heap)干签,學(xué)過 C 語言的同學(xué)應(yīng)該用過malloc 函數(shù)津辩,程序中 malloc 所申請的內(nèi)存空間就在這個(gè)堆中。程序的代碼和數(shù) 據(jù)區(qū)在程序一開始的時(shí)候就被指定了大小容劳,但是堆可以在運(yùn)行時(shí)動態(tài)的擴(kuò)展和 收縮.
接下來喘沿,就是共享庫的存放區(qū)域。這個(gè)區(qū)域主要存放像 C 語言的標(biāo)準(zhǔn)庫和數(shù)學(xué)庫這種共享庫的代碼和數(shù)據(jù)竭贩,例如 hello 程序中的 printf 函數(shù)就是存放在這里.
繼續(xù)往上看蚜印,這個(gè)區(qū)域稱為用戶棧(user stack),我們在寫程序的時(shí)候都使用過函數(shù)調(diào)用留量,實(shí)際上函數(shù)調(diào)用的本質(zhì)就是壓棧窄赋。這句話的意思是:每一次當(dāng)程序進(jìn)行函數(shù)調(diào)用的時(shí)候,棧就會增長楼熄,函數(shù)執(zhí)行完畢返回時(shí)忆绰,棧就會收縮。需要注意的是棧的增長方向是從高地址到低地址.
最后可岂,我們看一下地址空間的最頂部的區(qū)域错敢,這個(gè)區(qū)域是為內(nèi)核保留的區(qū)域,應(yīng)用程序代碼不能讀寫這個(gè)區(qū)域的數(shù)據(jù)缕粹,也不能直接調(diào)用內(nèi)核中定義的函數(shù)稚茅,也就是說,這個(gè)區(qū)域?qū)?yīng)用程序是不可見的.
文件
所有的 IO 設(shè)備平斩,包括鍵盤亚享,磁盤,顯示器双戳,甚至網(wǎng)絡(luò)虹蒋,這些都可以看成文件, 系統(tǒng)中所有的輸入和輸出都可以通過讀寫文件來完成飒货。
系統(tǒng)之間利用網(wǎng)絡(luò)通信
使用本地計(jì)算機(jī)上的telnet客戶端連接遠(yuǎn)程主機(jī)上的 telnet服務(wù)器
當(dāng)我們在 ssh 的 客戶端中輸入 hello 字符串并且敲下回車之后,客戶端的軟件就會通過網(wǎng)絡(luò)將 字符串發(fā)送到 ssh 服務(wù)端峭竣,ssh 服務(wù)端從網(wǎng)絡(luò)端接收到這個(gè)字符串以后塘辅,會將這 個(gè)字符串傳遞給遠(yuǎn)程主機(jī)上的 shell 程序,然后 shell 負(fù)責(zé) hello 程序的加載皆撩,運(yùn) 行結(jié)果返回給 ssh 的服務(wù)端扣墩,最后 ssh 的服務(wù)端通過網(wǎng)絡(luò)將程序的運(yùn)行結(jié)果發(fā) 送給 ssh 的客戶端哲银,ssh 客戶端在屏幕上顯示運(yùn)行結(jié)果
阿姆達(dá)爾定律 (Amdahl’s Law, 1967)
記 α ∈ [0, 1] 是某任務(wù)無法并行處理部分所占的比例. 假設(shè)該任務(wù)的工作量固定,則對任意 n 個(gè)處理器呻惕,相比于 1 個(gè)處理器荆责,能夠取得的加速比滿足:S(n) < 1 .
古斯塔法森定律 (Gustafson’s Law, 1988)
記 α ∈ [0, 1] 是某任務(wù)無法并行處理部分所占的比例. 假設(shè)該任務(wù)的工作量可以隨著 處理器個(gè)數(shù)縮放,從而保持處理時(shí)間固定. 則對任意 n 個(gè)處理器亚脆,相比于 1 個(gè)處理 器做院,能夠取得的加速比 S (n) 不存在上界.
孫-倪定律 (Sun-Ni’s Law, 1990)
并發(fā)并行
如何獲得更高的計(jì)算能力呢?可以通過以下三種途徑:
線程級并發(fā);
指令級并行;
單指令多數(shù)據(jù)并行
線程級并發(fā)
首先我們看一個(gè)多核處理器的組織結(jié)構(gòu)濒持,下圖的處理器芯片具有四個(gè) CPU 核 心键耕,由于篇幅限制,另外兩個(gè)用省略號代替了柑营。每個(gè) CPU 核心都有自己的 Ll cache 和 L2 cache 屈雄,四個(gè)CPU核心共享 L3 cache,這 4 個(gè) CPU 核心集成在一 顆芯片上官套。
-
對于許多高性能的服務(wù)器芯片酒奶,單顆芯片集成的 CPU 數(shù)量高達(dá)幾十個(gè),甚至上百個(gè)奶赔。通過增加 CPU 的核心數(shù)讥蟆,可以提高系統(tǒng)的性能。
還有一個(gè)技術(shù)就是超線程(hyperthreading)纺阔,也稱同時(shí)多線程瘸彤。如果每 個(gè) CPU 核心可以執(zhí)行兩個(gè)線程,那么四個(gè)核心就可以并行的執(zhí)行 8 個(gè)線程笛钝。在 CPU 內(nèi)部质况,像程序計(jì)數(shù)器和寄存器文件這樣的硬件部件有多個(gè)備份,而像浮點(diǎn) 運(yùn)算部件這個(gè)樣的硬件還是只有一份玻靡,常規(guī)單線程處理器在做線程切換時(shí)结榄,大概需 要 20000 個(gè)時(shí)鐘周期,而超線程處理器可以在單周期的基礎(chǔ)上決定執(zhí)行哪一個(gè)線程囤捻, 這樣一來臼朗,CPU 可以更好地利用它的處理資源。當(dāng)一個(gè)線程因?yàn)樽x取數(shù)據(jù)而進(jìn)入等 待狀態(tài)時(shí)蝎土,CPU 可以去執(zhí)行另外一個(gè)線程视哑,其中線程之間的切換只需要極少的時(shí)間代價(jià)。
指令級并行
現(xiàn)代處理器可以同時(shí)執(zhí)行多條指令的屬性稱為指佘級并行誊涯,每條指令從開始到結(jié)束大概需要 20 個(gè)時(shí)鐘周期或者更多挡毅,但是處理器采用了非常多的技巧可以同時(shí)處理多達(dá) 100 條指命,因此暴构,近幾年的處理器可以保持每個(gè)周期24條指令的執(zhí)行速率跪呈。
單指令多數(shù)據(jù)并行
現(xiàn)代處理器擁有特殊的硬件部件段磨,允許一條指令產(chǎn)生多個(gè)并行的操作,這種方式稱為單指令多數(shù)據(jù)(Single Instruction Multiple Data)耗绿。SIMD 的指令多是為了提高處 理視頻苹支、以及聲音這類數(shù)據(jù)的執(zhí)行速度,比較新的 Intel 以及 AMD 的處理器都是支持 SIMD 指令加速误阻。