当前位置:嗨网首页>书籍在线阅读

18-进程

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

1.4.2 进程

如果说文件是UNIX系统最重要的抽象概念,进程则仅次于文件。进程是执行时的目标代码:活动的、正在运行的程序。但是进程不仅包含目标代码,它还包含数据、资源、状态和虚拟计算机。

进程的生命周期是从可执行目标代码开始,这些机器可运行的代码是以内核能够理解的形式存在,在Linux下,最常见的格式称为“可执行和可链接的格式(Executable and Linkable Format,ELF)”。可执行性格式包含元数据、多个代码段和数据段。代码段是线性目标代码块,可以加载到线性内存块中。数据段中的所有数据都一视同仁,有相同的权限,通常也用于相同的目的。

最重要和通用的段是文本段、数据段和bss段。文本段包含可执行代码和只读数据如常量,通常标记为只读和可执行。数据段包含初始化的数据,如包含给定值的C变量,通常标记为可读写。bss段包含未初始化的全局数据。因为C标准规定了C变量的默认值全部为0,因此没有必要在磁盘上把0保存到目标代码中。相反地,根据目标代码可以很容易地列举出bss段中未初始化的变量,内核在加载到内存时可以映射bss段中的全0页面(页面中全部都是0),bss段的设计完全是出于性能优化。bss这个取名存在历史遗留原因,是block started by symbol的简称。ELF可执行性程序的其他通用段都是绝对地址段(包含不可再定位的符号)和未定义地址段(包罗万象)。

进程还和系统资源关联,系统资源是由内核决定和管理的。一般来说,进程只通过系统调用请求和管理资源。资源包括计时器、挂起的信号量、打开的文件、网络连接、硬件和IPC机制。进程资源以及该进程相关的数据和统计保存在内核中该进程的进程描述符中。

进程是一种虚拟抽象。进程内核同时支持抢占式多任务和虚拟内存,为每个进程提供虚拟处理器和虚拟内存视图。从进程角度看,系统看起来好像完全由进程控制。也就是说,虽然某个进程可以和其他进程一起调度,该进程在运行时看起来似乎独立控制整个系统。系统内核会无缝、透明地抢占和重新调度进程,所有进程共享系统处理器,而进程感不到其中的区别。同样,每个进程都获得独立的线性地址空间,好像它独立控制整个系统内存。通过虚拟内存和分页,内核支持多个进程共享系统,每个进程的操作都运行在独立的地址空间中。内核通过现代处理器的硬件支持来管理这种虚拟化方式,支持操作系统并发管理多个独立的进程的状态。

线程

每个进程包含一个或多个执行线程(通常简称线程threads)。线程是进程内的活动单元,换句话说,线程是负责执行代码和管理进程运行状态的抽象。

绝大多数进程只包含一个线程,这些进程被称为单线程;包含多个线程的进程称为多线程。从传统上讲,由于UNIX系统一直很简洁,进程创建很快并拥有健壮的IPC机制,这些都减少了对线程的需求。因此,UNIX进程绝大部分是单线程的。

线程包括栈(正如非线程系统的进程栈一样,用于存储局部变量)、处理器状态、目标代码的当前位置(通常是保存在处理器的指令指针中)。进程的其他部分由所有线程共享,最主要是进程地址空间。在这种情况下,线程在维护虚拟进程抽象时,也共享虚拟内存抽象。

在Linux系统内部,Linux内核实现了独特的线程模型:它们其实是共享某些资源的普通进程。在用户空间,Linux依据POSIX 1003.1c实现线程模型(称为Pthreads)。目前Linux线程实现称为POSIX Threading Library(NPTL),它是glibc的一部分。我们将在第7章对线程进行更多的讨论。

进程层次结构

每个进程都由唯一的正整数标识,称为进程ID(pid)。第一个进程的pid是1,后续的每个进程都有一个新的、唯一的pid。

在Linux中,进程有严格的层次结构,即进程树。进程树的根是第一个进程,称为init进程,通常是init程序。新的进程是通过系统调用fork()创建的。fork()会创建调用进程的副本。原进程称为父进程,fork()创建的新进程称为子进程。除了第一个进程外,每个进程都有父进程。如果父进程先于子进程终止,内核会将init进程指定为它的父进程。

当进程终止时,并不会立即从系统中删除。相反地,内核将在内存中保存该进程的部分内容,允许父进程查询其状态,这被称为等待终止进程。一旦父进程确定某个子进程已经终止,该子进程就会完全被删除。如果一个进程已经终止,但是父进程不知道其状态,该进程称为僵尸进程(zombie)。init进程会等待所有的子进程结束,确保子进程永远不会处于僵死状态。