5、 进程虚拟内存空间的管理
主要逻辑

重点函数
task_struct函数(进程在内核中的描述符函数)

进程在内核中的描述符task_struct结构:
struct task_struct{ //进程的描述符
//进程id
pid_t pid;
//用于标识线程所属的进程
pid_t tgid;
//进程打开的文件信息
struct files_struct *files;
//内存描述符 表示进程虚拟地址空间
struct mm_struct *mm;
//省略
}
mm_struct中包含进程虚拟内存空间的全部信息
每一个进程都有唯一的mm_struc结构体,也就是每个进程的虚拟地址空间都是独立,互不干扰。
_do_fork函数(用于创建一个新的进程)
_do_fork函数,它用于创建一个新的进程,并在进程地址空间中建立 task_struct 结构。
long _do_fork(unsigned long clone_flags,//控制新进程的创建方式(如是否共享地址空间、文件描述符等)
unsigned long stack_start,//指定新进程的栈信息。
unsigned long stack_size,
int __user *parent_tidptr,//指向用户空间中的线程 ID 位置。
int __user *child_tidptr,//
unsigned long tls)//用于线程局部存储(Thread Local Storage
{
........ 省略 ........
struct pid *pid;//用于存储进程 ID。
struct task_struct *p;//指向新创建的进程描述符。
........ 省略 ........
// 为进程创建 task_struct 结构,用父进程的资源填充 task_struct 信息,copy_process() 负责实际的进程创建和资源复制。
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
//clone_flags决定进程是否共享资源,stack_start,stack_size用于设置新进程的栈信息。child_tidptr传递线程ID信息,
........ 省略 ........
}
在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,//子进程的线程 ID 。
struct pid *pid,//进程 ID 结构体。
int trace,//跟踪信息。
unsigned long tls,//线程本地存储地址。
int node//:NUMA 相关参数。
)
{
struct task_struct *p;//是 Linux 进程的核心数据结构。
p = dup_task_struct(current, node);//复制当前进程的 task_struct,为新进程创建基础信息。
//以下为继承父进程的资源
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);//继承内存管理 !!!此处若CLONE_VM置为,则子进程与父进程共享地址空间,适用于线程。否则子进程获得父进程的地址空间副本
retval = copy_namespaces(clone_flags, p);//继承 namespace(命名空间),影响进程的 PID、网络、用户等视图。
retval = copy_io(clone_flags, p);//继承 IO 上下文,涉及 IO 资源管理。
retval = sched_fork(clone_flags, p);//sched_fork() 为进程在调度器中创建调度实体,设置 CPU 亲和性等信息。
pid = alloc_pid(p->nsproxy->pid_ns_for_children);//为新进程分配一个唯一的 PID。
}
copy_mm函数
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk) //clone_flags是控制子进程是否共享父进程的地址空间的变量,tsk指向新创建的子进程的task_struct结构
{
//子进程虚拟内存空间,父进程虚拟内存空间
struct mm_struct *mm, *oldmm;//指向子进程的 mm_struct,用于存储虚拟内存信息。指向父进程的 mm_struct,用于继承或共享虚拟内存。
int retval;
//省略
tsk ->mm = NULL
tsk ->active_mm=NULL;
//获取父进程虚拟内存空间
oldmm = current->mm;
//如果 oldmm 为空(意味着父进程没有虚拟地址空间,如内核线程),则直接返回 0,子进程不需要地址空间。
if (!oldmm)
return 0;
//省略
//通过vfork或者clone系统调用创建出的子进程(线程)和父进程共享虚拟内存空间
if (clone_flags & CLONE_VM) {//CLONE_VM 选项表示 线程,子进程应该和父进程共享地址空间:
mmget(oldmm); // 增加 `mm_struct` 引用计数
mm = oldmm; // 共享父进程的 `mm_struct`
goto good_mm;//跳转到good_mm无需创建新mm_struct
}
retval = -ENOMEM;//如果 未设置 CLONE_VM,说明是 fork() 创建的普通进程,需要复制地址空间:
// 如果是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;
// 省略
}
对copy_mm函数的小结:
copy_mm函数分两种情况:
1、子进程模式
copy_mm函数首先将父进程的虚拟内存空间赋值给指针oldmm,然后通过dup_mm函数将父进程的虚拟内存空间和相关页表拷贝到子进程的mm_struct结构中,最后将拷贝的mm_struct赋值到子进程的task_struct结构
2、线程: CLONE_VM设定后,进入:
if (clone_flags & CLONE_VM)
然后这个分支中,父进程的虚拟内存空间和相关页表直接赋值给子进程,这样以来父子进程的虚拟内存空间就变成共享的了,而并不是一份拷贝。
这样子进程就共享了父进程的虚拟内存空间,这样子进程就变成了线程。
是否共享地址空间几乎是进程和线程的本质区别。Linux内核并不区别对待,对内核来说,线程仅仅是一个共享特定资源的进程。
注意到其中有对oldmm的判定,若为空,则说明是内核线程,内核线程对应的task_struct结构中的mm指向Null,所以内核线程之间调度不涉及地址空间切换。
5.1 内核如何划分用户态和内核态的虚拟内存空间
用到了进程的内存描述符task_struct的mm_struct结构体中的task_size变量**,task_size定义了用户态和内核态地址空间的分界线。**
struct mm struct{
unsigned long task size;/*size of task vm space */
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;/* base of mmap area */
unsigned long mmap base,
unsigned longtotal vm;/* Total pages mapped */
unsigned longlocked vm;/* Pages that have PG mlocked set */
unsigned long pinned vm;/* Refcount permanently increased */
/*VM WRITE & ~VM SHARED & ~VM STACK */
unsigned long data vm;/*VM WRITE & ~VM SHARED & ~VM STACK */
unsigned long exec vm;/* VM EXEC & ~VM WRITE & ~VM STACK */
unsigned long stack vm;/* VM STACK */
//省略
32位机器和64位机器不一样,起始地址分别为:


5.2 内核如何布局进程虚拟内存空间
struct mm struct{
unsigned long task size;/*size of task vm space */
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;/* base of mmap area */
unsigned long mmap base,
unsigned longtotal vm;/* Total pages mapped */
unsigned longlocked vm;/* Pages that have PG mlocked set */
unsigned long pinned vm;/* Refcount permanently increased */
/*VM WRITE & ~VM SHARED & ~VM STACK */
unsigned long data vm;/*VM WRITE & ~VM SHARED & ~VM STACK */
unsigned long exec vm;/* VM EXEC & ~VM WRITE & ~VM STACK */
unsigned long stack vm;/* VM STACK */
//省略
start_code和end_code定义代码段的起始和结束位置
start_data和end_data定义数据段的起始和结束位置

5.3 内核如何管理虚拟内存区域
一个新的结构体vm_area_struct,描述了这些虚拟内存区域VMA(virtural memory area)
struct vm_area_struct {
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end;
/* The first byte after our end address
within vm_mm. */
/*
* Access permissions of this VMA.
*/
pgprot_t vm_page_prot;
unsigned long vm_flags;
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
struct file * vm_file; /* File we map to (can be NULL). */
unsigned long vm_pgoff;
/* Offset (within vm_file) in PAGE_SIZE
units */
void * vm_private_data; /* was vm_pte (shared mem) */
/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;
}

5.4 定义虚拟内存区域的访问权限和行为规范
在5.3的vm_area_struct中,有vm_page_prot和vm_flags,vm_page_prot定义的是页这一级别的访问控制权限,可以直接应用在底层页中,vm_flags则偏向于定义整个虚拟内存区域的访问权限和行为规范,描述的是整体信息。

除了可读可写以外,VM_SHARD用于指定这块区域是否可以用于多进程共享,以达到多进程通讯

5.5 关联内存映射中的映射关系
vm_area_struct中剩下的三个属性anon_vma,vm_file,vm_pgoff分别与虚拟内存映射有关,虚拟内存区域可以映射到物理内存上(匿名映射),也可以映射到文件中(文件映射)。
调用malloc申请内存时,如果申请是小块内存,则使用do_brk()系统调用通过调整堆中的brk指针大小用于增加和回收堆内存
如果申请的是大块内存超过128k,则调用mmap在文件映射和匿名映射区创建出一块VMA内存区域。
5.6 针对虚拟内存区域的相关操作
vm_area_struct还有一个vm_ops用来只想针对虚拟内存区域VMA的相关操作的函数指针(略)
5.7 虚拟内存区域在内核中是如何被组织的

在内核中实际是通过一个struct vm_area_struct结构的双向链表将这些虚拟内存空间串联起来的


181

被折叠的 条评论
为什么被折叠?



