do_fork()实现

一、在分析do_fork()之前,我们需要对进程要有一定的认识,因为do_fork()是创建进程的。

进程四要素:

1、  有一段程序供其执行;

2、  有进程专用的系统堆栈空间,即内核栈;

3、  有进程控制块task_struct结构体;

4、  有独立的存储空间,专用的用户空间,即用于虚存管理的mm_struct结构、下属vm_area结构,以及相应的页面目录项和页面表,都从属于task_struct结构。

在include/linux/sched.h中定义了task_struct结构:PCB是进程存在和运行的唯一标识,在进程控制块task_struct结构中主要包含进程的状态、性质、资源和组织。

部分截取task_struct结构:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

进程系统堆栈示意图:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

二、Linux提供三个创建进程的系统调用,fork()vfork()无参数的,clone()带参数的。

原型:

pid_t fork(void);

pid_t vfork(void);

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

区别fork()是全部复制,父进程所有资源都通过数据结构的复制遗传给子进程;vfork()除了task_struct结构和系统空间堆栈以外的资源全部通过数据结构指针的复制遗传给子进程;而clone()则将资源有选择的复制给子进程,没有复制的数据结构则通过指针的复制让子进程共享。

由系统调用:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

系统调用服务例程:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

上图可得到:三种调用会进入同一个接口do_fork(),只是标志不同。

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF


三、do_fork()实现流程:

先看do_fork()函数:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

 

流程注解:

1、在do_fork()函数中首先创建一个task_struct结构体指针,再对传递给do_forkflag参数进行处理和检查,看当前进程是否设置了跟踪标记ptrace

2然后进入了copy_process函数,实现将父进程的寄存器以及所有进程执行环境的相关部分复制给子进程。

copy_process()函数中:首先对一些标志进行合法性检查,检查完之后调用dup_task_struct函数来为新进程创建一个内核栈、thread_infotask_struct,这里完全copy父进程的内容。

copy_process()函数流程图:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

dup_task_struct函数:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

注解:

调用alloc_task_struct_node()从系统高速缓存中分配task_struct空间;

调用alloc_thread_info_node()为新进程分配连续两个页面的空间,返回值指向thread_info的首地址,这是内核栈和thread_info的存放空间;

调用arch_dup_task_struct(tsk, orig)将父进程的task_struct复制给子进程的task_struct

tsk->stack = ti;将子进程task_struct结构体的stack指针指向thread_info结构,这样便建立了task_structthread_info之间的联系了;

setup_thread_stack(tsk, orig);函数是将父进程的thread_info的内容完全复制到子进程的thread_info中,并在子进程中建立thread_infotask_struct之间的联系。 

3、返回到copy_process()函数:

初始化task_struct结构中的各个成员了,主要涉及到拷贝clone_flags、初始化相关链表。调用sched_fork函数(Linux/kernel/sched/core.c)设置处理器的相关标记,并分配特定标号的处理器给该进程。其中的处理器相关标记包括:分配处理器标号、设置进程的优先级、设置与该进程调度相关的调度类等。

4、复制/共享进程的各个部分,某一部分是否父子进程共享主要由clone_flags中的相关标志位决定的。

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

注解:copy_process中包含的函数:copy_files()copy_fs()copy_sinhand()copy_mm()

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

关于copy_files()copy_fs()copy_fs_struct()copy_sinhand()copy_mm()dup_mmap()copy_page_range()函数原型可以在源代码中找到。

copy_process()copy_files()有条件的复制已打开文件的控制结构;copy_fs()共享或复制进程与文件系统的关系,主要通过copy_fs_struct()进行处理;copy_sighand()复制父进程对信号的处理;copy_mm()是对用户空间的继承,其中dup_mmap()复制vm_area_struct结构和页面映射表,copy_page_range()函数逐层处理页面目录项和页面表项。

5设置各个ID、进程关系

copy_process()中调用以上函数完成对task_struct的复制,接下来调用copy_thread()函数创建子进程系统堆栈,实际上是复制父进程的系统空间,即用父进程的内核栈内容更新子进程的内核栈

子进程系统堆栈示意图:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

6、  copy_process()函数成功执行之后返回do_fork()函数执行以下代码:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

注解:调用get_task_pid()函数返回进程p的PID,然后对一些标志位进行处理,调用函数wake_up_new_task函数初始化调度器的一些基本参数,把该任务放入到就绪队列中等待调度器的调度,并设置该任务的运行状态为TASK_RUNNING。最后return进程的PID。

三个系统调用比较:

viewfile?f=94A2AAFA23ED35DB7D163F898C8DF

注解:

fork()fork()通过sys_fork()进入do_fork()时,其clone_flagsSIGCHILD,即所有标志位均为0,所以copy_files()copy_fs()copy_sighand()copy_mm()全部执行。

vfork()vfork()通过sys_vfork()进入do_fork()时,其clone_flagsVFORK|CLONE_VM | SIGCHLD,所以执行copy_files()copy_fs()copy_sighand(),对于copy_mm(),由于标志位CLONE_VM1,通过指针共享mm_struct

clone():取决于调用时的参数。



经过上述过程,一个新的进程产生,关于进程的执行可以分析exec函数族。

报告只是简单分析了do_fork()大致实现流程,至于具体的实现可以阅读源码深入理解。