1,虚拟内存。虚拟内存是编译器与操作系统的一个约定。任何程序在编译无链接时得地址都是虚拟地址。为什么要用虚拟地址这个问题说来话长。话说在很久以前,大家都很穷,都没内存,但是要运行的程序很多,系统不可能为每个程序分配单独的内存,同时领导还要求同时所有程序都要运行,咋办呢?办法总比问题多,咱可以分时嘛,你上完 cpu 我再上,但是大家各自在用 cpu 时,其他只能看着,直到一个人说"下一个",这个人不管在干嘛都得放弃,让其他人用 cpu。这样对所有人都公平,而且每个人在用 cpu 是能感觉到 cpu 只被它独有,用户体验还挺好。所以一次解决可所有问题。而,这个组织人,就是那个喊“下一个”的家伙就是操作系统。那,说这么多,跟虚拟地址有啥关系呢?其实仔细想想如果大家都是用物理地址,而彼此在运行时都独占系统资源,那前一个程序修改了我的数据咋办,得了,都由操作系统说了算吧,它做内存映射的维护,大家都用统一的地址空间,但是运行时映射到不同的物理内存互不干扰来。所以你可以看到所有 linux 程序都从相同的虚拟地址开始执行。
2. 建立内存到文件得映射。我们知道,程序都不是一次性加载到内存的,而是一段段的,这是由著名的 copy on write 规则约束而来的。而这一段也是规定好大小的一般是操作系统簇的大小,也叫一页。当程序运行过程中发现某个数据在内存中没有则会报一个页读取错误,并触发操作系统的缺页中断。这时就要靠操作系统通过读取 elf 文件头建立的从文件系统到虚拟内存的映射来获取了。它等于是程序运行时到程序得一个索引结构,存储了运行时程序虚拟内存地址到文件地址的对应表。
3. 好了,第三步最简单,就是操作系统载人 main 函数后面跟的那个 char argc 与 char*argv 了。他们是程序启动参数。还要载入程序运行的环境变量,栈空间,堆空间,也就是静态数据与全局变量部分。然后把程序执行寄存器指向程序开始的地方。开始执行!看似简单,但是很复杂的过程开始了!
好了,这就是简单的程序如何被操作系统执行的简单描述,当然这只是静态链接程序的加载,动态链接稍微复杂点。原理差不多,呵呵。