一、机器语言和汇编语言

在开始之前,我们先回想一下之前了解到的CPU。CPU靠指令来做各种运算,每个CPU都有一些自己能够直接识别的指令,我们称之为CPU的"指令集",或者叫做机器语言(机器码)。

我们也提到过了,这些指令集其实都是一些0101的二进制组合,计算机会将它们转换为一系列高低电平的电信号,配合我们前面了解的继电器、门电路等完成对硬件的控制。而不同的CPU结构,即使是对同一功能的实现,其电平脉冲都是不一样的,所以我们说CPU的指令集对于同一类的CPU是“独有的”。而这些01组合非常难以识别,要以这种形式操作计算机,那难度那不是一般的大,比如将用0, 1数字编成的程序代码打在纸带或卡片上:1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机进行运算。早期的程序员们就是这么干的,简直6的飞起,但是他们觉得很累!而且对于不同结构的CPU还要使用不同01组合,想想都难过,所以,他们发明了“汇编语言”。

汇编语言其实可以认为是机器语言的另一种表现形式,它使用助记符和标号等来代替原来复杂的01组合,比如ROR、SHR等等。于是乎,程序员们就开始了用汇编语言编写程序。虽然汇编语言对于人类来说要简单的多了,但是计算机还是只认识0101这种格式啊,所以在引入汇编语言的同时,同时引入了一个“中间翻译人”:汇编编译器。汇编编译器的作用就是将用汇编语言编写的程序编译成计算机能认识的机器语言:也就是程序员们最开始用纸带、卡片编程的时候使用的语言。

由于这个汇编语言是机器语言的一种符号表示,而不同类型的CPU有不同的机器语言,所以汇编语言程序和CPU指令系统有很大的关系,这就导致了其在可移植性上有很大的局限性。一般计算机专业都会开设编译原理这种课程,它对于我们理解编程原理是很有帮助的,因为后面出现的高级语言都屏蔽了很多较底层的操作。比如将某个寄存器的内容送到另一个寄存器。

二、从机器语言到操作系统

上面说到的汇编语言,即使相对于机器语言好用的多,但是我们已经知道,它只是机器语言的另一种表现形式,其仍然要面向处理器编程,太过贴近于底层,编程和调试工程量仍然巨大,专业要求性也太高。所以,人们提出了一种更为接近自然语言的“算法语言”。算法语言则更加贴近人们的认知,极大的简化了编码调试工程,且它是独立于计算机的指令系统的,可移植性强。虽然这样说,但是我们也要明确,不论什么时候,程序运行最终都要依赖于CPU的指令集,这点是不变的。就如我们的汇编语言需要对应的汇编编译器一样,要保障算法语言的可移植性,也必须要求对应计算机上提供该语言的编译系统,能够将算法语言通过编译汇编,最终转换为对应CPU认识的机器语言。算法语言的提出是里程碑式的,我们使用的C、C++、VB等等都属于算法语言。

历史上出现的第一款算法语言是ALGO,之后剑桥大学在ALGO基础上设计出CPL语言,又将CPL改进为BCPL。BCPL是早期比较典型的面向过程的高级语言,它以一个主函数开始,可以将功能模块添加到各个函数中,函数已经支持递归、嵌套等等。但是BCPL在字符串处理、内存管理上都有不小的缺点,于是没过多久,美国贝尔实验室的Ken Thompson(图领奖得主之一)就将其进行了优化改进,取BCPL的首字母命名,称之为“B”语言。

同样是Ken Thompson这位大佬,因为他想玩游戏的缘故,就使用B语言开发出了第一款UNIX操作系统。之后Dennis M.Ritchie(C语言之父)因为同样的原因加入其中,而他主要负责改良B语言。没过多久,Ritchie就在B语言的基础上设计出了一门新的语言,他取BCPL的第二个字母命名,称其为:C语言。没错,就是我们熟悉的C,他也由此被称为“C语言之父”、“Unix之父”。之后一年不到,C语言的主体就完成了,然后他们就毫不犹豫地将Thompson之前使用B语言开发的UNIX“推翻”,使用C语言重写了一个新的UNIX。随着UNIX的发展,C语言自身也在不断地完善。直到今天,各种版本的UNIX内核和周边工具仍然使用C语言作为最主要的开发语言,其中还有不少继承Thompson和Ritchie之手的代码。(历史事件于网络收集)

操作系统本身其实也是应用程序,是使用编程语言开发出来的,而这些编程语言都是一步步从机器语言演变进化而来,其中有一个不可或缺的东西,就是我们不只一次提到的:编译器。而编译器是一个很大的概念,比如本地编译器、交叉编译器等,感兴趣的可以自行查阅相关资料。这里我们只需要知道,编译就是一个从源码(比如高级语言编写的程序)到目标代码(比如机器语言或汇编语言)的过程,一个程序(包括操作系统本身)要运行在一台计算机上,就必须要将其代码通过编译汇编等操作将其转化为对应计算机CPU能识别的指令,也就是机器语言。

笼统的说,Linux能在很多不同的机器上跑是因为其开源,人们可以“自主”、“自由”的提供对应多个不同CPU架构的编译器。为什么Windows XP只能运行在X86、X64架构的机器上?则是它又不开源,又没有提供支持其它CPU架构的编译器。感兴趣的可以去了解了解“WINTEL联盟”。

三、高级语言、编译器和操作系统

我们在在初学编程语言的时候都会了解到,一个源代码文件要经过编译器处理才能被执行,而它又和我们所使用的操作系统有什么关系呢?比如我们使用visual studio学习c或者c++,代码写完之后,要点击“编译”,然后再运行,计算机就开始执行我们的程序了。visual studio本身集成了编译器,我们点击“编译”的时候也就是在使用编译器。可是运行呢?(注:高级语言的运行方式一般分为编译型和解释型,编译型就是一次性编译成目标代码由机器运行,而解释型则先翻译成中间代码,再由解释器对中间代码进行解释运行,每执行一次都会进行翻译,因此相对于编译型效率较低,但跨平台性好。这个暂时不是我们讨论的重点,感兴趣的朋友可以先自行查阅资料)。

接下来需要先提到一个名词,叫做可执行文件。这个比较容易理解,我们平时使用windows,像exe就是一种可执行文件,双击可以运行。我们使用一个操作系统运行我们的程序,需要遵循一些约定,比如数据对齐、调用约定、编码等等,叫做:ABI(Application Binary Interface),也就是应用程序二进制接口。ABI掩盖了各种细节,它允许编译好的目标代码在使用兼容ABI的系统中无需改动就能运行。UNIX的主要执行文件格式ELF,就是作为ABI开发的,而对于Windows,则是PE文件格式(当然,COFF、PE都在使用,.exe使用PE文件格式,.obj使用COFF文件格式)。但不论是ELF还是PE,它们都源于COFF文件 ,即通用对象文件格式(戳这里看百科解释),而对象文件格式也不只COFF这一种。

现在再来说我们的VC。使用VC的编译器编译后生成的目标文件(.obj),其实就是使用COFF文件格式存储的,运行该目标文件其实就依托于ABI。所以,对于不兼容ABI的系统,目标代码是无法直接运行的。比如,在windows下编译的目标代码,拿到Linux直接运行是行不通的,需要一些特殊手段才行。

注:本文内容为博主个人理解,很多地方可能不一定正确,如有错误,希望读者朋友不吝指出,谢谢!