在大多数程序员的眼中,一个程序的执行时从main函数开始的。可是实际上,事情的真相确实如此吗?其实之前的博客中已经提到在程序的main函数执行之前需要在进程堆栈中提前布置好main的参数。也就可以想见其实程序的执行不是从main函数开始,在main函数之前已经有代码在进行执行了。而这个执行的程序叫做入口函数或入口点,是运行库的一部分。
一个程序的执行的大体过程如下:
1、os创建进城后,将程序的控制权交给入口函数
2、入口函数对运行库和程序执行的环境变量进行初始化,其中包括堆、I/O、线程、全局构造等
3、入口函数完成初始化之后,调用main函数,正式执行程序的主体部分。
4、main执行结束后,返回入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等。
首先说一下入口函数,其入口为_start,之后调用_libdc_start_main,对_libdc_start_main传入环境变量,main入口地址,main函数的参数等信息。由该函数完成对栈空间的布局。
其次在函数结束的时候调用exit,其循环调用atexit注册的函数,最后调用系统调用exit()。
C/C++的运行库在linux下为libc,其中包括入口函数,和各种标准库函数等。其主要功能包括:
1、启动与退出时环境变量和堆栈空间的管理
2、标准函数,c的标准库函数的实现
3、I/O 和I/O封装的实现
4、堆,和堆内存的封装和实现。
关于堆的管理,程序在使用malloc或new的时候需要从堆中申请空间。该空间随意的申请和释放可以交给内核去做,这样频繁的系统调用会造成很大的系统开销,实际上堆是由运行库管理的,当堆空间不足时就使用brk()系统调用向os申请一块内存空间(注意此处仅仅涉及到虚拟内存)。关于堆的分配算法,其中包括空闲链表法和位图法等。
另外运行库是平台相关的,与os紧密结合,实质上是os与C之间的抽象层。真正的c库提供和很多功能,不过向进程、线程的创建等需要由系统调用来完成,glibc中有一个可选的pthread库,用于创建线程是c运行库的超集。
linux的运行库包括两个部分,一部分是头文件,位于/usr/include中,一部分是库的二进制文件,二进制包括动态和静态两个版本,动态版本位于/lib/libc.so.6,静态版本位于/usr/lib/libc.a中
我们也可以不使用glibc,仅仅通过系统调用来构建一个自己的c运行库,实现包括入口函数,进程相关操作,堆操作、文件字符操作等。