1.进程的层次
每个进程都有父进程,父进程也有父进程,形成了一个以init进程为根的家族树。
2. 进程的创建之fork()
Linux系统下,进程可以调用fork函数来创建新的进程,调用进程为父进程,被创建进程为子进程。
# include <unistd.h>
pid_t fork(void);//fork函数的接口
fork函数会返回两次,fork函数向子进程返回0,并将子进程的进程ID返回给父进程,如果失败,该函数返回-1。
3.父子进程的内存关系
fork后的子进程完全拷贝了父进程的地址空间(数据段、栈和堆上的内存),但是子进程和父进程执行相同代码的情况比较少见,所以Linux提供了execv系统调用,这个系列函数会丢弃现存的程序代码段,并构建新的代码段和数据段,调用fork后,子进程是通过调用execv系列函数,执行新程序。
为了解决execv抛弃拷贝的地址空间这种现象,Linux引入了写时拷贝。
写时拷贝:子进程的页表项指向与父进程的相同的物理内存空间,如果父子进程都不修改,则共用一份物理内存页,如果父子进程有一方修改,内存会创建一个新的物理页,让父子进程各自拥有各自的物理内存页。
4.fork源码剖析
下面的Linux源码部分截图可以看到:fork()、vfork()、clone()统一调用do_fork()来实现只是传递的参数不同。
把寄存器信息传给fork(),再由fork()到do_fork()
do_fork()函数内部:
1.申请pid并且检查pid值(pid的总数,)
2.复制pcb
3.复制进程实体
(1)定义了pcb指针 struct task_struct *p;
(2)调用copy_process()函数创建子进程的task_struct。
copy_process()转到定义
(1)
(2)copy_files()复制父进程打开的文件描述符。
copy_mm()复制地址空间。
(3)copy_thread复制父进程的内核栈。
copy_thread()把寄存器对应字段的值(fork和clone系统调用在子进程中的返回值)强制置为0。
用父进程的现场信息初始化子进程的现场信息,并将子进程的eax寄存器的值强制为0,这就是子进程fork返回0的原因。
(4)把状态置为就绪,父进程时间片分子进程一半(防止程序通过fork恶意占用CPU资源)。