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

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

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. html阴影 box-shadow

    右下阴影 div { box-shadow: 10px 10px 5px #888888; }四周阴影 div { box-shadow: 0 0 5px #888888; } div {box-sh ...

  2. Shell系列(3)- 命令别名

    前言 使用alias命令创建命令别名,是Bash的一个基本功能:别名有两种形式,一种暂时的,Linux重启后失效.另外一种永久的通过该配置文件实现 使用更改别名 临时 命令格式:alias 别名='原 ...

  3. 如何实现Orchard Core CMS的全文索引

    Orchard Core提供了Lucene功能,允许您在网站上进行全文搜索.大多数情况下,在运行博客或简单的代理网站时,您可能需要在页面内容中进行搜索.在Orchard Core中,您可以使用Liqu ...

  4. 『GoLang』字典Map

    map是一种元素对的无序集合,一组称为元素value,另一组为唯一键索引key. 未初始化map的值为nil.map 是引用类型,可以使用如下声明: var map1 map[keytype]valu ...

  5. [转载]linux环境变量设置方法总结(PATH/LD_LIBRARY_PATH)

    http://blog.chinaunix.net/uid-354915-id-3568853.html PATH:  可执行程序的查找路径查看当前环境变量:echo $PATH设置: 方法一:exp ...

  6. Unity3D组成

    从宏观的角度来看,分为七个模块: 1.图形模块(Graphics). 2.物理模块(Physics) 3. 音效模块(Audio) 4.动作模块(Animation) 5.导航模块(Navigatio ...

  7. SDOI2015 排序

    SDOI2015 排序 今天看到这道题,没有一点思路,暴力都没的打...还是理解错题意了,操作不同位置不是说改不同的区间,而是不同操作的顺序...考场上如果知道这个的话最少暴力拿一半啊,因为正解本来就 ...

  8. 10.12 LNMP

    yum install nginx php php-fpm mariadb-server php-mysql php.conf server { listen 8000; # pass the PHP ...

  9. Java面向对象/面向过程

    面向过程 第一步做啥 第二部做啥 依此类推 层层递进 比如要弄一辆自行车 面向过程 搞车轮子 车链子 一步步来 如果有个地方坏了 说不定整个车都要拆了重新弄 扩展性很差 维护性也很差 速度比较快 面向 ...

  10. SphereEx 公司成立,推动 Apache ShardingSphere 社区加速发展

    近日,SphereEx 商业公司在中国红杉种子基金及初心资本助力下,已完成公司及团队组建.各大媒体平台及公众号已相继报道,并抢占新闻头条.作为以 Apache ShardingSphere 核心团队组 ...