进程创建
fork函数
在linux 中fork函数,它从已经存在的进程中创建一个新的进程,新进程为子进程,而原进程为父进程。
#include<unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程的id,出现错误返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
-
分配新的内存块和内核数据结构给子进程
-
将父进程部分数据结构内容拷贝到子进程
-
添加子进程到系统进程列表当中
-
fork返回,开始调度器调度
当一个进程调用fork之和,就有两个二进制代码相同的进程。而且它们都运行到相同的地方,但每个进程都将开始属于他们自己的旅程。
写时拷贝
通常,父子进程的代码是共享的,父子在不进行写入的时候数据也是共享的,让任何一方试图进行写入操作的时候,便以写时拷贝的方式各自各自有了一个数据的副本。
对于一个进程,它的pcb指向虚拟内存中的数据段和代码段,而虚拟内存通过页表的映射,映射到了对应的物理内存中的数据段和代码段。当这个进程进行fork 的时候,创建了一个子进程,此时子进程的pcb是继承于父进程,所以子进程的虚拟内存以及页表以及映射的物理内存等信息都是相同的,所以此时父子进程的虚拟内存是相同的,页表映射的数据段和代码段的物理内存也是相同的,当有一方进行数据修改的时候,进行写入的时候,为了双方互不影响,会给试图进行写入的一方拷贝一份代码块,将页表映射的代码块映射到拷贝的物理内存的地址处,然后再进行写入。
- 创建子进程的时候,不知道子进程是否需要对继承的数据代码进行操作,如果不操作,则会产生空间浪费,资源浪费。
- 因为不确定父子某一方对数据进行操作的方式(增删查改),所以进程进行写入的时候,需要将原进程的数据代码拷贝。
写时拷贝是如何做到的?
页表
页表权限
创建进程的时候,子进程拷贝了父进程的pcb属性,子进程具有与父进程相同的数据段和代码段,虚拟内存通过页表映射的物理内存也是相同的一块内存。
在页表中除去相对应的虚拟地址和物理地址,每一个映射还对应一个权限,例如虚拟地址映射的物理地址,对于这个物理地址可能具有只读、只写、读写权限。
对于创建进程的过程,
- 在创建进程之前,父进程的虚拟地址数码段与代码段对于页表映射的物理地址的数码段和代码段,的权限都是读写权限。
- 进程创建,当在父进程中执行了fork指令,此时进行了进程创建,子进程继承父进程的大部分属性,此时操作系统将父进程和子进程的虚拟地址和物理地址的页表映射权限都设置为了只读。
- 写时拷贝,当父进程或者子进程任意一方试图进行写入,对数据/代码进行写入操作(增删查改)时,在通过虚拟内存映射物理内存时,准备对物理内存进行写入的时候,发现没有权限,此时出现了缺页中断,写入操作暂停。此时操作系统会出现,进行检查,确认是自己将其设置为的只读权限。此时操作系统会将他们的操作权限进行恢复为读写权限,然后会给进行写入操作的进程,在物理内存中拷贝一份与父子进程相同内容的内存,此时将进行写入操作的进程的页表,虚拟地址的映射为新的物理地址。然后再进行进程的写入操作。
fork常规用法
- 一个父进程希望复制自己,使得父子进程同时执行不同的代码段。
- 一个进程要执行一个不同的程序
fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了限制