CSI-I:計算機系統漫遊-由hello程序所聯想到的問題

前言

         這章內容是對整本書內容的概括和描述,你可以通過本篇文章快速的瞭解本書所涉及的內容,瞭解學習書中所列內容的必要性。之後你就會明白學習本書其實是非常有趣的歷程,你會知道計算機是怎麼工作,自己的寫的程序是如何工作的,以及對如何對程序進行優化,同時對進程、線程、虛擬存儲器也會有更加清楚的認識。

    其次,我要說明的是每章內容並沒有必要的聯繫,所以你完全可以隨心所欲地閱讀自己感興趣的領域。好了,言歸正傳,開始我們的漫遊吧!

由helloworld聯想的問題

  首先我寫個程序

 #include<stdio.h>

 int main()

 {

      Printf(“hello,world!”);

 }

這是大家剛學編程時都會寫的一段代碼,現在我們就從這個簡單的程序說起。根據這個程序請容許我提出幾個問題:

1.這個程序編譯成可執行程序需要哪些步驟,各步驟都做了哪些事?

2.這個程序從加載執行到完成到底在計算機中經歷了什麼?

3.這個程序在執行過程中的進程狀態是怎麼樣的? 

第一個問題,相信很多人會很輕鬆的給出回答,請看下圖

             

這個過程需要四個主要的步驟,我做一個簡單的總結:

1) 預處理階段.預處理器根據以字符#開頭的命令,定義宏等修改原C程序,得到另一個C程序 ,通常以.i作爲拓展名hello.i。

2) 編譯階段。編譯器將文本文件hello.i編譯成文本文件hello.s,即彙編語言程序。這裏的彙編程序是對我們寫的C程序的低層翻譯。

3) 彙編階段。彙編器將hello.s翻譯成機器語言指令,把這些指令打包成一個叫做可重定位目標程序的格式,並將結果保存在hello.o中,hello文件是一個二進制文件,而不再是字符。

4) 鏈接階段。在程序中使用的printf函數,並不是我們寫的。printf存在於一個單獨編譯好的printf.o文件中,而這個文件必須合併到我們的hello.o中,連接器就負責處理這種合併。結果就得到hello程序

 

第二個問題,如果很清楚計算機的組織結構,相信你也能很快的給出答案。在我們用鍵盤上敲下”./hello”後,外殼程序(shell,dos)執行將它逐一讀取到寄存器中,再放到存儲器中。當我們敲下回車後,外殼程序知道我們命令輸入完畢,然後外殼程序執行一系列指令來加載可執行的hello文件,將hello目標文件中的指令和數據複製到內存中,其中就用到了DMA的存取技術,一旦目標文件中的指令和數據加載到內存,處理器就開始執行hello程序main中的機器語言指令,這些指令將”hello,world!”字符複製到寄存器,再從寄存器複製到顯示設備,最終顯示在屏幕上。下面給出幾幅圖來直觀地展示該過程。

                                                       


                                                                 


                                                                 

   不過,我們從上面的過程中可以看到系統在執行一個程序時,花費了大量的時間把數據從一個地方移動到另一個地方。Hello程序開始存放在磁盤上,隨後別加載到內存,然後再到處理器執行。從我們編程的角度來說,這些複製就是時間的開銷,減緩了真正的工作。因此,系統設計者的一個主要目標就是使這些複製操作更快完成。

   我們都知道從內存中讀取數據要比從磁盤中讀取數據快上很多,而從寄存器中讀取要比從內存快很多。而且處理器與內存之間的速度差異越來越大,針對這些差異,系統設計者們採用了一種叫做高速緩存存儲器的存儲設備,作爲暫時的數據存放,這些數據可能是處理器近期需要訪問的。而高速緩存正是利用了程序的局部性原理,即一段時間內處理器訪問的只是一個程序片段的局部數據和代碼。意識到這一點很重要,因爲應用程序員可以利用高速緩存將他們的程序性能提高一個數量級。

                  

