虽然篇幅很长,但大多是易懂的代码,不用担心看不完

这里的所有操作,都将在下面的代码中有所体现

fork会拷贝当前进程的内存,并创建一个新的进程。如上图,fork函数会将整个进程的内存镜像拷贝到新的内存地址,包括代码段、数据段、堆栈以及寄存器内容。之后,我们就有了两个拥有完全一样内存的进程。fork系统调用在两个进程中都会返回,在父进程中,fork系统调用会返回子进程的pid。而在新创建的进程中,fork系统调用会返回0。所以即使两个进程的内存是完全一样的,我们还是可以通过fork的返回值区分旧进程和新进程。

某种程度上来说这里的拷贝操作浪费了,因为所有拷贝的内存都被丢弃并被exec替换。在大型程序中这里的影响会比较明显。实际上操作系统会对其进行优化。(比如使用COW(copy on write)技术)

fork创建的新进程从fork语句后开始执行,因为新进程也继承了父进程的PC程序计数器。

在xv6中唯一不是通过fork创建进程的场景就是创建第一个进程的时候,之后的所有进程都是通过fork创建的。

以下内容以xv6(教学用Linux操作系统)源代码为例

代码解析
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
int i, pid;
struct proc *np;
//获取当前进程(进程只运行在用户态)
//fork函数是系统调用,实际上运行在内核态,用户态的寄存器内容、内存状态都会被保留下来,所以不用担心fork函数的运行影响原进程
struct proc *p = myproc(); // Allocate process.
//allocproc实际上从操作系统进程队列中找到一个未在使用状态的空进程,
//为其分配trapframe(用来存储寄存器内容),page table页表,并设置了一些寄存器状态。代码见附
if((np = allocproc()) == 0){
return -1;
} // Copy user memory from parent to child.
//将父进程的页表内容拷贝到子进程页表中
//uvmcopy user virtual memory copy
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz; // copy saved user registers.
//将父进程的寄存器内容拷贝到子进程中
*(np->trapframe) = *(p->trapframe); // Cause fork to return 0 in the child.
//a0为返回值寄存器,fork函数结束会将a0的值返回,因此子进程fork的返回值为0
np->trapframe->a0 = 0; // increment reference counts on open file descriptors.
//获取父进程打开的文件描述符
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd); safestrcpy(np->name, p->name, sizeof(p->name)); //在这里实现了父进程fork返回子进程pid
pid = np->pid;
//下面设置子进程的父进程,将子进程的状态设置为可运行
//之后,操作系统会在进程调度时,执行子进程
release(&np->lock); acquire(&wait_lock);
np->parent = p;
release(&wait_lock); acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock); return pid;
}
附:

alloproc代码

static struct proc*
allocproc(void)
{
struct proc *p; for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == UNUSED) {
goto found;
} else {
release(&p->lock);
}
}
return 0; found:
p->pid = allocpid();
p->state = USED; // Allocate a trapframe page.
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
} // An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
} // Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE; return p;
}

Trapframe代码:

struct trapframe {
/* 0 */ uint64 kernel_satp; // kernel page table
/* 8 */ uint64 kernel_sp; // top of process's kernel stack
/* 16 */ uint64 kernel_trap; // usertrap()
/* 24 */ uint64 epc; // saved user program counter
/* 32 */ uint64 kernel_hartid; // saved kernel tp
/* 40 */ uint64 ra;
/* 48 */ uint64 sp;
/* 56 */ uint64 gp;
/* 64 */ uint64 tp;
/* 72 */ uint64 t0;
/* 80 */ uint64 t1;
/* 88 */ uint64 t2;
/* 96 */ uint64 s0;
/* 104 */ uint64 s1;
/* 112 */ uint64 a0;
/* 120 */ uint64 a1;
/* 128 */ uint64 a2;
/* 136 */ uint64 a3;
/* 144 */ uint64 a4;
/* 152 */ uint64 a5;
/* 160 */ uint64 a6;
/* 168 */ uint64 a7;
/* 176 */ uint64 s2;
/* 184 */ uint64 s3;
/* 192 */ uint64 s4;
/* 200 */ uint64 s5;
/* 208 */ uint64 s6;
/* 216 */ uint64 s7;
/* 224 */ uint64 s8;
/* 232 */ uint64 s9;
/* 240 */ uint64 s10;
/* 248 */ uint64 s11;
/* 256 */ uint64 t3;
/* 264 */ uint64 t4;
/* 272 */ uint64 t5;
/* 280 */ uint64 t6;
};

