1.fork
我们在进程中使用fork函数,就会衍生出一个进程,我们去姑且称原本的进程为父进程,衍生出来的进程叫子进程,在下一篇文章会讲到我们可以用子进程做什么事情。
在fork之前父进程单独执行,fork之后两个执行流分别进行,fork之后子进程执行fork之后的代码,这段代码父子共享,那么是否fork只执行fork之后的代码就意味着只共享这后面的代码?
不是的,父子进程共享了所有的代码,只不过子进程只能从这里开始执行。这是怎么实现的呢?
在cpu中有一个程序计数器eip,它会保存当前正在执行的指令的下一条指令。
fork之后,eip程序计数器会拷贝给子进程,子进程便从该eip所指向的代码开始执行。
2.fork之后,os做了什么
每个进程都有独立性,子进程是进程当然也具有独立性。
首先,我们要知道进程=内核的进程数据结构+进程的数据和代码。
那么,os在fork之后创建了子进程的进程的内核数据结构(struct mm_struct+struct task_struct+页表)+代码继承父进程,数据以写时拷贝的方式,来进行共享或者独立。
3.什么是虚拟地址空间
每个进程都有自己独立的虚拟地址空间。
什么是虚拟地址空间呢?其实就是os给进程画的大饼,让每一个进程都认为自己独占系统中的所有资源(内存)。
为什么要画大饼,如实的让进程占有多好,反正内存那么大,
为什么要有虚拟地址空间?
①.保护内存,直接让进程访问物理内存是不安全的,也许哪一个阿猫阿狗(野指针)非法访问内存,导致不属于进程的内存发生改变。
②.对进程和内存更好的管理,通过地址空间,进行功能模块的解耦。
③.让进程和程序已统一的视角去看待内存。方便以统一的方式来编译和加载所有的可执行程序。以简化程序的设计和实现。
虚拟地址空间,在linux下是一个名为mm_struct的结构体来描述的。
看着可高大上了吧,其实在mm_stuct这些区段都是由一个个区段的开始位置和结束位置所组成的。
struct mm_struct
{
long code_start;
long code_end;
long heap_start;
long heap_end;
long stack_start;
long stack_end;
...
};
4.上文的写时拷贝是什么?
我们来看下,进程到真正的物理内存要经过什么?
通过页表映射到内存。
父子进程都如图进行执行。
写时拷贝,通常,父子进程共享代码,父子再不写入时,数据也是共享的,当一方试图写入时,编译以写时拷贝的方式拷贝一份出来,在另一处修改。在其他处修改,不会父或子进程的数据。
为什么要写时拷贝?
1.避免浪费空间,子进程不一定要进行写入,如果不进行写入,却也拷贝出一个和父进程的代码数据一样的数据再占一块空间,纯属浪费空间。
2.理性情况下,在子进程进行写入时,进行分离拷贝。不修改则公用。
3.无脑拷贝会增加fork的成本(内存和时间)。