编译程序与操作系统的关系
和很多程序员一样,编译器到目前为止对我还存在一些让我迷惑的地方。比如编译器与操作系统的关系,编译器与CPU的关系,动态链接器从哪查找共享库等。让人困惑的原因有几,第一是编译器的功能角色特殊,编译器是生成程序的程序;第二是编译过程变得越来越复杂[注],一支编译器支持多种程序语言、支持共享库、编译优化,编译与链接可分开等;第三,操作系统的介入。本文试着从第三点——操作系统介入编译过程后对编译器的影响,看看操作系统与编译器有什么关系。
注:编译过程变复杂源于计算机应用变复杂。例如应用程序项目越来越庞大,功能越来越多;为了管理大项目,拆分源程序文件为多个;为了提高程序的性能,目标程序文件的链接期被延迟到运行前;为了程序的灵活安装和升级,引入各种脚本工具,如make,configure(shell)。
编译程序
我们看看编译器的一种传统定义:
编译程序是一支将抽象度较高的编程语言程序(也称源程序)【转化】成抽象度较低的编程语言程序(也称目标程序)的【程序】[注]。抽象度的两端分别是机器语义和人理解语义。【处理器体系】和【编程语言】是一支编译程序的根本属性。
注:以下仅使用【程序】一语,“器”是一种形象的比喻,不够严谨;而软件(software)是产品性的程序,最好只用在商业语境中。
以上对编译程序的定义描述在【现代意义下】对全面认识编译程序是不够的,因为它没有涉及操作系统,没有涉及现代的复杂的程序构建过程。有一定开发经验的程序员都知道,程序的“编译过程”包括编译、链接(静态链接与动态链接)、调试,还可能包括组态配置和安装两步。“编译”一词已经不能很好描述这个过程。整个过程可称为【程序构建】,而编译只是第一步,在这一点上,编译程序在传统意义上与现代意义上产生了不同。由于本文试图讨论编译程序与操作系统的关系,为了避免产生歧义,本文的【编译程序】包括编译和静态链接两个部分,动态链接部分有点特殊,后面会提到它的角色。
下面我们给出有关【操作系统与编译程序关系】的三个问题,并试图回答它们:
- 第一,编译程序与操作系统的关系是什么?
- 第二,编译程序对操作系统有依赖么?
- 第三,编译程序与CPU的关系又是什么?
- 第四,操作系统对C标准库与C编译程序的关系有什么影响?
为了更好的进行下一步讨论,我先给出现代编译程序比较完整的定义,并由定义引出问题:
现代编译程序是一支将某抽象度较高的编程语言程序【转化】为运行在【某软硬体系下】的抽象度较低的编程语言程序的【程序】。所谓硬件体系是指处理器体系,软件体系指操作系统体系。
要回答前三个问题,我们得厘清现代编译程序定义中的【属概念】——程序,并对操作系统有更深一层认识。
程序
程序的分类是多种多样的,比如常见的两分法是【系统程序】和【应用程序】。这是一种粗粒度的按【计算任务】不同的分法。我们看程序的定义:
程序(program)是完成特定【计算任务】的【指令】序列,指令由相应的【图灵机】读取并操作。
由以上对程序的定义可知,还可根据——程序【指令的性质】和读取程序的【图灵机】性质——两个标准再进一步对程序分类。比如,按指令序列是否连续可以分为独立程序和共享程序(使用了共享库);按指令的抽象度可分为高级语言程序和低级语言程序。按【图灵机】的体系可分为X86程序和ARM程序,16位程序和32位程序等。
高级语言程序是不是【程序】?如果是,它的【图灵机】是什么?
我们一般理解下的【程序】是指二进制的可执行文件,那么高级语言的源程序是不是程序?从指令序列的定义看,【高级语言的源程序】是程序,因为【高级语言的源程序】与【二进制的可执行文件】一样,也是指令序列,只不过【高级语言的源程序】的【图灵机】不是CPU,也不是编译器或解释器,而是程序员。【高级语言的源程序】的功能更多体现在程序员间的相互学习和交流。
除了以上基本分类外,现代的程序还会受为其提供虚拟运行环境的操作系统影响,可以根据操作系统的体系属性对程序再分类,例如win32程序,linux程序。
操作系统
操作系统是什么类程序?
操作系统是一类比较独立的系统程序,操作系统有支持各种【图灵机】的体系类型,比如16位DOS,32位Windows,X86的BSD,ARM的 Linux等。而系统程序一般是指一支为应用程序直接提供半成品(为应用程序提供执行的虚拟环境)和协调多个应用程序并行运行的程序。所谓半成品是指,系统程序的一部分(指令序列)也是应用程序的一部分(指令序列),但这部分程序不专属任何应用程序,它是共享的。例如各种新硬件的驱动程序、C标准库函数、POSIX库函数等。而作一个协调程序,操作系统表现出与一般应用程序的程序性,如独立调度的线程,只是它们运行在权力更高的状态下。协调程序如线程调度程序。
非操作系统程序与操作系统的关系
这里的操作系统泛指像Linux这样的现代32位操作系统,而【非操作系统程序】运行在操作系统之上,对操作系统存在可能的依赖的程序。
其实只要是运行在某操作系统之上的程序都会烙上该操作系统的印,对操作系统有依赖,包括编译程序。不过这些程序对操作系统的依赖程度和依赖的内容确实有很多区别。例如一支最简单的【Hello world程序】都会对【操作系统的C库】产生依赖,如果去掉【Hello world程序】的输入输出功能,只作加减或逻辑运算,【Hello world程序】依然会对操作系统有少量依赖,因为【Hello world程序】由运行在该【操作系统上的编译程序】编译的,有特定的目标文件格式,并由该【操作系统的载入程序】载入内存运行[注]。这种只【在形式上】对OS存在依赖的“无用”程序可谓是最独立于OS的程序。在此基础之上,其它程序都对OS有不同程度的依赖,依赖表现在对OS内的各种程序库的依赖,比如C标准库,POSIX系统库,线程库、网络库和其它基于这些基础库的第三方应用代码库。
注:由此可见编译程序与引导程序、SHELL程序一样,是现代操作系统的基本部分。
问题初步解决
编译程序与操作系统的关系
有了以上的对程序以及操作系统本质的一定了解后,我们知道编译程序与操作系统有一定亲缘性。但这种亲缘性的一些表现会让人迷惑。例如Linux发行版可以不安装有编译程序的,只有开发工作站才需要编译程序。而所有Linux发行版的应用程序都可能使用了共享库,需要动态链接这些系统共享库。由此可见,应该分开【开发期】与【运行期】来看待编译程序与操作系统的关系。在开发期,编译程序运行操作系统之上,属于【非操作系统程序】,对操作系统有依赖;在运行期,编译程序的子部分——动态链接程序和加载程序属于操作系统有机部分。
由以可得编译程序与操作系统的关系有:
- 第一,编译程序的编译部分和静态链接部分是运行在操作系统上的系统程序;
- 第二,编译程序的动态链接部分与操作系统的亲缘性更强,所以完全可把动态链接部分独立出来[FIXME:动态链接程序与操作系统具体关系未知];
- 第三,编译程序的编译输出格式是操作系统相关的。
由此可见,编译程序是操作系统相关的,编译程序也是操作系统的功能很重要组成部分,但编译程序没有被集成入操作系统内核内,所以编译程序不算是操作系统的有机组成部分。
编译程序对操作系统的依赖
由上面可得,编译程序是运行操作系统之上【非操作系统程序】,对操作系统有依赖。编译程序是一支【计算集中】更大的程序,它相对于应用程序对OS依赖会少一些,依赖有:
- 形式依赖(由另一支同软硬体系的编译程序[行话本地编译器],编译得到或不同软硬体系的编译程序[行话交叉编译器],交叉编译得到)
- C库依赖,读取高级语言源码程序文件,写入低级语言的目标文件
编译程序与CPU的关系
这个问题在编译程序的定义里已经有答案了,一支编译程序只编译生成一种机器码。我们说编译程序的【操作系统相关性】是后天进化得到的,而编译程序的【处理器相关性】是天生的。
操作系统对C标准库与C编译程序的关系的影响
操作系统对C库没有什么影响,C库是一种通用代码库;但与[FIXME]