程序编译过程与软件启动过程

系统 1514 0

一、 软件执行流程

1、 软件编译流程

程序编译过程与软件启动过程

Ø 预编译完成宏展开工作。

Ø 为每一个 .cxx 源文件编译一个目标文件( .obj,.o ),目标文件中至少包含二进制的代码段和数据段。在 cxx 源文件中可能会引用在其他 cxx/hxx 中定义的符号,也可能是自己定义的一些符号,这些作用域超过一个 cxx 文件的符号称为“ public 符号”(例如非静态函数)。因此每一个目标文件中也包含一个符号表,用于记录自己引用的符号及自己提供的 public 符号。

Ø 编译器合成这些目标文件成一个库文件( .lib ),同时解析可以找到的符号引用。此时这个库文件包含了二进制的代码段和数据段,同样也会包含一个符号表,因为有一些符号需要引用其他静态 / 动态链接库的导出符号。

Ø 链接器负责把目标的库文件和所有需要引用的静态 / 动态链接库进行链接,即需要首先把静态库合成到可执行文件中。转换相应的符号引用为地址,然后确保所引用的其他动态链接库的符号存在。最后生成可执行文件,可执行文件的符号表只需要记录导入符号表。

2、 软件运行流程

程序编译过程与软件启动过程

Ø 当双击 App.exe 图标启动程序时,执行起来的 App 进程是 shell 调用 CreateProcess 激活的。 桌面上的带有小箭头的快捷方式 (shortcut) 就是一个 shell 链接, shell 负责管理一个叫 " 名字空间 " 的类似文件系统似的“超文件系统” , 它允许应用程序在任何地方在不知访问对象名字和位置的前提下访问到这个对象,此类对象有:文件,目录,驱动器,打印机以及网络资源。而名字空间就是 shell 把这些对象有层次组织起来的一个结构。名字空间为用户和应用程序提供了一种可靠和高效的方法来访问和管理对象。

Ø CreateProcess 函数被调用,系统创建一个“进程内核对象”。进程内核对象可以看作一个操作系统用来管理进程的内核对象,它是系统用来存放关于进程统计信息的地方(一个小的数据结构),真正创建者是 NtCreateProcess 的系统服务函数 ( 也叫执行体服务函数 ) ,他创建了进程内核对象供用户扩展。进程内核对象的初始使用计数为 1 。然后系统为该进程创建 4GB =2^32) 的虚拟地址空间 ( 所谓虚拟就不是真的创建 4GB 的物理内存空间,这些空间不是真在物理内存上 ). 用于加载 App.exe 可执行文件和任何必要的 dll 文件的数据和代码。 系统为该进程创建 4GB 的虚拟地址空间,对于 windows 来说,默认情况下每个用户进程可以占有 2GB 的私有地址空间;操作系统占有剩余的 2GB 空间。

32 x86 系统上 :

0x00000000 0x7fffffff 的空间中存放着 应用程序代码,全局变量,每个线程堆栈, dll 代码。
0x80000000 0xc0000000 的空间中存放着内核和执行体, HAL( 硬件抽象层 ), 引导驱动程序。
0xc0000000 0xc0800000 的空间中存放着进程页表和超空间。
0xc0800000 0xffffffff 的空间中存放着系统高速缓存,分页缓冲池,非分页缓冲池。

Ø CreateProcess 打开应用程序文件 (.exe), 它先扫描该文件的文件头,该文件头里含有文件能运行在那个环境之下,如果是 win32 环境,系统就直接加载文件的代码和数据并输入 (import) 该文件执行所需的 dll 函数。如果不是 win32 环境比如是 os/2 .exe 则先加载相应的环境子系统,再由该环境加载该文件的代码和数据以及该文件执行所需的 dll 函数。

加载器负责把可执行文件的数据段和代码段映射到进程的虚拟内存空间中,读入可执行程序的导入符号表,然后根据这些符号表可以查找出该可执行程序所有依赖的动态链接库。

Ø 进程加载代码和数据完毕后,就开始创建线程来执行进程空间内的代码。进程是静态的,它只是线程的容器。一个进程至少因该有一个线程 (main thread), 其它线程都是主线程通过调用 CreateThread 函数创建的。线程也是核心对象,他的实际创建者是 NtCreateThread 系统服务函数。一个线程只是一个线程核心对象和两个堆栈 ( 一个核心堆栈,用于线程运行在核心态;一个用户堆栈,用于线程运行在用户态 ) ,线程与进程类似,也拥有线程核心对象计数和线程句柄。线程用于描述进程中的运行路径。

Ø 每当进程被初始化时,系统就要创建一个主线程。该线程与 c/c++ 运行时库的启动代码一道开始运行,启动代码则调用进入点函数 ( 就是 main 函数,它也是主线程的进入点函数 ) ,并且继续运行直到进入点函数返回并且 c/c++ 运行时库的启动代码调用 ExitProcess 为止。每个线程都有自己的入口点函数,主线程入口点函数名字必须是 main,wmain,WinMain wWinMain. 而其他的线程入口点函数名字可使用任何名字。每个线程函数必须有一个返回值,它将作为线程的退出代码。对于主线程来说,这个返回值将传给 c/c++ 运行时库的启动函数。 c/c++ 运行时库的启动函数是一个程序调用的第一个函数,它是在程序链接时由链接程序选择相应的启动函数并加到程序的开始处。 c/c++ 运行时库有四个版本的启动函数,他们分别对应不同类型的应用程序。比如,需要 ANSI 字符和字符串的 GUI 应用程序的启动函数是 WinMainCRTStartup, 其对应的进入点函数是 WinMain, 需要 Unicode 字符和字符串的 GUI 应用程序的启动函数是 wWinMainCRTStartup, 其对应的进入点函数是 wWinMain, 而需要 ANSI 字符和字符串的 CUI 应用程序 ( 如控制台 console 程序 ) 的应用程序的启动函数是 mainCRTStartup, 对应的入口点函数为 main; 需要 Unicode 字符和字符串的 CUI 应用程序 ( 如控制台 console 程序 ) 的应用程序的启动函数为 wmainCRTStartup, 对应的入口点函数为 wmain c/c++ 运行时库的启动函数 ( WinMainCRTStartup 为例 ) 的功能如下 :
检索指向新进程的完整命令行指针;检索指向新进程的环境变量的指针;对 c/c++ 运行时的全局变量进行初始化;对 c 运行期的内存单元分配函数 ( 比如 malloc calloc) 和其他低层 I/O 例程使用的内存栈进行初始化。为 C++ 的全局和静态类调用构造函数。

Ø 启动函数条用主函数,进入应用程序的执行。

Ø 当主函数执行完毕返回时,启动函数就调用 c 运行期的 exit() 函数,将返回值 (nMainRetVal) 传递给启动函数。之后 exit() 便开始收尾工作。

Ø 运行时启动函数调用操作系统的 ExitProcess 函数,将 nMainRetVal 传递给它,这使得操作系统能够撤销进程并设置它的 exit 代码。

程序编译过程与软件启动过程


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论