重學電腦組成原理(八)- 程式的裝載

来源:https://www.cnblogs.com/JavaEdge/archive/2019/08/17/11370626.html
-Advertisement-
Play Games

比爾·蓋茨在上世紀80年代說的“640K ought to be enough for anyone” 也就是“640K記憶體對哪個人來說都夠用了” 那個年代,微軟開發的還是DOS操作系統,程式員們還在絞盡腦汁,想要用好這極為有限的640K記憶體 而現在,我手頭的Mac Book Pro已經是16G記憶體 ...


比爾·蓋茨在上世紀80年代說的“640K ought to be enough for anyone”

也就是“640K記憶體對哪個人來說都夠用了”

那個年代,微軟開發的還是DOS操作系統,程式員們還在絞盡腦汁,想要用好這極為有限的640K記憶體

而現在,我手頭的Mac Book Pro已經是16G記憶體了,上升了一萬倍還不止。

那比爾·蓋茨這句話在當時也是完全的無稽之談麽?有沒有哪怕一點點的道理呢?這一講里,我就和你一起來看一看。

1 程式裝載的挑戰

在運行這些可執行文件的時候,我們其實是通過一個裝載器,解析ELF或者PE格式的可執行文件

裝載器會把對應的指令和數據載入到記憶體裡面來,讓CPU去執行。

裝載到記憶體,裝載器需要滿足兩個要求

  • 可執行程式載入後占用的記憶體空間應該是連續的
    執行指令的時候,程式計數器是順序地一條一條指令執行。這意味著,這一條條指令需要連續地存儲在一起
  • 需要同時載入很多個程式,並且不能讓程式自己規定在記憶體中載入的位置
    雖然編譯出來的指令里已經有了對應的各種各樣的記憶體地址,但是實際載入的時候,我們其實沒有辦法確保,這個程式一定載入在哪一段記憶體地址上
    因為現在的電腦通常會同時運行很多個程式,可能你想要的記憶體地址已經被其他載入了的程式占用

要滿足這兩個基本的要求,我們很容易想到一個辦法。那就是我們可以在記憶體裡面,找到一段連續的記憶體空間,然後分配給裝載的程式,然後把這段連續的記憶體空間地址,和整個程式指令里指定的記憶體地址做一個映射。

指令里用到的記憶體地址叫作虛擬記憶體地址(Virtual Memory Address)

實際在記憶體硬體裡面的空間地址,我們叫物理記憶體地址(Physical Memory Address)

程式里有指令和各種記憶體地址,我們只需要關心虛擬記憶體地址就行了

對於任何一個程式來說,它看到的都是同樣的記憶體地址。我們維護一個虛擬記憶體到物理記憶體的映射表,這樣實際程式指令執行的時候,會通過虛擬記憶體地址,找到對應的物理記憶體地址,然後執行。因為是連續的記憶體地址空間,所以我們只需要維護映射關係的起始地址和對應的空間大小就可以了。

2 記憶體分段

這種找出一段連續的物理記憶體和虛擬記憶體地址進行映射的方法,我們叫分段(Segmentation)

這裡的段,就是指系統分配出來的那個連續的記憶體空間。

分段的辦法很好,解決了程式本身不需要關心具體的物理記憶體地址的問題,但它也有一些不足之處,第一個就是記憶體碎片(Memory Fragmentation)

舉個例子

電腦有1GB的記憶體

先啟動一個圖形渲染程式,占用了512MB的記憶體

接著啟動一個Chrome瀏覽器,占用了128MB記憶體

再啟動一個PY程式,占用了256MB記憶體

這個時候,我們關掉Chrome,於是空閑記憶體還有1024 - 512 - 256 = 256MB

按理來說,我們有足夠的空間再去裝載一個200MB的程式。但是,這256MB的記憶體空間不是連續的,而是被分成了兩段128MB的記憶體

因此,實際情況是,我們的程式沒辦法載入進來

當然了,有辦法解決 --- 記憶體交換(Memory Swapping)

我們可以把Python程式占用的256MB記憶體寫到硬碟,再從硬碟上讀回來到記憶體裡面

不過讀回來的時候,我們不再把它載入到原來的位置,而是緊緊跟在那已經被占用了的512MB記憶體後面

這樣,我們就有了連續的256MB記憶體空間,就可以去載入一個新的200MB的程式。如果你自己安裝過Linux操作系統,你應該遇到過分配一個swap硬碟分區的問題

這塊分出來的磁碟空間,其實就是專門給Linux操作系統進行記憶體交換用的。

虛擬記憶體、分段,再加上記憶體交換

看起來似乎已經解決了電腦同時裝載運行很多個程式的問題

不過三者的組合仍然會遇到一個性能瓶頸

  • 硬碟的訪問速度要比記憶體慢很多
  • 而每一次記憶體交換,我們都需要把一大段連續的記憶體數據寫到硬碟上

所以,如果記憶體交換的時候,交換的是一個很占記憶體空間的程式,這樣整個機器都會顯得卡頓。

3 記憶體分頁

既然問題出在記憶體碎片和記憶體交換的空間太大上,那麼解決問題的辦法就是,少出現一些記憶體碎片。

另外,當需要進行記憶體交換的時候,讓需要交換寫入或者從磁碟裝載的數據更少一點,這樣就可以解決這個問題。

這個辦法,在現在電腦的記憶體管理裡面,就叫作記憶體分頁(Paging)

**和分段這樣分配一整段連續的空間給到程式相比

分頁則是把整個物理記憶體空間切成一段段固定尺寸的大小**

而對應的程式所需要占用的虛擬記憶體空間,也會同樣切成一段段固定尺寸的大小。

這樣一個連續並且尺寸固定的記憶體空間,我們叫頁(Page)

從虛擬記憶體到物理記憶體的映射,不再是拿整段連續的記憶體的物理地址,而是按照一個個頁來的。

頁的尺寸一般遠遠小於整個程式的大小。

在Linux下,我們通常只設置成4KB。你可以通過命令看看你手頭的Linux系統設置的頁的大小。

由於記憶體空間都是預先劃分好的,也就沒有不能使用的碎片,而只有被釋放出來的很多4KB的頁。

即使記憶體空間不夠,需要讓現有的、正在運行的其他程式

通過記憶體交換釋放出一些記憶體的頁出來,一次性寫入磁碟的也只有少數的一個頁或者幾個頁,不會花太多時間,讓整個機器被記憶體交換的過程給卡住。

分頁的方式使得載入程式的時候,不再需要一次性把程式載入到物理記憶體中

可以在進行虛擬記憶體和物理記憶體的頁之間的映射後,並不真的把頁載入到物理記憶體里,而是只在程式運行中,需要用到對應虛擬記憶體頁裡面的指令和數據時,再載入到物理記憶體裡面去。

實際上,我們的操作系統,的確是這麼做的

當要讀取特定的頁,卻發現數據並沒有載入到物理記憶體里的時候,就會觸發一個來自於CPU的缺頁錯誤(Page Fault)

操作系統會捕捉到這個錯誤,然後將對應的頁,從存放在硬碟上的虛擬記憶體里讀取出來,載入到物理記憶體里。這種方式,使得我們可以運行那些遠大於我們實際物理記憶體的程式。同時,這樣一來,任何程式都不需要一次性載入完所有指令和數據,只需要載入當前需要用到就行了。

通過虛擬記憶體、記憶體交換和記憶體分頁這三個技術的組合,我們最終得到了一個讓程式不需要考慮實際的物理記憶體地址、大小和當前分配空間的解決方案。

這些技術和方法,對於我們程式的編寫、編譯和鏈接過程都是透明的。這也是我們在電腦的軟硬體開發中常用的一種方法,就是加入一個間接層

通過引入虛擬記憶體、頁映射和記憶體交換,我們的程式本身,就不再需要考慮對應的真實的記憶體地址、程式載入、記憶體管理等問題了。任何一個程式,都只需要把記憶體當成是一塊完整而連續的空間來直接使用。

4 總結

電腦只要640K記憶體就夠了嗎?很顯然,現在來看,比爾·蓋茨的這個判斷是不合理的,那為什麼他會這麼認為呢?因為他也是一個很優秀的程式員啊!

在虛擬記憶體、記憶體交換和記憶體分頁這三者結合之下,你會發現,其實要運行一個程式,“必需”的記憶體是很少的。CPU只需要執行當前的指令,極限情況下,記憶體也只需要載入一頁就好了。再大的程式,也可以分成一頁。每次,只在需要用到對應的數據和指令的時候,從硬碟上交換到記憶體裡面來就好了。以我們現在4K記憶體一頁的大小,640K記憶體也能放下足足160頁呢,也無怪乎在比爾·蓋茨會說出“640K ought to be enough for anyone”這樣的話。

不過呢,硬碟的訪問速度比記憶體慢很多,所以我們現在的電腦,沒有個幾G的記憶體都不好意思和人打招呼。

那麼,除了程式分頁裝載這種方式之外,我們還有其他優化記憶體使用的方式麽?下一講,我們就一起來看看“動態裝載”,學習一下讓兩個不同的應用程式,共用一個共用程式庫的辦法。

5 推薦閱讀

想要更深入地瞭解代碼裝載的詳細過程,推薦你閱讀《程式員的自我修養——鏈接、裝載和庫》的第1章和第6章。

6 思考

在Java這樣使用虛擬機的編程語言裡面,我們寫的程式是怎麼裝載到記憶體裡面來的呢?它也和我們講的一樣,是通過記憶體分頁和記憶體交換的方式載入到記憶體裡面來的麽?

jvm已經是上層應用,無需考慮物理分頁,一般更直接是考慮對象本身的空間大小,物理硬體管理統一由承載jvm的操縱系統去解決吧

參考

深入淺出電腦組成原理


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 查詢k8s集群部署pod的基本情況 如下圖,我們可知容器coredns和dnsutils都部署成功,但是由於功能變數名稱解析的問題,導致coredns和dnsutils的容器不斷重啟(原因heath檢查,無法請求成功,被kubelet重啟了pod) 命令如下: root >> kubectl get ...
  • vim多視窗操作 垂直方式打開多個視窗 vim filename1 filename2 -O :vsp filename 或者 :vs filename 視窗切換 ctrl + ww vim文件切換 在多個文件之間切換 ctrl + i #切換到下一個文件或同一個文件中的下一個索引 ctrl + o ...
  • 一、廢話兩句 在雲數據中心,一次幾十臺甚至幾百台伺服器上線,系統安裝將變得非常繁瑣,系統安裝好了後還會涉及很多配置,如果一臺台來安裝的話工作量非常大。(雖然有加班費,開個玩笑)為瞭解決這個問題,我們需要實現無人值守批量部署系統。 簡單看一下拓撲圖: 1. 什麼是PXE? 簡單來說:PXE主要是引導作 ...
  • 第一章Linux命令行簡介 1.1 Linux命令行概述 1.1.1 Linux 命令行的開啟和退出 開啟:登陸賬號密碼進入系統 退出:exit/logout 快捷鍵:Ctrl+d 1.1.2 Linux命令行提示符介紹 (1)提示符由PS1環境變數控制。實例代碼如下: [root@centos10 ...
  • 今天我們學習關於NTFS管理數據 以下是學習的內容NTFS分區和FAT32分區的區別,如何將FAT32分區轉化成NTFS分區,FAT 32 不支持大於4G ,NTFS許可權設置 ,EFS加密 ,文件夾的NTFS許可權 許可權累加, 查看對象的所有者,獲得對象的所有權,重置文件夾中所有對象的許可權,利用EFS ...
  • 把對應的不同文件內的代碼段,合併到一起,成為最後的可執行文件 鏈接的方式,讓我們在寫代碼的時候做到了“復用”。 同樣的功能代碼只要寫一次,然後提供給很多不同的程式進行鏈接就行了。 “鏈接”其實有點兒像我們日常生活中的 標準化、模塊化 生產。 有一個可以生產標準螺帽的生產線,就可生產很多不同的螺帽。 ...
  • 一、索引創建 1. 非結構化創建 2. 結構化創建 二、插入 1. 指定文檔ID插入 2. 自動產生文檔ID插入 三、修改 1. 直接修改文檔 2. 腳本修改文檔 四、刪除 1. 刪除文檔 2. 刪除索引 五、查詢 1. 簡單查詢 2. 條件查詢 3. 聚合查詢 ...
  • 參考:https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/api_concepts.html DataSet and DataStream Flink具有特殊類DataSet和DataStream來表示程式中的數據。 你可以 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...