前言
上一篇我們談論了task_struct這個結構體,它被叫做進程描述符,內部成員包含了很多與進程相關的信息,今天我們來看一下其中一個被叫做內存描述符的結構體——mm_struct,抽象的來描述linux下進程的地址空間的所有的信息。
1.概述
一個進程的虛擬地址空間主要由兩個數據結來描述。一個是最高層次的:mm_struct,一個是較高層次的:vm_area_structs。最高層次的mm_struct結構描述了一個進程的整個虛擬地址空間。較高層次的結構vm_area_truct描述了虛擬地址空間的一個區間(簡稱虛擬區)。每個進程只有一個mm_struct結構,在每個進程的task_struct結構中,有一個指向該進程的結構。可以說,mm_struct結構是對整個用戶空間的描述。
首先,我們來定位mm_struct文件所在位置和task_struct所在路徑是一樣的,不過他們所在文件是不一樣的,mm_struct所在的文件是mm_types.h,接下來我們就來分析這個結構好了。
首先我們來看下這個:
這就是我們所說的由task_struct到mm_struct,進程的地址空間的分佈。
每一個進程都會有自己獨立的mm_struct,這樣每一個進程都會有自己獨立的地址空間,這樣才能互不干擾。當進程之間的地址空間被共享的時候,我們可以理解爲這個時候是多個進程使用一份地址空間,這就是線程。
其實多個進程的地址空間分佈就是上面這張圖一樣,每一個進程的用戶空間在32位的平臺上就是上面這個圖的情況,對於物理內存當中的內核kernel,是隻存在一份,所有的進程是用來共享的,內核當中會利用PCB(進程控制塊)來管理不同的進程。對於linux的體系結構來說,linux當中爲了保護虛擬內核空間不被修改,所以linux體系結構是這樣的:
這種三層的體系結構,保證進程只能對最外面的應用程序進行修改,保證了內存的安全性。
另外,我們從第一張圖上可以發射線,每個區域是依靠着兩個指針進行維護的,比如[start_data,end_data)是用來維護data段,[start_code,end_data)用來維護code段,[start_brk,brk),用來維護heap和heap的指針。[start_stack,end_stack)是用來維護stack段空間範圍。mmap_base是維護共享映射區的起始地址。bss段表示的是所有的未初始化的全局變量,爲了效率,對處在bss段的變量,將它們匿名映射到“零頁”,這樣提高了程序的加載效率。
//指向線性區對象的鏈表頭
struct vm_area_struct * mmap; /* list of VMAs */
//指向線性區對象的紅黑樹
struct rb_root mm_rb;
在地址空間中,mmap爲地址空間的內存區域(用vm_area_struct結構來表示)鏈表,mm_rb用紅黑樹來存儲,鏈表表示起來更加方便,紅黑樹表示起來更加方便查找。區別是,當虛擬區較少的時候,這個時候採用單鏈表,由mmap指向這個鏈表,當虛擬區多時此時採用紅黑樹的結構,由mm_rb指向這棵紅黑樹。這樣就可以在大量數據的時候效率更高。所有的mm_struct結構體通過自身的mm_list域鏈接在一個雙向鏈表上,該鏈表的首元素是init_mm內存描述符,代表init進程的地址空間。
atomic_t mm_users;
atomic_t mm_count;
這兩個內容表示的各有不同。
成員 | 內容 |
---|---|
mm_users | 進程數量值(在多線程的情況下尤爲適用) |
mm_count | 引用計數(當計數爲0的時候表示沒有再被使用) |
使用mm_users和mm_count兩個計數器是爲了區別主使用計數器和使用該地址空間的進程的數目。
每一個進程都可以被別的進程來共享,也就是和別的進程來共享mm_struct.
所有的mm_struct結構以鏈表的形式存在mm_struct的。
另外需要說明的就是kernel線程是沒有地址空間的,也就沒有對應的mm_struct,kernel線程使用之前運行的進程的內存描述符。
程序中通常用到的地址常常具有局部性,當前最近一次用蛋糕的虛擬地址區間很可能下一次還是需要用到,所以我們採用局部性原理,通常時候我們去吧當前地址周圍一個區間的內存放入高速緩存當中,這個區間在mm_struct當中就是由mmap_cache來維護。
2.關於頁表
linux kernel 使用內存管理的時候,採取的是頁式的管理方式,應用程序給出的內存地址是虛擬地址,是經過若干層的頁表的轉換才能得到真正的物理地址,所以相對來說,進程的地址空間是一份虛擬的地址空間,每一個地址通過頁表的轉換映射到所謂的物理地址空間上。在這裏所共享的1G的kernel在內存地址是隻存一份的,但是對於每一個進程其他的3G的空間,是存儲其他不同的東西,另外,頁表具有權限限定,這樣也就提供給了每塊內存區域,比如我定義了:
char * p="12342";
這裏的“12342”是一個常量字符串,它被存放在只讀常量存儲區,所以這個區域的頁表的屬性就是隻讀,這樣就可以高效的維護整個進程的地址空間。
每一個進程都會有一個進程描述符,task_struct,task_strust當中的mm指針指向每個進程的內存描述符,而對於每個mm,有都會有單獨的頁表,
pgt區間是用來維護頁表的目錄,每一個進程的都有自己的頁表目錄,需要注意進程的頁目錄和內核的頁目錄是不一樣的,當程序調度器調度程序運行的時候,這個時候這個地址就會轉換成爲物理地址,linux一般採用三級頁表進行轉換。
3.task_struct和mm_strcuct的聯繫
不知道你是否還記得在task_struct當中的
//關於進程的地址空間,指向進程的地址空間。(鏈表和紅黑樹)
struct mm_struct *mm, *active_mm;
task_struct和mm_strcut通過這兩個成員進行和mm_struct聯繫,每一個進程都會有唯一的mm_struct結構體。
struct mm_struct {
//指向線性區對象的鏈表頭
struct vm_area_struct * mmap; /* list of VMAs */
//指向線性區對象的紅黑樹
struct rb_root mm_rb;
//指向最近找到的虛擬區間
struct vm_area_struct * mmap_cache; /* last find_vma result */
//用來在進程地址空間中搜索有效的進程地址空間的函數
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
unsigned long (*get_unmapped_exec_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
//釋放線性區時調用的方法,
void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
//標識第一個分配文件內存映射的線性地址
unsigned long mmap_base; /* base of mmap area */
unsigned long task_size; /* size of task vm space */
/*
* RHEL6 special for bug 790921: this same variable can mean
* two different things. If sysctl_unmap_area_factor is zero,
* this means the largest hole below free_area_cache. If the
* sysctl is set to a positive value, this variable is used
* to count how much memory has been munmapped from this process
* since the last time free_area_cache was reset back to mmap_base.
* This is ugly, but necessary to preserve kABI.
*/
unsigned long cached_hole_size;
//內核進程搜索進程地址空間中線性地址的空間空間
unsigned long free_area_cache; /* first hole of size cached_hole_size or larger */
//指向頁表的目錄
pgd_t * pgd;
//共享進程時的個數
atomic_t mm_users; /* How many users with user space? */
//內存描述符的主使用計數器,採用引用計數的原理,當爲0時代表無用戶再次使用
atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
//線性區的個數
int map_count; /* number of VMAs */
struct rw_semaphore mmap_sem;
//保護任務頁表和引用計數的鎖
spinlock_t page_table_lock; /* Protects page tables and some counters */
//mm_struct結構,第一個成員就是初始化的mm_struct結構,
struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung
* together off init_mm.mmlist, and are protected
* by mmlist_lock
*/
/* Special counters, in some configurations protected by the
* page_table_lock, in other configurations by being atomic.
*/
mm_counter_t _file_rss;
mm_counter_t _anon_rss;
mm_counter_t _swap_usage;
//進程擁有的最大頁表數目
unsigned long hiwater_rss; /* High-watermark of RSS usage */、
//進程線性區的最大頁表數目
unsigned long hiwater_vm; /* High-water virtual memory usage */
//進程地址空間的大小,鎖住無法換頁的個數,共享文件內存映射的頁數,可執行內存映射中的頁數
unsigned long total_vm, locked_vm, shared_vm, exec_vm;
//用戶態堆棧的頁數,
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
//維護代碼段和數據段
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 saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
struct linux_binfmt *binfmt;
cpumask_t cpu_vm_mask;
/* Architecture-specific MM context */
mm_context_t context;
/* Swap token stuff */
/*
* Last value of global fault stamp as seen by this process.
* In other words, this value gives an indication of how long
* it has been since this task got the token.
* Look at mm/thrash.c
*/
unsigned int faultstamp;
unsigned int token_priority;
unsigned int last_interval;
//線性區的默認訪問標誌
unsigned long flags; /* Must use atomic bitops to access the bits */
struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIO
spinlock_t ioctx_lock;
struct hlist_head ioctx_list;
#endif
#ifdef CONFIG_MM_OWNER
/*
* "owner" points to a task that is regarded as the canonical
* user/owner of this mm. All of the following must be true in
* order for it to be changed:
*
* current == mm->owner
* current->mm != mm
* new_owner->mm == mm
* new_owner->alloc_lock is held
*/
struct task_struct *owner;
#endif
#ifdef CONFIG_PROC_FS
/* store ref to file /proc/<pid>/exe symlink points to */
struct file *exe_file;
unsigned long num_exe_file_vmas;
#endif
#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
/* reserved for Red Hat */
#ifdef __GENKSYMS__
unsigned long rh_reserved[2];
#else
/* How many tasks sharing this mm are OOM_DISABLE */
union {
unsigned long rh_reserved_aux;
atomic_t oom_disable_count;
};
/* base of lib map area (ASCII armour) */
unsigned long shlib_base;
#endif
};
github註釋源碼鏈接:https://github.com/wsy081414/C_linux_practice/blob/master/mm_struct.c