ICS(計算機系統導論)|1、計算機系統漫遊——從程序的生命週期講起

計算機系統由硬件和系統軟件組成,它們共同工作來運行應用程序。

相信每一個一開始接觸編程的同學最先寫的就是hello world,這是程序員對計算機世界發出的第一聲問候。而本書這一部分解釋hello world的運行機制、生命週期,爲後續所有要介紹的計算機底層知識做一個鋪墊。

#include<stdio.h>

int main()
{
    printf("hello,world");
    return 0;
}

 

0.爲什麼選Linux系統

Linux操作系統是衆多繼承最初由貝爾實驗室開發的Unix的操作系統中的一種,具有高度兼容性。

 

1.信息就是位+上下文

 

hello程序的生命週期是從一個源文件開始的,即程序員通過編輯器創建並保存的文本文件,文件名是hello.c。

 

但我們都知道計算機是二進制的,源程序實際上就是一個由值0和1組成的位(又稱爲比特)序列,8個組成一組,稱爲字節,字節表示程序中的文本字符。

 

大部分計算機使用ASCII標準來表示文本字符,這種方法實際上就是用一個整數值來表示每個字符,這點和高中函數是一個道理,即一個數字對應一個字符。將上述的hello程序轉換爲ASCII文本。

 

https://bkimg.cdn.bcebos.com/pic/e850352ac65c103880a07b53bc119313b17e8941?x-bce-process=image/watermark,g_7,image_d2F0ZXIvYmFpa2UxMTY=,xp_5,yp_5

 

hello.c的程序以字節序列的方式存在文件中,每個字節對應一個字符,每個字符對應一個整數,而這些整數值用二進制儲存在電腦裏。

 

hello.c的表示方法說明了一個基本思想:系統中所有的信息——包括磁盤文件、內存中的程序、內存中存放的用戶數據以及網絡上傳送的數據,都是由一串比特表示的。區分不同數據對象的唯一方法是我們讀到這些數據對象時的上下文。比如,在不同的上下文中,一個同樣的字節序列可能表示一個整數、浮點數、字符串。

 

2.程序被其他程序翻譯成不同格式

爲了在系統上運行hello.c程序,每條C語句都必須被其他程序轉化爲一系列的低級機器語言指令。然而這些指令按照一種可執行目標程序的格式打好包,並以二進制磁盤文件的形式存放出來。

 

在Unix系統上,從源文件到目標文件的轉換是由編譯器驅動程序完成的。

 

linux> gcc -o hello hello.c

 

在這裏,GCC編譯器驅動程序讀取源程序文件後,把它翻譯成一個可執行目標文件hello。這個翻譯過程分爲四個階段。

 

 

執行這四個階段的預處理器、編譯器。彙編器和鏈接器一起構成編譯系統

 

預處理階段:預處理器(cpp)根據以字符#開頭的命令,修改原始的C程序。如hello.c中的#include<stdio.h>命令告訴預處理器讀取系統頭文件stdio.h的內容,並且把它插入到程序文本中。結果就得到了另外一個C程序,這個程序通常以.i爲結尾。

 

編譯階段:編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s,它包含一個彙編語言程序。其實就是將代碼編成彙編代碼,到了這一步,抄襲的代碼基本上就有相當高的重複度了,這不是改改變量名、代碼塊順序能改正的,所以,代碼查重時經常用到編譯反編譯的手段。

 

彙編階段:這一階段就是彙編語言變成機器語言的過程。彙編器(as)將hello.s翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程序的格式,保存到hello.o文件中。

 

鏈接階段:這個階段就是將散落在計算機內的各個要調用的文件鏈接起來的過程。hello程序調用了printf程序,這是每個C編譯器都提供的標準C庫的一個函數。此函數存在printf.o的單獨的預編譯好的目標文件,而這個文件必須以某種方式合併到hello.o程序中。鏈接器(ld)就負責處理這種合併,得到hello文件。這是一個可執行文件,可以被加載到內存中,由系統執行。

 

3.處理器讀並解釋存儲在內存中的指令

現在有了可執行文件hello,想要在Unix系的系統上運行文件,直接將文件名輸入到稱爲shell的應用程序中即可。

 

linux> ./hello

hello, world

linux>

 

這裏shell是一個命令行解釋器,它首先輸入一個提示符,就是linux>,然後等待用戶輸入一個命令行,這裏是./hello,然後執行這個命令。如果shell的第一個單詞不是內置的shell命令,那麼shell就會假設這是一個可執行文件的名字。因此,此例中shell加載運行hello程序,然後等待程序停止,程序執行完畢後再次彈出提示符。這一點和Windows的命令行有相似點。

 

