一步一圖帶你深入理解 Linux 虛擬內存管理( 六 )


5. 進程虛擬內存空間的管理在上一小節中,筆者為大家介紹了 Linux 操作系統在 32 位機器上和 64 位機器上進程虛擬內存空間的布局分布,我們發現無論是在 32 位機器上還是在 64 位機器上,進程虛擬內存空間的核心區域分布的相對位置是不變的,它們都包含下圖所示的這幾個核心內存區域 。

一步一圖帶你深入理解 Linux 虛擬內存管理

文章插圖
唯一不同的是這些核心內存區域在 32 位機器和 64 位機器上的絕對位置分布會有所不同 。
那么在此基礎之上,內核如何為進程管理這些虛擬內存區域呢?這將是本小節重點為大家介紹的內容~~
既然我們要介紹進程的虛擬內存空間管理,那就離不開進程在內核中的描述符 task_struct 結構 。
struct task_struct {// 進程idpid_tpid;// 用于標識線程所屬的進程 pidpid_ttgid;// 進程打開的文件信息struct files_struct*files;// 內存描述符表示進程虛擬地址空間struct mm_struct*mm;.......... 省略 .......}在進程描述符 task_struct 結構中,有一個專門描述進程虛擬地址空間的內存描述符 mm_struct 結構,這個結構體中包含了前邊幾個小節中介紹的進程虛擬內存空間的全部信息 。
每個進程都有唯一的 mm_struct 結構體,也就是前邊提到的每個進程的虛擬地址空間都是獨立,互不干擾的 。
當我們調用 fork() 函數創建進程的時候,表示進程地址空間的 mm_struct 結構會隨著進程描述符 task_struct 的創建而創建 。
long _do_fork(unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr,unsigned long tls){......... 省略 .......... struct pid *pid; struct task_struct *p;......... 省略 ..........// 為進程創建 task_struct 結構,用父進程的資源填充 task_struct 信息 p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace, tls, NUMA_NO_NODE);......... 省略 ..........}隨后會在 copy_process 函數中創建 task_struct 結構,并拷貝父進程的相關資源到新進程的 task_struct 結構里,其中就包括拷貝父進程的虛擬內存空間 mm_struct 結構 。這里可以看出子進程在新創建出來之后它的虛擬內存空間是和父進程的虛擬內存空間一模一樣的,直接拷貝過來 。
static __latent_entropy struct task_struct *copy_process(unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size,int __user *child_tidptr,struct pid *pid,int trace,unsigned long tls,int node){struct task_struct *p;// 創建 task_struct 結構p = dup_task_struct(current, node);....... 初始化子進程 .................. 開始繼承拷貝父進程資源.......// 繼承父進程打開的文件描述符 retval = copy_files(clone_flags, p);// 繼承父進程所屬的文件系統 retval = copy_fs(clone_flags, p);// 繼承父進程注冊的信號以及信號處理函數 retval = copy_sighand(clone_flags, p); retval = copy_signal(clone_flags, p);// 繼承父進程的虛擬內存空間 retval = copy_mm(clone_flags, p);// 繼承父進程的 namespaces retval = copy_namespaces(clone_flags, p);// 繼承父進程的 IO 信息 retval = copy_io(clone_flags, p);...........省略.........// 分配 CPUretval = sched_fork(clone_flags, p);// 分配 pidpid = alloc_pid(p->nsproxy->pid_ns_for_children);...........省略.........}這里我們重點關注 copy_mm 函數 , 正是在這里完成了子進程虛擬內存空間 mm_struct 結構的的創建以及初始化 。
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk){// 子進程虛擬內存空間,父進程虛擬內存空間 struct mm_struct *mm, *oldmm; int retval;...... 省略 ...... tsk->mm = NULL; tsk->active_mm = NULL;// 獲取父進程虛擬內存空間 oldmm = current->mm; if (!oldmm)return 0;...... 省略 ......// 通過 vfork 或者 clone 系統調用創建出的子進程(線程)和父進程共享虛擬內存空間 if (clone_flags & CLONE_VM) {// 增加父進程虛擬地址空間的引用計數mmget(oldmm);// 直接將父進程的虛擬內存空間賦值給子進程(線程)// 線程共享其所屬進程的虛擬內存空間mm = oldmm;goto good_mm; } retval = -ENOMEM;// 如果是 fork 系統調用創建出的子進程,則將父進程的虛擬內存空間以及相關頁表拷貝到子進程中的 mm_struct 結構中 。mm = dup_mm(tsk); if (!mm)goto fail_nomem;good_mm:// 將拷貝出來的父進程虛擬內存空間 mm_struct 賦值給子進程 tsk->mm = mm; tsk->active_mm = mm; return 0;...... 省略 ......由于本小節中我們舉的示例是通過fork() 函數創建子進程的情形,所以這里大家先占時忽略 if (clone_flags & CLONE_VM) 這個條件判斷邏輯,我們先跳過往后看~~
copy_mm函數首先會將父進程的虛擬內存空間 current->mm 賦值給指針 oldmm 。然后通過 dup_mm 函數將父進程的虛擬內存空間以及相關頁表拷貝到子進程的 mm_struct 結構中 。最后將拷貝出來的 mm_struct 賦值給子進程的 task_struct 結構 。

推薦閱讀