fork()子进程过程分析

时间:2021-07-04 19:22:31

         在讲fork()子进程之前,我们需要重申几个概念,以便更好理解fork()子进程。

         我们都知道程序在计算机上执行的时候是以进程为单位执行的,进程是可执行程序运行的基本单位。进程执行过程中就需要OS为其分配可供执行的资源。其中最难理解的就是内存资源的分配,OS到底是怎么样为新创建的进程合理地分配内存资源的?这就产生了虚拟地址空间的概念

1、虚拟地址空间

        对于学习过《操作系统原理》这门课的读者来说,大家都了解内存管理的概念。理解了内存管理,就会很容易明白虚拟地址空间的概念。计算机内存资源是相对匮乏的资源,每个可执行程序都需要OS为其分配一定的内存资源,现代计算机已经实现了多并发,多任务的处理要求,一台计算机每时每刻都会有很多程序在执行,而每个程序又会有许多进程在执行,每个进程有需要独立的堆栈为其服务,这些都需要内存作为载体才能正确完整的执行。而问题是,计算机内存容量大小是很有限的,不可能为每个可执行程序分配合理的内存资源供其独有。计算机设计这门就提出了虚拟内存的概念,我的个人理解是是虚拟内存是逻辑上的内存空间,也称逻辑地址空间,是在物理硬盘上划分出来的。比如,OS会为每个正在内存的程序分配大概4G的虚拟地址空间(是每个正在执行的程序都会有4G空间哦),这4G虚拟地址空间是和物理内存空间中存在某种映射关系的。
        一般虚拟内存4G的空间对应物理内存应该也是4G的,但这里有个小插曲:首先虚拟内存空间是在硬盘上的。当这段代码需要执行的时候(就必须加载到内存),OS就需要把需要执行的一部分利用某种算法加载到内存(只加载一部分,不可能把该程序虚拟地址空间中的所有内容都加载到内存),然后运行。

         关于虚拟内存空间和物理内存的概念到此为止。

2、下面介绍进程的执行过程

         虚拟地址空间分为两部分:用户地址空间和内核地址空间,i386体系结构的计算机的虚拟地址空间一般是4G,0~3G的空间为用户地址空间;3G~4G的地址空间为内核地址空间。相应地,程序的运行状态分为用户态和内核态,程序在用户态只能占用用户地址空间,程序在内核态占用内核地址空间。显而易见,内核态程序的执行级别高于用户态程序的执行级别。

        每一个进程的地址空间都分为这两种地址空间,对于内核进程,由于其始终运行在内核态,所以内核进程的tast_struct结构体中的mm域也就被赋值为NULL在内核地址空间中,不存在堆数据结构,所以堆的概念仅仅是用户地址空间中的数据结构,所以对于内核进程来说,也不会需要堆这种数据结构来存储变量或代码。kmalloc 或vmalloc用户内核进程在运行时申请内存。还有一点需要注意,不同程序的内核地址空间是相同的,即不同程序的内核地址都在一起,所以kmalloc或vmalloc申请到的虚拟内存在整个内核中都可以被其他程序使用(这里可能是黑客程序的破解点之一)。举个例子来说,加入内核线程1申请到了一块内存A, 只要把该内存的首地址传给另一个内核线程2,则在2中同样也可以使用这块内存。

        对于用户进程,其既有用户地址空间中的栈,也有它自己的内核栈。而内核进程就只有内核栈。所有进程都有一个内核栈,在x86的32位机器上内核栈大小可以为4KB或8KB,这个可以在编译内核的时候配置。创建内核堆栈的情况为:
  1. 当进程进入内核态,系统调用的参数就放在内核栈上,内核栈记录着进程的在内核中的调用链;
  2. 在内核栈被配 置成8KB大小的情况下,当中断服务程序中断当前进程时,它将使用当前被中断进程的内核栈。

3、fork创建进程过程

        fork()是内核程序创建进程的一种方式(其他还有vfork()和clone()方法),由fork()创建的新进程被称为子进程(child process)。需要特别注意的是:该函数被调用一次,但返回两次。两次分别返回父进程和子进程。子进程的返回的是0(内核代码中设定的为0),而父进程返回的是子进程的进程PID父进程和子进程共享代码段,但是分别拥有自己的数据段和堆栈段。一个进程的子进程可以不止一个。对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的PID;也可以调用getppid()来获取父进程的PID。
        fork之后,操作系统会拷贝出一个与父进程完全相同的子进程,这两个进程共享代码段空间,但是数据段是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的)。如果fork不成功,父进程会返回错误。

         有两个对立的观点,一是说:父进程先返回(因为对父进程来说,fork执行过程相当于函数调用);另一个中观点是说:父子进程哪个先运行与操作系统的调度有关,如果需要父子进程协同,可以通过原语的办法解决。


参考材料:http://blog.csdn.net/theone10211024/article/details/13774669