引言
如果每个进程都从零开始创建,必然有大量的初始化工作(如初始化PCB、构建虚拟内存空间)是重复的。于是有了新的原语出现–fork()/clone():使用已有的进程复制新进程,使得新进程完成了与已有进程相同的初始化操作,进程的复制可以越过大量的初始化工作。其中clone主要用于线程的创建。线程的管理、创建、复制将在后文做详细说明,本文主要对进程复制创建的过程进行分析和总结。
创建一个进程需要的三要素对应fork的主要工作
- PCB的管理,故fork需要申请PCB并进行初始化。
- 进程运行状态、数据记录,故fork需要将父进程在CPU寄存器中存储的CPU上下文复制到到新进程的CPU上下文中。新进程就有了与父进程相同的执行环境。
- 内存空间,进程实体是存储在内存中的,所以fork要给新进程分配物理内存。
三个工作的实现
1.申请PCB–复制父进程PCB–用复制的数据初始化新进程PCB
//源文件:include/asm/thread_info.h
//分配PCB
#define alloc_task_struct_node(node)({
struct page *page=alloc_pages_node(node,GFP_KERNEL|__GFP_COMP,KERNEL_STACK_SIZE_ORDER);
struct task_struct *ret=page?page_address(page):NULL;ret;
})
/*ai解析:在这段代码中,`ret`是一个指向`task_struct`结构体的指针。这段代码是一个宏定义,宏展开后可以看作是一个函数,用于在指定的NUMA节点上分配一个新的`task_struct`结构体,并返回指向该结构体的指针。
具体来说,`alloc_task_struct_node(node)`宏展开后会执行代码块中的内容,包括调用`alloc_pages_node()`函数分配内存页面,并将其映射为`task_struct`结构体。最后,将指向该结构体的指针存储在`ret`变量中,并返回该指针。
因此,在代码执行完成后,`ret`指针将指向新分配的`task_struct`结构体,程序可以通过这个指针来访问和操作该结构体所表示的任务信息。*/
//源文件kernel/process.c
//复制PCB
int arch_dup_task_struct(struct task_struct*dst,struct task_struct *src){
//...
*dst=*src;//将父进程结构体的各个成员复制给新进程
dst->thread.sve_state=NULL;
clear_tsk_thread_flag(dst,TIF_SVE);
return 0;
}
- CPU上下文复制:在第一个工作完成的前提下,新进程有了与父进程一样的PCB,但是这个进程还没有PCB进程父进程CPU寄存器当中存储的数据还要同样的复制过来,以此来拥有与父进程一样的执行环境。
//复制需要:新进程、父进程状态寄存器结构体,包括用户寄存器状态pt_regs(用户空间进入内核需要保存的用户进程寄存器状态)和内核态进程切换时的寄存器状态(存储进程切换时的CPU的上下文)thread_struct两种。
//源文件kernel/process.c
struct pt_regs *childregs=task_pt_regs(p);//获取pt_regs结构
memset(&p->thread.cpu_context,0,sizeof(struct cpu_context));//将新进程内核态需要的寄存器信息清零
//...
*childregs