复制进程
1.fork() 方法
引头文件
#include<unistd.h>
pid_t fork(void);
函数返回类型 pid_t 实质是 int 类型,Linux 内核 2.4.0 版本的定义是:
typedef int __kernel_pid_t;
typedef __kernel_pid_t pit_t;
fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程。在父进程中返回子进程的 pid(大于0),在子进程中返回 0,失败返回-1。
代码如下:
运行结果:
如图:子进程(son)打印三次结果,父进程(parent)打印四次结果
2. 对于 fork() 资源复制的理解
fork拷贝父进程资源,例如缓冲区,堆区资源等。
利用以下代码深度理解fork() 对缓冲区复制的理解:
示例一:
int main(){
for(int i=0; i<2; i++){
fork();
printf("A\n");
}
}
此处会打印六个A
解析如图:
示例二:
int main(){
for(int i=0; i<2; i++){
fork();
printf("A");
}
}
此处会打印八个A
解析如图:
3. 写时拷贝
传统的fork()系统调用直接把所有的资源复制给新创建的进程。 这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。
Linux 的 fork() 使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下一一举例来说,fork()后立即调用exec()——它们就无需复制了。
fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。由于
linux 强调进程快速执行的能力,所以这个优化是很重要的。
僵死进程
1. 僵死进程概念
子进程先于父进程结束,父进程没有调用 wait()函数获取子进程退出码。
代码如下:
运行结果:
具体分析:
从上图中可以看到,当子进程结束后,并没有消失,仍然可以在系统中观测到,但此时子进程其实已经运行结束了,此时子进程的状态被称为僵死状态,系统把处于该类状态的进程称为僵死进程 (< defunct >)。
2. 处理僵死进程
父进程通过调用 wait()
完成。
代码如下:
运行结果:
先运行子进程,父进程阻塞,子进程运行完后,再运行父进程。
具体分析:
孤儿进程
Init() 进程收养孤儿进程
代码如下:
运行结果:
具体分析:
如果父进程先结束,子进程最后是不会变为僵死进程的。