6.1 进程和程序

进程是一个可执行程序的实例

程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程,其包括内容如下:

  • 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核用此信息解释文件中的其他信息。现在大多数UNIX实现采用可执行连接格式(ELF)。
  • 机器语言指令:对程序算法进行编码。
  • 程序入口地址:标识程序开始执行时的起始指令位置
  • 数据:程序文件包含的变量初始值和程序使用的字面常量(literal constant)值(如字符串)
  • 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多种用途,包括调试和运行时的符号解析(动态链接)。
  • 共享库和动态链接信息:程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以及加载共享库的动态链接器的路径名。
  • 其他信息:其他信息,用以描述如何创建进程。

可以用同一个程序来创建许多进程,或者,许多进程运行的可以使同一程序

从内核角度看,进程由用户内存空间和一系列内核数据结构的内容组成,其中用户内存空间包含了程序代码和代码使用的变量,而内核数据结构用于维护进程状态信息

6.2 进程号和父进程号

getpid()返回调用进程的进程号:

  1. #include <unistd.h>
  2. pit_t getpid(void);

Linux内核限制进程号<=32767。新进程创建时,内核会按顺序将下一个可用进程号分配给它使用。每当进程号达到32767的限制时,内核将重置进程号计数器为300,以便从小整数开始分配。之所以重置为300,是低数值的进程号被系统进程和守护进程长期占用

getppid()可以检索到父进程的进程号:

  1. #include <unistd.h>
  2. pid_t getppid(void);

使用pstree 1命令可以看到进程的家族树。

如果有子进程的父进程终止,则子进程成为孤儿进程,由1号init进程收养,后续对该子进程调用getppid()会返回进程号1。

6.3 进程内存布局

进程分配的内存由各个组成:

  • 文本段(.text):包含进程运行的程序机器语言指令。文本段具有只读属性,一方进程通过错误指针以外修改自身指令。因为多个进程可同时运行同一程序,所以又将文本段设为可共享,如此,一份程序代码的拷贝可以映射到所有这些进程的虚拟地址空间中
  • 初始化数据段(.data):包含显示初始化的全局变量和静态变量。当程序加载到内存时,从可执行文件中读取这些变量的值。
  • 未初始化数据段(.bss):包含未进行显示初始化的全局变量和静态变量。将它和初始化过的数据分开存放,主要原因是程序在磁盘上存储时,没有必要为味精初始化的变量分配存储空间
  • 栈(stack):是一个动态增长和收缩的段,由栈帧组成。系统会为每个当前调用的函数分配一个栈帧栈帧中存放了函数的局部变量、实参和返回值
  • 堆(heap):是可在运行时动态分配内存的一块区域。

使用size *.out可以查看二进制可执行文件的各个段大小:
image.png
image.png