现在程序已经链接好了,接下来是操作系统进行装载与执行了。当然这是静态的链接,动态链接会稍微复杂,会写很多,这里不讨论。操作系统会打开elf文件的装载视图,它能根据装载视图的段表—segment这跟section在中文都是段,没办法!这个视图是将数据与代码分开的,相似section链接在一起,所以数量也比section少很多,目的是在装载时节约内存。因为,段映射到内存是要地址对齐的,如按照地址4096(一般簇大小为4k)整除来对齐,这样做是有好处的,能减少内存碎片,加快磁盘读写速度,磁盘小扇区512byte,所以整数倍读取能少一次寻址,当然效率更高。这在游戏引擎,数据库设计领域比较多见,毕竟io是大瓶颈,所以再这程序时也要考虑对象占用内存大小是否是操作系统小簇的整数倍来判断一个程序是否是高人所做。
  回来,操作系统会先读取可执行的文件头,因为里面有运行程序的信息,如段表位置,程序入口,程序类型等。对于操作系统重要的是段表与程序入口。其中段表是elf中有多少段,每个段在文件中的偏移,入口则是常说得main函数的虚拟地址。这里出现一个问题,程序非得以main函数开始吗?其实看出来了,不用!只是gcc认定符号main为c语言的入口,其他程序照抄罢了,当然你可以加入编译条件更改入口即可。gcc是stallman写的,他是个黑客,全世界只要运行c的地方,他都能黑,呵呵。
  操作系统在读取可执行程序头时做了三件事:
  1、创建虚拟内存空间来容纳一个进程;
  2、根据文件头内容建立程序虚拟内存地址与elf文件的映射关系表,vma(virtual memory area)结构;
  3、初始化程序的栈空间与堆空间。
  下面解释下这三个过程:
  1、虚拟内存。虚拟内存是编译器与操作系统的一个约定。任何程序在编译无链接时得地址都是虚拟地址。为什么要用虚拟地址这个问题说来话长。话说在很久以前,大家都很穷,都没内存,但是要运行的程序很多,系统不可能为每个程序分配单独的内存,同时领导还要求同时所有程序都要运行,咋办呢?办法总比问题多,咱可以分时嘛,你上完cpu我再上,但是大家各自在用cpu时,其他只能看着,直到一个人说”下一个”,这个人不管在干嘛都得放弃,让其他人用cpu。这样对所有人都公平,而且每个人在用cpu是能感觉到cpu只被它独有,用户体验还挺好。所以一次解决可所有问题。而,这个组织人,是那个喊“下一个”的家伙是操作系统。那,说这么多,跟虚拟地址有啥关系呢?其实仔细想想如果大家都是用物理地址,而彼此在运行时都独占系统资源,那前一个程序修改了我的数据咋办,得了,都由操作系统说了算吧,它做内存映射的维护,大家都用统一的地址空间,但是运行时映射到不同的物理内存互不干扰来。所以你可以看到所有linux程序都从相同的虚拟地址开始执行。
  2、建立内存到文件得映射。我们知道,程序都不是一次性加载到内存的,而是一段段的,这是由的copy on write规则约束而来的。而这一段也是规定好大小的一般是操作系统簇的大小,也叫一页。当程序运行过程中发现某个数据在内存中没有则会报一个页读取错误,并触发操作系统的缺页中断。这时要靠操作系统通过读取elf文件头建立的从文件系统到虚拟内存的映射来获取了。它等于是程序运行时到程序得一个索引结构,存储了运行时程序虚拟内存地址到文件地址的对应表。
  3、第三步简单,是操作系统载人main函数后面跟的那个char argc与char*argv了。他们是程序启动参数。还要载入程序运行的环境变量,栈空间,堆空间,也是静态数据与全局变量部分。然后把程序执行寄存器指向程序开始的地方。开始执行!看似简单,但是很复杂的过程开始了!
  好了,这是简单的程序如何被操作系统执行的简单描述,当然这只是静态链接程序的加载,动态链接稍微复杂点,原理差不多。