程序是怎么执行的

标签: 程序 | 发表时间:2015-04-02 21:07 | 作者:
出处:http://news.cnblogs.com/

Docker 是一个建立在操作系统+编译器基础之上的系统,所以了解操作系统,编译器以及程序运行机制对我们理解 Docker 来说非常重要。本文是一个自己的体会,有很多不精确的地方,目的是希望大家多关注低层,多修炼内功,多读好书。

一直想写篇文章来说明在程序运行过程中操作系统都干了些什么事。下面我试着说明:

首先,任何程序都是有格式的,所谓无规矩不成方圆,任何美的,精巧的事物都是精密组织的,程序也一样。我之前用的最多的是c#与 java,有趣的是,当时很多人嘲笑 java 与c#们一直在用脚本写程序,大概在他们眼里c与c++才是真正的程序。但是,现实就是现实,其实我们都是在一个叫做虚拟机的程序下写托管代码,它掌握着程序的编译,链接,加载,映射与最终执行与终止。它就是操作系统,准确的讲是操作系统+编译器。他们是真正的元虚拟机。

然后我来解释下如何运行一个程序:

程序是精巧与复杂的,熟悉它以后你也会觉得它是脆弱的,因为只要有一个 bit 发生错误,整个系统就会崩溃。这个系统就是执行文件格式,在 linux 下叫 elf(executable linkable format)而 windows 下叫 pe (portable execute)。我想写操作系统第一步就是制定这个规则,不然一切都没有规律。所以我想 linus 牛,但是 ken tomason 有过之而无不及,毕竟你是在人家基础之上发展而来的,计算机世界就是如此没办法,谁让你在人家下面呢?

我以 linux 系统为例,简单讲讲程序由编译链接装载与执行。elf 文件格式分为很多段—section,总体分为只读可执行的代码段与可读可写的数据段。.txt 就是典型的代码段,.data .rodata .symbl .rel .got .plt 都是数据段。那么,编译器负责将程序员写的程序,编译成 elf 文件,代码,注视,代码行对应机器码信息,就是调试信息啦会进去 .txt .code .comment .debug 段,常量与静态变量进入 .data .rodata .bss。接下来,编译器将引用的头文件中的代码(特指静态编译)与引用的 glibc 中的库函数打包(链接)到整个可执行文件中,然后在 elf 文件中设置文件头信息,如段表位置,程序入口位置等信息。当然,这里不得不提的是符号表,与重定位表,他们是整个程序最终能跑起来的关键。gcc 是靠符号,或者说程序是靠符号来链接的,不管是函数还是变量,都是符号而已,所以从侧面讲,写程序跟写文章没啥区别。程序就像个图书馆,每个函数与变量都是书,链接程序好比在图书馆看书,当你看到一个点时,就会叫你去某某位置拿另一本书,翻到特定位置开始继续读,如果没找到就会爆出链接错误。而重定位表就是一次性讲所有对需要跳转的位置进行更改,以确保程序中不存在没有拿到手的书。

好,现在程序已经链接好了,接下来就是操作系统进行装载与执行了。当然这是静态的链接,动态链接会稍微复杂,会写很多,这里不讨论。操作系统会打开 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 了。他们是程序启动参数。还要载入程序运行的环境变量,栈空间,堆空间,也就是静态数据与全局变量部分。然后把程序执行寄存器指向程序开始的地方。开始执行!看似简单,但是很复杂的过程开始了!

好了,这就是简单的程序如何被操作系统执行的简单描述,当然这只是静态链接程序的加载,动态链接稍微复杂点。原理差不多,呵呵。

本文链接

相关 [程序] 推荐:

Android 应用程序

- - CSDN博客推荐文章
Android 应用程序由四个模块构造而成:Activity、Intent 、Content Provider 、Service. 下面简单介绍一下如下模块的含义:. 1、Activity  "活动". 一个Activity就是单独的屏幕,每一个活动都被实现为一个独立的类,并且从活动基类中继承而来,活动类将会显示由视图控件组成的用户接口并对事件作出响应.

Linux程序调试

- - C++博客-首页原创精华区
Linux下的段错误产生的原因及调试方法    原文地址: http://www.upsdn.net/html/2006-11/775.html .    参考地址: http://www.cnblogs.com/khler/archive/2010/09/16/1828349.html .

Cppentry程序开发

- -
最近修改公司线上kafka集群配置然后直接kill掉进程来重启集群发现所有生产者都无法写入数据导致丢了数据,栽了一个大坑,接下来的工作肯定是补坑找原因,就分享一下. 系统环境说明:kafka版本为0.8.1.1,kafka集群配置为10.12.0.23:2181,10.12.0.24:2181,10.12.0.25:2181/kafka,因此在zookeeper中的根路径为:/kafka.

普通程序员、文艺程序员、2B程序员

- 可可 - 宇宙的心弦
希望能引起广大苦逼的正在学或者已经学过c++人的共鸣和会心一笑吧. 如何辨别自己在现实还是虚拟世界.

如何面试程序员?

- bluesnail - 阮一峰的网络日志
你要面试一个程序员,应该问他什么问题. 有人在Hacker News的讨论区里,请求指点,怎么才能在面试中发现合格的人. 众人纷纷出主意,有很多高质量的回帖,我觉得挺有启发,就整理出了下面这篇文章. 首先,最重要的是,你自己一开始就应该想清楚:. 哪些途径和方法可以发现这样的人. 只有明确这些根本性的问题,才能正确高效地完成面试.

从流水程序到SOA

- Allen - 阿朱=行业趋势+开发管理+架构
咱就从函数代码开始谈起,更史前的Goto和汇编代码咱就不谈了. 函数和变量写多了,自然也就发现有些函数和变量互相粘在一起很高耦合,而与其它的一些却没多达关系,于是为了显性化让其他的开发人员知道哪些函数和变量确实关联性很紧密,于是创造了类. 面向对象在80年代的国外代码开发界颇为流行. 但接口思想的风潮在90年代刮起了.

程序员的本质

- Allen - 译言-电脑/网络/数码科技
来源What do programmers really do?.   很多人(包括我岳母)认为计算机变得如此智能,所以在不久的未来将不再需要程序员. 另外一些人认为程序员是天才,他们在电脑前能不断地解决复杂的数学难题. 甚至不少程序员对他们是做什么的都没有清晰的概念.   在这篇文章中,我想给不知情的人解释一下程序员到底是做什么的:.

程序员人生之路

- myartings - 博客园-首页原创精华区
   程序员人生之路(强烈推荐,分析的透彻. ),某程序达人的人生感悟,估计没有半个甲子的时间,是绝对不可能感悟出来的.    相对同时刚出校门同学从事其它行业而言优厚的薪水,以及不断学习更新的专业知识不仅仅让你感到生活的充实,更满足了你那不让外人知的虚荣心. 在刚出校门的几年中,你经常回头看看被你落在后面的同学们,在内心怜悯他们的同时,你也会对自已天天加班的努力工作感到心里平衡:“有付出才会有回报”这句话在那几年中你说的最多,不管是对自已的朋友们还是自已的爱人.

iPhone应用程序推荐

- sylvia - 月光博客
  本文将为大家推荐一些笔者非常喜欢的iPhone应用程序. 注意,并不怎么包括游戏,因为笔者不太喜欢玩游戏,要玩也只玩小游戏. 这也有些遗憾,毕竟iPhone最大的卖点就是丰富的游戏了. 本文主要是推荐实用的软件和系统工具. 对新手应该帮助比较大,老鸟们也可以参考一下. 而且本文也不提供下载链接与安装方法.