6.1 进程和程序
进程是一个可执行程序的实例。
程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程,其包括内容如下:
- 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核用此信息解释文件中的其他信息。现在大多数UNIX实现采用可执行连接格式(ELF)。
- 机器语言指令:对程序算法进行编码。
- 程序入口地址:标识程序开始执行时的起始指令位置。
- 数据:程序文件包含的变量初始值和程序使用的字面常量(literal constant)值(如字符串)。
- 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多种用途,包括调试和运行时的符号解析(动态链接)。
- 共享库和动态链接信息:程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以及加载共享库的动态链接器的路径名。
- 其他信息:其他信息,用以描述如何创建进程。
可以用同一个程序来创建许多进程,或者,许多进程运行的可以使同一程序。
从内核角度看,进程由用户内存空间和一系列内核数据结构的内容组成,其中用户内存空间包含了程序代码和代码使用的变量,而内核数据结构用于维护进程状态信息。
6.2 进程号和父进程号
getpid()
返回调用进程的进程号:
#include <unistd.h>
pit_t getpid(void);
Linux内核限制进程号<=32767。新进程创建时,内核会按顺序将下一个可用进程号分配给它使用。每当进程号达到32767的限制时,内核将重置进程号计数器为300,以便从小整数开始分配。之所以重置为300,是低数值的进程号被系统进程和守护进程长期占用。
getppid()
可以检索到父进程的进程号:
#include <unistd.h>
pid_t getppid(void);
使用pstree 1
命令可以看到进程的家族树。
如果有子进程的父进程终止,则子进程成为孤儿进程,由1号init进程收养,后续对该子进程调用getppid()
会返回进程号1。
6.3 进程内存布局
进程分配的内存由各个段组成:
- 文本段(.text):包含进程运行的程序机器语言指令。文本段具有只读属性,一方进程通过错误指针以外修改自身指令。因为多个进程可同时运行同一程序,所以又将文本段设为可共享,如此,一份程序代码的拷贝可以映射到所有这些进程的虚拟地址空间中。
- 初始化数据段(.data):包含显示初始化的全局变量和静态变量。当程序加载到内存时,从可执行文件中读取这些变量的值。
- 未初始化数据段(.bss):包含未进行显示初始化的全局变量和静态变量。将它和初始化过的数据分开存放,主要原因是程序在磁盘上存储时,没有必要为味精初始化的变量分配存储空间。
- 栈(stack):是一个动态增长和收缩的段,由栈帧组成。系统会为每个当前调用的函数分配一个栈帧。栈帧中存放了函数的局部变量、实参和返回值。
- 堆(heap):是可在运行时动态分配内存的一块区域。
使用size *.out
可以查看二进制可执行文件的各个段大小: