您的位置:首页 > 编程语言 > C语言/C++

008-C语言开发的开始

2017-03-07 20:58 260 查看
上次介绍了如何进入保护模式以及完成程序的跳转,那么这一次我们就将转向高级语言编写主要的程序代码。但是在这之前我们必须考虑一个问题,那就是在汇编语言中,你可以自由指定你写的程序的加载和读取位置(至少也是可以指定相对位置),但是像C语言这样的高级语言恐怕就没这么简单了。使用C语言编写程序的时候,编译器会自己去识别栈区和堆去,自己来决定加载位置,具体的方案取决于编译器的选择以及优化参数。这算得上是C语言的缺点,也算是优点。之所以这么说是因为,你无法决定的东西你也就不用去关心,内存的分配你大可交给编译器自己来处理,我们程序员的任务就专心写代码逻辑就好。另一方面,我们为什么选择C语言而不是其他的高级语言呢?这是因为,C语言真的真的是太适合用来写操作系统了。在早先,有两位IT行业的大神,他们一个叫K,一个叫R,合称K&R,他们来原本是在开发UNIX操作系统,但是觉得,用纯粹的汇编来进行开发实在效率太低,而且还不太容易找帮手,因为门槛高,于是两人脑袋一热,创造了一个具备很多汇编语言性质的高级语言,专门用于开发UNIX,这便是C语言(当然那个时候还不叫C语言,后来经过改进以后才更名为C语言)。不过K&R也没有想到在今天,C语言居然可以这么盛行,C语言完成的事情已经远远超过了它本身的职责。但是,我们通过C语言的诞生至少可以证明一件事,C语言非常适合写操作系统,因为它本来就是为了写操作系统而诞生的,这才是老本行。

不过这还不能成为全部的理由,说C语言适合写操作系统,最主要的原因是在于,它同时具有高级语言的思维和汇编语言的灵活。我们看到C语句都是让人类更容易理解的语言,我们写C写的是逻辑,是程序逻辑,而不是计算机的执行逻辑,这使得C具备了高级语言的思维。与此同时,C语言有一个杀手锏,那就是指针,通过指针,我们可以直接去访问内存中的某一地址的内容,而不是像其他高级语言那样对底层实现的封装保护。因此我们说C同时具备了高级语言的思维和汇编语言的灵活,它是一个介于高级语言和汇编语言之间的一个语言。所以,你可以以一个适用于高级程序员的思维(程序逻辑思维)来写C代码,你也可以以一个适用于底层程序员的思维(机器执行思维)来写C代码,这就使得C语言成为了入门程序设计的首选语言,因为它易于上手,并且在上手以后,往高层可行,往底层亦可。不过这也同时造成了C语言易于上手难于深究的特性。想学会C语言很容易,但是想精通C语言,还是需要时间和功底的。说了这么多是想表达,我们所开发的操作系统是横跨底层和高层的,是属于软硬件界面的部分,所以,必须同时拥有高级语言思维和底层机器思维才能写出符合逻辑的优秀的操作系统来。如果非要把这一过程归到软件开发的某一部分的话,这应当属于非常经典又非常深入的面相过程的程序设计。

用C语言来写操作系统,我们不得不多多考虑其语言特性。由于其面相过程性,我们写出的C程序就是由很多个过程(可以暂时把一个过程理解成一个函数吧)组成的,函数之间有着自己的调用逻辑,但是,我们必须制定以下这整个程序的主过程是谁,其他的过程都将会直接或间接呗这一主过程调用。说白一点就是,我们得指定C程序的主函数,并且还要使它编译后被放在软盘的0x1b00的位置(之所以是这个地址,这是由上一次书写的代码来决定的),所以,在将C程序与我们之前写的程序进行拼接时,就要找到这个函数位置,然后才能进行正确的拼接。那么,如何来实现这一过程呢?

