Linux中的进程基本知识以及fork的简单理解


进程可以看作是程序的一次执行过程,在Linux下,每个进程有唯一的PID标识进程,PID是一个从1到32768的正整数,其中1一般是init,其他的进程从2开始依次编号,当用完了32768之后,从2重新开始。

Linux中有一个进程表的结构来存储当前正在进行的进程,我们可以用ps aux或者ps ef来查看正在进行的进程。进程在Linux中呈树状结构,init是根节点,其他的进程均有父进程。某进程的父进程就是启动这个进程的进程,这个进程叫做父进程的子进程。

fork的作用就是复制一个与当前进程一样的进程,新进程的所有数据,比如变量、环境变量、程序计数器等都和原进程一直,但是它是一个全新的进程,而且作为原进程的子进程。

出于效率的考虑,Linux引入了写时复制的技术,也就是只有进程空间的隔断的内容需要发生变化时,才会将父进程的内容复制一份给子进程。在fork之后exec之前两个进程使用的是相同的物理空间,也就是相同的内存区,子进程的代码段、数据段、堆栈段都是指向父进程的物理空间的,也就是说,虽然两者的虚拟空间不同,但是对应的物理空间是同一个。

当子进程有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间,至此两者都有了各自的进程空间,而且互不影响,而代码段会继续共享父进程的物理空间。如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

在fork之后内核会通过将子进程放到队列的前面,来让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因为无意义的复制而造成效率的下降。

也就是说,通过写时复制技术,内核只为新生成的子进程创建虚拟空间结构,它们用来复制父进程的虚拟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,但是当子进程有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

而vfork则是内核连子进程的虚拟地址空间结构也不创建了,直接共享了父进程的虚拟空间,也就直接共享了父进程的物理空间。

传统的fork调用是把所有的资源复制给新创建的进程,这种实现过于简单且效率地下,因为它拷贝的数据也许并不共享,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。

Linux的fork使用了写时拷贝,它可以推迟甚至避免拷贝数据,内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝,在写入数据时,数据才会被复制,从而使各个进程拥有自己的拷贝。也就是说,资源的复制只在需要的时候才进行,在此之前,是以只读方式共享的。

也就是说,fork的实际开销就是复制父进程的页表给子进程创建的唯一的进程描述符,在一般情况下,进程创建后都会马上运行一个可执行文件,这种优化可以避免拷贝大量根本不会使用的数据,因为UNIX强调进程快速执行的能力,所以这个优化还是蛮重要的。

相关内容