程序員需要了解的硬核知識之磁盤
- 2019 年 11 月 1 日
- 筆記
此篇文章是 《程序員需要了解的硬核知識》系列第四篇,歷史文章請戳
我們大家知道,計算機的五大基礎部件是 存儲器
、控制器
、運算器
、輸入和輸出設備
,其中從存儲功能的角度來看,可以把存儲器分為內存
和 磁盤
,內存我們上面的文章已經介紹過了,那麼此篇文章我們來介紹一下磁盤以及內存和磁盤的關係。
認識磁盤
首先,磁盤和內存都具有存儲功能,它們都是存儲設備。區別在於,內存是通過電流
來實現存儲;磁盤則是通過磁記錄技術
來實現存儲。內存是一種高速,造假昂貴的存儲設備;而磁盤則是速度較慢、造假低廉的存儲設備;電腦斷電後,內存中的數據會丟失,而磁盤中的數據可以長久保留。內存是屬於內部存儲設備
,硬盤是屬於 外部存儲設備
。一般在我們的計算機中,磁盤和內存是相互配合共同作業的。
一般內存指的就是主存(負責存儲CPU中運行的程序和數據);早起的磁盤指的是軟磁盤(soft disk,簡稱軟盤),就是下面這個
(2000年的時候我曾經我姑姑家最早的計算機中見到過這個,當時還不知道這是啥,現在知道了。)
如今常用的磁盤是硬磁盤(hard disk,簡稱硬盤),就是下面這個
程序不讀入內存就無法運行
在了解磁盤前,還需要了解一下內存的運行機制是怎樣的,我們的程序被保存在存儲設備中,通過使用 CPU 讀入來實現程序指令的執行。這種機制稱為存儲程序方式
,現在看來這種方式是理所當然的,但在以前程序的運行都是通過改變計算機的布線來讀寫指令的。
計算機最主要的存儲部件是內存和磁盤。磁盤中存儲的程序必須加載到內存中才能運行,在磁盤中保存的程序是無法直接運行的,這是因為負責解析和運行程序內容的 CPU 是需要通過程序計數器來指定內存地址從而讀出程序指令的。
磁盤構件
磁盤緩存
我們上面提到,磁盤往往和內存是互利共生的關係,相互協作,彼此持有良好的合作關係。每次內存都需要從磁盤中讀取數據,必然會讀到相同的內容,所以一定會有一個角色負責存儲我們經常需要讀到的內容。 我們大家做軟件的時候經常會用到緩存技術
,那麼硬件層面也不例外,磁盤也有緩存,磁盤的緩存叫做磁盤緩存
。
磁盤緩存指的是把從磁盤中讀出的數據存儲到內存的方式,這樣一來,當接下來需要讀取相同的內容時,就不會再通過實際的磁盤,而是通過磁盤緩存來讀取。某一種技術或者框架的出現勢必要解決某種問題的,那麼磁盤緩存就大大改善了磁盤訪問的速度。
Windows 操作系統提供了磁盤緩存技術,不過,對於大部分用戶來說是感受不到磁盤緩存的,並且隨着計算機的演進,對硬盤的訪問速度也在不斷演進,實際上磁盤緩存到 Windows 95/98 就已經不怎麼使用了。
把低速設備的數據保存在高速設備中,需要時可以直接將其從高速設備中讀出,這種緩存方式在web中應用比較廣泛,web 瀏覽器是通過網絡來獲取遠程 web 服務器的數據並將其顯示出來。因此,在讀取較大的圖片的時候,會耗費不少時間,這時 web 瀏覽器可以把獲取的數據保存在磁盤中,然後根據需要顯示數據,再次讀取的時候就不用重新加載了。
虛擬內存
虛擬內存
是內存和磁盤交互的第二個媒介。虛擬內存是指把磁盤的一部分作為假想內存
來使用。這與磁盤緩存是假想的磁盤(實際上是內存)相對,虛擬內存是假想的內存(實際上是磁盤)。
虛擬內存是計算機系統內存管理的一種技術。它使得應用程序認為它擁有連續可用
的內存(一個完整的地址空間),但是實際上,它通常被分割成多個物理碎片,還有部分存儲在外部磁盤管理器上,必要時進行數據交換。
計算機中的程序都要通過內存來運行,如果程序佔用內存很大,就會將內存空間消耗殆盡。為了解決這個問題,WINDOWS 操作系統運用了虛擬內存技術,通過拿出一部分硬盤來當作內存使用,來保證程序耗盡內存仍然有可以存儲的空間。虛擬內存在硬盤上的存在形式就是PAGEFILE.SYS
這個頁面文件。
通過藉助虛擬內存,在內存不足時仍然可以運行程序。例如,在只剩 5MB 內存空間的情況下仍然可以運行 10MB 的程序。由於 CPU 只能執行加載到內存中的程序,因此,虛擬內存的空間就需要和內存中的空間進行置換(swap)
,然後運行程序。
虛擬內存與內存的交換方式
剛才我們提到虛擬內存需要和內存中的部分內容做置換才可讓 CPU 繼續執行程序,那麼做置換的方式是怎樣的呢?又分為哪幾種方式呢?
虛擬內存的方法有分頁式
和 分段式
兩種。Windows 採用的是分頁式。該方式是指在不考慮程序構造的情況下,把運行的程序按照一定大小的頁進行分割,並以頁
為單位進行置換。在分頁式中,我們把磁盤的內容讀到內存中稱為 Page In
,把內存的內容寫入磁盤稱為 Page Out
。Windows 計算機的頁大小為 4KB ,也就是說,需要把應用程序按照 4KB 的頁來進行切分,以頁(page)為單位放到磁盤中,然後進行置換。
為了實現內存功能,Windows 在磁盤上提供了虛擬內存使用的文件(page file,頁文件)。該文件由 Windows 生成和管理,文件的大小和虛擬內存大小相同,通常大小是內存的 1 – 2 倍。
節約內存
Windows 是以圖形界面為基礎的操作系統。它的前身是 MS-DOC
,最初的版本可以在 128kb 的內存上運行程序,但是現在想要 Windows 運行流暢的花至少要需要 512MB 的內存,但通常往往是不夠的。
也許許多人認為可以使用虛擬內存來解決內存不足的情況,而虛擬內存確實能夠在內存不足的時候提供補充,但是使用虛擬內存的 Page In 和 Page Out 通常伴隨着低速的磁盤訪問,這是一種得不償失的表現。所以虛擬內存無法從根本上解決內存不足的情況。
為了從根本上解決內存不足的情況,要麼是增加內存的容量,加內存條;要麼是優化應用程序,使其儘可能變小。第一種建議往往需要衡量口袋的銀子,所以我們只關注第二種情況。
注意:以下的篇幅會涉及到 C 語言的介紹,是每個程序員(不限語言)都需要知道和了解的知識。
通過 DLL 文件實現函數共有
DLL(Dynamic Link Library)
文件,是一種動態鏈接庫
文件,顧名思義,是在程序運行時可以動態加載 Library(函數和數據的集合)
的文件。此外,多個應用可以共有同一個 DLL 文件。而通過共有一個 DLL 文件則可以達到節約內存的效果。
例如,假設我們編寫了一個具有某些處理功能的函數 MyFunc()
。應用 A 和 應用 B 都需要用到這個函數,然後在各自的應用程序中內置 MyFunc()(這個稱為Static Link,靜態鏈接)後同時運行兩個應用,內存中就存在了同一個函數的兩個程序,這會造成資源浪費。
為了改變這一點,使用 DLL 文件而不是應用程序的執行文件(EXE文件)。因為同一個 DLL 文件內容在運行時可以被多個應用共有,因此內存中存在函數 MyFunc()的程序就只有一個
Windows 操作系統其實就是無數個 DLL 文件的集合體。有些應用在安裝時,DLL文件也會被追加。應用程序通過這些 DLL 文件來運行,既可以節約內存,也可以在不升級 EXE 文件的情況下,通過升級 DLL 文件就可以完成更新。
通過調用 _stdcall 來減少程序文件的大小
通過調用 _stdcall
來減小程序文件的方法,是用 C 語言編寫應用時可以利用的高級技巧。我們來認識一下什麼是 _stdcall。
_stdcall 是 standard call(標準調用)
的縮寫。Windows 提供的 DLL 文件內的函數,基本上都是通過 _stdcall 調用方式來完成的,這主要是為了節約內存。另一方面,用 C 語言編寫的程序默認都不是 _stdcall 。C 語言特有的調用方式稱為 C 調用
。C 語言默認不使用 _stdcall 的原因是因為 C 語言所對應的函數傳入參數是可變的,只有函數調用方才能知道到底有多少個參數,在這種情況下,棧的清理作業便無法進行。不過,在 C 語言中,如果函數的參數和數量固定的話,指定 _stdcall 是沒有任何問題的。
C 語言和 Java 最主要的區別之一在於 C 語言需要人為控制釋放內存空間
C 語言中,在調用函數後,需要人為執行棧清理指令。把不需要的數據從接收和傳遞函數的參數時使用的內存上的棧區域中清理出去的操作叫做 棧清理處理
。
例如如下代碼
// 函數調用方 void main(){ int a; a = MyFunc(123,456); } // 被調用方 int MyFunc(int a,int b){ ... }
代碼中,從 main 主函數調用到 MyFunc() 方法,按照默認的設定,棧的清理處理會附加在 main 主函數這一方。在同一個程序中,有可能會多次調用,導致 MyFunc() 會進行多次清理,這就會造成內存的浪費。
彙編之後的代碼如下
push 1C8h // 將參數 456( = 1C8h) 存入棧中 push 7Bh // 將參數 123( = 7Bh) 存入棧中 call @LTD+15 (MyFunc)(00401014) // 調用 MyFunc 函數 add esp,8 // 運行棧清理
C 語言通過棧來傳遞函數的參數,使用 push
是往棧中存入數據的指令,pop
是從棧中取出數據的指令。32 位 CPU 中,1次 push 指令可以存儲 4 個位元組(32 位)的數據。上述代碼由於進行了兩次 push 操作,所以存儲了 8 位元組的數據。通過 call
指令來調用函數,調用完成後,棧中存儲的數據就不再需要了。於是就通過 add esp,8 這個指令,使存儲着棧數據的 esp 寄存器前進 8 位(設定為指向高 8 位位元組的地址),來進行數據清理。由於棧是在各種情況下都可以利用的內存領域,因此使用完畢後有必要將其恢復到原始狀態。上述操作就是執行棧的清理工作。另外,在 C 語言中,函數的返回值,是通過寄存器而非棧來返回的。
棧執行清理工作,在調用方法處執行清理工作和在反覆調用方法處執行清理工作不同,使用 _stdcall
標準調用的方式稱為反覆調用方法,在這種情況下執行棧清理開銷比較小。
磁盤的物理結構
之前我們介紹了CPU、內存的物理結構,現在我們來介紹一下磁盤的物理結構。磁盤的物理結構指的是磁盤存儲數據的形式。
磁盤是通過其物理表面劃分成多個空間來使用的。劃分的方式有兩種:可變長方式
和 扇區方式
。前者是將物理結構劃分成長度可變的空間,後者是將磁盤結構劃分為固定長度的空間。一般 Windows 所使用的硬盤和軟盤都是使用扇區這種方式。扇區中,把磁盤表面分成若干個同心圓的空間就是 磁道
,把磁道按照固定大小的存儲空間劃分而成的就是 扇區
扇區
是對磁盤進行物理讀寫的最小單位。Windows 中使用的磁盤,一般是一個扇區 512 個位元組。不過,Windows 在邏輯方面對磁盤進行讀寫的單位是扇區整數倍簇。根據磁盤容量不同功能,1簇可以是 512 位元組(1 簇 = 1扇區)、1KB(1簇 = 2扇區)、2KB、4KB、8KB、16KB、32KB( 1 簇 = 64 扇區)。簇和扇區的大小是相等的。
不管是硬盤還是軟盤,不同的文件是不能存儲在同一簇中的,否則就會導致只有一方的文件不能刪除。所以,不管多小的文件,都會佔用 1 簇的空間。這樣一來,所有的文件都會佔用 1 簇的整數倍的空間。
我們使用軟盤做實驗會比較簡單一些,我們先對軟盤進行格式化,格式化後的軟盤空間如下
接下來,我們保存一個 txt
文件,並在文件輸入一個字符,這時候文件其實只佔用了一個位元組,但是我們看一下磁盤的屬性卻佔用了 512 位元組
然後我們繼續寫入一些東西,當文件大小到達 512 個位元組時,已用空間也是 512 位元組,但是當我們繼續寫入一個字符時,我們點開屬性會發現磁盤空間會變為 1024 個位元組(= 2 簇),通過這個實驗我們可以證明磁盤是以簇為單位來保存的。
文章參考:
《程序是怎樣跑起來的 第四章》
http://www.intohard.com/article-436-1.html
下面為自己做個宣傳,歡迎關注公眾號 Java建設者,號主是Java技術棧,熱愛技術,喜歡閱讀,熱衷於分享和總結,希望能把每一篇好文章分享給成長道路上的你。關注公眾號回復 002 領取為你特意準備的大禮包,你一定會喜歡並收藏的。