RAM 的某些部分永久地分配給內核, 并用來存放內核代碼以及靜態內核數據結構. RAM 的其余部分稱為動態內存 (dynamic memory). 動態內存不僅是進程所需的寶貴資源, 也是內核本身所需的寶貴資源. 實際上,整個系統的性能取決于如何有效地管理動態內存. 因此, 現在所有多任務操作系統都在盡力優化對動態內存的使用, 也就是說, 盡可能做到當需要時分配, 不需要時釋放.

當給內核分配動態內存時, 是相對容易的, 有如下兩點原因:
內核是操作系統中優先級最高的成分. 如果某個內核函數請求動態內存, 那么, 必定有正當的理由發出這個請求, 因此, 沒有道理試圖推遲這個請求.
內核信任自己. 所有的內核函數都被假定是沒有錯誤的, 因此內核函數不必針對程序錯誤施加任何保護措施.
而當給用戶態進程分配內存時, 情況完全不同:
進程對動態內存的請求被認為是不緊急的. 例如, 當進程對應在磁盤上所存儲的可執行文件被裝入內存時, 進程并不一定會立即對所有的代碼和數據進行訪問. 類似地, 當進程調用malloc()以請求獲得額外的動態內存時, 也并不意味著進程很快就會訪問所獲得的額外的動態內存.因此, 一般來說, 內核總是盡量推遲給用戶態進程分配動態內存.
由于用戶進程是不可信任的, 因此, 內核必須能隨時準備捕獲用戶態進程引起的所有尋址錯誤.
為了使得動態內存得到最大限度的使用, 內核使用一種新的資源成功實現了對進程動態內存的推遲分配. 當用戶態進程請求動態內存時, 并沒有獲得請求的動態內存, 而僅僅得到了對一個新的線性地址區間的使用權, 這樣的線性地址區間有很多, 由允許進程使用的全部線性地址區間所組成的集合就叫做進程地址空間.
與進程地址空間有關的全部信息都包含在一個叫做內存描述符的數據結構中 (實際上就是描述進程虛擬內存的數據結構), 這個結構的類型為mm_struct, 進程描述符的mm字段就指向這個結構.
struct mm_struct *mm;
如下為Linux 2.6.11版的內核中mm_struct的實現.
struct mm_struct {
struct vm_area_struct * mmap;
struct rb_root mm_rb;
struct vm_area_struct * mmap_cache;
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
void (*unmap_area) (struct vm_area_struct *area);
unsigned long mmap_base;
unsigned long free_area_cache;
pgd_t * pgd;
atomic_t mm_users;
atomic_t mm_count;
int map_count;
struct rw_semaphore mmap_sem;
spinlock_t page_table_lock;
struct list_head mmlist;
* together off init_mm.mmlist, and are protected
* by mmlist_lock
*/
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;
unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;
unsigned long saved_auxv[42];
unsigned dumpable:1;
cpumask_t cpu_vm_mask;
mm_context_t context;
unsigned long swap_token_time;
char recent_pagein;
int core_waiters;
struct completion *core_startup_done, core_done;
rwlock_t ioctx_list_lock;
struct kioctx *ioctx_list;
struct kioctx default_kioctx;
unsigned long hiwater_rss;
unsigned long hiwater_vm;
};
其中用來標識相應進程特定線性區的字段如下:

start_code, end_code
正文代碼的起始地址和終止地址.
start_data, end_data
已初始化數據的起始地址和終止地址.
start brk, brk
堆的起始地址和當前終止地址.
start_stack
用戶態堆棧的起始地址.
arg_start, arg_end
命令行參數的起始地址和終止地址.
env_start, env_end
環境變量的起始地址和終止地址.
如下為進程地址空間的布局, 由一個一個的線性地址區間組成, 線性地址 (linear address), 也稱虛擬地址 (virtual address) 是一個 32 位無符號整數 (unsigned long), 可以用來表示數值高達 4GB 的地址, 也就是 4,294,967,296 個內存單元.線性地址通常用十六進制數字表示, 值的范圍從 0x00000000 到 0xffffffff.

0x00000000 ~ 0xbfffffff 這一線性地址區間被稱為用戶空間, 大小為 3GB; 而0xc0000000 ~ 0xffffffff 這一線性地址區間被稱為內核空間, 大小為 1GB.
可以通過以下代碼對進程地址空間的布局圖進行驗證.
#include#include int uninitialized_global_var; int initialized_global_var = 100; int main(int argc, char *argv[], char *envp[]) { printf("Code address:%p ", main); printf("Initialized Data address:%p ", &initialized_global_var); printf("Uninitialized Data address:%p ", &uninitialized_global_var); int *p = (int*)malloc(sizeof(int)); printf("Heap address:%p ", p); printf("Stack address:%p ", &p); for (int i = 0; i < argc; i++) { printf("Command-line Arguments address:%p ", argv[i]); } for (int i = 0; envp[i]; i++) { printf("Environment Variables address:%p ", envp[i]); } return 0; }
運行結果如下, 與進程地址空間的布局相吻合.

線性地址(虛擬地址)的集合稱為虛擬內存, 物理地址的集合稱為物理內存, 進程對于內存訪問的終點是物理內存而不是虛擬內存,所以必然存在一種將虛擬內存轉化為物理內存的結構, 這種結構被稱為頁表.
頁 (Page) && 頁幀 (Page Frame)
內核使用struct page作為基本單位來管理物理內存, 在內核看來,所有的 RAM 都被劃分成了固定長度的頁幀 (頁幀也叫頁框, 通常大小為4KB). 每一個頁幀包含了一個頁, 也就是說一個頁幀的長度和一個頁的長度相同.頁和頁幀的區別在于, 頁是抽象的數據結構, 可以存放在任意地方, 而頁幀是真實的存儲區域, 屬于主存的一部分.
如下為Linux 2.6.11版的內核中struct page的實現.
struct page {
page_flags_t flags;
* updated asynchronously */
atomic_t _count;
atomic_t _mapcount;
* to show when page is mapped
* & limit reverse map searches.
*/
unsigned long private;
* usually used for buffer_heads
* if PagePrivate set; used for
* swp_entry_t if PageSwapCache
* When page is free, this indicates
* order in the buddy system.
*/
struct address_space *mapping;
* inode address_space, or NULL.
* If page mapped as anonymous
* memory, low bit is set, and
* it points to anon_vma object:
* see PAGE_MAPPING_ANON below.
*/
pgoff_t index;
struct list_head lru;
* protected by zone->lru_lock !
*/
* On machines where all RAM is mapped into kernel address space,
* we can simply calculate the virtual address. On machines with
* highmem some memory is mapped into kernel virtual memory
* dynamically, so we need a place to store that address.
* Note that this field could be 16 bits on x86 ... ;)
*
* Architectures with slow multiplication can define
* WANT_PAGE_VIRTUAL in asm/page.h
*/
#if defined(WANT_PAGE_VIRTUAL)
void *virtual;
not kmapped, ie. highmem) */
#endif
};
CPU 管理物理地址, 因而虛擬地址需要轉化為物理地址才能給 CPU 使用.用于將進程(虛擬)地址空間映射成物理地址空間的數據結構稱為頁表.

進程地址空間, 頁表的存在有什么意義?
讓所有進程以統一的視角看待內存,進程地址空間的存在讓我們在編寫程序的時候只需關注虛擬地址, 而無需關注數據在物理內存當中實際的存儲位置.
頁表的存在讓進程在間接訪問內存的時候, 增加一個轉換的過程, 在這個轉換的過程中, 內核對進程的尋址請求進行檢查, 如果該進程的尋址請求異常, 則該請求被操作系統攔截, 從而實現對物理內存的保護.
進程地址空間與頁表的存在, 讓內核對于進程管理模塊與內存管理模塊進行了解耦.
審核編輯:湯梓紅
-
RAM
+關注
關注
8文章
1399瀏覽量
120550 -
Linux
+關注
關注
88文章
11760瀏覽量
219016 -
操作系統
+關注
關注
37文章
7402瀏覽量
129288 -
動態內存
+關注
關注
1文章
25瀏覽量
8241 -
進程
+關注
關注
0文章
211瀏覽量
14534
原文標題:Linux - 進程 - 進程地址空間
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Linux內核地址映射模型與Linux內核高端內存詳解
Linux進程地址空間詳解
評論