五分钟让你深刻理解进程
五分钟让你深刻理解进程
作者:harryshayne 撰写日期:2011.10.29
一、什么是程序?什么是进程?什么是线程?
首先先回顾一下经典的定义。程序(program)是为实现特定目标或解决特定问题而用计算机语言编写的命令序列的集合。进程(process)是一个执行中的程序的一个实例。线程(thread)是"进程"中某个单一顺序的控制流。
接下来通过两个生活中的实例来给出一个感性认识。第一个实例是拍电影的场景。程序就相当于一个剧本,事先写好的,而进程就相当于依照这个剧本来拍电影的整个过程,包括这个过程的环境,线程则是整部电影的各个小场景的拍摄过程,线程之间必须有个同步,就如同一部电影很多场景的拍摄的先后顺序要协调一下。大家想想,操作系统相当于电影整个过程中的哪个角色?
另一个实例就是元旦舞会的举办。举办舞会也需要事先写好的策划书,然后按照策划书来举办舞会,整个舞会也有很多不同的小事情。程序就相当于策划书,进程就相当于舞会的整个过程,线程就相当于舞会中的不同的小事情。
接下来让我们从初级程序员的角度来看看这些概念。程序不用说了,就是你用计算机语言编写的代码。进程对于程序来说,相当于一个容器,程序的运行就是把程序装入容器,然后操作系统根据程序的命令使得这个容器运转起来,期间还可能需要向容器中加入一些其他的东西(比如DLL等)来使得程序正常运行。当应用程序被运行两次时,前一个进程容器里的东西并不会被倒掉,系统会找一个新的进程容器来容纳它,重复上述的过程。程序的执行是为了完成某项任务,而完成这项任务可能有很多小步骤需要完成,如果这些小步骤可以并发执行,则可以把这些小步骤作为进程中的线程,线程就是进程这个大步骤中的不同的需要协调的小步骤。
进程中有什么内容呢?它的虚拟地址空间布局是什么样的呢?
图 1 进程组成内容
图2 进程的用户地址空间布局
应用程序的格式是什么样的呢?我们编写的代码和编译好的应用程度的对应关系是怎样的呢?
图3 ELF文件的格式
图4 代码和ELF格式应用程序的对应关系
进程的特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变
为什么要引入进程?
从理论角度看,是对正在运行的程序过程的抽象。传统的程序本身是一组指令的集合,是一个静态的概念,无法描述程序在内存中的执行情况,即我们无法从程序的字面上看出它何时执行,何时停顿,也无法看出它与其它执行程序的关系,因此,程序这个静态概念已不能如实反映程序并发执行过程的特征。
从实现角度看,进程是一种数据结构,目的在于清晰地刻划动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
一个程序的生与死是怎样的过程呢?
下面讲一个EXE格式的程序的具体运行过程。首先点击exe应用程序的图标,操作系统的shell进程(也就是explorer.exe桌面进程)就会监测到这一动作,它便查找注册表中应用程序的相关信息(如果是绿色版软件就不需要查找注册表),然后Explorer.exe以应用程序文件名调用CreateProcess函数来创建一个进程,CreateProcess函数做的工作就是创建进程内核对象(操作系统用它来管理进程的一个数据结构),创建进程的虚拟地址空间,初始化虚拟地址空间(也就是把exe文件代码,数据以及其他一些必须的DLL文件映射到这个空间,还记不记得进程相当于一个容器?),创建主线程(一个进程至少要有一个主线程才能运行,可以说进程只是充当一个容器的作用,而线程才是执行用户代码的载体。就像一个运作的企业,它只是提供一个大环境,真正做事的都是里面的人)。主线程获得CPU时间后(因为可能有很多程序同时执行,操作系统要切换进程,我们的进程才能获得CPU时间),接下来就是主线程要执行代码了,首先是找到主函数入口(当然之前要做一些运行库的初始化,因为代码中要调用这些库函数),然后开始执行代码咯(像windows程序的话就是先注册窗口类,然后创建窗口或者对话框,然后等待用户的点击或者输入,因为windows程序是基于消息的,你点击一下就会发消息给程序,程序就根据消息的不同来执行相应的代码,然后循环点击-响应这个过程)。最后当你点击关闭的时候,程序就会关闭窗口,退出消息循环的过程,然后做一些内存清理的工作(因为你运行的时候系统给你分配了很多资源啊,结束了肯定要退还啊),最后调用ExitProcess函数来销毁进程(销毁空间啊,内核对象啊等),然后程序的运行就终止了。
回顾一下:
1、Shell(Explorer.exe)调用CreateProcess函数激活exe 程序
2、系统创建一个进程内核对象,引用计数置为1
3、系统为进程创建一个4GB 的进程虚拟地址空间
4、PE 装载器把exe 的代码映射到地址空间,并查找ImportTable 引入相关的动态链接库(DLLs)
5、系统为进程创建一个主线程,线程得到CPU 后,把CS:IP指向.text节中的程序进入点(OEP),此处是一条JMP 指令,它跳到XXXCRTStartup函数处执行
6、这里完成c/c++运行期库的一些初始化设置,包括c++构造函数的调用全局变量,静态变量的初始化
7、调用WinMain/main函数,进入主函数
8、注册窗口类,创建窗口,显示窗口,更新窗口,进入消息循环
9、销毁窗口,退出消息循环,销毁进程,程序运行结束。