Linux内存模型
了解linux的內(nèi)存模型,或許不能讓你大幅度提高編程能力,但是作為一個(gè)基本知識(shí)點(diǎn)應(yīng)該熟悉。坐火車外出旅行時(shí),即時(shí)你對(duì)沿途的地方一無(wú)所知,仍然可以到達(dá)目標(biāo)地。但是你對(duì)整個(gè)路途都很比較清楚的話,每到一個(gè)站都知道自己在哪里,知道當(dāng)?shù)氐娘L(fēng)土人情,對(duì)比一下所見(jiàn)所想,旅程可能更有趣一些。?
類似的,了解linux的內(nèi)存模型,你知道每塊內(nèi)存,每個(gè)變量,在系統(tǒng)中處于什么樣的位置。這同樣會(huì)讓你心情愉快,知道這些,有時(shí)還會(huì)讓你的生活輕更松些。看看變量的地址,你可以大致斷定這是否是一個(gè)有效的地址。一個(gè)變量被破壞了,你可以大致推斷誰(shuí)是犯罪嫌疑人。?
Linux的內(nèi)存模型,一般為:
| 地址 | 作用 | 說(shuō)明 |
| >=0xc000 0000 | 內(nèi)核虛擬存儲(chǔ)器 | 用戶代碼不可見(jiàn)區(qū)域 |
| <0xc000 0000 | Stack(用戶棧) | ESP指向棧頂 |
| ? | ↓ ? ↑ | ? 空閑內(nèi)存 |
| >=0x4000 0000 | 文件映射區(qū) | ? |
| <0x4000 0000 | ? ? ↑ | ? 空閑內(nèi)存 ? |
| ? | Heap(運(yùn)行時(shí)堆) | 通過(guò)brk/sbrk系統(tǒng)調(diào)用擴(kuò)大堆,向上增長(zhǎng)。 |
| ? | .data、.bss(讀寫段) | 從可執(zhí)行文件中加載 |
| >=0x0804 8000 | .init、.text、.rodata(只讀段) | 從可執(zhí)行文件中加載 |
| <0x0804 8000 | 保留區(qū)域 | ? |
?
很多書上都有類似的描述,本圖取自于《深入理解計(jì)算機(jī)系統(tǒng)》p603,略做修改。本圖比較清析,很容易理解,但仍然有兩點(diǎn)不足。下面補(bǔ)充說(shuō)明一下:
?
1.???????? 第一點(diǎn)是關(guān)于運(yùn)行時(shí)堆的。
為說(shuō)明這個(gè)問(wèn)題,我們先運(yùn)行一個(gè)測(cè)試程序,并觀察其結(jié)果:
#include <stdio.h> intmain(intargc, char* argv[]) { int first = 0; int* p0 = malloc(1024); int* p1 = malloc(1024 * 1024); int* p2 = malloc(512 * 1024 * 1024 ); int* p3 = malloc(1024 * 1024 * 1024 ); printf("main=%p print=%p/n", main, printf); printf("first=%p/n", &first); printf("p0=%p p1=%p p2=%p p3=%p/n", p0, p1, p2, p3); getchar(); return 0; }
運(yùn)行后,輸出結(jié)果為:
main=0x8048404 print=0x8048324
first=0xbfcd1264
p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008
? main和print兩個(gè)函數(shù)是代碼段(.text)的,其地址符合表一的描述。
l???????? first是第一個(gè)臨時(shí)變量,由于在first之前還有一些環(huán)境變量,它的值并非0xbfffffff,而是0xbfcd1264,這是正常的。
l???????? p0是在堆中分配的,其地址小于0x4000 0000,這也是正常的。
l???????? 但p1和p2也是在堆中分配的,而其地址竟大于0x4000 0000,與表一描述不符。?
原因在于:運(yùn)行時(shí)堆的位置與內(nèi)存管理算法相關(guān),也就是與malloc的實(shí)現(xiàn)相關(guān)。關(guān)于內(nèi)存管理算法的問(wèn)題,我們?cè)诤罄^文章中有詳細(xì)描述,這里只作簡(jiǎn)要說(shuō)明。在glibc實(shí)現(xiàn)的內(nèi)存管理算法中,Malloc小塊內(nèi)存是在小于0x4000 0000的內(nèi)存中分配的,通過(guò)brk/sbrk不斷向上擴(kuò)展,而分配大塊內(nèi)存,malloc直接通過(guò)系統(tǒng)調(diào)用mmap實(shí)現(xiàn),分配得到的地址在文件映射區(qū),所以其地址大于0x4000 0000。?
從maps文件中可以清楚的看到一點(diǎn):
| 00514000-00515000 r-xp 00514000 00:00 0 00624000-0063e000 r-xp 00000000 03:01 718192???? /lib/ld-2.3.5.so 0063e000-0063f000 r-xp 00019000 03:01 718192???? /lib/ld-2.3.5.so 0063f000-00640000 rwxp 0001a000 03:01 718192???? /lib/ld-2.3.5.so 00642000-00766000 r-xp 00000000 03:01 718193???? /lib/libc-2.3.5.so 00766000-00768000 r-xp 00124000 03:01 718193???? /lib/libc-2.3.5.so 00768000-0076a000 rwxp 00126000 03:01 718193???? /lib/libc-2.3.5.so 0076a000-0076c000 rwxp 0076a000 00:00 0 08048000-08049000 r-xp 00000000 03:01 1307138??? /root/test/mem/t.exe 08049000-0804a000 rw-p 00000000 03:01 1307138??? /root/test/mem/t.exe 09f5d000-09f7e000 rw-p 09f5d000 00:00 0????????? [heap] 57e2f000-b7f35000 rw-p 57e2f000 00:00 0 b7f44000-b7f45000 rw-p b7f44000 00:00 0 bfb2f000-bfb45000 rw-p bfb2f000 00:00 0????????? [stack] |
?
2.???????? 第二是關(guān)于多線程的。
現(xiàn)在的應(yīng)用程序,多線程的居多。表一所描述的模型無(wú)法適用于多線程環(huán)境。按表一所述,程序最多擁有上G的棧空間,事實(shí)上,在多線程情況下,能用的棧空間是非常有限的。為了說(shuō)明這個(gè)問(wèn)題,我們?cè)倏戳硗庖粋€(gè)測(cè)試:
#include <stdio.h> #include <pthread.h> void* thread_proc(void* param) { int first = 0; int* p0 = malloc(1024); int* p1 = malloc(1024 * 1024); printf("(0x%x): first=%p/n", pthread_self(), &first); printf("(0x%x): p0=%p p1=%p /n", pthread_self(), p0, p1); return 0; } #define N 5 intmain(intargc, char* argv[]) { intfirst = 0; inti= 0; void* ret = NULL; pthread_t tid[N] = {0}; printf("first=%p/n", &first); for(i = 0; i < N; i++) { pthread_create(tid+i, NULL, thread_proc, NULL); } for(i = 0; i < N; i++) { pthread_join(tid[i], &ret); } return 0; }
運(yùn)行后,輸出結(jié)果為:
first=0xbfd3d35c
(0xb7f2cbb0): first=0xb7f2c454
(0xb7f2cbb0): p0=0x84d52d8 p1=0xb4c27008
(0xb752bbb0): first=0xb752b454
(0xb752bbb0): p0=0x84d56e0 p1=0xb4b26008
(0xb6b2abb0): first=0xb6b2a454
(0xb6b2abb0): p0=0x84d5ae8 p1=0xb4a25008
(0xb6129bb0): first=0xb6129454
(0xb6129bb0): p0=0x84d5ef0 p1=0xb4924008
(0xb5728bb0): first=0xb5728454
(0xb5728bb0): p0=0x84d62f8 p1=0xb7e2c008
?
我們看一下:
主線程與第一個(gè)線程的棧之間的距離:0xbfd3d35c - 0xb7f2c454=0x7e10f08=126M
第一個(gè)線程與第二個(gè)線程的棧之間的距離:0xb7f2c454 - 0xb752b454=0xa01000=10M
其它幾個(gè)線程的棧之間距離均為10M。
也就是說(shuō),主線程的棧空間最大為126M,而普通線程的棧空間僅為10M,超這個(gè)范圍就會(huì)造成棧溢出。
系統(tǒng)為進(jìn)程分配數(shù)據(jù)空間有三種形式。
靜態(tài)分配
整塊靜態(tài)分配空間,包括其中的所有數(shù)據(jù)實(shí)體,都是在進(jìn)程創(chuàng)建時(shí)由系統(tǒng)一次性分配的(同時(shí)為UNIX稱為Text的代碼分配空間)。這塊空間在進(jìn)程運(yùn)行期間保持不變。
初始化的和未初始化的實(shí)體分別放在初始化數(shù)據(jù)段和未初始化數(shù)據(jù)段(BSS)。后者和前者不同,在.o文件a.out文件里都不存在(只有構(gòu)架信息),在進(jìn)程的虛擬空間里才展開(kāi)。
extern變量和static變量采用靜態(tài)分配。
在進(jìn)程創(chuàng)建時(shí)做靜態(tài)分配,分配正文(text)段、數(shù)據(jù)段和棧空間。
正文和初始化數(shù)據(jù)是按a.out照樣復(fù)制過(guò)來(lái);未初始化數(shù)據(jù)按構(gòu)架信息展開(kāi),填以0或空;棧空間的大小由鏈接器開(kāi)關(guān)(具體哪個(gè)開(kāi)關(guān)忘了)決定。
棧分配
整個(gè)棧空間已在進(jìn)程創(chuàng)建時(shí)分配好。棧指針SP的初值的設(shè)定,確定了棧空間的大小。鏈接器的某個(gè)開(kāi)關(guān)可以設(shè)定棧空間的大小。在進(jìn)程運(yùn)行期間,棧空間的大小不變。但是,在進(jìn)程剛啟動(dòng)時(shí),棧空間是空的,里面沒(méi)有實(shí)體。在進(jìn)程運(yùn)行期間,對(duì)具體實(shí)體的棧分配是進(jìn)程自行生成(壓棧)和釋放(彈出)實(shí)體,系統(tǒng)并不參與。
auto變量和函數(shù)參數(shù)采用棧分配。
只要壓入的實(shí)體的總長(zhǎng)度不超過(guò)棧空間尺寸,棧分配就與系統(tǒng)無(wú)關(guān)。如果超過(guò)了,就會(huì)引發(fā)棧溢出錯(cuò)誤。
堆分配
當(dāng)進(jìn)程需要生成實(shí)體時(shí),向系統(tǒng)申請(qǐng)分配空間;不再需要該實(shí)體時(shí),可以向系統(tǒng)申請(qǐng)回收這塊空間。
堆分配使用特定的函數(shù)(如malloc()等)或操作符(new)。所生成的實(shí)體都是匿名的,只能通過(guò)指針去訪問(wèn)。
對(duì)實(shí)體來(lái)說(shuō),棧分配和堆分配都是動(dòng)態(tài)分配:實(shí)體都是在進(jìn)程運(yùn)行中生成和消失。而靜態(tài)分配的所有實(shí)體都是在進(jìn)程創(chuàng)建時(shí)全部分配好的,在運(yùn)行中一直存在。
同為動(dòng)態(tài)分配,棧分配與堆分配是很不相同的。前者是在進(jìn)程創(chuàng)建時(shí)由系統(tǒng)分配整塊棧空間,以后實(shí)體通過(guò)壓棧的方式產(chǎn)生,并通過(guò)彈出的方式取消。不管是否產(chǎn)生實(shí)體,產(chǎn)生多少實(shí)體,棧空間總是保持原來(lái)的大小。后者并沒(méi)有預(yù)設(shè)的空間,當(dāng)需要產(chǎn)生實(shí)體時(shí),才向系統(tǒng)申請(qǐng)正好能容納這個(gè)實(shí)體的空間。當(dāng)不再需要該實(shí)體時(shí),可以向系統(tǒng)申請(qǐng)回收這塊空間。因此,堆分配是真正的動(dòng)態(tài)分配。
顯然,堆分配的空間利用率最高。
棧分配和靜態(tài)分配也有共性:整塊空間是在進(jìn)程創(chuàng)建時(shí)由系統(tǒng)分配的。但是,后者同時(shí)分配了所有實(shí)體的空間,而前者在進(jìn)程啟動(dòng)時(shí)是空的。另外,棧上的實(shí)體和數(shù)據(jù)段里的實(shí)體都是有名實(shí)體,可以通過(guò)標(biāo)識(shí)符來(lái)訪問(wèn)。
| ? | 靜態(tài)分配 | 棧分配 | 堆分配 |
| 整塊空間生成 | 進(jìn)程創(chuàng)建時(shí) | 進(jìn)程創(chuàng)建時(shí) | 用一點(diǎn)分配一點(diǎn) |
| 實(shí)體生成時(shí)間 | 進(jìn)程創(chuàng)建時(shí) | 進(jìn)程運(yùn)行時(shí) | 進(jìn)程運(yùn)行時(shí) |
| 實(shí)體生成者 | 操作系統(tǒng) | 進(jìn)程 | 進(jìn)程申請(qǐng)/系統(tǒng)實(shí)施 |
| 生命期 | 永久 | 臨時(shí) | 完全可控 |
| 有名/匿名 | 有名 | 有名 | 匿名 |
| 訪問(wèn)方式 | 能以標(biāo)識(shí)訪問(wèn) | 能以標(biāo)識(shí)訪問(wèn) | 只能通過(guò)指針訪問(wèn) |
| 空間可否回收 | 不可 | 不可 | 可以 |
?
棧溢出的后果是比較嚴(yán)重的,或者出現(xiàn)Segmentation fault錯(cuò)誤,或者出現(xiàn)莫名其妙的錯(cuò)誤。
總結(jié)
- 上一篇: 在showModalDialog和sho
- 下一篇: Linux 指令篇