計算機底層原理雜談(白話文)


  簡單說一下寫這篇文章的緣由。首先這個不是教學類型的,是我Java實在學不下去了,因為好多計算機底層原理都不是很清楚,每次學新東西都由於想不明白底層原理困惑,所以下決心停止學習Java的新東西,開始搞明白底層。一開始搞的所謂的底層是“Java虛擬機”,然后又C語言匯編語言什么的,其實是想圖快,盡快接近現在做的事情。后來發現不行,這事快不了,所以干脆就從物理層面用導線燈泡集成芯片開始動手做一個cpu開始吧。其實也沒多久,大概三個多月吧,從我之前寫的【從零開始自制cpu】系列的學習文章也可以看到開始時間。cpu做好之后(其實后來由於老是燒壞各種電路沒做完),開始看操作系統,現在剛開始看一點。總結起來就是我認為的學習路線應該是:

  自制個cpu--微機原理--簡單數據結構與算法--計算機組成原理--匯編語言--操作系統--C語言--編譯原理--復雜數據結構與算法--計算機網絡--Java--Java虛擬機--源碼研究--多線程linux高並發Spring負載均衡各種以這些評判一個人Java水平的東東...

  我認為Java應該在那樣一個遙遠的地方,好多人卻以它為起點了。我現在粗略地看到操作系統,當然不斷地在補前面的知識。這篇文章想借此機會跟大家說說計算機底層的理解,學的知識很碎,大家順便幫我挑挑錯誤,如果對你有幫助那就更好了。我用大白話說,想到哪說到哪,這樣更容易展現出漏洞和錯誤,希望大家在評論區里積極吐槽~

 

----------------------------------------華麗的正文分割線------------------------------------------

  CPU就是由一堆導線將一堆部件連接在一塊的東西,每個部件里面又是由一堆導線和一堆更小的部件連接在一塊。把整個cpu看成一個部件,那它和RAM,磁盤等外設又是用一堆導線連接在一塊,我感覺就像套娃似的。然后每個部件從外部看就是暴露了一堆針腳,可以用導線連上,給他們傳遞要么高電平要么低電平的電流,你比如說下面這款74LS173(3態輸出端的4位D型寄存器,簡單說就是一個能存4位數據的東西,可以用來做寄存器。

  

  看這么多陣腳,我感覺分三類理解就行了,別管他們是數據輸入、數據輸出、信號控制啥的,他們都是一類,反正要給他們要么高要么低的電信號。還有一類就是接+接-的vcc和gnd,目的是讓這個部件有“電”。還有一個是時鍾clock,這你給他一個一會高一會低來回變的電信號就行了,目的一般是讓它電平上升沿的時候這個部件“觸發”一個什么效果,對這個寄存器來說,就是存了個數據或者讀出了數據,當然有的部件不需要時鍾信號。

  CPU以及它關聯的其他設備,全都是一個個這樣的“部件”,這些部件別管它多復雜,最終暴露出來的都是一個時鍾、一個接正接負、一堆其他的亂七八糟的針腳。有的部件用來存東西,有的用來做運算比如加法器,有的用來計數比如程序計數器,有的部件用來做各種翻譯呀轉換呀什么的,比如地址譯碼什么的。最終它們都連接在一個大boss上那就是時鍾信號發生裝置,反正它的作用就是特別快速地輸出一個高低高低高低電平來回轉換的電信號。所以現在我總結出cpu就是一個boss時鍾信號,一堆小弟部件,一堆導線把部件有規律地連在一塊,最終都連在大boss上。

  那這樣一堆部件是怎么運行的呢?首先第一個部件是程序計數器pc,它就是靠時鍾信號不斷累加,輸出的針腳從0000,0001,0002一直往上加。當然你可以一個時鍾周期(就是高低電平來一下)就+1,但這樣除非你其他部件一個時鍾周期內就能把一條指令執行完,一般不行,那怎么辦呢,就再弄一個計數器,比如只能從0加到5。這個計數器每次都從0開始,然后沒到5之前都把程序計數器的一個狀態針腳變成“不觸發加”的狀態,這樣程序計數器就不變了。然后各個部件在5個時鍾周期內把指令執行完,然后程序計數器再加1。這就是單指令周期,每個指令都用5個時鍾周期執行完。當然這不好,你可變化呀,讓這個計數器從0加到一個動態的值,這個值根據指令類型改變,這就多指令周期了。然后程序計數器一直往上加也不好,給他弄幾個針腳,再弄一個狀態,可以直接設置一個新值,這就實現程序跳轉了。一個指令周期可以動態改變,還可以強行設置程序計數器的值,這差不多就夠了。

  第二個部件是寄存器堆,其實就是一個存東西的地方,跟內存呀硬盤呀一樣,只不過離cpu近,就光用距離除以電流流速來說都能說明它比較快。所以一些運算出來的中間結果呀,甚至我從內存中讀出來要做加法或者要寫入內存的數據都先放寄存器里面。寄存器都一樣的你存哪個都行,只不過為了統一,別一個人一個樣,把寄存器分門別類弄了些專用的功能,地址數據就放你地址數據該存的寄存器,表示狀態的數據就放在你狀態該存的寄存器,僅此而已。寄存器正因為有不同人給它賦予定義才麻煩,就比如IO接口中的端口,就是寄存器而已,只不過比如像硬盤接口,你往它3號端口寫個011101啥的,他就表示你要讀數據了,然后硬盤把數據放到4號寄存器等着你讀。

  第三個部件是算術邏輯單元,你可以先假設它就是個只能執行加法的部件,8個針腳數據1,8個針腳數據2,再來8個針腳表示這倆數據的和,完事。

  第四個部件我不想叫它控制單元,我感覺我一開始困惑的就是因為這種叫法,我感覺它更像是一種布線的方式,只不過抽象地說出來逼格高,更容易寫成教材。簡單說就是幾個針腳接收指令,另幾個針腳輸出各種不同高低電平信號,連接在其他部件的針腳上起到一些控制作用。你比如我輸入一個“寫入內存”的指令,那我輸出的針腳肯定有一個是接在內存的“是否寫入”這個針腳上,這不就控制了么。

  總結起來,其實部件就那么幾種,存儲部件:寄存器呀,RAM ROM呀;控制部件:就是聯系所有部件的控制它們可讀可寫可加這種邏輯的;算數部件:數學運算用的;發動機部件:這我給命名成發動機部件吧,就是時鍾信號產生,還有程序計數器,這些都是將整個部件激活、發動的感覺,沒有它們就沒了源動力。外設部件:也可以叫IO部件,注意千萬不要把硬盤理解成存儲部件,它跟網口、鼠標、鍵盤是一樣的,都是IO,你能從磁盤中讀數據,你也能從鍵盤中讀數據,當它們接到IO接口上時,全都視為同一個東西了。

  我拿鍵盤舉個例子,不管誰家生產的鍵盤,都要接在我一個叫“鍵盤接口”的東西上,這個鍵盤接口中有5個端口,1號2號3號4號5號,其實就是寄存器,接口上面的寄存器就叫端口。我這個鍵盤生產商可以寫個說明書,告訴大家你們聽好啦,1號端口就是我的按鍵數據,我按了鍵盤中的A,我就往1號端口中寫00100011,你cpu讀到了怎么處理我就不管啦。當然我很好心,我給你2號端口也搞一個數據,為0的時候說明我沒按鍵,為1的時候我就按鍵了,這夠可以了吧。這時候cpu就可以處理了,我去讀這個2號端口的數據,就像我讀內存數據一樣,讀到了我發現它是1,那我知道鍵盤按鍵了,我接着讀1號端口的數據,然后各種處理最終給顯示器接口中的一堆端口寫上一堆奇奇怪怪的數據,顯示器讀到這些數據后又做了一堆處理最終在屏幕上亮了幾個燈泡,亮出了一個A。這里面cpu不斷讀2號端口看鍵盤有沒有操作就叫用輪詢IO的方式檢查設備,讀了1號端口的數據做各種處理最終給顯示器接口寫入數據,就是驅動程序。最后顯示器讀這些數據顯示到屏幕上,那這是另一個設備的物理細節了,它里面也有個像我們這個cpu的東西就不去細究了。

  完美,不過上面的過程又有些問題,如果io設備很多,輪詢io的方式就很沒效率了,最好是io有動作的時候主動通知cpu。那可以這樣做,比如鍵盤有動作,我不是往我2號端口寫數據了,而是往你cpu中一個寄存器中寫一個號碼,cpu讀到這個寄存器中有數據了,通過查它的號碼找到對應驅動程序的內存地址,執行這個程序。這個過程就叫中斷,而查詢號碼去找程序的地方,叫做中斷向量表。這里其實我真的也不想叫它中斷,因為又是這個詞讓我困惑好久。因為cpu是通過增加一個時鍾周期專門檢測是否有中斷信號產生,也就是說如果沒有任何中斷信號,這個時鍾周期也是需要空跑一次的。所以你看,從更物理的時鍾周期的層面看,這個中斷方式仍然是輪詢,只不過輪詢的單位不是指令,而是時鍾

  這說法完美,不過上面還有個問題,就是像鍵盤這樣的還好,因為它確實需要執行一段特殊的驅動程序去完成功能。但想磁盤這種,單純的就是讀出數據寫到內存或者讀內存數據寫到磁盤,這種操作很低級但是很耗時間,如果每次都是通過中斷然后數據通過cpu先傳到寄存器在一個個傳到內存,那就讓cpu太大材小用了。這種重復的耗時的勞動,最好別占用cpu,直接從硬盤通過某個設備到內存就好了,這個設備就叫做IO控制器DMA。硬盤接收到cpu的讀請求后,向dma發請求信號。dma完成了從硬盤寫入內存操作后,再向cpu發一個中斷信號,簡單執行一下數據處理完的中斷程序就行了,至於數據傳輸的過程,cpu可以做其他更高級的事情。

  完美,不過上面的又有一些問題,就是你雖然不占用我cpu時間,但你占用總線啊,我們是公用一條總線傳輸數據的,你傳輸數據占用總線的時候,我cpu就占不了了。或者你等我cpu不用總線的時候你在再用,這個叫做dma的時鍾周期竊取。但這樣也不好,我他媽就希望你離我越遠越好,別占我cpu時間也別占我的地方,讓專門一個可以執行簡單指令的設備和你公用一條單獨的總線去完成這件事,我稱之為low版cpu,他就是io通道

 

  

  再說說IO端口地址問題,cpu如何指定一個端口呢,可以用一個部分表示地址,另一個部分表示是IO地址還是內存地址。還有一種方式是,將io端口也加入到內存一樣的地址范圍中,然后訪問一個端口跟訪問一個內存地址沒什么差別。所以上述到就是IO端口的兩種編址方式,第一種是獨立編址,采用端口映射io,第二種是統一編制,采用內存映射io,現在基本都是內存映射。整個io這一塊大體的骨架就是這樣子的,你看剛剛所說的中斷呀,dma呀這些,我覺得可以理解為操作系統,或者說由於使用cpu的需求倒逼出的產物。當然所有這些都可以用軟件來實現,但當需求足夠大的時候可以讓cpu為操作系統做出一些改變的,這並不是cpu原本就是這個樣子。其實上面提到的中斷,是外部中斷,當然也可以是內部中斷,就是指令自己去出發一個中斷。這是根據中斷源的不同分的。當然本質是一樣的,都是往一個寄存器或者幾個寄存器里寫數,cpu一個時鍾周期專門查看一下這個寄存器,然后查下中斷向量表找到對應的程序執行一下,執行完了恢復之前的pc再繼續往下進行。

  整個io差不多就是這樣的骨架,所以你看為什么操作系統關注io,關注內存管理,關注多進程,因為沒啥別的東西可關注了,cpu原本能做的事情太簡單了,所謂操作系統也好,dma這些新增的硬件也好,沒有什么技術上高端的事情,或者說在計算機底層,高端的本質就是復雜和麻煩,這也回答了我好久之前寫過的一篇《究竟什么是技術》。你包括我的第一張74LS173的針腳圖,如果你看了我說的什么“部件”巴拉巴拉明白了,你可以說你懂,當然你把cpu主要部件的針腳圖都看了,都記住了並且在面包板或者焊接版上接過了,你也可以說你懂。但這層次就不同了,所謂理解得深不深,其實就在於細節。

  再說說內存地址管理,或者說尋址方式,當然你可以在指令中的地址就表示絕對的地址,不經過任何轉換直接到內存或者相應的設備中輸入這個地址信號然后讀數據。你或者把倆地址拼一塊,形成一個新地址。再或者你形成新地址后再通過某種方式轉換映射一下,或者再映射一下。等等,操作系統對內存的管理就是這些,全都是細節。我只是簡單入了個門,最開始cpu是絕對地址尋址,就是我指令中的地址直接輸入到某個部件的地址線上。第一個搞事情的是8086cpu,也就是x86架構的鼻祖cpu,它有16位數據線,但有20位地址線。當然你可以只用16位地址線但當時恰好人們覺得地址不夠用了,然后又各種原因不能弄成32位的cpu,於是乎尋址的時候就把一個寄存器當作段地址數據,另一個當作段內地址,其實別管那么多,就是應給湊成了20位地址罷了,這樣尋址范圍就擴大了。但這設計好不好?美其名曰段地址和段內地址,其實這很麻煩,如果cpu位數夠,沒人給自己找這種麻煩,以至於后來的32位cpu為了兼容以前的拍腦門設計,即便是尋址空間已經夠了但還是采用這種段方式。但后來又說操作系統變得復雜了,倒逼着cpu弄出實模式和保護模式,每個段也有自己的權限呀長度呀等等各種標志了,這樣段寄存器這樣的設計就硬生生變得有用起來了,指向一張段表記錄這些標志型的數據。

  段的長度是可以改變的,我們先假定它大小固定這樣好說明,假如我內存一共能容納10個段,然后我硬盤能容納1000個段,我段表就記錄我內存中的這10個段對應着硬盤中的哪段數據。然后呢我編程的時候,地址范圍寫成硬盤那樣大,然后有個專門的硬件mmu用來把我程序中的地址通過查表翻譯成內存中的地址,如果沒有,那就把硬盤中的那個地址的數據放在內存中,最終翻譯成的還是內存中的地址。這里就用到了虛擬地址的概念,我程序中的地址是虛擬地址,幫我查表翻譯娜硬盤到內存的裝置叫MMU,查的表叫做段表,最終翻譯成的內存地址是物理地址。用段的方式來管理這個虛擬內存就叫做段式內存管理。就醬。

  段式的管理好處是長度可變比較靈活,不好的地方是你比如你A段和C段原來是B段,大小是1000,現在不用了這個地方挪出來了,你新來個從硬盤調來的數據,大小是1001,是不是很膈應人。看着放進去正好卻偏偏差一個,於是乎你只能把整個C段往前娜。要是心來個數據是900,其他地方都放不下只能放在這,那剩下的100就很尷尬。這個尷尬的100就叫做內存碎片。根據角度不同,如果你說這個100是由於那個900擠進來了剩下的空間,那就叫內部碎片,如果你說這個100太小新來的程序放不進來,那就叫外部碎片。這塊我覺得通過段式叫外部頁式叫內部不好,希望大家來討論下。那為了解決這個問題就有了頁式管理,頁就是個概念而已,你願意叫他不變的段也行。硬盤被分為固定大小的物理頁,操作系統邏輯上頁分為了同等數量同等大小的邏輯頁,然后同樣有個叫頁表的東西記錄了邏輯頁和物理頁的對應關系,然后和段一樣當請求的一個頁不在頁表中,准確說是頁表中標志了這個頁不在內存,那就把硬盤中的頁調進來,這個過程就叫缺頁中斷。這個頁式當然頁有好有壞,然后又有個和稀泥的辦法就是段頁式管理。一句話,怎么的都行,現在操作系統基本都是頁,完事。

  再說說進程的部分,哎不說了,這塊是在連入門都不算,完全不懂就不bb了,留在下一篇吧。

  其實上面從某個地方突然就從計算機組成原理的畫風轉成操作系統了,下面簡單說說操作系統為啥出現。當然一開始那個cpu和外設已經可以做想做的任何事了,你完全可以純手工的方式去把內存中的一塊區域一個個地寫入代碼嘛,然后程序計數器搞一個初值,電一通跑起來。但這太惡心了,於是有了卡片機,再來個卡片機讀入的程序事先寫好,這樣你就不用手工操作內存了,你制作好卡片就好,反正是方便了一點,但本質一樣。這叫做手工操作系統,也可以叫沒有操作系統。后來發現即使是完全相同的工作,仍然需要每次取出紙片再放進去,比如有兩個卡片1和2,有個程序是執行1122121這種順序,那就需要一個人來來回回放紙,這不科學。於是有了批處理操作系統,人可以事先把1和2加載到內存,然后弄一個c卡片來負責調用這個1和2卡片,這就是調度程序,也可以叫監督程序。這就叫做批處理操作系統。再后來可以交替執行多個任務,一個任務遇到io操作就切換,這叫多道程序系統。但一個作業扔進去之后就不受用戶管了,沒有交互,於是有了分時操作系統,可以有多個終端使用cpu通過命令的方式並得到響應。但如果某些特別操作需要立刻相應可能就沒法做到,於是通過引入中斷和嚴格的中斷時間控制做到了實時操作系統。再后來就是我們現在的操作系統啦。其實對這些叫法和定義我並不是特別理解,所以你可以發現我講的其實挺亂的,這里也希望有大佬給個好的解釋。

  今天就寫到這吧,想着這些天好像學了很多東西,但自己寫出來發現把所有肚子里東西吐出來就這么點,沒什么系統就是想到哪寫到哪能串的盡量串一下了,工作之余偷偷溜走寫的。再有這里面的地址呀數據呀好多就是舉例,不是准確的值,為了方便理解而已,主要我也不願意花時間搞個准確的放在這樣一篇隨便寫的文章,以后會把某些地方具體拿出來講。希望各位大佬給出批評指正或者吐槽探討,感激不盡!  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM