內存模型
瞭解linux的內存模型,或許不能讓你大幅度提高編程能力,但是作爲一個基本知識點應該熟悉。坐火車外出旅行時,即時你對沿途的地方一無所知,仍然可以到達目標地。但是你對整個路途都很比較清楚的話,每到一個站都知道自己在哪裏,知道當地的風土人情,對比一下所見所想,旅程可能更有趣一些。
類似的,瞭解linux的內存模型,你知道每塊內存,每個變量,在系統中處於什麼樣的位置。這同樣會讓你心情愉快,知道這些,有時還會讓你的生活輕更鬆些。看看變量的地址,你可以大致斷定這是否是一個有效的地址。一個變量被破壞了,你可以大致推斷誰是犯罪嫌疑人。
Linux的內存模型,一般爲:
地址 |
作用 |
說明 |
>=0xc000 0000 |
內核虛擬存儲器 |
用戶代碼不可見區域 |
<0xc000 0000 |
Stack(用戶棧) |
ESP指向棧頂 |
|
↓
↑ |
空閒內存 |
>=0x4000 0000 |
文件映射區 |
|
<0x4000 0000 |
↑ |
空閒內存
|
|
Heap(運行時堆) |
通過brk/sbrk系統調用擴大堆,向上增長。 |
|
.data、.bss(讀寫段) |
從可執行文件中加載 |
>=0x0804 8000 |
.init、.text、.rodata(只讀段) |
從可執行文件中加載 |
<0x0804 8000 |
保留區域 |
|
很多書上都有類似的描述,本圖取自於《深入理解計算機系統》p603,略做修改。本圖比較清析,很容易理解,但仍然有兩點不足。下面補充說明一下:
1. 第一點是關於運行時堆的。
爲說明這個問題,我們先運行一個測試程序,並觀察其結果:
#include <stdio.h>
int main(int argc, 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; }
|
運行後,輸出結果爲:
main=0x8048404 print=0x8048324
first=0xbfcd1264
p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008
l main和print兩個函數是代碼段(.text)的,其地址符合表一的描述。
l first是第一個臨時變量,由於在first之前還有一些環境變量,它的值並非0xbfffffff,而是0xbfcd1264,這是正常的。
l p0是在堆中分配的,其地址小於0x4000 0000,這也是正常的。
l 但p1和p2也是在堆中分配的,而其地址竟大於0x4000 0000,與表一描述不符。
原因在於:運行時堆的位置與內存管理算法相關,也就是與malloc的實現相關。關於內存管理算法的問題,我們在後繼文章中有詳細描述,這裏只作簡要說明。在glibc實現的內存管理算法中,Malloc小塊內存是在小於0x4000 0000的內存中分配的,通過brk/sbrk不斷向上擴展,而分配大塊內存,malloc直接通過系統調用mmap實現,分配得到的地址在文件映射區,所以其地址大於0x4000 0000。
從maps文件中可以清楚的看到一點:
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. 第二是關於多線程的。
現在的應用程序,多線程的居多。表一所描述的模型無法適用於多線程環境。按表一所述,程序最多擁有上G的棧空間,事實上,在多線程情況下,能用的棧空間是非常有限的。爲了說明這個問題,我們再看另外一個測試:
#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 int main(int argc, char* argv[]) { int first = 0; int i= 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; }
|
運行後,輸出結果爲:
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
我們看一下:
主線程與第一個線程的堆之間的距離:0xbfd3d35c - 0xb7f2c454=0x7e10f08=126M
第一個線程與第二個線程的堆之間的距離:0xb7f2c454 - 0xb752b454=0xa01000=10M
其它幾個線程間的距離均爲10M。
也就是說,主線程的堆空間最大爲126M,而普通線程的棧空間僅爲10M,超這個範圍就會造成棧溢出。
棧溢出的後果是比較嚴重的,或者出現Segmentation fault錯誤,或者出現莫名其妙的錯誤。
轉自:http://dev.csdn.net/author/absurd/6360b845596c434ca9246c7fead47830.html