1. 程式人生 > >【OS學習筆記】一 處理器、記憶體和指令

【OS學習筆記】一 處理器、記憶體和指令

我們已經知道,處理器是一臺電子計算機的核心,它會在振盪器脈衝的激勵下,從記憶體中獲取指令,併發起一系列由該指令所定義的操作。當這些操作結束之後,它接著再取下一條指令。通常情況下,這個過程是連續不斷、迴圈往復的。

1、暫存器和算數邏輯部件

電子計算機能能做很多事情。計算天氣預報,看電影,聽音樂,上網等,實際上都是以數學計算為基礎。它之所以能夠計算,是因為它特殊的設計。想要了解計算機為什麼能夠自動計算,請去閱讀書籍《穿越計算機的迷霧》。本片文章主要講解處理器的簡單功能。

如圖是一個處理器:

在處理器的周圍,有大量的引腳,這些引腳可以接受從外面來的電訊號或者向外傳送電訊號(與外設進行訊號的互動)。有很多引腳是複用的,假如現在我們要進行加法運算,我們就需要重複使用某一些引腳來依次將加數與被加數送入。一旦被加數被送入處理器,代表這個二進位制數字的一組電訊號就會出現在與該引腳相連的內部線路上(我們稱這個內部線路為內部匯流排)。這是一組高低電平的組合,代表著二進位制數中的每一位。當被加數被送進來了,接下來就要將加數送進來,但是此時內部總線上有被加數,所以處理器內部需要有一個電路來將被加數“鎖存”。這個將被加數鎖存的電路就是暫存器。它是一個儲存器件。

同樣,加數也要鎖存進另一個暫存器。如圖中RA與RB分別鎖存的是被加數與加數。此後,被加數與加數將不再受處理器的內部匯流排的影響。暫存器是雙向器件,它會將它鎖存的數,在另一端產生一模一樣的電訊號(注意此時暫存器中依然鎖存著之前的電訊號,它只是在另一端產生一個一模一樣的電訊號),傳送給相應的算數邏輯部件(ALU)進行計算。算數邏輯部件(ALU)計算出結果後,將結果送出,給另一個暫存器RC鎖存,稍後再將RC中的內容通過內部匯流排送到處理器外部,或者再次送回RA RB暫存器進行其他計算。

那麼,處理器是如何知道什麼時候該幹什麼,各個部件在各個時間點該幹什麼,以及哪些部件在哪些時候使用內部匯流排以避免匯流排衝突呢?這些實際上都是由處理器內部的一個控制器控制的(圖中沒有畫出)。

處理器總是繁忙的,所有資料都只能在暫存器中寄存一會,就必須被送往別處。早期的暫存器只能儲存4位元、8位元或者16位元,分別叫做4位暫存器,8位暫存器和16位暫存器。現在的處理器大多是32位和64位的。

2、記憶體儲器

上一節我們知道,處理器的計算過程實際上是藉助於暫存器和算數邏輯部件進行的。那麼計算的資料是從哪裡來的呢?它是從一個可以儲存很多數字的電路,叫做儲存器。儲存器的種類很多,我們常見的U盤,硬碟,磁碟,記憶體條,甚至暫存器等都是儲存器。

我們這裡主要說記憶體條。由於它通常是和處理器直接相連的,所以叫記憶體儲器或者主儲存器。又或者記憶體或者主存。和暫存器不同,記憶體用於儲存更多的位元。對應用的最多的個人計算機來說,記憶體是按位元組來組織的,單次訪問的最小單位是1位元組,這是最基本的儲存單元。如下圖所示是一個記憶體和記憶體訪問示意圖:

記憶體中的每一個位元組都對應著一個地址。從0地址開始,由於上面的記憶體是65536位元組,所以該記憶體的最後一個位元組是FFFF(都是用的十六進位制表示的)。為了訪問一個記憶體,處理器需要給出一個地址。同時訪問記憶體是什麼方式?讀方式或者寫方式,所以處理器需要有一個讀寫控制。如果是寫資料,處理器還需要有一個資料傳輸的控制。最後,處理器單次訪問記憶體時,是以位元組為單位訪問還是以字為單位訪問等,需要有一個字長控制來告知。

3、指令和指令集

從一開始設計處理器,就是想讓它自動工作,另外,還需要提供一種機制,來允許程式設計師決定進行何種工作。處理器的設計者用某些數來表示該處理器該做什麼,這些數,我們稱為指令或者叫機器指令。

下面給個實際例子來分析指令在記憶體中的佈局:

假設現在我們的處理器是從記憶體的0000地址開始取指令(實際上肯定不是從0地址開始,這裡只是假設)。第一條指令B8 5D 00,該指令的意思是將立即數005D傳送到RA暫存器,它具有一個位元組的操作碼B8。由此我們可以看出簡單的一個B8 5D 00指令,是一個傳送指令,且該指令將兩個位元組(005D)傳送到RA暫存器,並且由於下一條指令的地址是在0003位元組處,所以該指令肯定還知道它自己是一條3位元組指令,以便下一次處理器可以直接去到0003位元組處去取指令。

