读取当前linux进程内存_(笔记)Linux上的内存分配
作者: LemonNan
原文: https://juejin.im/post/5ee3c34a518825430c3ad31d
前言
本篇是對Linux內(nèi)存分配的一個學(xué)習(xí)筆記.
程序內(nèi)存結(jié)構(gòu)
下面是在 Linux/x86-32 中典型的一個進程內(nèi)存結(jié)構(gòu)
文本段包含了進程運行的程序機器語言指令. 文本段具有只讀屬性, 以防止進程通過勘誤指針意外修改自身指令. 因為多個進程可以同時運行同一程序, 所以又將 文本段設(shè)為可共享 , 這樣一份程序代碼的拷貝可以映射到所有這些進程到虛擬地址空間中.
初始化數(shù)據(jù)段包含顯示初始化的全局變量和靜態(tài)變量. 程序加載到內(nèi)存時, 從可執(zhí)行文件中讀取這些變量的值.
未初始化數(shù)據(jù)段包含了未進行顯示初始化的全局變量和靜態(tài)變量 . (書上寫了一堆, 實際上就是懶加載)
棧(stack)是一個動態(tài)增長和收縮的段, 由棧幀(stack frames)組成 . 系統(tǒng)會為每個當(dāng)前調(diào)用的函數(shù)分配一個棧幀.棧幀中存儲局部變量(所謂自動變量), 實參和返回值.
堆(heap)時刻在運行時(為變量)動態(tài)進行內(nèi)存分配的一塊區(qū)域, 堆頂端稱做 program break .
3g(32位)以上的虛擬內(nèi)存地址, 程序無法訪問.
程序的起始地址為 0x08048000 (32位)、0x00400000 (64位)
malloc 和 free
malloc
void *malloc(size_t size);棧向下增長超出之前曾達到的位置
擋在堆中分配或者釋放內(nèi)存時, 通過調(diào)用 brk()、sbrk() 或 malloc 函數(shù)族來提升 program break 的位置.
調(diào)用 shmat() 連接 System V 共享內(nèi)存區(qū)或者當(dāng)調(diào)用 shmdt() 脫離共享內(nèi)存區(qū)時.
當(dāng)調(diào)用 mmap() 創(chuàng)建內(nèi)存映射或者 munmap() 解除內(nèi)存映射
特點
malloc() 返回的內(nèi)存快所采用的字節(jié)對齊的方式, 在大多數(shù)的硬件架構(gòu)上, 意味著 malloc 是基于 8字節(jié) 或者 16字節(jié) 邊界來分配內(nèi)存的.
malloc 之后的內(nèi)存, 在不使用的需要需要手動 free, malloc 和 free 一一對應(yīng), 否則可能會導(dǎo)致 未知錯誤(多次free) 或者 內(nèi)存泄漏(沒有調(diào)用free).
允許分配小塊內(nèi)存
允許隨意釋放內(nèi)存快, 它們被維護于一張空閑內(nèi)存列表中, 在后續(xù)內(nèi)存分配調(diào)用時循環(huán)使用
free
free() 函數(shù)釋放 ptr 參數(shù)所指向的內(nèi)存快
void free(void *ptr);特點
free 并不降低 program break 的位置, 而是將這塊內(nèi)存添加到空閑內(nèi)存列表, 供后續(xù)的 malloc() 函數(shù)循環(huán)使用.這么做有幾個原因:
被釋放的內(nèi)存快通常位于堆的中間, 而非堆堆頂部, 因而降低 program break 不能達到效果
最大限度減少程序必須執(zhí)行 sbrk() 調(diào)用次數(shù)(減少系統(tǒng)調(diào)用的開銷)
free 傳入空指針不會做任何處理(從設(shè)計上來說這不是錯誤代碼)
調(diào)用 free 后堆參數(shù) ptr 的使用, 比如再次調(diào)用 free, 會產(chǎn)生錯誤并且可能導(dǎo)致不可預(yù)知的結(jié)果.
為什么是8/16字節(jié)對齊
CPU 讀取8字節(jié)對齊, 比如 double/long, 不對齊的話需要讀寫2次
CPU高速緩存行大小通常是 32 或者 64 字節(jié). 如果對象是8字節(jié)對齊的數(shù)據(jù), 則只需要占用一個緩存行, 如果不是8字節(jié)對齊的話, 則可能一部分?jǐn)?shù)據(jù)在一個緩存行, 另一部分?jǐn)?shù)據(jù)在其它的緩存行, 所以讀寫這個數(shù)據(jù)需要用到2個緩存行的數(shù)據(jù)而不是一個, 所有(目前1、2、3)級別的緩存都會受到此影響.
對于在磁盤中的數(shù)據(jù), 都是以512字節(jié)為最低的單位(一個扇區(qū)的數(shù)據(jù)大小), 如果是8字節(jié)對齊的話, 則數(shù)據(jù)會被存放在一個扇區(qū)里, 可以只通過一次讀取將數(shù)據(jù)都讀取出來, 如果數(shù)據(jù)不是8字節(jié)對齊, 則 數(shù)據(jù)可能會被存放到不同的扇區(qū)中, 并且還有可能不是相鄰的扇區(qū), 這就會 導(dǎo)致隨機I/O , 降低數(shù)據(jù)處理的效率, 消耗更多的硬件資源. 對于上層來說, 數(shù)據(jù)是相連的(邏輯), 但是對于底層的物理硬件來說, 數(shù)據(jù)很有可能位于不相鄰的扇區(qū)(數(shù)據(jù)處理最小單元).
so, 總結(jié)下來就是, 非對齊的數(shù)據(jù)訪問 會因為增加硬件訪問次數(shù)?比對齊的數(shù)據(jù)訪問效率低.
說起緩存行, Java中有一些框架(比如Disruptor)考慮到了不同的CPU架構(gòu), 使用了CPU支持的緩存行填充, 以防止 偽共享(這里暫不做過多描述)?的發(fā)生從而降低效率.
通過 sysctl -a 查看
# 我的電腦中的數(shù)據(jù)hw.cachelinesize: 64hw.l1icachesize: 32768hw.l1dcachesize: 32768hw.l2cachesize: 262144hw.l3cachesize: 3145728虛擬內(nèi)存管理
內(nèi)核為每一個進程都維護一張頁表(page table) , 頁表中的每個條目要么指出一個虛擬頁面在 RAM 中的所在位置, 要么表明其當(dāng)前駐留在磁盤上, 若進程訪問的地址并無頁表條目與之對應(yīng), 進程將會收到一個 SIGSEGV 信號.
Q: 虛擬頁面的數(shù)據(jù)為什么會在磁盤上?
A: 每個程序中只有一部分 page 會駐留在 物理內(nèi)存(RAM) 中, 未使用的 page 會被拷貝保存到交換區(qū)(swap area)內(nèi), 這是磁盤空間中的保留區(qū)域, 作為 RAM 的補充, 只有在需要的時候才會載入 物理內(nèi)存.
進程在讀取的時候, 如果訪問的頁面沒有駐留在物理內(nèi)存中, 將會發(fā)生頁面錯誤(page fault), 內(nèi)核即刻掛起的執(zhí)行, 同時從磁盤中將該頁面載入內(nèi)存.
在 x86-32 中, page size 為 4096 字節(jié)(4KB), 一些其它的Linux使用的頁面比 4096 字節(jié)更大.
Alpha 使用的 page size = 8192 字節(jié)(8KB), IA-64 的page size是可以改變的, 默認(rèn)為 16384 字節(jié).程序通過調(diào)用 sysconf(_SC_PAGESIZE) 獲取系統(tǒng)虛擬內(nèi)存的 page size.
虛擬內(nèi)存的實現(xiàn)需要硬件中分頁內(nèi)存管理單元(PMMU)的支持, PMMU 把要訪問的每個虛擬內(nèi)存地址轉(zhuǎn)換成相應(yīng)的物理內(nèi)存地址, 當(dāng)特定虛擬內(nèi)存地址所對應(yīng)的頁沒有駐留于 RAM 中時, 將以頁面錯誤(page fault)通知內(nèi)核.
有效虛擬內(nèi)存范圍
由于 內(nèi)核能為進程分配和釋放頁(和頁表條目) , 所以進程的有效虛擬地址范圍在其生命周期中可以發(fā)生變化. 如下場景會導(dǎo)致范圍變化:
棧向下增長超出之前曾達到的位置
擋在堆中分配或者釋放內(nèi)存時, 通過調(diào)用 brk()、sbrk() 或 malloc 函數(shù)族來提升 program break 的位置.
調(diào)用 shmat() 連接 System V 共享內(nèi)存區(qū)或者當(dāng)調(diào)用 shmdt() 脫離共享內(nèi)存區(qū)時.
當(dāng)調(diào)用 mmap() 創(chuàng)建內(nèi)存映射或者 munmap() 解除內(nèi)存映射
局部性原理
在計算機中大多數(shù)程序都有一個共同特點, 訪問局部性 .
訪問局部性包含兩方面:
空間局部性: 程序傾向于訪問在最近訪問過的內(nèi)存地址附近的內(nèi)存(由于指令是順序執(zhí)行的, 并且有時會按順序處理數(shù)據(jù)結(jié)構(gòu))
時間局部性: 這意味著數(shù)據(jù)被訪問到, 在之后較短的時間內(nèi)會被再次訪問到(可能是由于循環(huán))
優(yōu)點
虛擬內(nèi)存使得進程的虛擬地址空間和RAM的物理地址空間隔離開, 有以下一些好處
進程與進程、進程與內(nèi)核相互隔離, 所以一個進程不能讀取其它進程或內(nèi)核的內(nèi)存, 因為每個進程的頁表條目指向截然不同的物理內(nèi)存地址.
適當(dāng)情況下, 多個進程鞥狗共享內(nèi)存. 因為不同的進程頁表條目可以指向相同的物理內(nèi)存(RAM)地址.通常發(fā)生在如下的場景:
執(zhí)行同一程序的多個進程, 共享一份程序代碼副本. 當(dāng)多個進程執(zhí)行相同的程序文件(或加載相同的共享庫), 會隱式實現(xiàn)這一類型的共享.
進程通過 shmget() 和 mmap() 系統(tǒng)調(diào)用顯示請求與其它進程共享內(nèi)存, 這樣的目的是為了進程間的通信.
實現(xiàn)保護機制: 相同的內(nèi)存, 不同的進程可以設(shè)置不同的訪問權(quán)限, 某些進程只讀、某些擁有所有權(quán)限等.
因為需要駐留在內(nèi)存中的僅是程序的一部分, 所以程序的加載和運行都變快了, 而且一個程序所占用的大小(虛擬內(nèi)存) 能夠超出 RAM 容量.(因為有的事通過虛擬內(nèi)存管理存放到了磁盤上)
一個進程所使用的RAM減少了, RAM中同時可容納的進程數(shù)量增多. 這樣的話加大了在任一時刻CPU可執(zhí)行至少一個進程的概率, 這樣往往也會提高CPU的利用率.
總結(jié)
以上是生活随笔為你收集整理的读取当前linux进程内存_(笔记)Linux上的内存分配的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 键盘无响应-如何修复键盘注册表
- 下一篇: 搜狗输入法在idea打不了汉字_IDEA