从C++中的const到MMU(存储器管理单元)(MMU部分为网页整理)
? ?先來回顧一段小代碼:
#include <iostream> using namespace std; int main() {char *s = "Hello World!";printf("%s\n",s);s[0] = 'B';printf("%s\n",s);return 0; }用G++編譯 : g++ test1.cpp
之后顯示:
對的,沒有報錯,編譯器的英文是說,這是一個過時的寫法。
不管他,我們來運行一下,畢竟只是警告!
看見結果了吧,Bus erro: 10. 說明后面一個printf出現了異常。下面是查找到的Bus Error錯誤解釋:
在《C專家編程》中提到了總線錯誤bus error(core dumped)。
總線錯誤幾乎都是由于未對齊的讀或寫引起的。
它之所以稱為總線錯誤,是因為出現未對齊的內存訪問請求時,被堵塞的組件就是地址總線。對齊的意思就是數據項只能存儲在地址是數據項大小的整倍數的內存位置上。
現代的計算機架構中,尤其是RISC架構,都需要字對齊,因為與任意的對齊有關的額外邏輯都會使內存系統更大且更慢。
通過迫使每個內存訪問局限在一個cache行或者一個單獨的頁面內,可以極大地簡化(并加速)如cache控制器和內存管理單元這樣的硬件。
頁和cache的大小都是經過精心設計的,這樣只要遵守對齊規則就可以保證一個原子數據項不會跨過一個頁或cache塊的邊界。
本地變量放在堆棧里,new出來的變量放在堆里面,全局變量放在全局數據區里面,而全局變量里的常量是放在代碼段里的,且會被默認為const,放在代碼段內,而代碼段是不寫的。
好了下面為了更便于理解,先簡單介紹下MMU(Memory Manage Unit),內存管理單元。MMU有一個內存保護作用,其中代碼段是不可寫的,要是寫了,內不會產生一個check,所以產生了Bus Error。
但是為什么沒有報錯呢,因為我們騙過了編譯器。編譯到s[0] = 'B'時,它不知道s是否為const。
正確的作法可在前面加一個const 。
那么考慮另外一段程序。
#include <iostream> using namespace std; int main() {char s[] = "Hello World!";printf("%s\n",s);s[0] = 'B';printf("%s\n",s);return 0; }用G++編譯,沒有報錯。OK,運行:
為什么這里沒有錯誤呢?
通常我們認為s[]是一個數組,數組是位于堆棧內的。堆棧里有很大一塊空間,用來放Hello world!,這里的賦值號“=”是拷貝的意思 。這個s[0]的賦值是可以的。好了 ,如果你還沒弄懂,繼續看下面一段代碼。
#include <iostream> using namespace std; int main() {const char *s1 = "Hello World!";//默認為const,在代碼段內,不可寫。計算機里面有一個MMU,內存管理單元。char s2[] = "Hello World!";printf("s1's address is %p\n",s1);printf("s2's address is %p\n",s2);printf("main's address is %p\n",main);//MMU,負責內存保護,//cout<<s<<endl; // s[0] = 'B'; // cout<<s<<endl;return 0; }用32位編譯:g++ test.cpp -m32后 顯示如下:
? ?
OK我們發現 s1,main的位于低地址區域,而s2位于高地址區域。s1,main在全局區,而s2位于堆棧內。
PS:教大家一個口訣,const在*前,則修飾的是對象,const在*后,則修飾的是指針。
接下來深入理解內存管理單元MMU(以下內容整理自某博客):
MMU的主要作用就是把虛擬地址轉換成物理地址。一個32位的CPU,它的地址范圍是0~0xFFFFFFFF (4G)而對于一個64位的CPU,它的地址范圍為0~0xFFFFFFFFFFFFFFFF (64T),這個范圍就是我們的程序能夠產生的地址范圍,我們把這個地址范圍稱為虛擬地址空間,該空間中的某一個地址我們稱之為虛擬地址。與虛擬地址空間和虛擬地址相對應的則是物理地址空間和物理地址,大多數時候我們的系統所具備的物理地址空間只是虛擬地址空間的一個子集,這里舉一個最簡單的例子直觀地說明這兩者,對于一臺內存為256MB的32bit x86主機來說,它的虛擬地址空間范圍是0~0xFFFFFFFF(4G),而物理地址空間范圍是0x000000000~0x0FFFFFFF(256MB)。在沒有使用虛擬存儲器的機器上,虛擬地址被直接送到內存總線上,使具有相同地址的物理存儲器被讀寫。而在使用了虛擬存儲器的情況下,虛擬地址不是被直接送到內存地址總線上,而是送到內存管理單元——MMU。
大多數使用虛擬存儲器的系統都使用一種稱為分頁(paging)。虛擬地址空間劃分成稱為頁(page)的單位,而相應的物理地址空間也被進行劃分,單位是頁框().頁和頁框的大小必須相同。接下來配合圖片我以一個例子說明頁與頁框之間在MMU的調度下是如何進行映射的:
在這個例子中我們有一臺可以生成16位地址的機器,它的虛擬地址范圍從0x0000~0xFFFF(64K),而這臺機器只有32K的物理地址,因此他可以運行64K的程序,但該程序不能一次性調入內存運行。這臺機器必須有一個達到可以存放64K程序的外部存儲器(例如磁盤或是FLASH)以保證程序片段在需要時可以被調用。在這個例子中,頁的大小為4K,頁框大小與頁相同(這點是必須保證的,內存和外圍存儲器之間的傳輸總是以頁為單位的),對應64K的虛擬地址和32K的物理存儲器,他們分別包含了16個頁和8個頁框。
? ? ?我們先根據上圖解釋一下分頁后要用到的幾個術語,在上面我們已經接觸了頁和頁框,上圖中綠色部分是物理空間,其中每一格表示一個物理頁框。橘×××部分是虛擬空間,每一格表示一個頁,它由兩部分組成,分別是 Index(頁框索引)和位p(present 存在位), Index的意義很明顯,它指出本頁是往哪個物理頁框進行映射的,位p的意義則是指出本頁的映射是否有效,如上圖,當某個頁并沒有被映射時(或稱映射無效, Index部分為X),該位為0,映射有效則該位為1。
? ?我們執行下面這些指令(本例子的指令不針對任何特定機型,都是偽指令)
例1:
? ?MOVE REG,0 //將0號地址的值傳遞進寄存器REG.
? ?虛擬地址0將被送往MMU,MMU看到該虛地址落在頁0范圍內(頁0范圍是0到4095),從上圖我們看到頁0所對應(映射)的頁框為2(頁框2的地址范圍是8192到12287),因此MMU將該虛擬地址轉化為物理地址8192,并把地址8192送到地址總線上。內存對MMU的映射一無所知,它只看到一個對地址8192的讀請求并執行它。MMU從而把0到4096的虛擬地址映射到8192到12287的物理地址。
例2:
? ?MOVE REG,8192
? ?被轉換為
? ?MOVE REG,24576
? ?因為虛擬地址8192在頁2中,而頁2被映射到頁框6(物理地址從24576到28671)
例3:
? ?MOVE REG,20500
? ?被轉換為
? ?MOVE REG,12308
? ?虛擬地址20500在虛頁5(虛擬地址范圍是20480到24575)距開頭20個字節處,虛頁5映射到頁框3(頁框3的地址范圍是 12288到16383),于是被映射到物理地址12288+20=12308。
? ? ?通過適當的設置MMU,可以把16個虛頁隱射到8個頁框中的任何一個,但是這個方法并沒有有效的解決虛擬地址空間比物理地址空間大的問題。從上圖中我們可以看到,我們只有8個頁框(物理地址),但我們有16個頁(虛擬地址),所以我們只能把16個頁中的8個進行有效的映射。我們看看例4會發生什么情況
? ? ?MOV REG,32780
? ? ?虛擬地址32780落在頁8的范圍內,從上圖總我們看到頁8沒有被有效的進行映射(該頁被打上X),這是又會發生什么?MMU注意到這個頁沒有被映射,于是通知CPU發生一個缺頁故障(page fault).這種情況下操作系統必須處理這個頁故障,它必須從8個物理頁框中找到1個當前很少被使用的頁框并把該頁框的內容寫入外圍存儲器(這個動作被稱為page copy),隨后把需要引用的頁(例4中是頁8)映射到剛才釋放的頁框中(這個動作稱為修改映射關系),然后從新執行產生故障的指令(MOV REG,32780)。假設操作系統決定釋放頁框1,那么它將把虛頁8裝入物理地址的4-8K,并做兩處修改:首先把標記虛頁1未被映射(原來虛頁1是被影射到頁框1的),以使以后任何對虛擬地址4K到8K的訪問都引起頁故障而使操作系統做出適當的動作(這個動作正是我們現在在討論的),其次他把虛頁8對應的頁框號由X變為1,因此重新執行MOV REG,32780時,MMU將把32780映射為4108。
? ? ?我們大致了解了MMU在我們的機器中扮演了什么角色以及它基本的工作內容是什么,下面我們將舉例子說明它究竟是如何工作的(注意,本例中的MMU并無針對某種特定的機型,它是所有MMU工作的一個抽象)。
? ? ?首先明確一點,MMU的主要工作只有一個,就是把虛擬地址映射到物理地址。
我們已經知道,大多數使用虛擬存儲器的系統都使用一種稱為分頁(paging)的技術,就象我們剛才所舉的例子,虛擬地址空間被分成大小相同的一組頁,每個頁有一個用來標示它的頁號(這個頁號一般是它在該組中的索引,這點和C/C++中的數組相似)。在上面的例子中0~4K的頁號為0,4~8K的頁號為1,8~12K的頁號為2,以此類推。而虛擬地址(注意:是一個確定的地址,不是一個空間)被MMU分為2個部分,第一部分是頁號索引(page Index),第二部分則是相對該頁首地址的偏移量(offset). 。我們還是以剛才那個16位機器結合下圖進行一個實例說明,該實例中,虛擬地址8196被送進MMU,MMU把它映射成物理地址。16位的CPU總共能產生的地址范圍是0~64K,按每頁4K的大小計算,該空間必須被分成16個頁。而我們的虛擬地址第一部分所能夠表達的范圍也必須等于16(這樣才能索引到該頁組中的每一個頁),也就是說這個部分至少需要4個bit。一個頁的大小是4K(4096),也就是說偏移部分必須使用12個bit來表示(2^12=4096,這樣才能訪問到一個頁中的所有地址),8196的二進制碼如下圖所示:
該地址的頁號索引為0010(二進制碼),既索引的頁為頁2,第二部分為000000000100(二進制),偏移量為4。頁2中的頁框號為6(頁2映射在頁框6,見上圖),我們看到頁框6的物理地址是24~28K。于是MMU計算出虛擬地址8196應該被映射成物理地址24580(頁框首地址+偏移量=24576+4=24580)。同樣的,若我們對虛擬地址1026進行讀取,1026的二進制碼為0000010000000010,page index="0000"=0,offset=010000000010=1026。頁號為0,該頁映射的頁框號為2,頁框2的物理地址范圍是8192~12287,故MMU將虛擬地址1026映射為物理地址9218(頁框首地址+偏移量=8192+1026=9218)。以上就是MMU的工作過程。
轉載于:https://blog.51cto.com/feilei/1371443
總結
以上是生活随笔為你收集整理的从C++中的const到MMU(存储器管理单元)(MMU部分为网页整理)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++中四种类型转换方式(ynamic_
- 下一篇: android 谷歌地图在模拟器的使用