述的步骤分解到两个单独的函数中去执行:fork()函数和exec函数族。首先,fork()函数通过复制当前进程创建一个子进程(注意此时资源还没有被复制过来,去了解一下写时复制页技术吧),子进程于父进程的区别仅仅在于不同的PID、PPID和某些资源及统计量。exec函数族负责读取可执行文件并将其载入地址空间开始运行。
(2)进程的终止
进程终结也需要很多繁琐的工作,系统必须保证回收进程所占用的资源,并通知父进程。Linux首先把终止的进程设置为僵尸状态,这时,进程无法投入运行,它的存在只为父进程提供信息,申请死亡。父进程得到信息后,开始调用 wait 函数族,最后终止子进程,子进程占用的所有资源被全部释放。
进程的内存结构
Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该地址空间是大小为 4GB的线性虚拟空间,用户所看到和接触到的都是该虚拟地址,无法看到实际的物理内存地址。利用这种虚拟地址不但能起到保护操作系统的效果(用户不能直接访问物理地址),而且,更重要的是,用户程序可以使用比实际物理内存更大的地址空间。
4GB的进程地址空间会被分成两个部分:用户空间与内核空间。用户地址空间是从0到3GB(0xC000 0000),内核地址空间占据3GB到4GB。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。只有用户进程使用系统调用(代表用户进程在内核态执行)时可以访问到内核空间。每当进程切换时,用户空间就跟着变化;而内核空间由内核负责映射,它不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,用户进程各自有不同的页表。每个进程的用户空间都是完全独立、互不相干的。进程的虚拟内存空间如图3所示,其中用户空间包括以下几个功能区域:
● 只读段: 包含程序代码(.init和.text)和只读数据(.rodata)。
● 数据段: 存放的是全局变量和静态变量。其中可读可写数据段(.data)存放已初始化的全局变量和静态变量,BSS数据段(.bss)存放未初始化的 局变量和静态变量。
● 堆: 由系统自动分配释放,存放函数的参数值、局部变量的值、返回地址等。
● 堆栈: 存放动态分配的数据,一般由程序员动态分配和释放。若程序员不释放,程序结束时可能由操作系统回收。
● 共享库的内存映射区域: 这是Linux动态链接器和其他共享代码库代码的映射区域。

由于在Linux系统中每一个进程都会有/proc文件系统下与之对应的一个目录(如将init进程的相关信息在/proc/1 目录下的文件中描述),因此通过 proc 文件系统可以查看某个进程的地址空间的映射情况。例如,运行一个应用程序,如果它的进程号为13703,则输入“ cat /proc/13703/maps”命令,可以查看该进程的内存映射情况。
线程
前面已经讲到,进程是系统中程序执行和资源分配的基本单位。每个进程都拥有自己的数据段、代码段和堆栈段,这就造成了进程在进行切换等动作时需要较复杂的上下文切换等动作。为了进一步减少处理机的空闲时间,支持多处理器及减少上下文切换开销,进程在演化中出现了另一个概念---线程。它是进程内独立的一条运行路线,是处理器调度的最小单元,也可以称为轻量级线程。线程可以对进程的内存空间和资源分配进行访问,并与同一进程中的其他线程共享。因此,线程的上下文切换的开销比创建进程小得多。
一个进程可以拥有多个线程,每个线程必须有一个父进程。线程不拥有系统资源,它只具有运行时所必须的一些数据结构,如堆栈/寄存器与线程控制块(TCB),线程与其父进程的其他进程共享该进程所拥有的全部资源。要注意的是,由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他进程带来影响。由此可知,多线程中的同步是一个非常重要的问题。在多线程系统中,进程与线程的关系如图4所示

在Linux系统中,线程分为3种:①用户线程 ②轻量级线程 ③内核线程