• 2004-05-11

    Make雕虫技

    Tag:向下走

    在一个规模相对较大的项目中,常常许多目录中都有自己的Makefile,负责处理这个目录中的内容。
    顶层Makefile中可以使用不同的方法调用子目录中的Makefile,下面是我所知道的两种:
    1 进入子目录编译,比如:
      cd kernel; make clean;
    2 利用Makefile的特性,比如:
      $(MAKE) clean -C kernel
    Linux的makefile使用了后一种方法。

    随之而来的是一个有趣的问题。

    编写Makefile的时候,我们常常定义一些变量,比如:
    CC = gcc

    基于程序员懒惰的特性,我们当然不想在多个地方定义这个变量,我们期望的效果是顶层定义,底层使用,也就是说,上面的定义只出现在顶层的文件中,至于底层Makefile,我们只是简单$(CC)就可以使用了。

    虽然这是一个合理的要求,但如果你只把这个变量定义在顶层的Makefile中,就期望使用前面提到方式的执行子目录的makefile能够如你所愿,那你只有失望的份了。底层Makefile是无法看到顶层Makefile定义的变量,make没有提供这种支持。

    要解决这个问题,我知道的也是两种方法:
    包含文件
    同我们了解的C/C++一样,Makefile中也支持包含文件。只要把定义了变量的文件包含在Makefile中,Makefile就可以看到这个变量定义了。
    听了这种说法,性子急的人直接就在底层的Makefile中把顶层的Makefile包含进来。请再给我个说话的机会,不要这么直接,好吗?
    我们可以这样做,把变量定义同具体的编译内容分离开来,专门用一个文件存放变量定义,假设这个文件叫Make.rules,用这种方法,我们把原来定义在顶层Makefile中的变量分离了出来,而我们只要对这个Makefile稍做修改,加上一句
      include Make.rules
    对于底层需要这些变量的Makefile,只要按照目录结构包含Make.rules就可以顺利的工作了。
    如果你问为什么不直接包含整个Makefile,那你一定算不上一个好程序员,好程序员怎么能容忍过分的暴露呢?
     
    导出变量
    这种方式是在变量定义之后,将它导出来,对于上面CC定义,我们还要额外的写上
      export CC
    这样,采用前面提到过的方式处理子目录的时候,就可以使用这些变量了。
    这是Linux的makefile使用的方式。
    同包含文件相比,这种方式有一个缺陷,因为这些变量都是在make执行的过程中定义的,所以如果没有在顶层执行make,那么这些变量实际上是不存在的。换句话说,直接在某个子目录中执行make,很有可能的结果是因为变量没有定义而执行失败。
     
    其实,对于解释我们的问题来说,CC并不是一个很好的例子。
    可能你会遇到这样的现象,底层的makefile中并不包含CC的定义,但它依然可能编译代码,我知道你已经准备置疑我前面的结论了。
    但在置疑之前请仔细看一下,执行的是我们定义的gcc吗?
    真正运行的是cc。

    为什么会这样?
    原因很简单,因为make中有一批预定义的变量,其中就包括CC,它预定义的值就是cc。如果你在应用中定义的变量同预定义的变量具有相同的变量名,那么起作用的将是你定义的值。
    用下面这个命令可以了解make有哪些预定义变量:
    make -p

  • 2004-04-07

    引导在哪里?

    Tag:向下走

    操作系统讲什么?快翻开你那本落满灰尘的操作系统教材看看。
    我的答案来自给了Linus开创世纪动力的《Operating Systems: Design and Implementation》(中文版《操作系统:设计与实现》)。除了前面的引言和后面的代码,书中讲到了进程、输入/输出系统、存储器管理、文件系统。

    不知道你有没有想过这样一个问题,操作系统如何开始自己的旅程。
    想当年,我兴致勃勃的准备以“好好学习,天天向上”的态度认认真真的学学操作系统。结果没有几天,我就被这个问题捆住了,久久不得解脱。

    当我通过各种渠道对操作系统的启动过程有了些许的认识时,另一个问题又冒了出来。既然启动是操作系统必不可少的一部分,那为什么各种操作系统教材对它却如此不屑。
    我能想到的答案只有平台差异。不是吗?大家有着各自的机器结构、有着各自的汇编。胆敢涉足此处,千差万别的细节足以让所谓的权威论断碰得头破血流。

    如果不是听说了Multiboot Specification,我一定会坚持认为自己得到就是标准答案。

    在Multiboot Specification出现之前,几乎每个OS都拥有自己的boot loader。也许你知道Boot loader要占据你可爱的硬盘上唯一的MBR(Master Boot Record,主引导扇区),如果每个OS要有自己的boot loader的话,那结果只能是只有一个操作系统可以启动。想像现在这样既装Linux又装Windows显然是不现实的。

    如果能够制订一套游戏规则,大家按照统一的方式启动,上面的问题也就迎刃而解了。在众多自由操作系统开发者的共同努力之下,Multiboot Specification应运而生。
    可能Multiboot Specification现在还只是在你眼前闪亮的新名词。如果我请出GRUB和LILO两个大名鼎鼎的名字,不知能否让你产生一丝亲切感。它们两个就是遵循Multiboot Specification的boot loader,Multiboot Specification的制订者之一就是GRUB的创始人Erich Boleyn。正是GRUB和LILO这样的boot loader的存在,我们才能过上Linux和Windows和平共处的生活。这样的boot loader替我们完成了本来要由我们自己的工作,对于我们的PC而言,这些工作可能加载内核、切换至保护模式、设置内核参数等等。

    回头看看我先前的问题。
    Multiboot Specification的出现,使得大家可以共享一个boot loader,于是boot loader变不再是操作系统的一部分了。
    这是由于这个原因,后来的开发者可以将更多的把精力集中在操作系统内核的开发上,至于那些繁琐的引导细节,我们就当它不存在吧!
    也许有人要说,不自己开发boot loader,我如何掌控全局?
    即便自己开发boot loader,加载MBR的过程依然由机器完成,我们不可能100%的掌控全局,支持不自己开发boot loader的另外一个理由就是众所周知的“不要重新发明轮子”。自己学习当然例外。

    这就是开放的力量,没有开放,累傻小子去吧!
    说到开放,不得不再说微软几句。尝试过Linux和Windows共存的朋友一定体会过重装Windows找不到Linux的悲惨。原因很简单,Windows的boot loader不符合Multiboot Specification,一旦它抢占了MBR这块高地,其它系统就只能忍气吞声了。
    微软霸道,无处不在!