簡單說一下寫這篇文章的原因。首先這個不是教學類型的,是我Java實在學不下去了,由於好多計算機底層原理都不是很清楚,每次學新東西都因爲想不明白底層原理困惑,因此下決心中止學習Java的新東西,開始搞明白底層。一開始搞的所謂的底層是「Java虛擬機」,而後又C語言彙編語言什麼的,實際上是想圖快,儘快接近如今作的事情。後來發現不行,這事快不了,因此乾脆就從物理層面用導線燈泡集成芯片開始動手作一個cpu開始吧。其實也沒多久,大概三個多月吧,從我以前寫的【從零開始自制cpu】系列的學習文章也能夠看到開始時間。cpu作好以後(其實後來因爲總是燒壞各類電路沒作完),開始看操做系統,如今剛開始看一點。總結起來就是我認爲的學習路線應該是:html
自制個cpu--微機原理--簡單數據結構與算法--計算機組成原理--彙編語言--操做系統--C語言--編譯原理--複雜數據結構與算法--計算機網絡--Java--Java虛擬機--源碼研究--多線程linux高併發Spring負載均衡各類以這些評判一我的Java水平的東東...linux
我認爲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經過命令的方式並獲得響應。但若是某些特別操做須要馬上相應可能就無法作到,因而經過引入中斷和嚴格的中斷時間控制作到了實時操做系統。再後來就是咱們如今的操做系統啦。其實對這些叫法和定義我並非特別理解,因此你能夠發現我講的其實挺亂的,這裏也但願有大佬給個好的解釋。
今天就寫到這吧,想着這些天好像學了不少東西,但本身寫出來發現把全部肚子裏東西吐出來就這麼點,沒什麼系統就是想到哪寫到哪能串的儘可能串一下了,工做之餘偷偷溜走寫的。再有這裏面的地址呀數據呀好多就是舉例,不是準確的值,爲了方便理解而已,主要我也不肯意花時間搞個準確的放在這樣一篇隨便寫的文章,之後會把某些地方具體拿出來說。但願各位大佬給出批評指正或者吐槽探討,感激涕零!