1.进程与线程创建
fork调用结束时,在返回点这个想用位置上,父进程恢复执行,子进程开始执行,fork从内核返回两次,一次回到父进程,一次回到子进程。
进程调用exit退出,退出后被设置为僵死状态,直到他的父进程调用了wait或waitpid
每个进程有自己的地址空间,如果父进程希望和子进程共享地址空间,可以在调用clone()时,设置CLONE_VM标志,我们把这种进程称为线程
是否共享地址空间几乎是线程和进程本质上唯一的区别
调用fork时,子进程调用allocate_mm()申请mm_struct结构体的空间(即每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间),然后利用copy_mm()复制父进程内存描述符;而线程则在创建时,调用clone(),并设置CLONE_VM标志,这样内核就不会在调用allocate_mm函数申请mm_struct空间,仅需要调用copy_mm()将mm域指向其父进程的内存描述符即可。
综上:子进程会申请自己的进程地址空间,并复制父进程地址空间里的内容;而子线程不会申请自己的地址空间,而是共享其父进程的进程地址空间
2. 写时拷贝技术
- 创建pcb
- 拷贝父进程中的pcb的数据(拥有相同的虚拟地址空间,相同的页表)
- 父子进程一开始映射同一块物理内存
- 等到物理内存修改时,才为子进程重新开辟内存,拷贝数据。
写时拷贝技术例:
一开始的时候,通过PCB描述的进程1和进程2上的变量val都是100,在进程1和进程2的虚拟地址空间上都是相同的位置,所以我们在程序中看到的变量val的地址相同,其实是虚拟地址,假地址。此时通过页表映射到物理内存中的相同位置,但是当进程1改变val值的时候,就不一样了。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int okc=0;
int main()
{
int ret=fork();
if(ret<0){
perror("fork");
return 1;
}
else if(ret==0){
okc=100;
printf("child:%d %d %p\n",getpid(),okc,&okc);//子进程先退出,修改了okc的值
}
else
{
sleep(3);
printf("father:%d %d %p\n",getpid(),okc,&okc);
}
return 0;
}
结果:可以看出来变量的地址相同的,但是值是不同的,说明变量在两个进程地址空间中的地址是相同的,但是他们映射在物理地址上的地址是不同的。
每个进程都有自己的4G虚拟地址(线性地址),上面看到的两个进程的该变量线性地址相同,该变量在两个进程中的线性地址是相同的,但是由于进程切换时,CR3寄存器会切换到新进程的页目录表物理内存基地址,即切换到了新进程的地址空间,所以这两个进程的该变量的线性地址虽然是相同的(即对应的页表地址是一样的),但是页表项里存储的内容(物理地址)是不同的
https://www.bilibili.com/read/cv8129309/
https://blog.csdn.net/SweeNeil/article/details/106171361