第六章 进程总结


进程是可执行程序的实例

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

1:二进制格式标识符:执行方式

2:机器语言指令:对程序算法进行编码

3:程序入口地址:标识程序执行时的起始指令位置

4:数据:各种变量和常量

5:符号表和重定位表

6:共享库和动态链接库

7:许多其他信息

每个进程有一个进程号PID,每个进程有自己父进程号,getppid()可以获取父进程号。

子进程的父进程终止,子进程就会变成“孤儿”,pstree可以查看“家族树“(family tree)

进程内存布局

每个进程分配的内存由很多部分组成,通常称之为”段(Segment)“

文本段:包含了程序运行的程序机器语言指令。
初始化数据段:包含显式初始化的全局变量和静态变量
未初始化数据段:包含了未显式初始化的全局变量和静态变量(BSS段)
栈(Stack):是一个动态增长收缩的段,由栈帧(stack frames)组成。系统会为每个当前调用的函数分配一个栈帧。栈帧中储存了函数的局部变量,实参和返回值
堆(heap):是运行时为(变量)动态进行内存分配的一块区域。

虚拟内存(6.4节的讲解实在牛b,看的我十分佩服,对虚拟内存有了更深入的理解)

上述的进程内布局忽略了一个事实,内存布局是存在虚拟内存中的。

linux,像大多数现代内核一样,采用了虚拟内存管理技术,此技术利用了大多数程序共有的典型特性,即局部访问性,以求高效使用CPU和RAM(物理内存)。有这两种类型的局部性。

?? 空间局部性(Spatial locality):是指程序倾向于访问在最近访问过的内存地址附近的内存(由于指令是顺序执行的,且有时会按顺序处理数据结构)。
?? 时间局部性(Temporal locality):是指程序倾向于在不久的将来再次访问最近刚访问过的内存地址(由于循环)。

正是由于访问局部性特征,使得程序即便仅有部分地址空间存在于RAM 中,依然可能得
以执行。

虚拟内存的规划是将程序使用的内存切割成小型的固定大小的页。同样的将RAM划分成与虚拟内存页尺寸相同的页帧,任何时刻,每个程序仅有部分分页需要驻留在物理内存页帧中,这些页构成驻留集(resident set),程序未使用的页保存在交换区(swap area)——磁盘内。若进程欲访问的页面目前并未驻留在物理内存中,将会发生页面错误(page fault),内核即刻挂起进程的执行,同时从磁盘中将该页面载入内存。

为支持虚拟内存这种组织方式,内核需要为每个进程维护一张页表(page table)(见图6-2)。该页
表描述了每页在进程虚拟地址空间(virtual address space)中的位置(可为进程所用的所有虚
拟内存页面的集合)。页表中的每个条目要么指出一个虚拟页面在RAM 中的所在位置,要么
表明其当前驻留在磁盘上。

 虚拟内存管理使进程的虚拟地址空间与RAM 物理地址空间隔离开来,这带来许多优点。

进程与进程、进程与内核相互隔离,所以一个进程不能读取或修改另一进程或内核的内存。这是因为每个进程的页表条目指向RAM(或交换区)中截然不同的物理页面集合。

适当情况下,两个或者更多进程能够共享内存。这是由于内核可以使不同进程的页表条目指向相同的RAM 页。(共享内存技术)

便于实现内存保护机制;也就是说,可以对页表条目进行标记,以表示相关页面内容是可读、可写、可执行亦或是这些保护措施的组合。多个进程共享RAM 页面时,允许每个进程对内存采取不同的保护措施。例如,一个进程可能以只读方式访问某页面,而另一进程则以读写方式访问同一页面。

程序员和编译器、链接器之类的工具无需关注程序在RAM 中的物理布局.

因为需要驻留在内存中的仅是程序的一部分,所以程序的加载和运行都很快。而且,一个进程所占用的内存(即虚拟内存大小)能够超出RAM 容量。

栈和栈帧

函数的调用和返回使栈的增长和收缩呈线性。X86-32 体系架构之上的Linux(和多数其
他Linux 和UNIX 实现),栈驻留在内存的高端并向下增长(朝堆的方向)。专用寄存器—
栈指针(stack pointer),用于跟踪当前栈顶。每次调用函数时,会在栈上新分配一帧,每当函
数返回时,再从栈上将此帧移去。

 (用户)栈,可能包含多个栈帧,每个栈帧包含如下信息

函数实参和局部变量
函数调用的链接信息

命令行参数(argc, argv)

每个C 语言程序都必须有一个称为main()的函数,作为程序启动的起点。当执行程序时,
命令行参数(command-line argument)(由shell 逐一解析)通过两个入参提供给main()函数。
第一个参数int argc,表示命令行参数的个数。第二个参数char *argv[],是一个指向命令行参
数的指针数组,每一参数又都是以空字符(null)结尾的字符串。

剩下的就不多讲了。

 环境列表

每一个进程都有与其相关的称之为环境列表(environment list)的字符串数组,或简称为环
境(environment)。其中每个字符串都以名称=值(name=value)形式定义。因此,环境是“名称
-值”的成对集合,可存储任何信息。常将列表中的名称称为环境变量(environment variables)

新进程在创建之时,会继承其父进程的环境副本。这是一种原始的进程间通信方式,却
颇为常用。环境(environment)提供了将信息从父进程传递给子进程的方法。由于子进程只
有在创建时才能获得其父进程的环境副本,所以这一信息传递是单向的、一次性的。子进程
创建后,父、子进程均可更改各自的环境变量,且这些变更对对方而言不再可见。

printenv 可显示当前环境列表

extern char **environ 可访问环境列表

int putenv(char *string);可修改环境变量,添加变量

int setenv(const char *name,const char *value,int overwrite);复制环境变量到新的缓冲区,对这个环境变量的修改不会影响到其父进程。

非局部跳转:setjmp()和longjmp()

这俩东西可牛b了,setjump设立跳转点,longjump可以跳转到设立的跳转点,实现跨函数跳转。但是少用,像潘多拉莫克一样,不好把握。

相关