在解释这一问题之前,要先明白程序从书写到执行的具体过程。首先来说下汇编程序,一个汇编程序(后缀为.asm)文件将由汇编器来进行转换,把我们写的每一句汇编指令都替换成为对应的机器码,生成一个.bin文件,也就是纯二进制文件。不过这个.bin文件是没办法直接使用的,只有你像我们之前那样,把它当做一个镜像文件加载到软盘里,然后放到计算机上去执行,并且还是要按照BIOS加载的方式来执行才能完成。但是,我们现在的情况并不是这样,因为,我们不是用一个.asm文件就完成了整个操作系统的开发,我们有很多很多的源文件,而最终,这些源文件要整体生成一个镜像文件才可以。那么,要使得这些文件组合在一起,并且是按照正确的方式组合在一起,就需要另外一个工具,叫做链接器。我们在编译源文件的时候不要直接把它生成目标二进制代码,而是生成一个用于链接的中间文件,这个文件中除了程序的具体过程外,应当还需要一个索引表,用于高速链接器,我这个文件里有哪些函数,有哪些变量是用于链接的,换句话说,是可以被其他文件来使用的,与此同时,还需要写出这个文件需要其他文件的哪些函数和变量。链接器集合这些信息之后,就可以把所有的这些用于链接的文件拼接成一个最终的二进制文件了。

用于链接的文件的扩展名以前是用.obj,但是由于后来在平面设计中,一个产品工程的保存扩展名也用了.obj,虽然这二者没有半毛钱关系,但是经常容易搞混,所以后来的链接文件多以.o作为扩展名,其实这二者并没有任何区别,现在都能够见到。所以,这个.o文件和.bin文件还是有区别的,.bin是成品文件,而.o就是用来链接的,它一定要经过链接器的处理才行,哪怕仅仅只有一个.o文件也是如此。

然而,我们的C源文件还需要更多的步骤,我们想,如果能够把.c文件转化为.asm文件,那后面的东西就好办了,而这个过程已经有软件实现了,就是C的原始编译器,例如在GCC中集成的CC1就可以吧.c转化为.gas(也就是GNU的汇编),当然可以继续使用GNU汇编工具编译成.o,也存在gas向asm转化的工具,这个都无所谓了,编译器做的事情。

我们要明白的就是,C语言文件要先呗转化为汇编文件,然后再转化为可链接文件,最终转化为最终的二进制文件。这期间可能会经过很过过程,也会生成很多中间文件,但这些本质是不变的。那么在了解了这些之后,我们就可以放心使用C语言进行开发了。

既然C语言最终还是要被编译成汇编语言,那么,其实C函数和汇编函数是没有什么本质区别的,我们当然可以用C语言去调用汇编函数,也可以用汇编语言去调用C函数,只不过都需要加入链接头(C语言中的体现就是函数声明),剩下的工作交给链接器就好。在开始敲C代码之前,我们还必须了解一件事情,就是所谓C语言中数据类型的实质。在经历了汇编的洗礼之后,我们了解到无论是数据还是指令,在冯诺依曼体系结构的计算机中,没有本质区别。既然指令和数据都没有本质区别了,那数据和数据之间就更没有本质区别了。C语言中的数据类型其实在底层看来都是同样的产物,他们只不过是在内存中一个又一个字节的二进制数而已。那么C语言的数据类型有什么意义呢?这当然是针对高级语义上的意义,同样一段数据,你把它理解成整数,还是浮点数,还是字符,当然会得到不同的意义,注意这个意义是我们人类所理解的意义,而不是对于计算机的意义。我们使用计算机一定不是为了让计算机自娱自乐就好,我们费尽心机苦心积虑到最后的目的就是为了让计算机为我们人类服务。所以,把计算机能够存储的数据按照合适的方式解读出来它才能具有意义。这个步骤是相当重要的。因此,C语言作为高级语言,自然也要达成这个目的。数据类型首先决定了,要一次读取几个字节的内存空间,其次还决定了按照哪种方式来解读这些数据。而至于数据类型之间的类型屏障以及隐式转换,只不过是编译器的保护行为而已。想打破它,用强制类型转换即可。强制类型转换并不会改变数据,只是改变了我们读取数据的方式而已。(注:整型和浮点型的转换同样不会改变原始数据,但并不是简单的换了解读方式,在这之间还做了一步等值转换。)所以,在我们进行底层开发的时候,一定要考虑到这一点。再一点要说明的是,作为C语言的杀手锏——指针,首先我们要清楚指针也只不过是一种数据类型罢了,但是这个数据会被解读为某一内存地址,在实际的开发过程中,我们一定要明白哪些变量是堆栈中的,哪些我们是通过一个指针来直接控制内存中指定位置的数据的,这一点相当重要,如果不搞清楚的话一定会出很多bug,而且无解。关于这些要注意的事项,在以后遇到的时候回专门来说明,也会经常强调。

在了解透彻C语言的这些特性之后,我们就可以正式开始用C语言进行操作系统的开发了。下次将会开始OSMain函数的书写与讲解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: