linux程序記憶體佈局
在多工作業系統中的每一個程序都執行在一個屬於它自己的記憶體沙盤中。這個沙盤就是虛擬地址空間(virtual address space),在32位模式下它總是一個4GB的記憶體地址塊。這些虛擬地址通過頁表(page table)對映到實體記憶體,頁表由作業系統維護並被處理器引用。每一個程序擁有一套屬於它自己的頁表,但是還有一個隱情。只要虛擬地址被使能,那麼它就會作用於這臺機器上執行的所有軟體,包括核心本身。因此一部分虛擬地址必須保留給核心使用:
這並不意味著核心使用了那麼多的實體記憶體,僅表示它可支配這麼大的地址空間,可根據核心需要,將其對映到實體記憶體。核心空間在頁表中擁有較高的特權級(ring 2或以下),因此只要使用者態的程式試圖訪問這些頁,就會導致一個頁錯誤(page fault)。在Linux中,核心空間是持續存在的,並且在所有程序中都對映到同樣的實體記憶體。核心程式碼和資料總是可定址的,隨時準備處理中斷和系統呼叫。與此相反,使用者模式地址空間的對映隨程序切換的發生而不斷變化:
色區域表示對映到實體記憶體的虛擬地址,而白色區域表示未對映的部分。在上面的例子中,Firefox使用了相當多的虛擬地址空間,因為它是傳說中的吃記憶體大戶。地址空間中的各個條帶對應於不同的記憶體段(memory segment),如:堆、棧之類的。記住,這些段只是簡單的記憶體地址範圍,與Intel處理器的段沒有關係。不管怎樣,下面是一個Linux程序的標準的記憶體段佈局:
當計算機開心、安全、可愛、正常的運轉時,幾乎每一個程序的各個段的起始虛擬地址都與上圖完全一致,這也給遠端發掘程式安全漏洞打開了方便之門。一個發掘過程往往需要引用絕對記憶體地址:棧地址,庫函式地址等。遠端攻擊者必須依賴地址空間佈局的一致性,摸索著選擇這些地址。如果讓他們猜個正著,有人就會被整了。因此,地址空間的隨機排布方式逐漸流行起來。Linux 通過對
程序地址空間中最頂部的段是棧,大多數程式語言將之用於儲存區域性變數和函式引數。呼叫一個方法或函式會將一個新的棧楨(stack frame)壓入棧中。棧楨在函式返回時被清理。也許是因為資料嚴格的遵從LIFO的順序,這個簡單的設計意味著不必使用複雜的資料結構來追蹤棧的內容,只需要一個簡單的指標指向棧的頂端即可。因此壓棧(pushing)和退棧(popping)過程非常迅速、準確。另外,持續的重用棧空間有助於使活躍的棧記憶體保持在CPU快取中,從而加速訪問。程序中的每一個執行緒都有屬於自己的棧。
通過不斷向棧中壓入的資料,超出其容量就有會耗盡棧所對應的記憶體區域。這將觸發一個頁故障(page fault),並被 Linux 的
動態棧增長是唯一一種訪問未對映記憶體區域(圖中白色區域)而被允許的情形。其它任何對未對映記憶體區域的訪問都會觸發頁故障,從而導致段錯誤。一些被對映的區域是隻讀的,因此企圖寫這些區域也會導致段錯誤。
在棧的下方,是我們的記憶體對映段。此處,核心將檔案的內容直接對映到記憶體。任何應用程式都可以通過 Linux 的
mmap() 系統呼叫(實現)或 Windows 的
CreateFileMapping()/MapViewOfFile()請求這種對映。記憶體對映是一種方便高效的檔案 I/O 方式,所以它被用於載入動態庫。建立一個不對應於任何檔案的匿名記憶體對映也是可能的,此方法用於存放程式的資料。在 Linux 中,如果你通過
malloc()請求一大塊記憶體,C 執行庫將會建立這樣一個匿名對映而不是使用堆記憶體。‘大塊’意味著比MMAP_THRESHOLD 還大,預設是 128KB ,可以通過mallopt()調整。
說到堆,它是接下來的一塊地址空間。與棧一樣,堆用於執行時記憶體分配;但不同點是,堆用於儲存那些生存期與函式呼叫無關的資料。大部分語言都提供了堆管理功能。因此,滿足記憶體請求就成了語言執行時庫及核心共同的任務。在 C 語言中,堆分配的介面是malloc()系列函式,而在具有垃圾收集功能的語言(如 C# )中,此介面是 new
關鍵字。
如果堆中有足夠的空間來滿足記憶體請求,它就可以被語言執行時庫處理而不需要核心參與。否則,堆會被擴大,通過brk()系統呼叫(實現)來分配請求所需的記憶體塊。堆管理是很複雜的,需要精細的演算法,應付我們程式中雜亂的分配模式,優化速度和記憶體使用效率。處理一個堆請求所需的時間會大幅度的變動。實時系統通過特殊目的分配器來解決這個問題。堆也可能會變得零零碎碎,如下圖所示:
最後,我們來看看最底部的記憶體段:BSS,資料段,程式碼段。在C語言中,BSS和資料段儲存的都是靜態(全域性)變數的內容。區別在於BSS儲存的是未被初始化的靜態變數內容,它們的值不是直接在程式的原始碼中設定的。BSS記憶體區域是匿名的:它不對映到任何檔案。如果你寫static int cntActiveUsers,則cntActiveUsers的內容就會儲存在BSS中。
另一方面,資料段儲存在原始碼中已經初始化了的靜態變數內容。這個記憶體區域不是匿名的。它映射了一部分的程式二進位制映象,也就是原始碼中指定了初始值的靜態變數。所以,如果你寫static int cntWorkerBees = 10,則cntWorkerBees的內容就儲存在資料段中了,而且初始值為10。儘管資料段映射了一個檔案,但它是一個私有記憶體對映,這意味著更改此處的記憶體不會影響到被對映的檔案。也必須如此,否則給全域性變數賦值將會改動你硬碟上的二進位制映象,這是不可想象的。
下圖中資料段的例子更加複雜,因為它用了一個指標。在此情況下,指標gonzo(4位元組記憶體地址)本身的值儲存在資料段中。而它所指向的實際字串則不在這裡。這個字串儲存在程式碼段中,程式碼段是隻讀的,儲存了你全部的程式碼外加零零碎碎的東西,比如字串字面值。程式碼段將你的二進位制檔案也對映到了記憶體中,但對此區域的寫操作都會使你的程式收到段錯誤。這有助於防範指標錯誤,雖然不像在C語言程式設計時就注意防範來得那麼有效。下圖展示了這些段以及我們例子中的變數:
你可以通過閱讀檔案/proc/pid_of_process/maps來檢驗一個Linux程序中的記憶體區域。記住一個段可能包含許多區域。比如,每個記憶體對映檔案在mmap段中都有屬於自己的區域,動態庫擁有類似BSS和資料段的額外區域。下一篇文章講說明這些“區域”(area)的真正含義。有時人們提到“資料段”,指的就是全部的資料段 + BSS + 堆。
你可以通過nm和objdump命令來察看二進位制映象,列印其中的符號,它們的地址,段等資訊。最後需要指出的是,前文描述的虛擬地址佈局在Linux
中是一種“靈活佈局”(flexible layout),而且以此作為預設方式已經有些年頭了。它假設我們有值 RLIMIT_STACK。當情況不是這樣時, Linux 退回使用“經典佈局”(classic layout),如下圖所示:
對虛擬地址空間的佈局就講這些吧。下一篇文章將討論核心是如何跟蹤這些記憶體區域的。我們會分析記憶體對映,看看檔案的讀寫操作是如何與之關聯的,以及記憶體使用概況的含義。
記憶體管理是作業系統的核心之一,最近在研究核心的記憶體管理以及C
執行時庫對記憶體的分配和管理,涉及到程序在記憶體的佈局,在此對程序的記憶體佈局做一下總結:
1. 32 位模式下的 linux 記憶體佈局
圖上的各個部分描述得比較清楚,不需再做過多的描述。從上圖可以看到,棧至頂向下擴充套件,並且棧是有界的。堆至底向上擴充套件,mmap 對映區域至頂向下擴充套件,mmap 對映區域和堆相對擴充套件,直至耗盡虛擬地址空間中的剩餘區域,這種結構便於C 執行時庫使用mmap 對映區域和堆進行記憶體分配。上圖的佈局形式是在核心2.6.7 以後才引入的,這是32 位模式下的預設記憶體佈局形式。看看cat 命令在2.6.36 上記憶體佈局:
08048000-08051000 r-xp 00000000 08:01 786454/bin/cat
08051000-08052000 r--p 00008000 08:01 786454/bin/cat
08052000-08053000 rw-p 00009000 08:01 786454/bin/cat
08053000-08074000 rw-p 00000000 00:00 0[heap]
b73e3000-b75e3000 r--p 00000000 08:01 400578/usr/lib/locale/locale-archive
b75e3000-b75e4000 rw-p 00000000 00:00 0
b75e4000-b773b000 r-xp 00000000 08:01 1053967/lib/libc-2.12.1.so
b773b000-b773c000 ---p 00157000 08:01 1053967/lib/libc-2.12.1.so
b773c000-b773e000 r--p 00157000 08:01 1053967/lib/libc-2.12.1.so
b773e000-b773f000 rw-p 00159000 08:01 1053967/lib/libc-2.12.1.so
b773f000-b7742000 rw-p 00000000 00:00 0
b774f000-b7750000 r--p 002a1000 08:01 400578/usr/lib/locale/locale-archive
b7750000-b7752000 rw-p 00000000 00:00 0
b7752000-b7753000 r-xp 00000000 00:00 0[vdso]
b7753000-b776f000 r-xp 00000000 08:01 1049013/lib/ld-2.12.1.so
b776f000-b7770000 r--p 0001b000 08:01 1049013/lib/ld-2.12.1.so
b7770000-b7771000 rw-p 0001c000 08:01 1049013/lib/ld-2.12.1.so
bfbed000-bfc0e000 rw-p 00000000 00:00 0[stack]
可以看到,棧和mmap 對映區域並不是從一個固定地址開始,並且每次的值都不一樣,這是程式在啟動時隨機改變這些值的設定,使得使用緩衝區溢位進行攻擊更加困難。當然也可以讓程式的棧和mmap 對映區域從一個固定位置開始,只需要設定全域性變數randomize_v a_space 值為 0 ,這個變數預設值為 1 。使用者可以通過設定 /proc/sys/kernel/randomize_va_space 來停用該特性,也可以用如下命令:
sudo sysctl -w kernel.randomize_va_space=0
設定randomize_va_space 為 0 後,再看看 cat 的記憶體佈局:
08048000-08051000 r-xp 00000000 08:01 786454/bin/cat
08051000-08052000 r--p 00008000 08:01 786454/bin/cat
08052000-08053000 rw-p 00009000 08:01 786454/bin/cat
08053000-08074000 rw-p 00000000 00:00 0[heap]
b7c72000-b7e72000 r--p 00000000 08:01 400578/usr/lib/locale/locale-archive
b7e72000-b7e73000 rw-p 00000000 00:00 0
b7e73000-b7fca000 r-xp 00000000 08:01 1053967/lib/libc-2.12.1.so
b7fca000-b7fcb000 ---p 00157000 08:01 1053967/lib/libc-2.12.1.so
b7fcb000-b7fcd000 r--p 00157000 08:01 1053967/lib/libc-2.12.1.so
b7fcd000-b7fce000 rw-p 00159000 08:01 1053967/lib/libc-2.12.1.so
b7fce000-b7fd1000 rw-p 00000000 00:00 0
b7fde000-b7fdf000 r--p 002a1000 08:01 400578/usr/lib/locale/locale-archive
b7fdf000-b7fe1000 rw-p 00000000 00:00 0
b7fe1000-b7fe2000 r-xp 00000000 00:00 0[vdso]
b7fe2000-b7ffe000 r-xp 00000000 08:01 1049013/lib/ld-2.12.1.so
b7ffe000-b7fff000 r--p 0001b000 08:01 1049013/lib/ld-2.12.1.so
b7fff000-b8000000 rw-p 0001c000 08:01 1049013/lib/ld-2.12.1.so
bffdf000-c0000000 rw-p 00000000 00:00 0[stack]
可以看出,棧和mmap 區域都從固定位置開始了,stack 的起始位置為0x c0000000, mmap區域的起始位置為 0x b8000000 ,可見系統為 stack 區域保留了 128M 記憶體地址空間。
在某些情況下,設定randomize_va_space 為 0 ,便於對系統做一些針對性的研究,例如:程序的記憶體對映有個叫 vdso的區域,也就是用 ldd 命令看到的那個” linux-gate.so.1 “,這塊區域可以看成是核心用於實現 vsyscall 而建立的 virtual shared object ,遵循 elf 的格式,並且可以被使用者程式訪問。在設定 randomize_va_space 為 0 的情況下,使用如下命令就可以把這個區域 dump 出來看過究竟。如果不設定 randomize_va_space ,每次 vdso 的地址都是隨機的,下面的命令也無能為力。
[email protected]:~$ dd if=/proc/self/mem of=gate.so bs=4096 skip=$[0xb7fe1] count=1
dd: `/proc/self/mem': cannot skip to specified offset
1+0 records in
1+0 records out
4096 bytes (4.1 kB) copied, 0.00144225 s, 2.8 MB/s
[email protected]:~$ objdump -d gate.so
gate.so: file format elf32-i386
Disassembly of section .text:
ffffe400 <__kernel_sigreturn>:
ffffe400: 58 pop%eax
ffffe401: b8 77 00 00 00 mov$0x77,%eax
ffffe406: cd 80 int$0x80
ffffe408: 90 nop
ffffe409: 8d 76 00 lea0x0(%esi),%esi
ffffe40c <__kernel_rt_sigreturn>:
ffffe40c: b8 ad 00 00 00 mov$0xad,%eax
ffffe411: cd 80 int$0x80
ffffe413: 90 nop
ffffe414 <__kernel_vsyscall>:
ffffe414: 51 push%ecx
ffffe415: 52 push%edx
ffffe416: 55 push%ebp
ffffe417: 89 e5 mov%esp,%ebp
ffffe419: 0f 34 sysenter
ffffe41b: 90 nop
ffffe41c: 90 nop
ffffe41d: 90 nop
ffffe41e: 90 nop
ffffe41f: 90 nop
ffffe420: 90 nop
ffffe421: 90 nop
ffffe422: eb f3 jmpffffe417 <__kernel_vsyscall+0x3>
ffffe424: 5d pop%ebp
ffffe425: 5a pop%edx
ffffe426: 59 pop%ecx
ffffe427: c3 ret
2. 32 為模式下的經典佈局:
這種佈局mmap 區域與棧區域相對增長,這意味著堆只有1GB 的虛擬地址空間可以使用,繼續增長就會進入mmap 對映區域,這顯然不是我們想要的。這是由於32 模式地址空間限制造成的,所以核心引入了前一種虛擬地址空間的佈局形式。但是對 64 位模式,提供了巨大的虛擬地址空間,這個佈局就相當好。如果要在 2.6.7 以後的核心上使用 32 位模式記憶體經典佈局,有兩種辦法可以設定:
方法一:sudo sysctl -w vm.legacy_va_layout=1
方法二:ulimit -s unlimited
同時設定randomize_va_space 為 0 後, cat 的記憶體佈局已經回到經典形式了:
08048000-08051000 r-xp 00000000 08:01 786454/bin/cat
08051000-08052000 r--p 00008000 08:01 786454/bin/cat
08052000-08053000 rw-p 00009000 08:01 786454/bin/cat
08053000-08074000 rw-p 00000000 00:00 0[heap]
40000000-4001c000 r-xp 00000000 08:01 1049013/lib/ld-2.12.1.so
4001c000-4001d000 r--p 0001b000 08:01 1049013/lib/ld-2.12.1.so
4001d000-4001e000 rw-p 0001c000 08:01 1049013/lib/ld-2.12.1.so
4001e000-4001f000 r-xp 00000000 00:00 0[vdso]
4001f000-40021000 rw-p 00000000 00:00 0
40021000-40022000 r--p 002a1000 08:01 400578/usr/lib/locale/locale-archive
4002f000-40186000 r-xp 00000000 08:01 1053967/lib/libc-2.12.1.so
40186000-40187000 ---p 00157000 08:01 1053967/lib/libc-2.12.1.so
40187000-40189000 r--p 00157000 08:01 1053967/lib/libc-2.12.1.so
40189000-4018a000 rw-p 00159000 08:01 1053967/lib/libc-2.12.1.so
4018a000-4018e000 rw-p 00000000 00:00 0
4018e000-4038e000 r--p 00000000 08:01 400578/usr/lib/locale/locale-archive
bffdf000-c0000000 rw-p 00000000 00:00 0[stack]
3. 64 位模式下的記憶體佈局
在64 位模式下各個區域的起始位置是什麼呢?對於AMD64 ,記憶體佈局採用的是經典模式, text 的起始地址為 0x0000000000400000 ,堆緊接著 BSS 段向上增長, mmap 對映區域開始位置一般設為 TASK_SIZE/3 ,
#define TASK_SIZE_MAX((1UL << 47) - PAGE_SIZE)
#define TASK_SIZE(test_thread_flag(TIF_IA32) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)
#define STACK_TOPTASK_SIZE
#define TASK_UNMAPPED_BASE(PAGE_ALIGN(TASK_SIZE / 3))
計算一下可知,mmap 的開始區域地址為 0x0000 2AAAAAAAA000,棧頂地址為 0x00007FFFFFFFF000
相關推薦
linux程序記憶體佈局
在多工作業系統中的每一個程序都執行在一個屬於它自己的記憶體沙盤中。這個沙盤就是虛擬地址空間(virtual address space),在32位模式下它總是一個4GB的記憶體地址塊。這些虛擬地址通過頁表(page table)對映到實體記憶體,頁表由作業系統維護並被處理
Linux程序地址空間 程序記憶體佈局
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
Linux程序地址空間 && 程序記憶體佈局
轉載自:https://blog.csdn.net/yusiguyuan/article/details/45155035 一 程序空間分佈概述 對於一個程序,其空間分佈如下圖所示:
Linux程序地址空間 && 程序記憶體佈局
一 程序空間分佈概述 對於一個程序,其空間分佈如下圖所示: 程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。 初始化過的資料(Data):在程式執
Linux程序地址空間 程序記憶體佈局
一 程序空間分佈概述 對於一個程序,其空間分佈如下圖所示: 程式段(Text):程式程式碼在記憶體中的對映,存放函式體的二進位制程式碼。初始化過的資料(Data):在程式執行初已經對變數進行初始化的資
檢視LINUX程序記憶體佔用情況
可以直接使用top命令後,檢視%MEM的內容。可以選擇按程序檢視或者按使用者檢視,如想檢視oracle使用者的程序記憶體使用情況的話可以使用如下的命令: (1)top top命令是Linux下常用的效能分析工具,能夠實時顯示系統中各個程序的資源佔用狀況,類似於W
Linux 程序記憶體空間分為那幾段?作用分別是什麼
Linux的記憶體空間簡單可以分為5個部分: Text(程式碼區):存放可執行的指令操作,其只讀不能寫 Bss(靜態區or全域性區):存放未初始化的全域性變數和靜態變數 &nbs
一個由程序記憶體佈局異常引起的問題
前段時間業務反映某類伺服器上更新了 bash 之後,ssh 連上去偶發登陸失敗,客戶端吐出錯誤資訊如下所示: 該版本 bash 為部門這邊所定製,但實現上並沒有改動原有邏輯,只是加入了些監控功能,那麼這些錯誤從哪裡來? 是 bash 的鍋嗎 從上面的錯誤資訊可以猜測,異常是 bash 在啟動過程中分配記憶
Linux程序記憶體空間分為幾段,各有什麼作用
linux程序記憶體空間分為幾段,各有什麼作用 Linux程序可分為五部分: Text(程式碼區):存放可執行的指令操作,只能讀不能寫 全域性區:存放未初始
檢視LINUX程序記憶體佔用情況(轉)
可以直接使用top命令後,檢視%MEM的內容。可以選擇按程序檢視或者按使用者檢視,如想檢視oracle使用者的程序記憶體使用情況的話可以使用如下的命令: (1)top top命令是Linux下常用的效能分析工具,能夠實時顯示系統中各個程序的資源佔用狀況,類似於Windows的工作管理員 可
檢視LINUX程序記憶體佔用情況 top pmap ps
可以直接使用top命令後,檢視%MEM的內容。可以選擇按程序檢視或者按使用者檢視,如想檢視oracle使用者的程序記憶體使用情況的話可以使用如下的命令: (1)top top命令是Linux下常用的效能分析工具,能夠實時顯示系統中各個程序的資源佔用狀況,類似於W
linux 程序記憶體佔用高分析
通過free命令可以檢視系統記憶體使用情況: free -m total used free shared buff/cache available Mem: 79
linux--程序在記憶體中的佈局
先從(Linux平臺下)虛擬記憶體管理說起, 寫C程式時,我們經常會列印一個指標地址,說這個指標指向某某記憶體地址.可這些地址是真實實體記憶體地址嗎?不是!這些只是虛擬記憶體地址. 當一個C程式調入記憶體開始執行後,在記憶體中就會產生一個程序.而在多工作業系統中每個
Linux-程序通訊-訊息佇列/訊號燈/共享記憶體
訊息佇列 訊息佇列提供了程序間傳送資料塊的方法,每個資料塊都可以被認為是有一個型別,接受者接受的資料塊可以有不同的型別;我們可以通過傳送訊息來避免命名管道的同步和阻塞問題;訊息佇列與命名管道一樣,每個資料塊都有一個最大長度的限制;我們可以將每個資料塊當作是一
c/c++ linux 程序間通訊系列4,使用共享記憶體
linux 程序間通訊系列4,使用共享記憶體 1,建立共享記憶體,用到的函式shmget, shmat, shmdt 函式名 功能描述 shmget 建立共享記憶體,返回pic key
linux 程序佔用記憶體詳解
想必在linux上寫過程式的同學都有分析程序佔用多少記憶體的經歷,或者被問到這樣的問題——你的程式在執行時佔用了多少記憶體(實體記憶體)?通常我們可以通過top命令檢視程序佔用了多少記憶體。這裡我們可以看到VIRT、RES和SHR三個重要的指標,他們分別代表什麼意思呢?這是本文需要跟大家一
Linux 程序通訊之:記憶體共享(Shared Memory)
一、簡介 共享記憶體允許兩個程序訪問同一塊記憶體區域,它們使用同一個 key 值標記。 二、特點 優點: 通訊方便,兩個程序也是直接訪問同一塊記憶體區域,減少了資料複製的操作,速度上也有明顯優勢。 缺點: 沒有提供同步機制,往往需要我們使用其它(例如訊號)等手段實
Linux 程序通訊之:記憶體對映(Memory Map)
一、簡介 正如其名(Memory Map),mmap 可以將某個裝置或者檔案對映到應用程序的記憶體空間中。通過直接的記憶體操作即可完成對裝置或檔案的讀寫。. 通過對映同一塊實體記憶體,來實現共享記憶體,完成程序間的通訊。由於減少了資料複製的次數,一定程度上提高了程序間通訊的效率。
Linux效能優化 第五章 效能工具:特定程序記憶體
5.1 Linux記憶體子系統 在診斷記憶體效能問題的時候,也許有必要觀察應用程式在記憶體子系統的不同層次上是怎樣執行的。在頂層,作業系統決定如何利用交換記憶體和實體記憶體。它決定應用程式的哪一塊地址空間將被放到實體記憶體中
Linux查詢記憶體或CPU佔用最多的幾個程序
一、可以使用以下命令查使用記憶體最多的10個程序 方法1: ps -aux | sort -k4nr | head -10 如果是最高的三個,10改為3即可 命令解釋: 1. ps:引數a指代all——所有的程序,u指代userid——執行該程序的使用者id