fork函数详解(附代码)的更多相关文章

  1. Linux环境fork()函数详解

    Linux环境fork()函数详解 引言 先来看一段代码吧, 1 #include <sys/types.h> 2 #include <unistd.h> 3 #include ...

  2. 【转】linux 中fork()函数详解

    在看多线程的时候看到了这个函数,于是学习了下,下面文章写的通俗易懂,于是就开心的看完了,最后还是很愉快的算出了他最后一个问题. linux 中fork()函数详解 一.fork入门知识 一个进程,包括 ...

  3. Linux中fork()函数详解(转载)

    linux中fork()函数详解 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事, ...

  4. fork函数详解

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...

  5. Linux C 中 fork() 函数详解

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork() 函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同 ...

  6. fork()函数详解

    原文链接:http://blog.csdn.net/jason314/article/details/5640969  一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函 ...

  7. Linux中fork()函数详解(转)

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...

  8. 【Linux 进程】fork函数详解

    一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...

  9. linux中fork函数详解(转)

    add by zhj: 在Linux,创建进程是用fork(),它其实就是拷贝父进程的数据段和其它数据,这相当于C函数调用中的值传递,这是 此后两者的修改都互不影响.因为两者的数据虽相同,但却在不同的 ...

随机推荐

  1. MYSQL分页 limit 太慢优化

    limit分页原理 当我们翻到最后几页时,查询的sql通常是:select * from table where column=xxx order by xxx limit 1000000,20.查询 ...

  2. jmeter跑脚本的注意事项

    指标主要看以下几点: 1.jmeter性能测试的报告,不要看平均响应时间,而是看90%响应时间,一般不能超过3s,超过3s则不符合标准2.响应时间超过3s就要优化,但不是平均响应时间,因为最小响应时间 ...

  3. YbtOJ#662-交通运输【线段树合并,树状数组】

    正题 题目链接:http://www.ybtoj.com.cn/contest/122/problem/2 题目大意 给出\(n\)个点的一棵有根树,对于每个\(x\)求,删除点\(x\)后修改某个点 ...

  4. The art of multipropcessor programming 读书笔记-硬件基础2

    本系列是 The art of multipropcessor programming 的读书笔记,在原版图书的基础上,结合 OpenJDK 11 以上的版本的代码进行理解和实现.并根据个人的查资料以 ...

  5. (Java)面向对象的三大特征

    封装.继承与多态 封装 封装的作用(好处) 提高程序安全性,保护数据 隐藏代码的实现细节 统一接口 增加系统可维护性 属性私有(关键字private) 加上Private可使该属性私有于一个类,在其他 ...

  6. 看动画学算法之:栈stack

    目录 简介 栈的构成 栈的实现 使用数组来实现栈 使用动态数组来实现栈 使用链表来实现 简介 栈应该是一种非常简单并且非常有用的数据结构了.栈的特点就是先进后出FILO或者后进先出LIFO. 实际上很 ...

  7. WEB 标准以及 W3C 的理解与认识

    01.  WEB标准 ①  web标准 简单来说可以分为结构.表现和行为. ②  结构:主要是有HTML标签组成(通俗点说,在页面body里面我们写入的标签都是为了页面的结构)   表现:即指css样 ...

  8. javascriptRemke之深入迭代

    javascriptRemke之深入迭代 前言:"迭代"意为按照顺序反复多次执行一段程序,ECMAscript6中新增了两个高级特性:迭代器与生成器,使用这两个特性能更高效地实现迭 ...

  9. std::sort 的注意事项

    Luogu P1177 [模板]快速排序 \(\Large{AC}\) 代码: #include<bits/stdc++.h> using namespace std; int n,a[1 ...

  10. 内网渗透DC-1靶场通关(CTF)

    最新博客见我的个人博客地址 DC系列共9个靶场,本次来试玩一下DC-1,共有5个flag,下载地址. 下载下来后是 .ova 格式,建议使用vitualbox进行搭建,vmware可能存在兼容性问题. ...