對於一些複雜的指令,一個位元組的操作碼肯定不夠用,所以第二條指令8B 1E 3F 00的指令操作碼是兩位元組8B 1E。

同時我們注意到上述前兩條指令中,第一條指令是將一個數005D直接傳送到暫存器,第二條指令,是需要從另外一個地址(003F)先取出資料,然後再將資料傳送到暫存器。第一種的運算元005D我們稱為立即數(表示可以立即將數傳送而不需要再去另外一個地址取資料)。

由以上分析,我們很容易知道,上述的幾條指令,首先將兩個數放到暫存器中去運算,然後運算結果放到其中一個暫存器中,最終還需要將運算結果再傳回到記憶體中以便該結果作為其他用處時處理器好可以從記憶體中得到計算結果。最後F4指令停機。

指令和非指令的二進位制資料是一模一樣的,在組成記憶體的電路中,都是一些高低電平的組合。因為處理器是按順序取指令,並加以執行的,這樣的話,如果指令和資料存放在一起,處理器很容易將資料的二進位制當成指令,從而導致錯誤。所以我們需要將指令與資料分開存放。分別存放在不同的區域。存放指令的區域叫做程式碼區,存放資料的區域叫做資料區(這就是為什麼程序的虛擬地址空間中程式碼和資料區分開的原因)。

一個處理器能夠識別的指令的集合,稱為指令集。

4、Intel 8086處理器

要想學好程式設計,就必須要懂底層原理。但是想要徹底學好底層,就要把彙編學好。想要學好彙編,不得不先學好針對8086的彙編技術。

4.1、 8086的通用暫存器

8086處理器內部有8個16位暫存器。分別被命名為:AX,BX,CX,DX,SI,DI,BP,SP。通用的意思是他們中大部分可以根據需要用於多種目的。他們的用處,將在後面的文章逐漸給出。

下圖是幾個暫存器:
在這裡插入圖片描述

可以看到,有介個暫存器,AX,BX,CX,DX,又可以分為兩個8位的暫存器來使用。在後面文章中,我們會看到具體的應用。

4.2、 程式的重定位問題

我們知道,處理器是自動取指令和執行指令的,只要每條指令都正確無誤,它就能準確的知道下一條指令的地址。所以,指令必須集中在一起,形成一個段,叫做程式碼段。
為了做某件事而編寫的指令,形成我們所知道的程式。程式中肯定需要操作大量的資料,這大量的資料也應該集中在一起,位於記憶體中的某個地方,叫做資料段。

段在記憶體中的位置並不重要,因為處理器是可控的,我們可以讓處理器在記憶體中的任何一個位置開始取指令並加以執行。

下面看一個圖示:

假設上面是一個程式片段在記憶體中的位置,相關指令的用處已經在圖中表明。這裡不再贅述。但是現在有一個問題,就是我們寫的程式(不管是C語言還是C++語言或者Java語言),最終要執行在記憶體中的位置,是無法確定的。因為你想一想,本身你的電腦中有各種程式在跑了,有qq程式在跑,微信程式在跑,或者還有微博等在跑,他們可能把你的記憶體都佔完了,當你想執行你寫的程式的時候,你的程式本想從0100H處開始存放程式碼段,但是可能這個時候0100H處有其他程式的程式碼或者指令在執行,此時你的程式只能去另一個位置存放你的程式碼和資料,假設你的程式碼下一次執行的時候程式碼段和資料段在如下位置:

此時你的程式的代資料段在記憶體的位置為1000H處,但是你會發現,你的程式碼段的第一條指令,還是將地址單元0100H處的內容傳送到AX暫存器中。但是!!!此時0100H處的內容,並不是我們想要的內容。

產生這種情況的原因就是我們在程式中使用的是絕對記憶體地址。這樣的程式是無法重新定位的。所以我們需要使用另一種方式訪問記憶體:相對地址或者叫做邏輯地址。在任何時候,程式的重定位都是非常棘手的事情。在8086處理器中,這個問題得到解決。它使用了分段機制。

4.3、 記憶體分段機制

在記憶體分段中,段,是很多個連續的位元組組成的。如圖:

上圖有一個7個位元組的段。段的起始地址為A532。那麼段中的地址從第一個位元組開始,他們的地址此時就是段內的偏移地址(0000,0001,0002…)。而他們的實際的實體地址,又恰好等於段地址加上段內的偏移地址。可以用“段地址:偏移地址”,來表示段中的每一個位元組的地址。

為了在硬體一級提供對“段地址:偏移地址”的支援。處理器至少要提供兩個段暫存器,分別是程式碼段暫存器CS,資料段暫存器DS。

對程式碼段CS內容的改變,將導致處理器從新的程式碼段開始執行。當然,在訪問資料之前,也是必須要提前設定好資料段暫存器DS的,使DS之指向資料段。

很重要的一點是,當處理器取指令執行指令的時候,它是把指令中指定的記憶體地址看成是段內偏移地址的。而不是記憶體的實體地址。那麼當處理器遇到一條訪問記憶體的指令時,它就會將DS中的資料段起始地址加上指令中提供的段內偏移地址得到訪問記憶體所需要的實體地址的。

如下圖所示:

程式碼段地址為1020H,資料段地址為1000H,在程式碼段中有一條指令:A1 02 00,它的功能是將地址0002H處的的一個字傳送到暫存器AX。在這裡處理器將0002H看成是段內偏移地址,段地址在DS中,應該在執行這條指令之前就已經用別的地址將資料段地址傳送到DS暫存器中了。當處理器執行到指令A1 02 00時,處理器會將DS中的內容和指令中指定的便宜地址相加,得到記憶體中的一個實體地址,這個實體地址就是處理器要去訪問的記憶體地址,就可以從該地址獲取一個字:00A0H。

如果下一次執行該程式,程式碼段和資料段發生變化,只需要將程式的程式碼段地址和資料段地址分別傳送到CS和DS就可以正常的執行程式了。

4.4 、8086的記憶體分段機制

前面講了如何從邏輯地址轉換到實體地址,以使得程式的執行和它在記憶體中的位置是無關的。上述策略在很多處理器上應用得到了支援。但是在8086處理器上,由於8086處理器是16位處理器,如果按照正常計算,給它提供16根地址線的話,那麼8086處理器就只能用於定址最多64KB的記憶體空間(65536位元組=2的15次方+1)。在當時的年代64KB的記憶體還是不夠的。所以8086處理器就將16根地址線擴充套件到20根地址線。從而使得可定址的空間變為1M(16*64KB)。

但是有一個問題就是16位的段地址加上16位的段內偏移地址,還是16位的,並不是20位的。所以有一個解決辦法就是在計算記憶體的實體地址時,先將段地址先左移4位,然後加上段內偏移地址,這樣就可以得到20位的實體地址,就可以將整個1M的地址空間表示完全。

8086在進行分段時,並不是每一個地址都可以作為段地址,地址必須是16的倍數,才能作為段地址。

如下圖,是其中的一種情況,我們從0地址開始分段,每段16位元組(從任何地址只要這個地址是16的倍數,都可以作為段地址,每一個段內最低有16位元組(可以分為65536個段)的記憶體,最高有65536(可以分為16個段)個位元組的記憶體)

4.5 、8086 處理器內部組成框圖

最後我們再來看一下8086處理器內部組成框圖:

在這裡插入圖片描述

上圖中,我們知道8086內部有8個16位通用暫存器,分別為AX,BX,CX,DX,SI,DI,BP,SP。ALU是算數邏輯部件,用於算數邏輯運算或者資料傳送。標誌暫存器是用於控制各種依賴於標誌位的標誌來進行相應的指令執行的,這個可以等到以後的文章中進行詳細的說明,目前不需要理解。處理器可以自動執行,依賴於控制器的控制。

指令佇列緩衝器,又名:指令預取佇列。什麼意思呢?就是當你的CPU在忙於做其他事情(一般是指執行那些不需要去記憶體中取的指令)而並沒有去記憶體中取指令執行時,此時指令佇列緩衝器(指令預取佇列)就會去記憶體中取出6個位元組的指令放到指令佇列緩衝器,那麼當CPU該執行記憶體中的指令時,它就會就近向指令佇列緩衝器中去取指令,這樣的話就會比去記憶體中取指令要快很多,畢竟記憶體還是通過外部匯流排與記憶體條相連線的。

CS是程式碼段暫存器。DS是資料段暫存器。SS是棧段暫存器(很重要,以後會講)。ES是額外的段暫存器,它的用處相當於補充段暫存器,當你的DS在使用時,但是你還要訪問另一個數據段,那麼此時就可以使用ES暫存器(後面的文章中我們就用ES暫存器指向顯示卡的記憶體區域,用來定址視訊記憶體,從而控制顯示卡)。IP暫存器,是存程式碼段的段內偏移地址的。那麼指令的地址此時就可以用“CS:IP”來表示。

上面的與匯流排控制邏輯相連的16位匯流排,實際上是16位的資料匯流排與20位的地址匯流排的低16位複用的。20位代表的是20位的地址匯流排是(當然,低16位是與資料匯流排複用的)。

5、 總結

想要寫出優秀的應用程式,就必須對系統程式設計有深入的理解。想要對系統程式設計有一定的理解,就必須理解作業系統原理。我選擇從X86彙編開始學習,逐步深入理解作業系統與計算機系統之間的關係。

本片文章可以讓我們學會

  • 暫存器與算數邏輯部件的簡單關係
  • 記憶體儲器
  • 指令和指令集
  • 程式的重定位問題
  • 處理器與記憶體之間的關係
  • 記憶體分段機制

學習探討加
qq:1126137994
微信:liu1126137994