from:
http://m.blog.csdn.net/article/details?id=7883614
程序运行到main()函数之前,CPU做了那些事情?main()是程序运行的第一个函数吗?
在早期的计算机中,由于所有程序在运行时所访问的地址都是物理地址,所以各个程序所使用的地址空间不是 互相隔离的,如果因为“Bug”不小心改写了其它程序的数据,那么对于实时系统来说,这是不能容忍的。因为用户非常希望做到,即使其中一个模块出现了问 题,至少不要影响其它功能的正常运行。
由于缺乏有效的内存管理机制,监控程序必须将所有的程序装入内存(RAM)中才能开始运行,从而造成内 存的使用效率极低。如果突然要运行一个程序A,由于内存空间不够,这时只好将其它程序的数据暂时保存到其它存储介质(磁盘)中,等到要用时再读回来。由于 程序所需要的空间是连续的,有可能将程序 B 保存到存储介质中所释放出来的内存空间不够,那么只好将程序 C 再换到存储介质中,然后才将程序 A 读入内存开始运 行。由于内存释放出来的地址的不确定性,有可能是0x3000,也有可能是0x5000,而程序转移的目标地址却是固定的,比如,0x2000,这就要涉 及到程序的“重定位”问题。 其实“重定位”就是给程序中的每个地址重新确定它在物理内存中的位置。
在程序编写的过程中,经常会涉及到主程序调用子程序的情况 。编译程序将源程序翻译成二进制机器码时,事 实上各个模块都是分开翻译(编译)的,因为在翻译主程序时并不知道子程序的地址,所以只好暂时将调用子程序的指令的目标地址搁置,等到合适的时候才将每个 调用子程序的指令修正,然后再填入正确的转移地址参数(链接)。当程序装入内存时,这些位置又要重新计算,那么重新修改目标地址的过程就是重定位。
怎样把编译程序解放出来,使得其在编译时并不关心物理内存的地址,而按自己的意愿给程序编址?唯一的解 决办法就是增加中间层,即使用一种间接的编址方式,也就是对程序中的指令连续的编址,这些地址不是真正的物理地址,而是一种“虚拟地址”(或称逻辑地 址),然后通过某些映射方法,将这个虚拟地址转化为物理地址。通过前面的学习,大家已经知道,物理地址是实实在在存在的,80C51系列单片机有 16 条地 址线,所以可以支持 64KB 的存储空间。而虚拟地址空间则是一种逻辑空间,就像我们给一个班的学生编学号一样,这些学号的集合就是一种虚拟(逻辑)空间, 每个班的同学都有从学号1开始的独立地址空间,由此可以推知,每个运行的程序都有自己独立的虚拟空间,而且每个运行的程序只能访问自己的地址空间,这样就 能做到程序在运行时的有效隔离,从而大大地提高了系统的安全性。
虚拟存储的实现需要依靠硬件的支持,比如,32位 ARM9 以上的 CPU,用其内部集成的 MMU(Memory Managemnet Unit)来进行页映射,在页映射下,CPU接收到的地址是虚拟地址,经过 MMU 转换以后就变成了物理地址。由于 80C51系列单片机没有 MMU,所以无法实现虚拟内存管理机制。
凡是学过 C 语言的人都知道 main() 函数,但未必一定清楚其鲜为人知的“私隐”,下面将为此揭开其神秘的面纱。
对于标准的 C 语言来说,main 是所有 C 程序中都必须包含的一个函数名字。main() 是 C 程序中第一个要执行的函数,必须通过 main() 中的函数调用才能执行其它函数。只有当 main() 执行结束时,整个程序才会执行结束。尽管在逻辑上 main() 是程序中第一个要执行的函数,但在程序行文上,它不一定是第一个函数。
main() 函数是由程序员自行编写的,但其函数说明符是由语言规定好的。它可以定义成无参函数,详见程序清单1。
程序清单 无参数函数
int main(void)
{
......
}
也可以带有 2 个特定参数 argc 与 argy,详见程序清单2。
程序清单 2 int main(int argc,char argy[]) 函数int main(int argc,char argy[])
{
......
}
argc 与 argy 特定参数名字是由语言预定义的标识符,尽管有些 C 编译器也允许使用其它的一些参数,但标准 C 只支持 argc 与 argy。在 C 语言中,编译器处理 main() 函数与其它函数的方式基本上是一样的,其区别主要表现在运行时,编译器可以支持 argc 与 argy 这 2 个特殊的参数。由于计算机仅认识机器码,而不认识 C 语言中的 main() 函数,因此必须有一段程序来识别C编译器编译的 main()函数并调用它。其实在 C 语言的标准中,有一个非常重要的概念,那就是环境。
环境是指程序翻译与执行所在的计算背景(或称计算上下文),环境可以分为翻译环境与执行环境 2 类。对于 C 程序员来说,一般不必关心翻译环境。而在 C 语言的标准中,对执行环境的解释大致如下:
执行环境是指程序经过翻译后执行的环境,执行环境与翻译环境可以是同一环境,也可以是不同环境。 ANSIC 规定了2种执行环境,即独立式执行环境与非独立式执行环境。两者的主要区别在于,独立式执行环境不需要操作系统支持,它实际上是一种裸机执行环 境, 如洗衣机控制器的微控制器实际上就是这样一种执行环境,而非独立式执行环境则需要操作系统的支持,而用于程序设计的执行环境绝大多数都是非独立式执行 环境。ANSIC 只规定了独立式执行环境的最小规则,因为这种环境可能千差万别。但 ANSIC 却仔细地规定了在非独立式环境下程序执行的具体规则,包括程 序启动、程序执行与程序终止的规则。
对于执行环境的规则,不开发编译器是不需要详细了解的。但从使用者的角度来看,可以这样理解:执行环境 是一个软件(及其所运行的硬件)的集合,这个软件提供了基本的库函数和调用 main() 函数的方式。对于非独立执行环境来说,C语言程序通常编译成对应操 作系统的应用程序(一个文件)。假如 C 语言程序已经编译成应用程序abc.exe,如果在命令行中输入“abc.exe [参数字符串]”后,那么操作系统将执行以下步骤:
- 找到 abc.exe 文件并进行分析;
- 根据分析的结果将指令和数据加载到内存的指定位置;
- 将参数字符串存储到特定位置(可能是预处理过的);
- 根据操作系统的不同,执行一些特定的初始化;
- 初始化堆栈指针;
- 将所有的寄存器设置为预定义的值;
- 执行应用程序的第一条指令。
- 编译器会一般将 C 语言程序编译为符合某种格式的目标文件;
- 通过某种烧录或下载工具将目标文件以系统能识别的方式存储到系统的特定位置;
- 复位系统,系统执行复位向量指定的第一条指令。
- 初始化堆栈指针;
- 初始化总线控制器、中断控制器、MMU 等必须初始化的硬件;
- 初始化 C 运行时库;
- 可能为 main() 函数提供参数;
- 调用 main() 函数。
对于独立执行环境来说,main() 函数是一般不返回的。尽管一些编译器也提供返回处理,但都是为停机、软件复位或调用用户提供的函数。
其实 main() 函数与其它函数最大的区别在于 main() 是根函数。在一个标准的 C 语言程序(仅调用标准的库函数和自己编写的函数)中,所有的其它函数 都由 main() 函数直接或间接调用。如果将函数调用关系画成一个图(一般为树形结构),那么一个标准的C语言程序只有一颗函数调用树,其树根就是 main()。
需要注意的是程序只有执行后才会调用别的函数,所以“根函数”是动态执行的概念。
当然,还牵涉到词法分析、语义分析等,在此不再阐述。