到這裏,程序hello.c的生命週期完全結束。

 

4.系統的硬件組成

 

hello程序的大體處理流程我們知道了,而計算機是由一系列硬件構成的,對我們而言,瞭解計算機硬件在這個過程中發揮什麼樣的作用是有必要的。

 

 

4.1總線

總線是貫穿整個系統的一組電子管道,它攜帶信息字節並負責在各個部件間傳遞。一般總線被設計成傳送定長的字節塊,也稱字。字中的字節長度要麼4個,要麼8個,分別對應32位系統和64位系統。

4.2 I/O設備

I/O(輸入/輸出)設備是系統與外部世界聯繫的通道,如我們常用的鍵鼠、顯示屏、U盤。每個I/O設備都通過一個控制器或適配器與I/O總線相連。

控制器和適配器之間的區別主要在於它們的封裝方式。控制器是I/O設備本身或者主板的芯片組,而適配器是插在主板上的卡。

4.3內存

內存是一個臨時存儲設備,在處理器執行程序時,用來存放程序和數據。物理上,內存由一組DRAM(動態隨機存儲存儲器)組成。從邏輯上講,存儲器是一個線性的字節數組,每個字節都有其唯一的地址(這裏可以存放數組索引),這些地址從零開始。

4.4處理器CPU

中央處理單元CPU簡稱處理器,是解釋存儲在內存中指令的引擎。處理器的核心是一個大小爲一個字(就是前面所說的32位或者64位)的寄存器,稱爲程序計數器(PC)。在任何時刻,PC都只想內存中的某條機器語言指令。

從系統通電開始,到系統斷電,CPU一直在不斷地執行PC指向的指令,再更新PC指向下一條指令,新的指令不一定與上一條指令在內存中的地址相同,如條件語句時的指令跳轉。是不是很簡單,而且這種簡單操作種類並不多,主要是加載、存儲、操作和跳轉4種操作。操作圍繞內存、寄存器文件和算術/邏輯單元(ALU)進行。寄存器文件是一個小的存儲設備,由一組寄存器構成。ALU計算新的數據和地址值。

 

加載:從內存複製一個字節或者一個字到寄存器,以覆蓋寄存器原本的內容

存儲:從寄存器複製一個字節或者一個字到內存的某個位置,覆蓋此位置原來的內容

操作:把兩個寄存器的內容複製到ALU,ALU對這兩個字做算術運算,並將結果存放到一個寄存器中,以覆蓋該寄存器中原來的內容。

跳轉:從指令本身中抽取一個字,並將這個字複製到PC中,以覆蓋PC中原來的值。

現在回到hello程序,還記得我們輸入運行hello的命令到shell中嗎?

 

linux> ./hello

hello, world

linux>

 

shell程序將字符一一讀入寄存器,再把它放到到內存中。當我們敲入回車鍵後,shell程序就知道我們已經結束了命令的輸入,然後shell執行一些列指令來加載可執行的hello文件,這些指令將hello目標文件中的代碼和數據從磁盤複製到內存,此時CPU就開始執行hello程序的main程序中的機器語言指令。這些指令將我們要的結果“hello, world\n”字符串中的字節從內存複製到寄存器文件,再從寄存器文件中複製到顯示設備。

 

5.高數緩存至關重要

現在,我們知道了一個程序的運行週期和它在機器上運行時硬件的工作順序和流程。但是看似合理的過程其實存在一個問題,即系統花費了大量時間將信息從一個地方挪到另外一個地方。

我們來看看hello程序的機器指令遷移過程,首先指令存在磁盤上,當程序加載時,它們被複制到內存;CPU運行時,它們有到了CPU上。這些複製就是開銷,在空間和時間上的雙重開銷,減慢了程序的速度。因此,系統設計者需要通過設計使這些複製儘快完成。

 

根據機械原理,我們知道一個前提,較大的存儲設備要比較小的存儲設備運行得慢,而快速設備的造價遠高於同類的低速設備。一個典型的寄存器文件只存儲幾百字節的信息,而內存可以存放幾十億字節,然而,CPU從寄存器中讀取數據比從內存中讀取要快100倍,更麻煩的是,隨着技術提高,CPU的運行速度還在迅速提高,但內存運行速度的提高相對而言要慢得多。

 

針對這種處理器與內存之間的差異,系統設計者採用高速緩存存儲器,作爲暫時的集結區域,存放CPU近期可能會需要的信息。

系統可以獲得一個很大的存儲器,同時訪問速度也很快,原因是利用了高速緩存的局部性原理,即程序具有訪問局部區域裏的數據和代碼的趨勢。通過讓高速緩存裏存放可能經常訪問的數據,大部分的內存操作都能在快速的高速緩存中完成。

 