其實,高速緩存只是計算機存儲系統結構的一部分,每個計算機系統中的存儲設備都被組織成了一個存儲器層次結構,如下圖所示,在這個層次結構中,從上至下,設備訪問速度越來越慢,容量越來越大。

                    

    第三個問題,這裏涉及到計算機系統中一個很重要的概念–進程。你可以把進程看做是程序的一個容器,容器裏包括了程序執行所必須的資源,如處理器,主存和I/O設備,以及程序本身的數據。每執行一個程序,系統就爲程序創建一個這樣的容器,並賦予不同的編號,而每個程序都覺得自己在獨自地享用系統資源:-,直到程序執行完畢退出後,系統才把該容器的資源收回。可是,如果有多個程序同時都在運行,那作爲系統是如何管理這些容器?這時候系統會跟蹤和保存它們的狀態信息,即上下文信息。有了這些上下文信息後,系統就可以在容器間靈活地進行切換。

    在hello程序執行過程中,就有兩個併發的進程:外殼進程和hello進程。起初只有外殼進程在運行,等待輸入。當我們讓它執行hello程序時,外殼通過一個專門的函數,即系統調用來執行我們的請求,系統調用會將控制權傳遞給系統,系統保存外殼進程的上下文,然後創建hello進程,然後將控制權轉給hello 進程,hello進程執行完後,系統恢復外殼進程的上下文,並將控制權轉給它,外殼系統繼續執行等待輸入。

                      

    其實在上面的場景中,我們知道進程是一個容器,但真正的執行單元並不是這個容器,而是另一個很重要的執行單元,即線程,線程存在於該容器,並可以有多個,他們共享容器中所有的資源和數據,所以在它們之間進行切換變得非常容易,所以顯得線程比進程高效了很多。

虛擬存儲器

     在第三個問題中,我們都知道了進程在執行中都自認爲在獨佔着系統資源(處理器,內存等),而這只是系統提供的一個假象。每個進程看到的都是一致的存儲器,稱爲虛擬地址空間。我們先看下它的結構,圖中地址是從下往上增大.

                        

虛擬地址空間是由幾個區構成的,下面我逐一介紹:

1) 程序代碼和數據.對於所有的進程來說,代碼是從同一固定的地址開始的,緊接着

是和C全局變量對應的數據位置。代碼和數據區是直接按照可執行文件的數據初始化的。

2) 堆.代碼和數據區後緊隨着的是運行時堆。代碼和數據區是在進程以開始是就被規定了大小,與此不同,當調用如malloc ,free這些C函數是,堆會動態地擴展和收縮。

3) 共享庫。從字面意思上理解就是多個進程對該存儲映射區域的共享,更直觀的來說,比如多個進程使用同一個C函數,爲了節省系統資源,這多個進程都會到共享存儲區來加載這個函數,而不是每個進程都拷貝一份。

4) 用戶棧。編譯器用它來實現函數調用,調用的時候棧空間會增長,函數返回後棧空間會收縮。後面我們會詳細介紹編譯器是如何使用棧空間的。

5) 內核虛擬存儲器。這部分區域是用來存放系統內核的,系統內核會在系統啓動後常駐該區域,並且應用程序不能訪問該區域的數據。


   可是,我們到底該怎麼理解虛擬存儲器?系統是如何使用虛擬存儲器的呢?

   虛擬存儲器的確是一個很抽象的概念,因爲它並不是事實存在的東西。而是一種基於swapping技術,由外存和內存構成的存儲機制。Swapping即存儲交換技術,通過該技術能夠實現外存和內存的數據交換,從而大大提高內存利用率。

    我們都知道對於32位的系統有2^32(4G)的尋址空間,而這個尋址空間只是邏輯上的,跟計算機的物理內存要區分開。那麼程序進程是怎麼執行的呢?其實這裏就會用到虛擬存儲技術,進程在一開始加載的時候,系統將虛擬存儲地址空間劃分成和物理內存同樣大小的頁面,這時會加載一部分頁面到物理內存開始執行,等到需要某些頁面的時候再去加載,其實這也是得益於程序的局部性原理,因爲沒必要加載所有的數據到內存中,指令一般都是順序執行的。與此同時,在這個過程中會有一個地址翻譯的硬件對邏輯地址到物理地址進行映射。

同樣的,對於多個進程來說,執行的過程也是如此,系統通過合適的頁面置換算法來保證多個進程共享的使用內存資源。

    Ok,關於虛擬存儲器的內容我先講這些,至於虛擬存儲器的詳細內容我會在後面的篇幅中再進行介紹。

    可見,這個hello程序的確涉及到這本書中的很多內容,但還有一小部分內容並沒有涉及到,比如信息的表示和處理,程序機器級表示,以及程序優化、網絡編程、併發編程等。當然,以上的所介紹的內容只是對本書部分內容的簡單介紹,個人認爲,本書和操作系統有一定的聯繫,但並不偏向操作系統,只是從程序的角度來講解系統而不是純粹的理論。

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