5. 进程虚拟内存空间的管理在上一小节中,笔者为大家介绍了 Linux 操作系统在 32 位机器上和 64 位机器上进程虚拟内存空间的布局分布,我们发现无论是在 32 位机器上还是在 64 位机器上,进程虚拟内存空间的核心区域分布的相对位置是不变的,它们都包含下图所示的这几个核心内存区域 。
文章插图
唯一不同的是这些核心内存区域在 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 结构 。
推荐阅读
- Dubbo 03: 直连式 + 接口工程
- 一篇文章带你了解热门版本控制系统——Git
- 一篇文章带你了解网页框架——Vue简单入门
- 我用canvas带你看一场流星雨
- 一篇文章带你掌握主流办公框架——SpringBoot
- 带你认识JDK8中超nice的Native Memory Tracking
- SpringBoot+Vue3 AgileBoot - 手把手一步一步带你Run起全栈项目
- 带你读AI论文丨ACGAN-动漫头像生成
- 一篇文章带你掌握主流服务层框架——SpringMVC
- 一篇文章带你掌握主流基础框架——Spring