6.存儲設備形成層次結構

通過上面的磁盤-內存-緩存結構你想到了什麼?在CPU和一個較大較慢的設備中間插入一個更小更快的存儲設備的想法已經成爲了計算機硬件結構設計中的一個普遍的觀念。於是,形成了常見的金字塔結構。

 

 

7.操作系統管理硬件

好了,現在我們回顧一下,計算機是硬件構成的,但是shell加載hello程序時,shell和程序都沒有直接訪問這些硬件,如鍵盤、顯示器等。取而代之的是,它們依靠操作系統提供的服務實現對硬件的調用。操作系統可以看出是應用程序和硬件之間插入的一層軟件,所有應用程序對硬件的操作嘗試都必須通過操作系統。

操作系統由兩個基本功能:

  1. 防止硬件被失控的應用程序濫用
  2. 嚮應用程序提供簡單一致的機制來控制複雜而又通常大不相同的低級硬件設備。

而實現這兩個功能,操作系統用到了進程、虛擬內存和文件系統。

文件是I/O設備的抽象表示,虛擬內存是對內存和磁盤I/O設備的抽象表示,進程是對CPU、內存和I/O設備的抽象表示。

7.1進程

進程是操作系統對一個正在進行的程序進行的抽象。一個進程上可以同時運行多個進程,而每個進程都好像在獨佔地使用硬件。進程通俗的講就是你在電腦上運行不同的軟件,每個軟件工作佔用一個進程。

併發運行,指一個進程的指令和另一個進程的指令是交錯執行的。在傳統計算機中,一個CPU只能執行一個進程,但是通過併發運行,一個多核CPU可以同時運行多個程序,這是通過CPU進程切換來實現的。

這種交錯執行的機制稱爲上下文切換。操作系統保持跟蹤進程運行所需的所有狀態信息(上下文),包括PC(程序計數器,還記得嗎)和寄存器文件的當前值、內存的內容等。在任何一個時刻,單CPU系統只能執行一個進程,通過頻繁切換進程實現CPU對多進程的運行,即併發運行。當操作系統決定要把控制權從當前進程轉移到新進程時,就會發生上下文切換——保存當前進程上下文,恢復新進程上下文,控制權傳遞到新進程,新進程就會從上次停止的地方開始。

不同進程之間的轉換,是由操作系統的內核管理的。內核是操作系統代碼常駐內存的部分。當應用程序需要操作系統的某些操作時,它就執行系統調用指令,將控制權傳給內核,然後內核執行請求的操作並返回應用程序。

7.2線程

在現代系統中,一個進程實際上可以有多個線程的執行單元組成,每個線程都運行在進程的上下文中,並共享同樣的代碼和全局數據。其實線程很明顯,在我們打開應用時,圖片往往比文字加載得慢得多,但是先出現了所有文字,而不是按順序顯示,這就是線程在起作用,一個線程顯示文字,一個線程顯示圖片。

7.3虛擬內存

虛擬內存是一個抽象概念,爲每一個進程提供了一個想象,即每個進程都在獨佔地使用主存。每個進程看到的內存都是一致的,稱爲虛擬地址空間。

在Linux系統中地址空間最上面的區域是保留給操作系統中的代碼和數據的,這對所有進程來說都是一樣,而地址空間的底部區域存放用戶進程定義的代碼和數據。

7.4文件

文件就是字節序列,僅此而已。每個I/O設備,包括磁盤、鍵盤、顯示器甚至網絡都可以看成文件。(驚不驚喜意不意外,但你仔細想想,確實如此)

文件嚮應用程序提供了一個統一的視圖,來看待系統中可能含有的所有各式各樣的I/O設備。

8.系統之間通過網絡通信

系統漫遊到此結束,而我們可以稍作延伸。前面我們一直將系統和軟件當做一個孤立的個體來研究。那麼個體多起來之後呢,不同的個體有交流的慾望(想想疫情期間關在家的你),於是網絡誕生了。系統通過網絡和其他系統連接到一起。網絡可以看成一個大型I/O設備。

當系統從內存複製一串字節到網絡適配器時,數據流經過網絡到達另外一臺機器,而不是到本地磁盤。相似的,系統可以接受從其他機器發送過來的數據。

隨着Internet這樣的全球網絡的出現,從一臺主機複製信息到另外一臺主機已經成爲計算機系統最重要的用途之一。

 

本次概略性介紹也到此結束,計算機,從01開始,通過連接、處理,便組成了我們現在豐富多彩的信息世界。

 

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