fork(四源码剖析)

一:基础了解

1.fork,vfork,_clone都是通过系统调用clone来实现,然后再用clone去调用do_fork

2.内核把进程存放在叫做任务队列的双向循环链表中。链表中的每一项都是类型为task_struct,称为进程描述符的结构。进程描述符包含一个具体进程的所有信息。

3.进程描述符的存放:内核通过一个唯一的进程标识值或PID来标识每个进程,PID是一个数,表示为pid_t隐含类型,实际上就是一个int类型。为了与老版本的Unix和Linux兼容,PID的最大值默认设置为32768(short int短整型的最大值)PID存放在各自的进程描述符中。

4.current宏查找到当前正在运行进程的进程描述符

5.有的硬件体系结构可以拿出一个专门的寄存器来存放指向当前进程task_struct的指针,用与加快访问速度但是向x86这样的结构体系地址寄存器并不富余,就只能在内核栈的尾端创建thread_info结构,通过计算偏移间接的查找task_struct结构

6.进程是处于执行期的程序以及它所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)的总称。注意,程序并不是进程,实际上两个或多个进程不仅有可能执行同一程序,而且还有可能共享地址空间等资源。

7.Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息

 

二:源码理解

fork()底层调 用do_fork()实现:(总体概述:申请pid,复制PCB,复制进程主体)

 

  1. 获取pid 申请一个pid号

(1)Linux允许用户使用一个叫做进程标识符process ID(pid)的数来标识进程,PID存放在进程描述符的pid字段。

(2)内核中使用位图来管理这些pid,由于使用内嵌的汇编语言扫描位图(pidmap-arra位图),所以效率较高,数据结构为pidmap-array,内核使用页框来存放位图,一个页框包含32768个位,所以32位体系结构中pidmap-array位图存放在一个单独的页中。然而,在64位体系结构中,可能需要为PID位图增加更多的页,

(3)在2.6内核中,进程中最大的进程pid32768,但是不考虑内存等因素制约,不超过一个整型数(int)所表示的数的大小

(4)首先是last pid加1作为新进程的pid,如果pid大于等于pid_max,就把pid置为RESERVED_PIDS(300),从RESERVED_PIDS开始查找空闲pid,然后获取页内偏移和该pid所对应的pidmap结构体,)接下来会通过一个for循环来遍历位图,max_scan表明遍历次数,如果offset为0,即从0开始查找空闲pid,max_scan为0,那么只需要遍历一次;如果offset不为0,max_scan为1,需要遍历二次。

(5)32位体系结构上,pid位图是一个物理页,但是pid的值可以超过一页含有的位数32768,

 

2.开始准备复制PCB,申请一个PCB:调用copy_process():复制进程描述符,创建进程描述符以及子进程执行所需要的所有其他数据结构,copy_process复制进程描述符.如果所有必须的资源都是可用的,该函数返回刚创建的task_struct描述符的地址.

(1)检查标志位

(2)调用dup_task_struct()为子进程获取进程描述符此时传入的参数current的作用是通过current宏查找到当前正在运行进程的进程描述符,

定义两个变量:struct thread_info *ti  ,和struct task_struct *tsk。

  • alloc_task_struct宏为新进程获取进程描述符,并将描述符保存到tsk局部变量中。

  • alloc_thread_info宏获取一块空闲内存区,用来存放新进程的thread_info结构和内核栈。这块内存区字段的大小是8KB或者4KB。

③将current进程描述符的内容复制到tsk所指向的task_struct结构中,然后把tsk_thread_info置为ti

 将current进程的thread_info内容复制给ti指向的结构中,并将ti_task置为tsk.

此处相当于是让task_sturct与thrread_info互相指向,方便之后查找内核栈

(3)用写时拷贝来复制进程实体:

 Copy_files复制文件描述符, copy_mm():struct  mm_struct:内存描述符,用来描述内存的地址空间,包含了内存地址空间的全部信息,

Copy_mm()函数注释

/**

 * 当创建一个新的进程时,内核调用copy_mm函数,

 * 这个函数通过建立新进程的所有页表和内存描述符来创建进程的地址空间。

 * 通常,每个进程都有自己的地址空间,但是轻量级进程共享同一地址空间,即允许它们对同一组页进行寻址。

 */

(4)父进程现场已保存,从保存处执行,子进程和父进程只有pid’不同所以父进程从哪里开始子进程从哪里开始,调用copy_thread(),   恢复子进程后,把eax字段对应字段强行置为0,fork返回值为0的原因

用父进程现场信息初始化子进程现场,并将子进程的eax寄存器的值强置为0,这就是子进程fork返回0的原因

 

此段代码注释:

调用copy_thread,用发出clone系统调用时CPU寄存器的值(它们保存在父进程的内核栈中)

 * 来初始化子进程的内核栈。不过,copy_thread把eax寄存器对应字段的值(这是fork和clone系统调用在子进程中的返回值)

 * 强行置为0。子进程描述符的thread.esp字段初始化为子进程内核栈的基地址。ret_from_fork的地址存放在thread.eip中。

 * 如果父进程使用IO权限位图。则子进程获取该位图的一个拷贝。

 * 最后,如果CLONE_SETTLS标志被置位,则子进程获取由CLONE系统调用的参数tls指向的用户态数据结构所表示的TLS段。

(5)将状态置为就绪,父进程时间片分子进程一半,防止通过fork恶意占用CPU资源

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值