深入理解計算機操作系統(二)


閱讀經典——《深入理解計算機系統》01

  1. 信息是什么
  2. 文件
  3. Hello World程序的生命周期
  4. 開始運行Hello World
  5. 虛擬地址空間
  6. 總結

<h3 id="what_is_information">信息是什么?</h3>

信息就是+上下文

怎么理解呢?其實計算機系統中的所有信息都是一個一個的二進制位,不論是硬盤上的文件、內存中的代碼還是網絡上傳輸的數據,毫無例外。它們唯一的區別就是所處不同的上下文,可什么又是上下文呢?做過應用程序開發的應該很熟悉context對象,當你創建一個新的控件的時候,往往要向構造方法中傳入上下文對象,我們一般會傳入this指針,這個上下文對象就是用來告訴新的控件它所處的位置,或者說它所處的環境。在計算機系統中,二進制數據所處的環境決定了它們表達的含義,同樣的一段數據,作為整型、浮點型、字符串型或是作為機器指令時,表達的含義是完全不同的。

<h3 id="file">文件</h3>

通常來說,文件分為兩種,文本文件二進制文件。起初我是不理解的,難道文本文件不是二進制組成的?文本文件當然也是二進制組成的,只不過比純粹的二進制文件多了點上下文特征,即編碼。以ASCII編碼的文本文件來說,每個字節表示一個字符,於是這些二進制數據在這個上下文環境中表現為一個個字符,成了可以閱讀的文本,這就是文本文件的特殊之處。

<h3 id="lifecycle">Hello World程序的生命周期</h3>

先來看一個程序員再熟悉不過的Hello World程序

#include <stdio.h> int main() { print("hello, world\n"); } 

這是用高級編程語言C語言寫的程序,這個程序需要轉換成低級的機器語言才能夠被計算機識別並執行。我們可以通過運行一條命令

unix> gcc -o hello hello.c

來生成可執行文件。(以上命令是在unix環境下調用的gcc編譯器的命令,本書將經常采用unix環境。)但是,gcc編譯器實際上做的工作不只如此,下圖為從hello.c源程序到生成hello可執行程序的完整過程:

 
編譯系統

首先經過預處理器預處理,然后經過編譯器編譯得到匯編程序hello.s,再經過匯編器匯編得到可重定位目標程序hello.o,最后,鏈接器將目標程序和標准庫中的printf.o程序鏈接成為可執行目標程序hello。每一步的詳細過程將在后面的章節中敘述,此處只做簡要介紹。

需要補充的是,gcc來自於赫赫有名的GNU項目,該項目為Linux的開發提供了全面的開發工具,包括GCC編譯器、GDB調試器、EMACS編輯器、匯編器、鏈接器等等。有興趣的朋友可以搜索一下這方面的知識。

另外,我們經常用的另一款編譯器是微軟提供的MSVC,當我們使用Visual Studio時,用的就是它自帶的編譯器。它和gcc在語法要求等方面有所不同,所以會出現gcc正常編譯的代碼在MSVC中出錯的情況,我就曾遇到過這種錯誤,希望大家注意。

<h3 id="excute">開始運行Hello World</h3>

好啦,有了可執行文件,我們就可以運行它,在命令行中敲如下命令:

unix> ./hello

顯而易見,運行的結果為打印了一行字符串

hello, world

可是在我們發出命令和打印出結果期間都發生了什么呢?這就不得不提計算機系統的硬件結構了。下圖是計算機系統的硬件結構圖,我用紅線標出了當我們在shell中輸入hello命令時,計算機中的信息流向。

 
從鍵盤讀取hello命令

當我們想要輸入命令時,其實CPU中已經有一個正在運行的程序,那就是shell。shell程序一直在等待我們的輸入,所以我們隨時可以在鍵盤上輸入內容。先看左下角的USB控制器,它負責所有USB接口,所以這里有鼠標、鍵盤等外設。我們在鍵盤上輸入“./hello”命令時,該命令通過USB控制器向上經過I/O總線傳遞給I/O橋,也就是我們平常所說的南橋北橋,它是CPU和外界溝通的橋梁。再經過系統總線傳遞給寄存器,到了寄存器后還不是終點,因為shell程序需要把用戶輸入的內容作為一個變量使用,而這個變量一定在內存中有個地址,所以它最終會到達內存。

之后,shell程序解析我們的命令內容,知道了我們希望運行hello這個程序。於是shell程序開始從硬盤加載hello文件到內存中。可是這次,這些數據不會經過CPU,而是直接從硬盤到內存,這種方式稱為DMA。DMA(直接存儲器訪問)有利於減輕CPU的負荷,使CPU可以在數據轉移的同時做其它任務。數據轉移路線如下圖:

 
hello可執行程序從磁盤加載到內存

加載完hello文件后,CPU將會開始從hello程序的主函數處執行指令。於是hello中的print語句將要打印的字符串傳遞給CPU,CPU再將它傳遞給顯示器,這一過程字符串“hello, world”經過的路徑如下圖所示:

 
從內存輸出到顯示器

終於,我們在屏幕上看到了“hello, world”這一字符串。過程很復雜,但卻只是一瞬間的事情,可見計算機運行速度之快!

<h3 id="virtual_memory">虛擬地址空間</h3>

hello程序我們分析透徹了嗎,似乎沒有。很多時候我們還會關心程序運行時內存的變化,當啟動一個新進程的時候,操作系統是不是要為這個進程分配內存空間呢?答案是肯定的。

這就是我們要講的虛擬地址空間。虛擬地址空間是操作系統中一個非常復雜的概念,操作系統負責創建進程,同時為該進程分配內存。在現代操作系統中,出於進程間互不干擾,以及保護操作系統內核安全的考慮,每個進程享有完全獨立的一套完整地址空間。對於32位計算機來說,虛擬地址空間大小為2GB,范圍從 0x00000000 至 0x7FFFFFFF;對於64位計算機來說,虛擬地址空間大小為8TB,范圍從0x000'00000000 至 0x7FF'FFFFFFFF。這就是說,每個進程都可以隨意使用這2GB或8TB的內存空間,但是,由於是虛擬地址空間,這些地址映射到真實物理內存的時候是打亂的,用戶無法得知自己進程的數據到底存在物理內存的什么地方。接下來,我們來看看用戶進程的這2GB或8TB虛擬地址空間是怎么用的。

 
進程虛擬地址空間

上圖將虛擬地址空間分為了若干個部分,並用箭頭表示該部分的擴展方向。最下端地址為0,向上地址逐漸增長。每個部分作用如下:

  • 只讀程序數據區和靜態數據區:這一部分用來存放可執行程序代碼和代碼中的全局變量。
  • :用於動態申請的內存變量,比如malloc函數申請的動態內存空間,可以向上擴展。
  • 共享庫內存映射區:位於虛擬內存空間的中部,用於存放C語言庫函數的代碼和數據。本例中即printf的代碼和數據。
  • :位於虛擬地址空間的頂部,用於函數調用、存放局部變量等。當我們調用一個函數時,棧會向下擴展,返回時,向上收縮。
  • 內核虛擬地址空間:這個東西前面沒提到過,但是它占據了棧向上直到4GB或256TB的所有空間。這個空間是保留給操作系統內核用的,用戶進程無權訪問這些地址。可是它到底是干什么用的,要等到后面的章節才能解開謎底。

總結

結束了Hello World的旅程,在后面的章節中,我們將一步步深入,探索計算機系統那些不為人知的奧秘。

文中若有錯誤或不當之處,懇請各位讀者指出。

關注作者文集《深入理解計算機系統》,第一時間獲取最新發布文章。

參考資料



作者:金戈大王
鏈接:https://www.jianshu.com/p/5c33a0bcf244
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM