Java程序員必須了解的計算機底層知識

這期內(nèi)容當中小編將會給大家?guī)碛嘘P(guān)Java 程序員必須了解的計算機底層知識,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

創(chuàng)新互聯(lián)建站專注于隴川企業(yè)網(wǎng)站建設(shè),響應式網(wǎng)站開發(fā),商城系統(tǒng)網(wǎng)站開發(fā)。隴川網(wǎng)站建設(shè)公司,為隴川等地區(qū)提供建站服務。全流程按需求定制制作,專業(yè)設(shè)計,全程項目跟蹤,創(chuàng)新互聯(lián)建站專業(yè)和態(tài)度為您提供的服務

我們每個程序員或許都有一個夢,那就是成為大牛,我們或許都沉浸在各種框架中,以為框架就是一切,以為應用層才是最重要的,你錯了。在當今計算機行業(yè)中,會應用是基本素質(zhì),如果你懂其原理才能讓你在行業(yè)中走的更遠,而計算機基礎(chǔ)知識又是重中之重。下面,跟隨我的腳步,為你介紹一下計算機底層知識。

CPU

還不了解 CPU 嗎?現(xiàn)在就帶你了解一下 CPU 是什么

CPU 的全稱是 Central Processing Unit,它是你的電腦中最硬核的組件,這種說法一點不為過。CPU 是能夠讓你的計算機叫計算機的核心組件,但是它卻不能代表你的電腦,CPU 與計算機的關(guān)系就相當于大腦和人的關(guān)系。CPU 的核心是從程序或應用程序獲取指令并執(zhí)行計算。此過程可以分為三個關(guān)鍵階段:提取,解碼和執(zhí)行。CPU從系統(tǒng)的主存中提取指令,然后解碼該指令的實際內(nèi)容,然后再由 CPU 的相關(guān)部分執(zhí)行該指令。

CPU 內(nèi)部處理過程

下圖展示了一般程序的運行流程(以 C 語言為例),可以說了解程序的運行流程是掌握程序運行機制的基礎(chǔ)和前提。

Java 程序員必須了解的計算機底層知識

在這個流程中,CPU 負責的就是解釋和運行最終轉(zhuǎn)換成機器語言的內(nèi)容。

CPU 主要由兩部分構(gòu)成:控制單元 和 算術(shù)邏輯單元(ALU)

  • 控制單元:從內(nèi)存中提取指令并解碼執(zhí)行
  • 算數(shù)邏輯單元(ALU):處理算數(shù)和邏輯運算

CPU 是計算機的心臟和大腦,它和內(nèi)存都是由許多晶體管組成的電子部件。它接收數(shù)據(jù)輸入,執(zhí)行指令并處理信息。它與輸入/輸出(I / O)設(shè)備進行通信,這些設(shè)備向 CPU 發(fā)送數(shù)據(jù)和從 CPU 接收數(shù)據(jù)。

從功能來看,CPU 的內(nèi)部由寄存器、控制器、運算器和時鐘四部分組成,各部分之間通過電信號連通。

Java 程序員必須了解的計算機底層知識

  • 寄存器是中央處理器內(nèi)的組成部分。它們可以用來暫存指令、數(shù)據(jù)和地址。可以將其看作是內(nèi)存的一種。根據(jù)種類的不同,一個 CPU 內(nèi)部會有 20 - 100個寄存器。
  • 控制器負責把內(nèi)存上的指令、數(shù)據(jù)讀入寄存器,并根據(jù)指令的結(jié)果控制計算機
  • 運算器負責運算從內(nèi)存中讀入寄存器的數(shù)據(jù)
  • 時鐘 負責發(fā)出 CPU 開始計時的時鐘信號

CPU 是一系列寄存器的集合體

在 CPU 的四個結(jié)構(gòu)中,我們程序員只需要了解寄存器就可以了,其余三個不用過多關(guān)注,為什么這么說?因為程序是把寄存器作為對象來描述的。

不同類型的 CPU ,其內(nèi)部寄存器的種類,數(shù)量以及寄存器存儲的數(shù)值范圍都是不同的。不過,根據(jù)功能的不同,可以將寄存器劃分為下面這幾類

種類功能
累加寄存器存儲運行的數(shù)據(jù)和運算后的數(shù)據(jù)。
標志寄存器用于反應處理器的狀態(tài)和運算結(jié)果的某些特征以及控制指令的執(zhí)行。
程序計數(shù)器程序計數(shù)器是用于存放下一條指令所在單元的地址的地方。
基址寄存器存儲數(shù)據(jù)內(nèi)存的起始位置
變址寄存器存儲基址寄存器的相對地址
通用寄存器存儲任意數(shù)據(jù)
指令寄存器儲存正在被運行的指令,CPU內(nèi)部使用,程序員無法對該寄存器進行讀寫
棧寄存器存儲棧區(qū)域的起始位置

其中程序計數(shù)器、累加寄存器、標志寄存器、指令寄存器和棧寄存器都只有一個,其他寄存器一般有多個。

Java 程序員必須了解的計算機底層知識

下面就對各個寄存器進行說明

程序計數(shù)器

程序計數(shù)器(Program Counter)是用來存儲下一條指令所在單元的地址。

程序執(zhí)行時,PC的初值為程序第一條指令的地址,在順序執(zhí)行程序時,控制器首先按程序計數(shù)器所指出的指令地址從內(nèi)存中取出一條指令,然后分析和執(zhí)行該指令,同時將PC的值加1指向下一條要執(zhí)行的指令。

我們還是以一個事例為準來詳細的看一下程序計數(shù)器的執(zhí)行過程

Java 程序員必須了解的計算機底層知識

這是一段進行相加的操作,程序啟動,在經(jīng)過編譯解析后會由操作系統(tǒng)把硬盤中的程序復制到內(nèi)存中,示例中的程序是將 123 和 456 執(zhí)行相加操作,并將結(jié)果輸出到顯示器上。

地址 0100 是程序運行的起始位置。Windows 等操作系統(tǒng)把程序從硬盤復制到內(nèi)存后,會將程序計數(shù)器作為設(shè)定為起始位置 0100,然后執(zhí)行程序,每執(zhí)行一條指令后,程序計數(shù)器的數(shù)值會增加1(或者直接指向下一條指令的地址),然后,CPU 就會根據(jù)程序計數(shù)器的數(shù)值,從內(nèi)存中讀取命令并執(zhí)行,也就是說,程序計數(shù)器控制著程序的流程

條件分支和循環(huán)機制

高級語言中的條件控制流程主要分為三種:順序執(zhí)行、條件分支、循環(huán)判斷三種,順序執(zhí)行是按照地址的內(nèi)容順序的執(zhí)行指令。條件分支是根據(jù)條件執(zhí)行任意地址的指令。循環(huán)是重復執(zhí)行同一地址的指令。

  • 順序執(zhí)行的情況比較簡單,每執(zhí)行一條指令程序計數(shù)器的值就是+ 1
  • 條件和循環(huán)分支會使程序計數(shù)器的值指向任意的地址,這樣一來,程序便可以返回到上一個地址來重復執(zhí)行同一個指令,或者跳轉(zhuǎn)到任意指令。

下面以條件分支為例來說明程序的執(zhí)行過程(循環(huán)也很相似)

Java 程序員必須了解的計算機底層知識

程序的開始過程和順序流程是一樣的,CPU 從0100處開始執(zhí)行命令,在0100和0101都是順序執(zhí)行,PC 的值順序+1,執(zhí)行到0102地址的指令時,判斷0106寄存器的數(shù)值大于0,跳轉(zhuǎn)(jump)到0104地址的指令,將數(shù)值輸出到顯示器中,然后結(jié)束程序,0103 的指令被跳過了,這就和我們程序中的 if() 判斷是一樣的,在不滿足條件的情況下,指令會直接跳過。所以 PC 的執(zhí)行過程也就沒有直接+1,而是下一條指令的地址。

標志寄存器

條件和循環(huán)分支會使用到 jump(跳轉(zhuǎn)指令),會根據(jù)當前的指令來判斷是否跳轉(zhuǎn),上面我們提到了標志寄存器,無論當前累加寄存器的運算結(jié)果是正數(shù)、負數(shù)還是零,標志寄存器都會將其保存

CPU 在進行運算時,標志寄存器的數(shù)值會根據(jù)當前運算的結(jié)果自動設(shè)定,運算結(jié)果的正、負和零三種狀態(tài)由標志寄存器的三個位表示。標志寄存器的第一個字節(jié)位、第二個字節(jié)位、第三個字節(jié)位各自的結(jié)果都為1時,分別代表著正數(shù)、零和負數(shù)。

Java 程序員必須了解的計算機底層知識

CPU 的執(zhí)行機制比較有意思,假設(shè)累加寄存器中存儲的 XXX 和通用寄存器中存儲的 YYY 做比較,執(zhí)行比較的背后,CPU 的運算機制就會做減法運算。而無論減法運算的結(jié)果是正數(shù)、零還是負數(shù),都會保存到標志寄存器中。結(jié)果為正表示 XXX 比 YYY 大,結(jié)果為零表示 XXX 和 YYY 相等,結(jié)果為負表示 XXX 比 YYY 小。程序比較的指令,實際上是在 CPU 內(nèi)部做減法運算。

函數(shù)調(diào)用機制

接下來,我們繼續(xù)介紹函數(shù)調(diào)用機制,哪怕是高級語言編寫的程序,函數(shù)調(diào)用處理也是通過把程序計數(shù)器的值設(shè)定成函數(shù)的存儲地址來實現(xiàn)的。函數(shù)執(zhí)行跳轉(zhuǎn)指令后,必須進行返回處理,單純的指令跳轉(zhuǎn)沒有意義,下面是一個實現(xiàn)函數(shù)跳轉(zhuǎn)的例子

Java 程序員必須了解的計算機底層知識

圖中將變量 a 和 b 分別賦值為 123 和 456 ,調(diào)用 MyFun(a,b) 方法,進行指令跳轉(zhuǎn)。圖中的地址是將 C 語言編譯成機器語言后運行時的地址,由于1行 C 程序在編譯后通常會變?yōu)槎嘈袡C器語言,所以圖中的地址是分散的。在執(zhí)行完 MyFun(a,b)指令后,程序會返回到 MyFun(a,b) 的下一條指令,CPU 繼續(xù)執(zhí)行下面的指令。

函數(shù)的調(diào)用和返回很重要的兩個指令是 call 和 return 指令,再將函數(shù)的入口地址設(shè)定到程序計數(shù)器之前,call 指令會把調(diào)用函數(shù)后要執(zhí)行的指令地址存儲在名為棧的主存內(nèi)。函數(shù)處理完畢后,再通過函數(shù)的出口來執(zhí)行 return 指令。return 指令的功能是把保存在棧中的地址設(shè)定到程序計數(shù)器。MyFun 函數(shù)在被調(diào)用之前,0154 地址保存在棧中,MyFun 函數(shù)處理完成后,會把 0154 的地址保存在程序計數(shù)器中。

這個調(diào)用過程如下

Java 程序員必須了解的計算機底層知識

在一些高級語言的條件或者循環(huán)語句中,函數(shù)調(diào)用的處理會轉(zhuǎn)換成 call 指令,函數(shù)結(jié)束后的處理則會轉(zhuǎn)換成 return 指令。

通過地址和索引實現(xiàn)數(shù)組

接下來我們看一下基址寄存器和變址寄存器,通過這兩個寄存器,我們可以對主存上的特定區(qū)域進行劃分,來實現(xiàn)類似數(shù)組的操作

首先,我們用十六進制數(shù)將計算機內(nèi)存上的 00000000 - FFFFFFFF 的地址劃分出來。那么,凡是該范圍的內(nèi)存地址,只要有一個 32 位的寄存器,便可查看全部地址。但如果想要想數(shù)組那樣分割特定的內(nèi)存區(qū)域以達到連續(xù)查看的目的的話,使用兩個寄存器會更加方便。

例如,我們用兩個寄存器(基址寄存器和變址寄存器)來表示內(nèi)存的值

Java 程序員必須了解的計算機底層知識

這種表示方式很類似數(shù)組的構(gòu)造,數(shù)組是指同樣長度的數(shù)據(jù)在內(nèi)存中進行連續(xù)排列的數(shù)據(jù)構(gòu)造。用數(shù)組名表示數(shù)組全部的值,通過索引來區(qū)分數(shù)組的各個數(shù)據(jù)元素,例如: a[0] - a[4],[]內(nèi)的 0 - 4 就是數(shù)組的下標。

CPU 指令執(zhí)行過程

幾乎所有的馮·諾伊曼型計算機的CPU,其工作都可以分為5個階段:取指令、指令譯碼、執(zhí)行指令、訪存取數(shù)、結(jié)果寫回

  • 取指令階段是將內(nèi)存中的指令讀取到 CPU 中寄存器的過程,程序寄存器用于存儲下一條指令所在的地址
  • 指令譯碼階段,在取指令完成后,立馬進入指令譯碼階段,在指令譯碼階段,指令譯碼器按照預定的指令格式,對取回的指令進行拆分和解釋,識別區(qū)分出不同的指令類別以及各種獲取操作數(shù)的方法。
  • 執(zhí)行指令階段,譯碼完成后,就需要執(zhí)行這一條指令了,此階段的任務是完成指令所規(guī)定的各種操作,具體實現(xiàn)指令的功能。
  • 訪問取數(shù)階段,根據(jù)指令的需要,有可能需要從內(nèi)存中提取數(shù)據(jù),此階段的任務是:根據(jù)指令地址碼,得到操作數(shù)在主存中的地址,并從主存中讀取該操作數(shù)用于運算。
  • 結(jié)果寫回階段,作為最后一個階段,結(jié)果寫回(Write Back,WB)階段把執(zhí)行指令階段的運行結(jié)果數(shù)據(jù)“寫回”到某種存儲形式:結(jié)果數(shù)據(jù)經(jīng)常被寫到CPU的內(nèi)部寄存器中,以便被后續(xù)的指令快速地存取;

內(nèi)存

CPU 和 內(nèi)存就像是一堆不可分割的戀人一樣,是無法拆散的一對兒,沒有內(nèi)存,CPU 無法執(zhí)行程序指令,那么計算機也就失去了意義;只有內(nèi)存,無法執(zhí)行指令,那么計算機照樣無法運行。

那么什么是內(nèi)存呢?內(nèi)存和 CPU 如何進行交互?下面就來介紹一下

什么是內(nèi)存

內(nèi)存(Memory)是計算機中最重要的部件之一,它是程序與CPU進行溝通的橋梁。

計算機中所有程序的運行都是在內(nèi)存中進行的,因此內(nèi)存對計算機的影響非常大,內(nèi)存又被稱為主存,其作用是存放 CPU 中的運算數(shù)據(jù),以及與硬盤等外部存儲設(shè)備交換的數(shù)據(jù)。只要計算機在運行中,CPU 就會把需要運算的數(shù)據(jù)調(diào)到主存中進行運算,當運算完成后CPU再將結(jié)果傳送出來,主存的運行也決定了計算機的穩(wěn)定運行。

內(nèi)存的物理結(jié)構(gòu)

內(nèi)存的內(nèi)部是由各種 IC 電路組成的,它的種類很龐大,但是其主要分為三種存儲器

  • 隨機存儲器(RAM):內(nèi)存中最重要的一種,表示既可以從中讀取數(shù)據(jù),也可以寫入數(shù)據(jù)。當機器關(guān)閉時,內(nèi)存中的信息會 丟失
  • 只讀存儲器(ROM):ROM 一般只能用于數(shù)據(jù)的讀取,不能寫入數(shù)據(jù),但是當機器停電時,這些數(shù)據(jù)不會丟失。
  • 高速緩存(Cache):Cache 也是我們經(jīng)常見到的,它分為一級緩存(L1 Cache)、二級緩存(L2 Cache)、三級緩存(L3 Cache)這些數(shù)據(jù),它位于內(nèi)存和 CPU 之間,是一個讀寫速度比內(nèi)存更快的存儲器。當 CPU 向內(nèi)存寫入數(shù)據(jù)時,這些數(shù)據(jù)也會被寫入高速緩存中。當 CPU 需要讀取數(shù)據(jù)時,會直接從高速緩存中直接讀取,當然,如需要的數(shù)據(jù)在Cache中沒有,CPU會再去讀取內(nèi)存中的數(shù)據(jù)。

內(nèi)存 IC 是一個完整的結(jié)構(gòu),它內(nèi)部也有電源、地址信號、數(shù)據(jù)信號、控制信號和用于尋址的 IC 引腳來進行數(shù)據(jù)的讀寫。下面是一個虛擬的 IC 引腳示意圖

Java 程序員必須了解的計算機底層知識

圖中 VCC 和 GND 表示電源,A0 - A9 是地址信號的引腳,D0 - D7 表示的是控制信號、RD 和 WR 都是好控制信號,我用不同的顏色進行了區(qū)分,將電源連接到 VCC 和 GND 后,就可以對其他引腳傳遞 0 和 1 的信號,大多數(shù)情況下,+5V 表示1,0V 表示 0

我們都知道內(nèi)存是用來存儲數(shù)據(jù),那么這個內(nèi)存 IC 中能存儲多少數(shù)據(jù)呢?D0 - D7 表示的是數(shù)據(jù)信號,也就是說,一次可以輸入輸出 8 bit = 1 byte 的數(shù)據(jù)。A0 - A9 是地址信號共十個,表示可以指定 00000 00000 - 11111 11111 共 2 的 10次方 = 1024個地址。每個地址都會存放 1 byte 的數(shù)據(jù),因此我們可以得出內(nèi)存 IC 的容量就是 1 KB。

內(nèi)存的讀寫過程

讓我們把關(guān)注點放在內(nèi)存 IC 對數(shù)據(jù)的讀寫過程上來吧!我們來看一個對內(nèi)存IC 進行數(shù)據(jù)寫入和讀取的模型

Java 程序員必須了解的計算機底層知識

來詳細描述一下這個過程,假設(shè)我們要向內(nèi)存 IC 中寫入 1byte 的數(shù)據(jù)的話,它的過程是這樣的:

  • 首先給 VCC 接通 +5V 的電源,給 GND 接通 0V 的電源,使用 A0 - A9 來指定數(shù)據(jù)的存儲場所,然后再把數(shù)據(jù)的值輸入給 D0 - D7 的數(shù)據(jù)信號,并把 WR(write)的值置為 1,執(zhí)行完這些操作后,即可以向內(nèi)存 IC 寫入數(shù)據(jù)
  • 讀出數(shù)據(jù)時,只需要通過 A0 - A9 的地址信號指定數(shù)據(jù)的存儲場所,然后再將 RD 的值置為 1 即可。
  • 圖中的 RD 和 WR 又被稱為控制信號。其中當WR 和 RD 都為 0 時,無法進行寫入和讀取操作。

內(nèi)存的現(xiàn)實模型

為了便于記憶,我們把內(nèi)存模型映射成為我們現(xiàn)實世界的模型,在現(xiàn)實世界中,內(nèi)存的模型很想我們生活的樓房。在這個樓房中,1層可以存儲一個字節(jié)的數(shù)據(jù),樓層號就是地址,下面是內(nèi)存和樓層整合的模型圖

Java 程序員必須了解的計算機底層知識

我們知道,程序中的數(shù)據(jù)不僅只有數(shù)值,還有數(shù)據(jù)類型的概念,從內(nèi)存上來看,就是占用內(nèi)存大小(占用樓層數(shù))的意思。即使物理上強制以 1 個字節(jié)為單位來逐一讀寫數(shù)據(jù)的內(nèi)存,在程序中,通過指定其數(shù)據(jù)類型,也能實現(xiàn)以特定字節(jié)數(shù)為單位來進行讀寫。

二進制

我們都知道,計算機的底層都是使用二進制數(shù)據(jù)進行數(shù)據(jù)流傳輸?shù)模敲礊槭裁磿褂枚M制表示計算機呢?或者說,什么是二進制數(shù)呢?在拓展一步,如何使用二進制進行加減乘除?下面就來看一下

什么是二進制數(shù)

那么什么是二進制數(shù)呢?為了說明這個問題,我們先把 00100111 這個數(shù)轉(zhuǎn)換為十進制數(shù)看一下,二進制數(shù)轉(zhuǎn)換為十進制數(shù),直接將各位置上的值 * 位權(quán)即可,那么我們將上面的數(shù)值進行轉(zhuǎn)換

Java 程序員必須了解的計算機底層知識

也就是說,二進制數(shù)代表的 00100111 轉(zhuǎn)換成十進制就是 39,這個 39 并不是 3 和 9 兩個數(shù)字連著寫,而是 3 * 10 + 9 * 1,這里面的 10 , 1 就是位權(quán),以此類推,上述例子中的位權(quán)從高位到低位依次就是 7 6 5 4 3 2 1 0。這個位權(quán)也叫做次冪,那么最高位就是2的7次冪,2的6次冪 等等。二進制數(shù)的運算每次都會以2為底,這個2 指得就是基數(shù),那么十進制數(shù)的基數(shù)也就是 10 。在任何情況下位權(quán)的值都是 數(shù)的位數(shù) - 1,那么第一位的位權(quán)就是 1 - 1 = 0, 第二位的位權(quán)就睡 2 - 1 = 1,以此類推。

那么我們所說的二進制數(shù)其實就是 用0和1兩個數(shù)字來表示的數(shù),它的基數(shù)為2,它的數(shù)值就是每個數(shù)的位數(shù) * 位權(quán)再求和得到的結(jié)果,我們一般來說數(shù)值指的就是十進制數(shù),那么它的數(shù)值就是 3 * 10 + 9 * 1 = 39。

移位運算和乘除的關(guān)系

在了解過二進制之后,下面我們來看一下二進制的運算,和十進制數(shù)一樣,加減乘除也適用于二進制數(shù),只要注意逢 2 進位即可。二進制數(shù)的運算,也是計算機程序所特有的運算,因此了解二進制的運算是必須要掌握的。

首先我們來介紹移位 運算,移位運算是指將二進制的數(shù)值的各個位置上的元素坐左移和右移操作,見下圖

Java 程序員必須了解的計算機底層知識

補數(shù)

剛才我們沒有介紹右移的情況,是因為右移之后空出來的高位數(shù)值,有 0 和 1 兩種形式。要想?yún)^(qū)分什么時候補0什么時候補1,首先就需要掌握二進制數(shù)表示負數(shù)的方法。

二進制數(shù)中表示負數(shù)值時,一般會把最高位作為符號來使用,因此我們把這個最高位當作符號位。 符號位是 0 時表示正數(shù),是 1 時表示 負數(shù)。那么 -1 用二進制數(shù)該如何表示呢?可能很多人會這么認為:因為 1 的二進制數(shù)是 0000 0001,最高位是符號位,所以正確的表示 -1 應該是 1000 0001,但是這個答案真的對嗎?

計算機世界中是沒有減法的,計算機在做減法的時候其實就是在做加法,也就是用加法來實現(xiàn)的減法運算。比如 100 - 50 ,其實計算機來看的時候應該是 100 +  (-50),為此,在表示負數(shù)的時候就要用到二進制補數(shù),補數(shù)就是用正數(shù)來表示的負數(shù)。

為了獲得補數(shù),我們需要將二進制的各數(shù)位的數(shù)值全部取反,然后再將結(jié)果 + 1 即可,先記住這個結(jié)論,下面我們來演示一下。

Java 程序員必須了解的計算機底層知識

具體來說,就是需要先獲取某個數(shù)值的二進制數(shù),然后對二進制數(shù)的每一位做取反操作(0 ---> 1 , 1 ---> 0),最后再對取反后的數(shù) +1 ,這樣就完成了補數(shù)的獲取。

補數(shù)的獲取,雖然直觀上不易理解,但是邏輯上卻非常嚴謹,比如我們來看一下 1 - 1 的這個過程,我們先用上面的這個 1000 0001(它是1的補數(shù),不知道的請看上文,正確性先不管,只是用來做一下計算)來表示一下

Java 程序員必須了解的計算機底層知識

奇怪,1 - 1 會變成 130 ,而不是0,所以可以得出結(jié)論 1000 0001 表示 -1 是完全錯誤的。

那么正確的該如何表示呢?其實我們上面已經(jīng)給出結(jié)果了,那就是 1111 1111,來論證一下它的正確性

Java 程序員必須了解的計算機底層知識

我們可以看到 1 - 1 其實實際上就是 1 + (-1),對 -1 進行上面的取反 + 1 后變?yōu)?nbsp;1111 1111, 然后與 1 進行加法運算,得到的結(jié)果是九位的 1 0000 0000,結(jié)果發(fā)生了溢出,計算機會直接忽略掉溢出位,也就是直接拋掉 最高位 1 ,變?yōu)?nbsp;0000 0000。也就是 0,結(jié)果正確,所以 1111 1111 表示的就是 -1 。

所以負數(shù)的二進制表示就是先求其補數(shù),補數(shù)的求解過程就是對原始數(shù)值的二進制數(shù)各位取反,然后將結(jié)果 + 1

算數(shù)右移和邏輯右移的區(qū)別

在了解完補數(shù)后,我們重新考慮一下右移這個議題,右移在移位后空出來的最高位有兩種情況0 和 1

將二進制數(shù)作為帶符號的數(shù)值進行右移運算時,移位后需要在最高位填充移位前符號位的值( 0 或 1)。這就被稱為算數(shù)右移。如果數(shù)值使用補數(shù)表示的負數(shù)值,那么右移后在空出來的最高位補 1,就可以正確的表示 1/2,1/4,1/8等的數(shù)值運算。如果是正數(shù),那么直接在空出來的位置補 0 即可。

下面來看一個右移的例子。將 -4 右移兩位,來各自看一下移位示意圖

Java 程序員必須了解的計算機底層知識

如上圖所示,在邏輯右移的情況下, -4 右移兩位會變成 63, 顯然不是它的 1/4,所以不能使用邏輯右移,那么算數(shù)右移的情況下,右移兩位會變?yōu)?nbsp;-1,顯然是它的 1/4,故而采用算數(shù)右移。

那么我們可以得出來一個結(jié)論:左移時,無論是圖形還是數(shù)值,移位后,只需要將低位補 0 即可;右移時,需要根據(jù)情況判斷是邏輯右移還是算數(shù)右移。

下面介紹一下符號擴展:將數(shù)據(jù)進行符號擴展是為了產(chǎn)生一個位數(shù)加倍、但數(shù)值大小不變的結(jié)果,以滿足有些指令對操作數(shù)位數(shù)的要求,例如倍長于除數(shù)的被除數(shù),再如將數(shù)據(jù)位數(shù)加長以減少計算過程中的誤差。

以8位二進制為例,符號擴展就是指在保持值不變的前提下將其轉(zhuǎn)換成為16位和32位的二進制數(shù)。將0111 1111這個正的 8位二進制數(shù)轉(zhuǎn)換成為 16位二進制數(shù)時,很容易就能夠得出0000 0000 0111 1111這個正確的結(jié)果,但是像 1111 1111這樣的補數(shù)來表示的數(shù)值,該如何處理?直接將其表示成為1111 1111 1111 1111就可以了。

也就是說,不管正數(shù)還是補數(shù)表示的負數(shù),只需要將 0 和 1 填充高位即可。

內(nèi)存和磁盤的關(guān)系

我們大家知道,計算機的五大基礎(chǔ)部件是 存儲器控制器運算器輸入和輸出設(shè)備,其中從存儲功能的角度來看,可以把存儲器分為內(nèi)存和 磁盤,我們上面介紹過內(nèi)存,下面就來介紹一下磁盤以及磁盤和內(nèi)存的關(guān)系

程序不讀入內(nèi)存就無法運行

計算機最主要的存儲部件是內(nèi)存和磁盤。磁盤中存儲的程序必須加載到內(nèi)存中才能運行,在磁盤中保存的程序是無法直接運行的,這是因為負責解析和運行程序內(nèi)容的 CPU 是需要通過程序計數(shù)器來指定內(nèi)存地址從而讀出程序指令的。

Java 程序員必須了解的計算機底層知識

磁盤構(gòu)造

磁盤緩存

我們上面提到,磁盤往往和內(nèi)存是互利共生的關(guān)系,相互協(xié)作,彼此持有良好的合作關(guān)系。每次內(nèi)存都需要從磁盤中讀取數(shù)據(jù),必然會讀到相同的內(nèi)容,所以一定會有一個角色負責存儲我們經(jīng)常需要讀到的內(nèi)容。我們大家做軟件的時候經(jīng)常會用到緩存技術(shù),那么硬件層面也不例外,磁盤也有緩存,磁盤的緩存叫做磁盤緩存

磁盤緩存指的是把從磁盤中讀出的數(shù)據(jù)存儲到內(nèi)存的方式,這樣一來,當接下來需要讀取相同的內(nèi)容時,就不會再通過實際的磁盤,而是通過磁盤緩存來讀取。某一種技術(shù)或者框架的出現(xiàn)勢必要解決某種問題的,那么磁盤緩存就大大改善了磁盤訪問的速度

Java 程序員必須了解的計算機底層知識

虛擬內(nèi)存

虛擬內(nèi)存是內(nèi)存和磁盤交互的第二個媒介。虛擬內(nèi)存是指把磁盤的一部分作為假想內(nèi)存來使用。這與磁盤緩存是假想的磁盤(實際上是內(nèi)存)相對,虛擬內(nèi)存是假想的內(nèi)存(實際上是磁盤)。

虛擬內(nèi)存是計算機系統(tǒng)內(nèi)存管理的一種技術(shù)。它使得應用程序認為它擁有連續(xù)可用的內(nèi)存(一個完整的地址空間),但是實際上,它通常被分割成多個物理碎片,還有部分存儲在外部磁盤管理器上,必要時進行數(shù)據(jù)交換。

通過借助虛擬內(nèi)存,在內(nèi)存不足時仍然可以運行程序。例如,在只剩 5MB 內(nèi)存空間的情況下仍然可以運行 10MB 的程序。由于 CPU 只能執(zhí)行加載到內(nèi)存中的程序,因此,虛擬內(nèi)存的空間就需要和內(nèi)存中的空間進行置換(swap),然后運行程序。

虛擬內(nèi)存與內(nèi)存的交換方式

虛擬內(nèi)存的方法有分頁式 和 分段式 兩種。Windows 采用的是分頁式。該方式是指在不考慮程序構(gòu)造的情況下,把運行的程序按照一定大小的頁進行分割,并以為單位進行置換。在分頁式中,我們把磁盤的內(nèi)容讀到內(nèi)存中稱為 Page In,把內(nèi)存的內(nèi)容寫入磁盤稱為 Page Out

Windows 計算機的頁大小為 4KB ,也就是說,需要把應用程序按照 4KB 的頁來進行切分,以頁(page)為單位放到磁盤中,然后進行置換。

Java 程序員必須了解的計算機底層知識

為了實現(xiàn)內(nèi)存功能,Windows 在磁盤上提供了虛擬內(nèi)存使用的文件(page file,頁文件)。該文件由 Windows 生成和管理,文件的大小和虛擬內(nèi)存大小相同,通常大小是內(nèi)存的 1 - 2 倍。

磁盤的物理結(jié)構(gòu)

之前我們介紹了CPU、內(nèi)存的物理結(jié)構(gòu),現(xiàn)在我們來介紹一下磁盤的物理結(jié)構(gòu)。磁盤的物理結(jié)構(gòu)指的是磁盤存儲數(shù)據(jù)的形式

磁盤是通過其物理表面劃分成多個空間來使用的。劃分的方式有兩種:可變長方式 和 扇區(qū)方式。前者是將物理結(jié)構(gòu)劃分成長度可變的空間,后者是將磁盤結(jié)構(gòu)劃分為固定長度的空間。一般 Windows 所使用的硬盤和軟盤都是使用扇區(qū)這種方式。扇區(qū)中,把磁盤表面分成若干個同心圓的空間就是 磁道,把磁道按照固定大小的存儲空間劃分而成的就是 扇區(qū)

Java 程序員必須了解的計算機底層知識

扇區(qū)是對磁盤進行物理讀寫的最小單位。Windows 中使用的磁盤,一般是一個扇區(qū) 512 個字節(jié)。不過,Windows 在邏輯方面對磁盤進行讀寫的單位是扇區(qū)整數(shù)倍簇。根據(jù)磁盤容量不同功能,1簇可以是 512 字節(jié)(1 簇 = 1扇區(qū))、1KB(1簇 = 2扇區(qū))、2KB、4KB、8KB、16KB、32KB( 1 簇 = 64 扇區(qū))。簇和扇區(qū)的大小是相等的。

壓縮算法

我們想必都有過壓縮和 解壓縮文件的經(jīng)歷,當文件太大時,我們會使用文件壓縮來降低文件的占用空間。比如微信上傳文件的限制是100 MB,我這里有個文件夾無法上傳,但是我解壓完成后的文件一定會小于 100 MB,那么我的文件就可以上傳了。

此外,我們把相機拍完的照片保存到計算機上的時候,也會使用壓縮算法進行文件壓縮,文件壓縮的格式一般是JPEG

那么什么是壓縮算法呢?壓縮算法又是怎么定義的呢?在認識算法之前我們需要先了解一下文件是如何存儲的

文件存儲

文件是將數(shù)據(jù)存儲在磁盤等存儲媒介的一種形式。程序文件中最基本的存儲數(shù)據(jù)單位是字節(jié)。文件的大小不管是 xxxKB、xxxMB等來表示,就是因為文件是以字節(jié) B = Byte 為單位來存儲的。

文件就是字節(jié)數(shù)據(jù)的集合。用 1 字節(jié)(8 位)表示的字節(jié)數(shù)據(jù)有 256 種,用二進制表示的話就是 0000 0000 - 1111 1111 。如果文件中存儲的數(shù)據(jù)是文字,那么該文件就是文本文件。如果是圖形,那么該文件就是圖像文件。在任何情況下,文件中的字節(jié)數(shù)都是連續(xù)存儲的。

Java 程序員必須了解的計算機底層知識

壓縮算法的定義

上面介紹了文件的集合體其實就是一堆字節(jié)數(shù)據(jù)的集合,那么我們就可以來給壓縮算法下一個定義。

壓縮算法(compaction algorithm)指的就是數(shù)據(jù)壓縮的算法,主要包括壓縮和還原(解壓縮)的兩個步驟。

其實就是在不改變原有文件屬性的前提下,降低文件字節(jié)空間和占用空間的一種算法。

根據(jù)壓縮算法的定義,我們可將其分成不同的類型:

有損和無損

無損壓縮:能夠無失真地從壓縮后的數(shù)據(jù)重構(gòu),準確地還原原始數(shù)據(jù)。可用于對數(shù)據(jù)的準確性要求嚴格的場合,如可執(zhí)行文件和普通文件的壓縮、磁盤的壓縮,也可用于多媒體數(shù)據(jù)的壓縮。該方法的壓縮比較小。如差分編碼、RLE、Huffman編碼、LZW編碼、算術(shù)編碼。

有損壓縮:有失真,不能完全準確地恢復原始數(shù)據(jù),重構(gòu)的數(shù)據(jù)只是原始數(shù)據(jù)的一個近似。可用于對數(shù)據(jù)的準確性要求不高的場合,如多媒體數(shù)據(jù)的壓縮。該方法的壓縮比較大。例如預測編碼、音感編碼、分形壓縮、小波壓縮、JPEG/MPEG。

對稱性

如果編解碼算法的復雜性和所需時間差不多,則為對稱的編碼方法,多數(shù)壓縮算法都是對稱的。但也有不對稱的,一般是編碼難而解碼容易,如 Huffman 編碼和分形編碼。但用于密碼學的編碼方法則相反,是編碼容易,而解碼則非常難。

幀間與幀內(nèi)

在視頻編碼中會同時用到幀內(nèi)與幀間的編碼方法,幀內(nèi)編碼是指在一幀圖像內(nèi)獨立完成的編碼方法,同靜態(tài)圖像的編碼,如 JPEG;而幀間編碼則需要參照前后幀才能進行編解碼,并在編碼過程中考慮對幀之間的時間冗余的壓縮,如 MPEG。

實時性

在有些多媒體的應用場合,需要實時處理或傳輸數(shù)據(jù)(如現(xiàn)場的數(shù)字錄音和錄影、播放MP3/RM/VCD/DVD、視頻/音頻點播、網(wǎng)絡(luò)現(xiàn)場直播、可視電話、視頻會議),編解碼一般要求延時 ≤50 ms。這就需要簡單/快速/高效的算法和高速/復雜的CPU/DSP芯片。

分級處理

有些壓縮算法可以同時處理不同分辨率、不同傳輸速率、不同質(zhì)量水平的多媒體數(shù)據(jù),如JPEG2000、MPEG-2/4。

這些概念有些抽象,主要是為了讓大家了解一下壓縮算法的分類,下面我們就對具體的幾種常用的壓縮算法來分析一下它的特點和優(yōu)劣

幾種常用壓縮算法的理解

RLE 算法的機制

接下來就讓我們正式看一下文件的壓縮機制。首先讓我們來嘗試對 AAAAAABBCDDEEEEEF這 17 個半角字符的文件(文本文件)進行壓縮。雖然這些文字沒有什么實際意義,但是很適合用來描述 RLE 的壓縮機制。

由于半角字符(其實就是英文字符)是作為 1 個字節(jié)保存在文件中的,所以上述的文件的大小就是 17 字節(jié)。如圖

Java 程序員必須了解的計算機底層知識

那么,如何才能壓縮該文件呢?大家不妨也考慮一下,只要是能夠使文件小于 17 字節(jié),我們可以使用任何壓縮算法。

最顯而易見的一種壓縮方式我覺得你已經(jīng)想到了,就是把相同的字符去重化,也就是 字符 * 重復次數(shù) 的方式進行壓縮。所以上面文件壓縮后就會變成下面這樣

Java 程序員必須了解的計算機底層知識

從圖中我們可以看出,AAAAAABBCDDEEEEEF 的17個字符成功被壓縮成了A6B2C1D2E5F1 的12個字符,也就是 12 / 17  = 70%,壓縮比為 70%,壓縮成功了。

像這樣,把文件內(nèi)容用 數(shù)據(jù) * 重復次數(shù) 的形式來表示的壓縮方法成為 RLE(Run Length Encoding, 行程長度編碼)  算法。RLE 算法是一種很好的壓縮方法,經(jīng)常用于壓縮傳真的圖像等。因為圖像文件的本質(zhì)也是字節(jié)數(shù)據(jù)的集合體,所以可以用 RLE 算法進行壓縮

哈夫曼算法和莫爾斯編碼

下面我們來介紹另外一種壓縮算法,即哈夫曼算法。在了解哈夫曼算法之前,你必須舍棄半角英文數(shù)字的1個字符是1個字節(jié)(8位)的數(shù)據(jù)。下面我們就來認識一下哈夫曼算法的基本思想。

文本文件是由不同類型的字符組合而成的,而且不同字符出現(xiàn)的次數(shù)也是不一樣的。例如,在某個文本文件中,A 出現(xiàn)了 100次左右,Q僅僅用到了 3 次,類似這樣的情況很常見。

哈夫曼算法的關(guān)鍵就在于 多次出現(xiàn)的數(shù)據(jù)用小于 8 位的字節(jié)數(shù)表示,不常用的數(shù)據(jù)則可以使用超過 8 位的字節(jié)數(shù)表示。A 和 Q 都用 8 位來表示時,原文件的大小就是 100次 * 8 位 + 3次 * 8 位 = 824位,假設(shè) A 用 2 位,Q 用 10 位來表示就是 2 * 100 + 3 * 10 = 230 位。

不過要注意一點,最終磁盤的存儲都是以8位為一個字節(jié)來保存文件的。

哈夫曼算法比較復雜,在深入了解之前我們先吃點甜品,了解一下 莫爾斯編碼,你一定看過美劇或者戰(zhàn)爭片的電影,在戰(zhàn)爭中的通信經(jīng)常采用莫爾斯編碼來傳遞信息,例如下面

Java 程序員必須了解的計算機底層知識

接下來我們來講解一下莫爾斯編碼,下面是莫爾斯編碼的示例,大家把 1 看作是短點(嘀),把 11 看作是長點(嗒)即可。

Java 程序員必須了解的計算機底層知識

莫爾斯編碼一般把文本中出現(xiàn)最高頻率的字符用短編碼 來表示。如表所示,假如表示短點的位是 1,表示長點的位是 11 的話,那么 E(嘀)這一數(shù)據(jù)的字符就可以用 1 來表示,C(滴答滴答)就可以用 9 位的 110101101來表示。

在實際的莫爾斯編碼中,如果短點的長度是 1 ,長點的長度就是 3,短點和長點的間隔就是1。這里的長度指的就是聲音的長度。比如我們想用上面的 AAAAAABBCDDEEEEEF 例子來用莫爾斯編碼重寫,在莫爾斯曼編碼中,各個字符之間需要加入表示時間間隔的符號。這里我們用 00 加以區(qū)分。

所以,AAAAAABBCDDEEEEEF 這個文本就變?yōu)榱?A * 6 次 + B * 2次 + C * 1次 + D * 2次 + E * 5次 + F * 1次 + 字符間隔 * 16 = 4 位 * 6次 + 8 位 * 2次 + 9 位 * 1 次 + 6位 * 2次 + 1位 * 5次 + 8 位 * 1次 + 2位 * 16次 = 106位  = 14字節(jié)。

所以使用莫爾斯電碼的壓縮比為 14 / 17 = 82%。效率并不太突出。

用二叉樹實現(xiàn)哈夫曼算法

剛才已經(jīng)提到,莫爾斯編碼是根據(jù)日常文本中各字符的出現(xiàn)頻率來決定表示各字符的編碼數(shù)據(jù)長度的。不過,在該編碼體系中,對 AAAAAABBCDDEEEEEF 這種文本來說并不是效率最高的。

下面我們來看一下哈夫曼算法。哈夫曼算法是指,為各壓縮對象文件分別構(gòu)造最佳的編碼體系,并以該編碼體系為基礎(chǔ)來進行壓縮。因此,用什么樣的編碼(哈夫曼編碼)對數(shù)據(jù)進行分割,就要由各個文件而定。用哈夫曼算法壓縮過的文件中,存儲著哈夫曼編碼信息和壓縮過的數(shù)據(jù)。

Java 程序員必須了解的計算機底層知識

接下來,我們在對 AAAAAABBCDDEEEEEF 中的 A - F 這些字符,按照出現(xiàn)頻率高的字符用盡量少的位數(shù)編碼來表示這一原則進行整理。按照出現(xiàn)頻率從高到低的順序整理后,結(jié)果如下,同時也列出了編碼方案。

字符出現(xiàn)頻率編碼(方案)位數(shù)
A601
E511
B2102
D2112
C11003
F11013

在上表的編碼方案中,隨著出現(xiàn)頻率的降低,字符編碼信息的數(shù)據(jù)位數(shù)也在逐漸增加,從最開始的 1位、2位依次增加到3位。不過這個編碼體系是存在問題的,你不知道100這個3位的編碼,它的意思是用 1、0、0這三個編碼來表示 E、A、A 呢?還是用10、0來表示 B、A 呢?還是用100來表示 C 呢。

而在哈夫曼算法中,通過借助哈夫曼樹的構(gòu)造編碼體系,即使在不使用字符區(qū)分符號的情況下,也可以構(gòu)建能夠明確進行區(qū)分的編碼體系。不過哈夫曼樹的算法要比較復雜,下面是一個哈夫曼樹的構(gòu)造過程。

Java 程序員必須了解的計算機底層知識

自然界樹的從根開始生葉的,而哈夫曼樹則是葉生枝

哈夫曼樹能夠提升壓縮比率

使用哈夫曼樹之后,出現(xiàn)頻率越高的數(shù)據(jù)所占用的位數(shù)越少,這也是哈夫曼樹的核心思想。通過上圖的步驟二可以看出,枝條連接數(shù)據(jù)時,我們是從出現(xiàn)頻率較低的數(shù)據(jù)開始的。這就意味著出現(xiàn)頻率低的數(shù)據(jù)到達根部的枝條也越多。而枝條越多則意味著編碼的位數(shù)隨之增加。

接下來我們來看一下哈夫曼樹的壓縮比率,用上圖得到的數(shù)據(jù)表示 AAAAAABBCDDEEEEEF 為 000000000000 100100 110 101101 0101010101 111,40位 = 5 字節(jié)。壓縮前的數(shù)據(jù)是 17 字節(jié),壓縮后的數(shù)據(jù)竟然達到了驚人的5 字節(jié),也就是壓縮比率 = 5 / 17 = 29% 如此高的壓縮率,簡直是太驚艷了。

大家可以參考一下,無論哪種類型的數(shù)據(jù),都可以用哈夫曼樹作為壓縮算法

文件類型壓縮前壓縮后壓縮比率
文本文件14862字節(jié)4119字節(jié)28%
圖像文件96062字節(jié)9456字節(jié)10%
EXE文件24576字節(jié)4652字節(jié)19%
可逆壓縮和非可逆壓縮

最后,我們來看一下圖像文件的數(shù)據(jù)形式。圖像文件的使用目的通常是把圖像數(shù)據(jù)輸出到顯示器、打印機等設(shè)備上。常用的圖像格式有 : BMPJPEGTIFFGIF 格式等。

  • BMP :是使用 Windows 自帶的畫筆來做成的一種圖像形式
  • JPEG:是數(shù)碼相機等常用的一種圖像數(shù)據(jù)形式
  • TIFF: 是一種通過在文件中包含"標簽"就能夠快速顯示出數(shù)據(jù)性質(zhì)的圖像形式
  • GIF:是由美國開發(fā)的一種數(shù)據(jù)形式,要求色數(shù)不超過 256個

圖像文件可以使用前面介紹的 RLE 算法和哈夫曼算法,因為圖像文件在多數(shù)情況下并不要求數(shù)據(jù)需要還原到和壓縮之前一摸一樣的狀態(tài),允許丟失一部分數(shù)據(jù)。我們把能還原到壓縮前狀態(tài)的壓縮稱為 可逆壓縮,無法還原到壓縮前狀態(tài)的壓縮稱為非可逆壓縮 。

Java 程序員必須了解的計算機底層知識

一般來說,JPEG格式的文件是非可逆壓縮,因此還原后有部分圖像信息比較模糊。GIF 是可逆壓縮

操作系統(tǒng)

操作系統(tǒng)環(huán)境

程序中包含著運行環(huán)境這一內(nèi)容,可以說 運行環(huán)境 = 操作系統(tǒng) + 硬件 ,操作系統(tǒng)又可以被稱為軟件,它是由一系列的指令組成的。我們不介紹操作系統(tǒng),我們主要來介紹一下硬件的識別。

我們肯定都玩兒過游戲,你玩兒游戲前需要干什么?是不是需要先看一下自己的筆記本或者電腦是不是能肝的起游戲?下面是一個游戲的配置(懷念一下 wow)

Java 程序員必須了解的計算機底層知識

圖中的主要配置如下

  • 操作系統(tǒng)版本:說的就是應用程序運行在何種系統(tǒng)環(huán)境,現(xiàn)在市面上主要有三種操作系統(tǒng)環(huán)境,Windows 、Linux 和 Unix ,一般我們玩兒的大型游戲幾乎都是在 Windows 上運行,可以說 Windows 是游戲的天堂。Windows 操作系統(tǒng)也會有區(qū)分,分為32位操作系統(tǒng)和64位操作系統(tǒng),互不兼容。

  • 處理器:處理器指的就是 CPU,你的電腦的計算能力,通俗來講就是每秒鐘能處理的指令數(shù),如果你的電腦覺得卡帶不起來的話,很可能就是 CPU 的計算能力不足導致的。想要加深理解,請閱讀博主的另一篇文章:程序員需要了解的硬核知識之CPU

  • 顯卡:顯卡承擔圖形的輸出任務,因此又被稱為圖形處理器(Graphic Processing Unit,GPU),顯卡也非常重要,比如我之前玩兒的劍靈開五檔(其實就是圖像變得更清晰)會卡,其實就是顯卡顯示不出來的原因。

  • 內(nèi)存:內(nèi)存即主存,就是你的應用程序在運行時能夠動態(tài)分析指令的這部分存儲空間,它的大小也能決定你電腦的運行速度,想要加深理解,請閱讀博主的另一篇文章 程序員需要了解的硬核知識之內(nèi)存

  • 存儲空間:存儲空間指的就是應用程序安裝所占用的磁盤空間,由圖中可知,此游戲的最低存儲空間必須要大于 5GB,其實我們都會遺留很大一部分用來安裝游戲。

從程序的運行環(huán)境這一角度來考量的話,CPU 的種類是特別重要的參數(shù),為了使程序能夠正常運行,必須滿足 CPU 所需的最低配置。

CPU 只能解釋其自身固有的語言。不同的 CPU 能解釋的機器語言的種類也是不同的。機器語言的程序稱為 本地代碼(native code),程序員用 C 等高級語言編寫的程序,僅僅是文本文件。文本文件(排除文字編碼的問題)在任何環(huán)境下都能顯示和編輯。我們稱之為源代碼。通過對源代碼進行編譯,就可以得到本地代碼

下圖反映了這個過程

Java 程序員必須了解的計算機底層知識

Windows 操作系統(tǒng)克服了CPU以外的硬件差異

計算機的硬件并不僅僅是由 CPU 組成的,還包括用于存儲程序指令的數(shù)據(jù)和內(nèi)存,以及通過 I/O 連接的鍵盤、顯示器、硬盤、打印機等外圍設(shè)備。

在 WIndows 軟件中,鍵盤輸入、顯示器輸出等并不是直接向硬件發(fā)送指令。而是通過向 Windows 發(fā)送指令實現(xiàn)的。因此,程序員就不用注意內(nèi)存和 I/O 地址的不同構(gòu)成了。Windows 操作的是硬件而不是軟件,軟件通過操作 Windows 系統(tǒng)可以達到控制硬件的目的。

Java 程序員必須了解的計算機底層知識

不同操作系統(tǒng)的 API 差異性

接下來我們看一下操作系統(tǒng)的種類。同樣機型的計算機,可安裝的操作系統(tǒng)類型也會有多種選擇。

例如:AT 兼容機除了可以安裝 Windows 之外,還可以采用 Unix 系列的 Linux 以及 FreeBSD (也是一種Unix操作系統(tǒng))等多個操作系統(tǒng)。

當然,應用軟件則必須根據(jù)不同的操作系統(tǒng)類型來專門開發(fā)。CPU 的類型不同,所對應機器的語言也不同,同樣的道理,操作系統(tǒng)的類型不同,應用程序向操作系統(tǒng)傳遞指令的途徑也不同

應用程序向系統(tǒng)傳遞指令的途徑稱為 API(Application Programming Interface)。Windows 以及 Linux 操作系統(tǒng)的 API,提供了任何應用程序都可以利用的函數(shù)組合。因為不同操作系統(tǒng)的 API 是有差異的。所以,如何要將同樣的應用程序移植到另外的操作系統(tǒng),就必須要覆蓋應用所用到的 API 部分。

鍵盤輸入、鼠標輸入、顯示器輸出、文件輸入和輸出等同外圍設(shè)備進行交互的功能,都是通過 API 提供的。

這也就是為什么 Windows 應用程序不能直接移植到 Linux 操作系統(tǒng)上的原因,API 差異太大了。

在同類型的操作系統(tǒng)下,不論硬件如何,API 幾乎相同。但是,由于不同種類 CPU 的機器語言不同,因此本地代碼也不盡相同。

操作系統(tǒng)功能的歷史

操作系統(tǒng)其實也是一種軟件,任何新事物的出現(xiàn)肯定都有它的歷史背景,那么操作系統(tǒng)也不是憑空出現(xiàn)的,肯定有它的歷史背景。

在計算機尚不存在操作系統(tǒng)的年代,完全沒有任何程序,人們通過各種按鈕來控制計算機,這一過程非常麻煩。

于是,有人開發(fā)出了僅具有加載和運行功能的監(jiān)控程序,這就是操作系統(tǒng)的原型。通過事先啟動監(jiān)控程序,程序員可以根據(jù)需要將各種程序加載到內(nèi)存中運行。雖然仍舊比較麻煩,但比起在沒有任何程序的狀態(tài)下進行開發(fā),工作量得到了很大的緩解。

Java 程序員必須了解的計算機底層知識

隨著時代的發(fā)展,人們在利用監(jiān)控程序編寫程序的過程中發(fā)現(xiàn)很多程序都有公共的部分。例如,通過鍵盤進行文字輸入,顯示器進行數(shù)據(jù)展示等,如果每編寫一個新的應用程序都需要相同的處理的話,那真是太浪費時間了。

因此,基本的輸入輸出部分的程序就被追加到了監(jiān)控程序中。初期的操作系統(tǒng)就是這樣誕生了。

Java 程序員必須了解的計算機底層知識

類似的想法可以共用,人們又發(fā)現(xiàn)有更多的應用程序可以追加到監(jiān)控程序中,比如硬件控制程序編程語言處理器(匯編、編譯、解析)以及各種應用程序等,結(jié)果就形成了和現(xiàn)在差異不大的操作系統(tǒng),也就是說,其實操作系統(tǒng)是多個程序的集合體。

Java 程序員必須了解的計算機底層知識

Windows 操作系統(tǒng)的特征

Windows 操作系統(tǒng)是世界上用戶數(shù)量最龐大的群體,作為 Windows 操作系統(tǒng)的資深用戶,你都知道 Windows 操作系統(tǒng)有哪些特征嗎?下面列舉了一些 Windows 操作系統(tǒng)的特性

  • Windows 操作系統(tǒng)有兩個版本:32位和64位
  • 通過 API 函數(shù)集成來提供系統(tǒng)調(diào)用
  • 提供了采用圖形用戶界面的用戶界面
  • 通過 WYSIWYG 實現(xiàn)打印輸出,WYSIWYG 其實就是 What You See Is What You Get ,值得是顯示器上顯示的圖形和文本都是可以原樣輸出到打印機打印的。
  • 提供多任務功能,即能夠同時開啟多個任務
  • 提供網(wǎng)絡(luò)功能和數(shù)據(jù)庫功能
  • 通過即插即用實現(xiàn)設(shè)備驅(qū)動的自設(shè)定

這些是對程序員來講比較有意義的一些特征,下面針對這些特征來進行分別的介紹

32位操作系統(tǒng)

這里表示的32位操作系統(tǒng)表示的是處理效率最高的數(shù)據(jù)大小。Windows 處理數(shù)據(jù)的基本單位是 32 位。這與最一開始在 MS-DOS 等16位操作系統(tǒng)不同,因為在16位操作系統(tǒng)中處理32位數(shù)據(jù)需要兩次,而32位操作系統(tǒng)只需要一次就能夠處理32位的數(shù)據(jù),所以一般在 windows 上的應用,它們的最高能夠處理的數(shù)據(jù)都是 32 位的。

比如,用 C 語言來處理整數(shù)數(shù)據(jù)時,有8位的 char 類型,16位的short類型,以及32位的long類型三個選項,使用位數(shù)較大的 long 類型進行處理的話,增加的只是內(nèi)存以及磁盤的開銷,對性能影響不大。

現(xiàn)在市面上大部分都是64位操作系統(tǒng)了,64位操作系統(tǒng)也是如此。

通過 API 函數(shù)集來提供系統(tǒng)調(diào)用

Windows 是通過名為 API 的函數(shù)集來提供系統(tǒng)調(diào)用的。API是聯(lián)系應用程序和操作系統(tǒng)之間的接口,全稱叫做 Application Programming Interface,應用程序接口。

當前主流的32位版 Windows API 也稱為 Win32 API,之所以這樣命名,是需要和不同的操作系統(tǒng)進行區(qū)分,比如最一開始的 16 位版的 Win16 API,和后來流行的 Win64 API 。

API 通過多個 DLL 文件來提供,各個 API 的實體都是用 C 語言編寫的函數(shù)。所以,在 C 語言環(huán)境下,使用 API 更加容易,比如 API 所用到的 MessageBox() 函數(shù),就被保存在了 Windows 提供的 user32.dll 這個 DLL 文件中。

提供采用了 GUI 的用戶界面

GUI(Graphical User Interface) 指得就是圖形用戶界面,通過點擊顯示器中的窗口以及圖標等可視化的用戶界面,舉個例子:Linux 操作系統(tǒng)就有兩個版本,一種是簡潔版,直接通過命令行控制硬件,還有一種是可視化版,通過光標點擊圖形界面來控制硬件。

通過 WYSIWYG 實現(xiàn)打印輸出

WYSIWYG 指的是顯示器上輸出的內(nèi)容可以直接通過打印機打印輸出。在 Windows 中,顯示器和打印機被認作同等的圖形輸出設(shè)備處理的,該功能也為 WYSIWYG 提供了條件。

借助 WYSIWYG 功能,程序員可以輕松不少。最初,為了是現(xiàn)在顯示器中顯示和在打印機中打印,就必須分別編寫各自的程序,而在 Windows 中,可以借助 WYSIWYG 基本上在一個程序中就可以做到顯示和打印這兩個功能了。

提供多任務功能

多任務指的就是同時能夠運行多個應用程序的功能,Windows 是通過時鐘分割技術(shù)來實現(xiàn)多任務功能的。時鐘分割指的是短時間間隔內(nèi),多個程序切換運行的方式。在用戶看來,就好像是多個程序在同時運行,其底層是 CPU 時間切片,這也是多線程多任務的核心。

Java 程序員必須了解的計算機底層知識

CPU分片,也是時鐘分割

提供網(wǎng)絡(luò)功能和數(shù)據(jù)庫功能

Windows 中,網(wǎng)絡(luò)功能是作為標準功能提供的。數(shù)據(jù)庫(數(shù)據(jù)庫服務器)功能有時也會在后面追加。

網(wǎng)絡(luò)功能和數(shù)據(jù)庫功能雖然并不是操作系統(tǒng)不可或缺的,但因為它們和操作系統(tǒng)很接近,所以被統(tǒng)稱為中間件而不是應用。意思是處于操作系統(tǒng)和應用的中間層,操作系統(tǒng)和中間件組合在一起,稱為系統(tǒng)軟件。應用不僅可以利用操作系統(tǒng),也可以利用中間件的功能。

Java 程序員必須了解的計算機底層知識

應用可以使用操作系統(tǒng)和中間件

相對于操作系統(tǒng)一旦安裝就不能輕易更換,中間件可以根據(jù)需要進行更換,不過,對于大部分應用來說,更換中間件的話,會造成應用也隨之更換,從這個角度來說,更?換中間件也不是那么容易。

通過即插即用實現(xiàn)設(shè)備驅(qū)動的自動設(shè)定

即插即用(Plug-and-Play)指的是新的設(shè)備連接(plug) 后就可以直接使用的機制,新設(shè)備連接計算機后,計算機就會自動安裝和設(shè)定用來控制該設(shè)備的驅(qū)動程序

設(shè)備驅(qū)動是操作系統(tǒng)的一部分,提供了同硬件進行基本的輸入輸出的功能。鍵盤、鼠標、顯示器、磁盤裝置等,這些計算機中必備的硬件的設(shè)備驅(qū)動,一般都是隨操作系統(tǒng)一起安裝的。

有時 DLL 文件也會同設(shè)備驅(qū)動文件一起安裝。這些 DLL 文件中存儲著用來利用該新追加的硬件API,通過 API ,可以制作出運行該硬件的心應用。

匯編語言和本地代碼

我們在之前的文章中探討過,計算機 CPU 只能運行本地代碼(機器語言)程序,用 C 語言等高級語言編寫的代碼,需要經(jīng)過編譯器編譯后,轉(zhuǎn)換為本地代碼才能夠被 CPU 解釋執(zhí)行。

但是本地代碼的可讀性非常差,所以需要使用一種能夠直接讀懂的語言來替換本地代碼,那就是在各本地代碼中,附帶上表示其功能的英文縮寫,比如在加法運算的本地代碼加上add(addition) 的縮寫、在比較運算符的本地代碼中加上cmp(compare)的縮寫等,這些通過縮寫來表示具體本地代碼指令的標志稱為 助記符,使用助記符的語言稱為匯編語言。這樣,通過閱讀匯編語言,也能夠了解本地代碼的含義了。

不過,即使是使用匯編語言編寫的源代碼,最終也必須要轉(zhuǎn)換為本地代碼才能夠運行,負責做這項工作的程序稱為編譯器,轉(zhuǎn)換的這個過程稱為匯編。在將源代碼轉(zhuǎn)換為本地代碼這個功能方面,匯編器和編譯器是同樣的。

用匯編語言編寫的源代碼和本地代碼是一一對應的。因而,本地代碼也可以反過來轉(zhuǎn)換成匯編語言編寫的代碼。把本地代碼轉(zhuǎn)換為匯編代碼的這一過程稱為反匯編,執(zhí)行反匯編的程序稱為反匯編程序

Java 程序員必須了解的計算機底層知識

本地代碼和匯編語言一對一的轉(zhuǎn)換

哪怕是 C 語言編寫的源代碼,編譯后也會轉(zhuǎn)換成特定 CPU 用的本地代碼。而將其反匯編的話,就可以得到匯編語言的源代碼,并對其內(nèi)容進行調(diào)查。不過,本地代碼變成 C 語言源代碼的反編譯,要比本地代碼轉(zhuǎn)換成匯編代碼的反匯編要困難,這是因為,C 語言代碼和本地代碼不是一一對應的關(guān)系。

通過編譯器輸出匯編語言的源代碼

我們上面提到本地代碼可以經(jīng)過反匯編轉(zhuǎn)換成為匯編代碼,但是只有這一種轉(zhuǎn)換方式嗎?顯然不是,C 語言編寫的源代碼也能夠通過編譯器編譯稱為匯編代碼,下面就來嘗試一下。

首先需要先做一些準備,需要先下載 Borland C++ 5.5 編譯器,為了方便,我這邊直接下載好了讀者直接從我的百度網(wǎng)盤提取即可 (鏈接:https://pan.baidu.com/s/19LqVICpn5GcV88thD2AnlA  密碼:hz1u)

下載完畢,需要進行配置,下面是配置說明 (https://wenku.baidu.com/view/22e2f418650e52ea551898ad.html),教程很完整跟著配置就可以,下面開始我們的編譯過程

首先用 Windows 記事本等文本編輯器編寫如下代碼

// 返回兩個參數(shù)值之和的函數(shù)
int AddNum(int a,int b){
 return a + b;
}

// 調(diào)用 AddNum 函數(shù)的函數(shù)
void MyFunc(){
 int c;
 c = AddNum(123,456);
}

編寫完成后將其文件名保存為 Sample4.c ,C 語言源文件的擴展名,通常用.c 來表示,上面程序是提供兩個輸入?yún)?shù)并返回它們之和。

在 Windows 操作系統(tǒng)下打開 命令提示符,切換到保存 Sample4.c 的文件夾下,然后在命令提示符中輸入

bcc32 -c -S Sample4.c

bcc32 是啟動 Borland C++ 的命令,-c 的選項是指僅進行編譯而不進行鏈接,-S 選項被用來指定生成匯編語言的源代碼

作為編譯的結(jié)果,當前目錄下會生成一個名為Sample4.asm 的匯編語言源代碼。

匯編語言源文件的擴展名,通常用.asm 來表示,下面就讓我們用編輯器打開看一下 Sample4.asm 中的內(nèi)容

	.386p
ifdef ??version
if    ??version GT 500H
.mmx
endif
endif
model flat
ifndef ??version
?debug macro
endm
endif
?debug S "Sample4.c"
?debug T "Sample4.c"
_TEXT segment dword public use32 'CODE'
_TEXT ends
_DATA segment dword public use32 'DATA'
_DATA ends
_BSS segment dword public use32 'BSS'
_BSS ends
DGROUP group _BSS,_DATA
_TEXT segment dword public use32 'CODE'
_AddNum proc near
?live1@0:
  ;
  ; int AddNum(int a,int b){
  ;
push      ebp
mov       ebp,esp
  ;
  ;
  ;    return a + b;
  ;
@1:
mov       eax,dword ptr [ebp+8]
add       eax,dword ptr [ebp+12]
  ;
  ; }
  ;
@3:
@2:
pop       ebp
ret
_AddNum endp
_MyFunc proc near
?live1@48:
  ;
  ; void MyFunc(){
  ;
push      ebp
mov       ebp,esp
  ;
  ;    int c;
  ;    c = AddNum(123,456);
  ;
@4:
push      456
push      123
call      _AddNum
add       esp,8
  ;
  ; }
  ;
@5:
pop       ebp
ret
_MyFunc endp
_TEXT ends
public _AddNum
public _MyFunc
?debug D "Sample4.c" 20343 45835
end

這樣,編譯器就成功的把 C 語言轉(zhuǎn)換成為了匯編代碼了。

不會轉(zhuǎn)換成本地代碼的偽指令

網(wǎng)頁名稱:Java程序員必須了解的計算機底層知識
標題來源:http://m.kartarina.com/article16/pgoggg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站ChatGPT網(wǎng)站維護網(wǎng)站設(shè)計公司品牌網(wǎng)站建設(shè)企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設(shè)計
主站蜘蛛池模板: 无码av中文一二三区| 亚洲AV无码久久| 精品无码成人片一区二区98| 亚洲AV无码一区东京热久久 | 久久午夜福利无码1000合集| 日韩激情无码免费毛片| 亚洲AV无码专区亚洲AV伊甸园| 久久久久亚洲AV无码专区首JN| 久久久久无码精品国产不卡| 无遮掩无码h成人av动漫| 久久久久久久久无码精品亚洲日韩| 精品无码综合一区| 亚洲av无码有乱码在线观看| 亚洲精品无码日韩国产不卡?V| 亚洲av无码一区二区三区天堂| 久久无码人妻精品一区二区三区| 亚洲av无码国产精品夜色午夜| 国产精品无码一区二区在线观 | 亚洲精品97久久中文字幕无码| 国产精品va无码一区二区| 亚洲av无码专区亚洲av不卡| 亚洲AV无码1区2区久久| 中文字幕在线无码一区| 精品亚洲AV无码一区二区三区 | 亚洲国产AV无码一区二区三区| 免费无码黄网站在线看| 亚洲国产成人精品无码区在线网站| 亚洲国产精品无码久久SM| 无码8090精品久久一区| a级毛片无码免费真人| 无码少妇一区二区性色AV| 久久精品aⅴ无码中文字字幕| 国产精品无码av在线播放| 日产无码1区2区在线观看| 无码视频免费一区二三区| 亚洲aⅴ无码专区在线观看春色 | 亚洲AV无码久久精品成人 | 人妻丝袜无码专区视频网站| 高清无码中文字幕在线观看视频| 无码人妻精品中文字幕| 国产免费久久久久久无码|