fork原理分析

首先必须有一点要清楚,函数的返回值是储存在寄存器eax中的。

其次,当fork返回时,新进程会返回0是因为在初始化任务结构时,将eax设置为0;

在fork中,把子进程加入到可运行的队列中,由进程调度程序在适当的时机调度运行。也就是从此时开始,当前进程分裂为两个并发的进程。

无论哪个进程被调度运行,都将继续执行fork函数的剩余代码,执行结束后返回各自的值。

【NOTE5】

对于fork来说,父子进程共享同一段代码空间,所以给人的感觉好像是有两次返回,其实对于调用fork的父进程来说,如果fork出来的子进程没有得到 调度,那么父进程从fork系统调用返回,同时分析sys_fork知道,fork返回的是子进程的id。再看fork出来的子进程,由 copy_process函数可以看出,子进程的返回地址为ret_from_fork(和父进程在同一个代码点上返回),返回值直接置为0。所以当子进 程得到调度的时候,也从fork返回,返回值为0。
关键注意两点:1.fork返回后,父进程或子进程的执行位置。(首先会将当前进程eax的值做为返回值)2.两次返回的pid存放的位置。(eax中)

进程调用copy_process得到lastpid的值(放入eax中,fork正常返回后,父进程中返回的就是lastpid)
子进程任务状态段tss的eax被设置成0,
fork.c 中
p->tss.eax=0;(如果子进程要执行就需要进程切换,当发生切换时,子进程tss中的eax值就调入eax寄存器,子进程执行时首先会将eax的内容做为返回值)
当子进程开始执行时,copy_process返回eax的值。
fork()后,就是两个任务同时进行,父进程用他的tss,子进程用自己的tss,在切换时,各用各的eax中的值.

所以,“一次调用两次返回”是2个不同的进程!

看这一句:pid=fork()
当执行这一句时,当前进程进入fork()运行,此时,fork()内会用一段嵌入式汇编进行系统调用:int 0×80(具体代码可参见内核版本0.11的unistd.h文件的133行_syscall0函数)。这时进入内核根据此前写入eax的系统调用功能号 便会运行sys_fork系统调用。接着,sys_fork中首先会调用C函数find_empty_process产生一个新的进程,然后会调用C函数 copy_process将父进程的内容复制给子进程,但是子进程tss中的eax值赋值为0(这也是为什么子进程中返回0的原因),当赋值完成 后,copy_process会返回新进程(该子进程)的pid,这个值会被保存到eax中。这时子进程就产生了,此时子进程与父进程拥有相同的代码空 间,程序指针寄存器eip指向相同的下一条指令地址,当fork正常返回调用其的父进程后,因为eax中的值是新创建的子进程号,所以,fork()返回 子进程号,执行else(pid>0);当产生进程切换运行子进程时,首先会恢复子进程的运行环境即装入子进程的tss任务状态段,其中的 eax值(copy_process中置为0)也会被装入eax寄存器,所以,当子进程运行时,fork返回的是0执行if(pid==0)。

【NOTE5】

理解它关键在于理解堆栈的切换和压栈,弹栈!

关于子进程的返回:
子进程复制了父进程的栈内容,从高到低
SS
ESP
EFLAGS
CS
EIP 此是int 0×80 的下一条指令,也是子进程开始执行的地方!!!!
DS
ES
FS
EDX
ECX
EBX
GS
ESI
EDI
EBP
EAX(0)

由于 EAX = 0,所以子进程返回 0 给 fork.

注:新进程的用户栈设为其父进程的用户栈(最后弹出的SS,ESP)。如果父子进程以copy_on_write方式共用用户堆栈
(Linux之下就是这样的),而且在此之前父进程修改了该堆栈(如果父进程先返回,这几乎是肯定的),那么,系统已经为父进程创建了该用户栈的副本,父进程原来的用户栈留给了子进程。那么新进程的系统栈已经清空,新进程回到了用户态,返回到了函数fork。


### Fork 函数的实现及工作原理 Fork 是 Unix 和 Linux 系统中的一个重要系统调用,用于创建新进程。以下是关于其源码实现及其工作原理的具体分析: #### 1. 用户空间与内核空间交互 当用户程序调用 `fork()` 时,实际上会触发一次系统调用,进入内核模式。在 Linux 中,`fork()` 的最终实现依赖于内核函数 `do_fork()`[^1]。 #### 2. 内核函数 `do_fork()` 的作用 `do_fork()` 负责完成子进程的实际创建工作。它通过一系列参数来控制如何复制父进程的状态到新的子进程中。这些参数通常包括标志位(flags)、栈地址等信息。具体来说,`do_fork()` 主要执行以下几个操作: - **分配 PID**: 使用 `find_empty_process()` 查找并分配一个新的进程 ID (PID)[^3]。 - **复制上下文**: 利用 `copy_process()` 复制当前进程的任务结构体以及资源状态。 #### 3. 关键函数解析 以下是几个核心函数的作用说明: - **`sys_fork`**: 这是一个封装层,负责接收来自用户态的请求并将它们传递给更底层的实现逻辑。它的主要功能可以分为两步:查找可用的进程槽位 (`find_empty_process`) 和复制现有进程的信息 (`copy_process`)[^3]。 - **`copy_process`**: 此函数实现了大部分具体的克隆行为,例如设置寄存器值、初始化内存管理单元 (MMU) 结构以及其他必要的环境变量配置[^4]。 #### 4. 示例代码片段 下面展示了一个简化版的伪代码表示形式,展示了部分关键步骤是如何被执行的: ```c struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size){ // 创建一个新的task_struct实例... p = alloc_task_struct(); if (!p) goto fork_out; // 初始化一些基本属性... memset(p, 0, sizeof(*p)); ... } ``` 上述代码摘自官方文档或相关研究资料,其中可以看到 `copy_process` 如何逐步构建起完整的子进程描述符对象。 #### 5. 流程总结 整个过程大致如下: 1. 接收到来自用户的 `fork()` 请求后转入内核; 2. 执行 `do_fork()` 并进一步调用辅助方法如 `find_empty_process`, `copy_process`; 3. 完成所有必要数据项的拷贝之后返回至用户层面,在这里会产生两个几乎完全相同的线程/进程副本——除了某些特定字段外均保持一致;最后分别赋予各自独立的身份标识即 pid 值以便区分彼此关系结束此次操作序列。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值