1.fork():复制创建进程,出错返回-1,调用一次返回两次,在父进程中返回子进程的pid,在子进程中返回0.
2.子进程会继承父进程中的数据和程序计数器,且从fork()之后开始执行,fork()之后父子进程就是两个独立的进程,谁先运行由操作系统决定。
3.两个例子:
int main()
{
int i=0;
for(;i<2;i++)
{
if(fork==0)
{
printf("A\n");
}
else
{
printf("B\n");
}
}
return 0;
}
//打印的结果是三个A三个B
int main()
{
int i=0;
for(;i<2;i++)
{
if(fork==0)
{
printf("A");
}
else
{
printf("B");
}
}
return 0;
}
//打印的结果是四个A四个B
第一个例子图示:
第二个例子图示:
输出缓冲区:printf()是将数据写入缓冲区 缓冲区刷新的四个条件:(1)\n (2)fflush (3)缓冲区满(4)程序已exit结束
4.父子进程的关系中数据和描述符的关系如下:
(1)数据包括全局变量、局部变量和堆区数据:这些数据都不共享,地址一样,只是因为逻辑地址相同而已。
(2)堆区数据:malloc之后仅仅是只是把虚拟的地址空间开辟出来,真正开辟物理空间是在程序使用开辟的空间时。刚开始虚拟地址并没有指向一块有效的地址空间,只有在使用时才会把虚拟地址映射到物理地址上,这个时候才会真正的开辟。在子进程中也是这样。
(3)fork并非直接拷贝所有父进程空间给子进程,fork之后,给子进程拷贝的就只有PCB然后对 PCB中的一些数据做一些改变。刚开始父进程的页表直接拷贝给子进程,并没有改变页表,父子进程共享所有的数据空间。当父子进程有任意一个尝试修改数据时,操作系统就会将要修改的页直接复制出来页号不变帧号改变。这也就是写时拷贝。
(4)fork采用写时拷贝的主要原因是一般fork之后紧跟着就是进程替换,之前的子程序会被用一个新的程序替换。如果每次直接复制则替换之后复制的那份代码相当于没用浪费内存,所以采用写时拷贝。
(5)文件描述符:此处的文件描述符主要指fork之前打开的文件描述符,因为fork之后父子进程是相互独立的,会单独执行并且拥有自己的空间
①进程打开一个文件最初始是记录在PCB中的struct file结构体类型的数组中,文件描述符fd就是该数组的一个下标,指向一个struct file结构,在struct file结构中有一个f_pos也就是文件读写偏移量,在该结构中还有一个指针指向struct m_inode(在fork源码剖析中有详细解释)。由于拷贝PCB时直接拷贝属于浅拷贝所以父子进程共享一个偏移量
②虽然即使 父子进程虚拟地址空间相同一般映射的空间是不同的,但是文件偏移量在PCB中,PCB中有指针指向页表,页表才会映射,文件偏移量显然不在页表中,所以并不会导致文件文件偏移量的不同
5.
PCB在内核中,虚拟地址空间一共有4G,3G的用户空间,1G的内核空间,1G的内核空间所有地址都是共享的,3G的用户空间是独立的,PCB在操作系统中,操作系统独一份,在我们的内核空间根本没有所谓的虚拟地址,直接使用物理地址。操作系统是为了安全控制才会启用虚拟地址空间