天天看點

計算機系統-理論-CPU操作/處理資料的方式/尋址方式/局部性原理/位運算/進制轉換

在計算機中,cpu将一切硬體,都看成記憶體,各種硬體在CPU的眼中都是下面這樣子的

計算機系統-理論-CPU操作/處理資料的方式/尋址方式/局部性原理/位運算/進制轉換

CPU對硬體的操作,其實就是和這些硬體的記憶體或者BIOS進行互動,CPU能做且隻能做三件事

1.将某個位址中存儲的位元組轉移到另一個位址處

2.将兩個位址處的内容相加,并将結果存入某個位址

3.判斷某位址處的位元組是否為0

下面是8086CPU對記憶體的操作大緻流程

計算機系統-理論-CPU操作/處理資料的方式/尋址方式/局部性原理/位運算/進制轉換
日記:上圖中可以看出是先取代碼,IP自增,然後執行代碼,而不是先執行代碼,再IP自增,我個人認為此處應該與CPU的指令流水線實作有關,如果先執行,再IP自增,則指令流水線意義不大,此處純屬個人猜測

上圖中涉及到8086段寄存器的概念,至于為什麼會有段寄存器的存在​​可以參考這裡​​

同時我們根據上圖可以知道指令緩沖器是用來做什麼的,如果CPU真的是一條一條的從記憶體中擷取指令,那麼效率實在是有些低了,正因為如此,CPU在指令緩沖器的位置又将其分成了一二三級緩存,在此,先要了解下面兩個概念:局部性原理和緩存命中率

局部性原理

人們總是基于現在的情況,來推測未來的情況,比如,最近十分鐘之内在下雨,那麼我可以推測,未來一分鐘之内還會下雨,畢竟未來一分鐘之内下雨的機率比不下雨的機率要大很多,而緩存也同樣是基于這個原理,緩存總是趨于最近使用過的資料或者指令,基于這個原理,是以,資料的分布位址不是随機的,而是聚集到一起的,這個原理,被稱為局部性原理,局部性原理具體分為

空間局部性:接下來要通路的資料,和之前通路的資料,在記憶體位址中是臨近的,是以當緩存資料A時,那麼和A實體位置臨近的資料,也會緩存起來

時間局部性:如果一個資料被通路,那麼它接下來的一段時間之内,很有可能還會被通路,是以擷取資料A時,會把A緩存起來

注意,局部性原理,并不是一個真正的原理,它是經驗性主義,并且從統計學的角度來看,雖然不能百分之百為真,但是的确很有效

緩存命中率

既然了解了局部性原理,這個概念也就好解釋了,因為局部性原理并不能保證緩存的資料一定有用,也并不能保證接下來要用的資料一定被緩存起來,但是,現代計算機依據統計學以及大量的樣本實驗,局部性原理的緩存命中率能高達0.9(百分之90,但是通常不說百分之多少,都用小數表示)

根據局部性原理和緩存命中率,就可以設計出緩存,絕大多數的緩存都是如下設計的:當從記憶體中擷取一條指令的時候,順便擷取這條指令臨近的一些指令,緩存起來

CPU可以處理下面三處過來的資料

1.CPU内部

直接從寄存器中擷取資料,例如下面的彙編代碼都是正确的

mov ax,cx
mov ax,dx
mov ax,ds      

2.記憶體中

在彙編中,使用中括号包含寄存器的方式來描述從記憶體中擷取資料,且隻有4種寄存器可以用在[]中,它們分别是​​

​bx,bp,si,di​

​,例如下面的彙編代碼是正确的

mov ax,[bx]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+di]      

下面的彙編代碼是錯誤的

mov ax,[cx]
mov ax,[dx]
mov ax,[ds]      

3.端口中

從端口中擷取資料,本質上也是從記憶體中擷取資料,隻不過這個記憶體,不是我們常見的記憶體條,而是各個硬體的緩存中,例如網卡的緩存,鍵盤的緩存

CPU要讀取多長的資料

在計算機中作為一個整體來處理或運算的一組二進制數位,稱為一個字。英文是word。字是二進制數的基本機關,是資料總線寬度。比如在32位機器上,是按照4個位元組作為一個整體來處理,是以32系統一個字的大小為4位元組,同理,64位系統是按照8位元組作為一個整體,是以64位系統中一個字的大小為8位元組

彙編代碼​

​mov ax,[0]​

​表示從ds+0的實體位址處讀取資料,但是讀取多長的資料呢?cpu是有一套自己的規範的,規則如下

1.通過寄存器位數決定操作多長的資料

mov ax,1;因為ax是16位寄存器,是以後面的數字1是2個位元組
mov al,1;因為al是8位寄存器,是以後面的數字1是1個位元組
mov eax,1;在32位cpu中,eax是32位寄存器,是以後面的數字1是4位元組      

2.通過指定位數決定是操作多少資料

使用X ptr參數指定

// 數字1是2個位元組的數字1
mov word ptr ds:[0],1
// 數字1是1個位元組的數字1
inc byte ptr [bx]
// 數字1是4個位元組的數字1
inc dword ptr [ebx]      

3.對棧的操作一定是字操作

具體字是多長取決于cpu位數

// 32位cpu壓4個位元組入棧,16位壓1個位元組入棧
push ax
// 32位cpu壓4個位元組入棧,16位不存在eax
push eax      

尋址方式

“尋址”這兩個字指的是給定一個實體位址,然後去記憶體中尋找該實體位址中的資料

段位址和偏移位址組合,叫做邏輯位址,偏移位址又叫做有效位址,在彙編代碼中,給出有效位址的方式,叫做尋址方式,不同的寫法,表示了不同的尋址方式,現在我用idata表示一個立即數,也可以說idata是一個常量

直接尋址

尋址方式:​​

​[idata]​

​ 示例:

mov ax,[66]      

寄存器間接尋址

尋址方式:​​

​[bx]​

​​或者​

​[bp]​

​​或者​

​[si]​

​​或者​

​[di]​

​ 示例:

mov ax,[bx]
mov ax,[bp]
mov ax,[si]
mov ax,[di]      

寄存器相對尋址

尋址方式:​​

​[bx+idata]​

​​或者​

​[bp+idata]​

​​或者​

​[si+idata]​

​​或者​

​[di+idata]​

​ 示例:

mov ax,[bx+123]
mov ax,[bp+234]
mov ax,[si+11H]
mov ax,[di+5638]      

基址變址尋址

尋址方式:​​

​[bx+si]​

​​或者​

​[bx+di]​

​​或者​

​[bp+si]​

​​或者​

​[bp+di]​

​​,此處可參考​​通用寄存器(7)BP​​​會更好記憶

示例:

mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]      

相對基址變址尋址

尋址方式:​​

​[bx+si+idata]​

​​或者​

​[bx+di+idata]​

​​或者​

​[bp+si+idata]​

​​或者​

​[bp+di+idata]​

​​,就是在基址變址尋址的方式上加上了立即數(idata)

示例:

mov ax,[bx+si+0123H]
mov ax,[bx+di+89AB]
mov ax,[bp+si+89CC]]
mov ax,[bp+di+666H]      

進制的概念

進制轉換

由于10進制轉換成16進制或者轉8進制很複雜,是以通常情況都先将10進制轉換成2進制,在從2進制轉換成16或者8

例如:把二進制數101111轉換成八進制數和十六進制數

二進制數轉換成八進制數的方法是:整數部分從小數點向左數,每三位二進制數位為一組,最後不足三位補0,讀出三位二進制數對應的十進制數值,就是整數部分轉換的八進制數;小數部分從小數點向右數,也是每三位二進制數位為一組,最後不足三位補0,讀出三位二進制數對應的十進制數值,就是小數部分轉換的八進制小數的數值。即:2進制101111=8進制57。下面是分解步驟

1.因為2的3次方是8,是以每3位分解101111,于是變成了【101,111】

2.101對應的8進制是5,111對應的8進制是7,是以等于57

同理,2進制101111轉換16進制

1.因為2的4次方是16,是以每4位分解101111,于是變成了【0010,1111】

2.0010對應的16進制是2,1111對應的16進制是15,是以等于2F

在C++中的表示

std::cout << 10 << std::endl; //列印10
std::cout << 0b10 << std::endl;//0b開頭表示二進制,列印出2
std::cout << 010 << std::endl;//0開頭表示8進制
std::cout << 0x10 << std::endl;//0x開頭表示16進制      

負數的補碼:

補碼是負數在記憶體中的表示,比方說-9這個數,會将-9的補碼存在記憶體中,因為補碼的存在,是以cpu就可以把減法當成加法來處理,這樣cpu就不需要設計兩套電路(一個加法電路,一個減法電路),這是cpu使用補碼的唯一原因,下面是對負9取其補碼

(1)-9的絕對值是9

(2)9的二進制是:0000 1001

(3)對9的二進制按位取反:1111 0110

(4)對上述的值加1:1111 0111

結果:是以-9的補碼就是:1111 0111

現有10-9=1這個計算,則用上面的補碼表達就是

10 -9 1
0000 1010 +1111 0111 =0000 0001

NOTE: 可以根據首位是不是0,也就是說char類型是不是<0來判斷該字元是不是ASCII字元,因為ASCII隻使用了後7位,最高位沒用

左移/右移

邏輯右移:補0

算數右移:補符号位,如果符号位是1,則補1,符号位是0,則補0

左移:左移不區分邏輯還是算數,也不區分正負數,右側補0

注:多數C++編譯器會采用算數右移

按位與:參與運算的雙方都為1,結果為1,否則結果為0,該操作可以實作下面功能

1.清0,如果将一個數清0,則可以直接與0做與運算,下面是清0的java代碼

int a = 3;
  a &= 0;
  System.out.println(a);      

2.可以取出指定數中的某些位,下面該操作的java代碼

// 十進制666對應的二進制位:10 1001 1010
int a = 666;
// 取右側4位,那麼就與1111
a &= 0xF;
System.out.println(a);
// 取右側1位,那麼就與0001
a &= 0x1;
System.out.println(a);
// 取右側5位,那麼就與1 1111
a &= 0x1F;
System.out.println(a);
// 取從右側開始數第3位,那麼就與0100
a &= 0x4;
System.out.println(a);      

按位異或:參與運算的兩個位不相同,結果為1,若相同,結果為0

1^1==0;
0^0==0;
1^0==1;      
// 十進制666對應的二進制位:10 1001 1010
int a = 666;
// 異或1111
int b=0xF;
// c為661,對應的二進制是:10 1001 0101
int c=a^b;      
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println(a);
System.out.println(b);      

繼續閱讀