GNU make 指南

译者按: 本文是一篇介绍 GNU Make 的文章,读完后读者应该基本掌握了 make 的用法。而 make 是所有想在 Unix (当然也包括 Linux )系统上编程的用户必须掌握的工具。如果你写的程序中没有用到 make ,则说明你写的程序只是个人的练习程序,不具有任何实用的价值。也许这么说有点 儿偏激,但 make 实在是应该用在任何稍具规模的程序中的。希望本文可以为中国的 Unix 编程初学者提供一点儿有用的资料。中国的 Linux 用户除了学会安装红帽子以外, 实在应该尝试写一些有用的程序。个人想法,大家参考。


C-Scene 题目 #2
多文件项目和 GNU Make 工具
作者: 乔治富特 (Goerge Foot)
电子邮件: george.foot@merton.ox.ac.uk
Occupation: Student at Merton College, Oxford University, England
职业:学生,默尔顿学院,牛津城大学,英格兰
IRC匿名: gfoot

拒绝承诺:作者对于任何因此而对任何事物造成的所有损害(你所拥有或不 拥有的实际的,抽象的,或者虚拟的)。所有的损坏都是你自己的责任,而 与我无关。

所有权: “多文件项目”部分属于作者的财产,版权归乔治富特1997年 五月至七月。其它部分属 CScene 财产,版权 CScene 1997年,保留所有 版权。本 CScene 文章的分发,部分或全部,应依照所有其它 CScene 的文章 的条件来处理。

0) 介绍
~~~~~~~~~~~~~~~
本文将首先介绍为什么要将你的C源代码分离成几个合理的独立档案,什么时 候需要分,怎么才能分的好。然后将会告诉你 GNU Make 怎样使你的编译和连 接步骤自动化。对于其它 Make 工具的用户来说,虽然在用其它类似工具时要 做适当的调整,本文的内容仍然是非常有用的。如果对你自己的编程工具有怀 疑,可以实际的试一试,但请先阅读用户手册。

1) 多文件项目
~~~~~~~~~~~~~~~~~~~~~~
  • 1.1为什么使用它们?

    首先,多文件项目的好处在那里呢?
    它们看起来把事情弄的复杂无比。又要 header 文件,又要 extern 声明,而且如果需要查找一个文件,你要在更多的文件里搜索。

    但其实我们有很有力的理由支持我们把一个项目分解成小块。当你改 动一行代码,编译器需要全部重新编译来生成一个新的可执行文件。 但如果你的项目是分开在几个小文件里,当你改动其中一个文件的时 候,别的源文件的目标文件(object files)已经存在,所以没有什么 原因去重新编译它们。你所需要做的只是重现编译被改动过的那个文 件,然后重新连接所有的目标文件罢了。在大型的项目中,这意味着 从很长的(几分钟到几小时)重新编译缩短为十几,二十几秒的简单 调整。

    只要通过基本的规划,将一个项目分解成多个小文件可使你更加容易 的找到一段代码。很简单,你根据代码的作用把你的代码分解到不同 的文件里。当你要看一段代码时,你可以准确的知道在那个文件中去 寻找它。

    从很多目标文件生成一个程序包 (Library)比从一个单一的大目标文件 生成要好的多。当然实际上这是否真是一个优势则是由你所用的系统 来决定的。但是当使用 gcc/ld (一个 GNU C 编译/连接器) 把一个程 序包连接到一个程序时,在连接的过程中,它会尝试不去连接没有使 用到的部分。但它每次只能从程序包中把一个完整的目标文件排除在 外。因此如果你参考一个程序包中某一个目标档中任何一个符号的话, 那么这个目标文件整个都会被连接进来。要是一个程序包被非常充分 的分解了的话,那么经连接后,得到的可执行文件会比从一个大目标 文件组成的程序包连接得到的文件小得多。

    又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那 就有很多好处——可以很容易的追踪到臭虫,这些模块经常是可以用 在其它的项目里的,同时别人也可以更容易的理解你的一段代码是干 什么的。当然此外还有许多别的好处……

    1.2 何时分解你的项目

    很明显,把任何东西都分解是不合理的。象“世界,你们好”这样的 简单程序根本就不能分,因为实在也没什么可分的。把用于测试用的 小程序分解也是没什么意思的。但一般来说,当分解项目有助于布局、 发展和易读性的时候,我都会采取它。在大多数的情况下,这都是适 用的。(所谓“世界,你们好”,既 'hello world' ,只是一个介 绍一种编程语言时惯用的范例程序,它会在屏幕上显示一行 'hello world' 。是最简单的程序。)

    如果你需要开发一个相当大的项目,在开始前,应该考虑一下你将 如何实现它,并且生成几个文件(用适当的名字)来放你的代码。 当然,在你的项目开发的过程中,你可以建立新的文件,但如果你 这么做的话,说明你可能改变了当初的想法,你应该想想是否需要 对整体结构也进行相应的调整。

    对于中型的项目,你当然也可以采用上述技巧,但你也可以就那么开 始输入你的代码,当你的码多到难以管理的时候再把它们分解成不同 的档案。但以我的经验来说,开始时在脑子里形成一个大概的方案, 并且尽量遵从它,或在开发过程中,随着程序的需要而修改,会使开 发变得更加容易。

    1.3 怎样分解项目

    先说明,这完全是我个人的意见,你可以(也许你真的会?)用别的 方式来做。这会触动到有关编码风格的问题,而大家从来就没有停止 过在这个问题上的争论。在这里我只是给出我自己喜欢的做法(同时 也给出这么做的原因):
    • i) 不要用一个 header 文件指向多个源码文件(例外:程序包 的 header 文件)。用一个 header定义一个源码文件的方式 会更有效,也更容易查寻。否则改变一个源文件的结构(并且 它的 header 文件)就必须重新编译好几个文件。

      ii) 如果可以的话,完全可以用超过一个的 header 文件来指向同 一个源码文件。有时将不可公开调用的函数原型,类型定义 等等,从它们的C源码文件中分离出来是非常有用的。使用一 个 header 文件装公开符号,用另一个装私人符号意味着如果 你改变了这个源码文件的内部结构,你可以只是重新编译它而 不需要重新编译那些使用它的公开 header 文件的其它的源文 件。

      iii) 不要在多个 header 文件中重复定义信息。 如果需要, 在其中一个 header 文件里 #include 另一个,但 是不要重复输入相同的 header 信息两次。原因是如果你以后改 变了这个信息,你只需要把它改变一次,不用搜索并改变另外一 个重复的信息。

      iv) 在每一个源码文件里, #include 那些声明了源码文件中的符 号的所有 header 文件。这样一来,你在源码文件和 header 文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。

    1.4 对于常见错误的注释

    • a) 定义符 (Identifier) 在源码文件中的矛盾:在C里,变量和函数的缺 省状态是公用的。因此,任何C源码档案都可以引用存在于其它源 码档中的通用 (global) 函数和通用变量,既使这个档案没有那个变 量或函数的声明或原型。因此你必须保证在不同的两个档案里不能 用同一个符号名称,否则会有连接错误或者在编译时会有警告。

      一种避免这种错误的方法是在公用的符号前加上跟其所在源文件有 关的前缀。比如:所有在 gfx.c 里的函数都加上前缀“gfx_”。如果 你很小心的分解你的程序,使用有意义的函数名称,并且不是过分 使用通用变量,当然这根本就不是问题。

      要防止一个符号在它被定义的源文件以外被看到,可在它的定义前 加上关键字“static”。这对只在一个档案内部使用,其它档案都 都不会用到的简单函数是很有用的。

      b) 多次定义的符号: header 档会被逐字的替换到你源文件里 #include 的位置的。因此,如果 header 档被 #include 到一个以上的源文件 里,这个 header 档中所有的定义就会出现在每一个有关的源码文件 里。这会使它们里的符号被定义一次以上,从而出现连接错误(见 上)。

      解决方法: 不要在 header 档里定义变量。你只需要在 header 档里声明它们然后在适当的C源码文件(应该 #include 那个 header 档的那个)里定义它们(一次)。对于初学者来说,定义和声明是 很容易混淆的。声明的作用是告诉编译器其所声明的符号应该存在, 并且要有所指定的类型。但是,它并不会使编译器分配贮存空间。 而定义的做用是要求编译器分配贮存空间。当做一个声明而不是做 定义的时候,在声明前放一个关键字“extern”。

      例如,我们有一个叫“counter”的变量,如果想让它成为公用的, 我们在一个源码程序(只在一个里面)的开始定义它:“int counter;”,再在相关的 header 档里声明它:“extern int counter;”。

      函数原型里隐含着 extern 的意思,所以不需顾虑这个问题。

      c) 重复定义,重复声明,矛盾类型:
      请考虑如果在一个C源码文件中 #include 两个档 a.h 和 b.h, 而 a.h 又 #include 了 b.h 档(原因是 b.h 档定义了一些 a.h 需要的类型),会发生什么事呢?这时该C源码文件 #include 了 b.h 两次。因此每一个在 b.h 中的 #define 都发生了两次,每一 个声明发生了两次,等等。理论上,因为它们是完全一样的拷贝, 所以应该不会有什么问题,但在实际应用上,这是不符合C的语法 的,可能在编译时出现错误,或至少是警告。

      解决的方法是要确定每一个 header 档在任一个源码文件中只被包 含了一次。我们一般是用预处理器来达到这个目的的。当我们进入 每一个 header 档时,我们为这个 header 档 #define 一个巨集 指令。只有在这个巨集指令没有被定义的前提下,我们才真正使用 该 header 档的主体。在实际应用上,我们只要简单的把下面一段 码放在每一个 header 档的开始部分:

      #ifndef FILENAME_H
      #define FILENAME_H

      然后把下面一行码放在最后:

      #endif

      用 header 档的档名(大写的)代替上面的 FILENAME_H,用底线 代替档名中的点。有些人喜欢在 #endif 加上注释来提醒他们这个 #endif 指的是什么。例如:

      #endif /* #ifndef FILENAME_H */

      我个人没有这个习惯,因为这其实是很明显的。当然这只是各人的 风格不同,无伤大雅。

      你只需要在那些有编译错误的 header 档中加入这个技巧,但在所 有的 header 档中都加入也没什么损失,到底这是个好习惯。

    1.5 重新编译一个多文件项目

    清楚的区别编译和连接是很重要的。编译器使用源码文件来产生某种 形式的目标文件(object files)。在这个过程中,外部的符号参考并 没有被解释或替换。然后我们使用连接器来连接这些目标文件和一些 标准的程序包再加你指定的程序包,最后连接生成一个可执行程序。 在这个阶段,一个目标文件中对别的文件中的符号的参考被解释,并 报告不能被解释的参考,一般是以错误信息的形式报告出来。

    基本的步骤就应该是,把你的源码文件一个一个的编译成目标文件的格 式,最后把所有的目标文件加上需要的程序包连接成一个可执行文件。 具体怎么做是由你的编译器决定的。这里我只给出 gcc (GNU C 编译 器)的有关命令,这些有可能对你的非 gcc 编译器也适用。

    gcc 是一个多目标的工具。它在需要的时候呼叫其它的元件(预处理 程序,编译器,组合程序,连接器)。具体的哪些元件被呼叫取决于 输入文件的类型和你传递给它的开关。

    一般来说,如果你只给它C源码文件,它将预处理,编译,组合所有 的文件,然后把所得的目标文件连接成一个可执行文件(一般生成的 文件被命名为 a.out )。你当然可以这么做,但这会破坏很多我们 把一个项目分解成多个文件所得到的好处。

    如果你给它一个 -c 开关,gcc 只把给它的文件编译成目标文件, 用源码文件的文件名命名但把其后缀由“.c”或“.cc”变成“.o”。 如果你给它的是一列目标文件, gcc 会把它们连接成可执行文件, 缺省文件名是 a.out 。你可以改变缺省名,用开关 -o 后跟你指定 的文件名。

    因此,当你改变了一个源码文件后,你需要重新编译它: 'gcc -c filename.c' 然后重新连接你的项目: 'gcc -o exec_filename *.o'。 如果你改变了一个 header 档,你需要重新编译所有 #include 过 这个档的源码文件,你可以用 'gcc -c file1.c file2.c file3.c' 然后象上边一样连接。

    当然这么做是很繁琐的,幸亏我们有些工具使这个步骤变得简单。 本文的第二部分就是介绍其中的一件工具:GNU Make 工具。

    (好家伙,现在才开始见真章。您学到点儿东西没?)

2) GNU Make 工具
~~~~~~~~~~~~~~~~
  • 2.1 基本 makefile 结构

    GNU Make 的主要工作是读进一个文本文件, makefile 。这个文 件里主要是有关哪些文件(‘target’目的文件)是从哪些别的 文件(‘dependencies’依靠文件)中产生的,用什么命令来进行 这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果 目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一 个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。 (目的文件不一定是最后的可执行档,它可以是任何一个文件。)

    makefile 一般被叫做“makefile”或“Makefile”。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定,它会寻 找“makefile”或“Makefile”,因此使用这两个名字是最简单 的。

    一个 makefile 主要含有一系列的规则,如下:

    : ...
    (tab)<command>
    (tab)<command>
    .
    .
    .

    例如,考虑以下的 makefile :

    === makefile 开始 ===
    myprog : foo.o bar.o
      gcc foo.o bar.o -o myprog

    foo.o : foo.c foo.h bar.h
      gcc -c foo.c -o foo.o

    bar.o : bar.c bar.h
      gcc -c bar.c -o bar.o
    === makefile 结束 ===

    这是一个非常基本的 makefile —— make 从最上面开始,把上 面第一个目的,‘myprog’,做为它的主要目标(一个它需要保 证其总是最新的最终目标)。给出的规则说明只要文件‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将 会被执行。

    但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟 上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc -o foo.o foo.c' 将会执行,从而更新 文件 foo.o 。

    接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。

    现在, make 回到‘myprog’的规则。如果刚才两个规则中的任 何一个被执行,myprog 就需要重建(因为其中一个 .o 档就会比 ‘myprog’新),因此连接命令将被执行。

    希望到此,你可以看出使用 make 工具来建立程序的好处——前 一章中所有繁琐的检查步骤都由 make 替你做了:检查时间戳。 你的源码文件里一个简单改变都会造成那个文件被重新编译(因 为 .o 文件依靠 .c 文件),进而可执行文件被重新连接(因为 .o 文件被改变了)。其实真正的得益是在当你改变一个 header 档的时候——你不再需要记住那个源码文件依靠它,因为所有的 资料都在 makefile 里。 make 会很轻松的替你重新编译所有那 些因依靠这个 header 文件而改变了的源码文件,如有需要,再 进行重新连接。

    当然,你要确定你在 makefile 中所写的规则是正确无误的,只 列出那些在源码文件中被 #include 的 header 档……

    2.2 编写 make 规则 (Rules)

    最明显的(也是最简单的)编写规则的方法是一个一个的查 看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被 包括的文件所包括的文件……然后你会发现要对越来越多的文件 进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸 色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的 电脑显示器,停止编程。到低有没有些容易点儿的方法呢?

    当然有!向编译器要!在编译每一个源码文件的时候,它实在应 该知道应该包括什么样的 header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件 做为目的,而这个C文件和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以 相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc,那些用角括 号包围的 header 档将不会被包括。(这会节省一些编译时间)

    由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让 make 使用它的隐含的规则(参考下面的 2.4 节)。

    2.3 Makefile 变量

    上面提到 makefiles 里主要包含一些规则。它们包含的其它的东 西是变量定义。

    makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make 的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:
    • i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。

      ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使用一个不同的编译器,你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。

      iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall -O -g);如果你把这组选项存 入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。
    要设定一个变量,你只要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用 这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在 下面,我们把前面的 makefile 利用变量重写一遍:

    === makefile 开始 ===
    OBJS = foo.o bar.o
    CC = gcc
    CFLAGS = -Wall -O -g

    myprog : $(OBJS)
      $(CC) $(OBJS) -o myprog

    foo.o : foo.c foo.h bar.h
      $(CC) $(CFLAGS) -c foo.c -o foo.o

    bar.o : bar.c bar.h
      $(CC) $(CFLAGS) -c bar.c -o bar.o
    === makefile 结束 ===

    还有一些设定好的内部变量,它们根据每一个规则内容定义。三个 比较有用的变量是 $@, $< 和 $^ (这些变量不需要括号括住)。 $@ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第 一个依靠文件,而 $^ 扩展成整个依靠的列表(除掉了里面所有重 复的文件名)。利用这些变量,我们可以把上面的 makefile 写成:

    === makefile 开始 ===
    OBJS = foo.o bar.o
    CC = gcc
    CFLAGS = -Wall -O -g

    myprog : $(OBJS)
      $(CC) $^ -o $@

    foo.o : foo.c foo.h bar.h
      $(CC) $(CFLAGS) -c $< -o $@

    bar.o : bar.c bar.h
      $(CC) $(CFLAGS) -c $< -o $@
    === makefile 结束 ===

    你可以用变量做许多其它的事情,特别是当你把它们和函数混合 使用的时候。如果需要更进一步的了解,请参考 GNU Make 手册。 ('man make', 'man makefile')

    2.4 隐含规则 (Implicit Rules)

    请注意,在上面的例子里,几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和相关文件里产生 .o 文件,这是一个标准的步 骤。其实 make 已经知道怎么做——它有一些叫做隐含规则的内 置的规则,这些规则告诉它当你没有给出某些命令的时候,应该 怎么办。

    如果你把生成 foo.o 和 bar.o 的命令从它们的规则中删除, make 将会查找它的隐含规则,然后会找到一个适当的命令。它的命令会 使用一些变量,因此你可以按照你的想法来设定它:它使用变量 CC 做为编译器(象我们在前面的例子),并且传递变量 CFLAGS (给 C 编译器,C++ 编译器用 CXXFLAGS ),CPPFLAGS ( C 预 处理器旗标), TARGET_ARCH (现在不用考虑这个),然后它加 入旗标 '-c' ,后面跟变量 $< (第一个依靠名),然后是旗 标 '-o' 跟变量 $@ (目的文件名)。一个C编译的具体命令将 会是:

    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@

    当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开关输出的码可以直接用在一个 makefile 里。

    2.5 假象目的 (Phony Targets)

    假设你的一个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文件,但这两个文件是相互独立的——如果一 个文件需要重建,并不影响另一个。你可以使用“假象目的”来 达到这种效果。一个假象目的跟一个正常的目的几乎是一样的, 只是这个目的文件是不存在的。因此, make 总是会假设它需要 被生成,当把它的依赖文件更新后,就会执行它的规则里的命令 行。

    如果在我们的 makefile 开始处输入:

    all : exec1 exec2

    其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 'all' 做为它的主要目的,每次执行时都会尝试把 'all' 更新。但既然这行规则里没有哪个命令来作用在一个叫 'all' 的 实际文件(事实上 all 并不会在磁碟上实际产生),所以这个规 则并不真的改变 'all' 的状态。可既然这个文件并不存在,所以 make 会尝试更新 all 规则,因此就检查它的依靠 exec1, exec2 是否需要更新,如果需要,就把它们更新,从而达到我们的目的。

    假象目的也可以用来描述一组非预设的动作。例如,你想把所有由 make 产生的文件删除,你可以在 makefile 里设立这样一个规则:

    veryclean :
      rm *.o
      rm myprog

    前提是没有其它的规则依靠这个 'veryclean' 目的,它将永远 不会被执行。但是,如果你明确的使用命令 'make veryclean' , make 会把这个目的做为它的主要目标,执行那些 rm 命令。

    如果你的磁碟上存在一个叫 veryclean 文件,会发生什么事?这 时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是 最新的了(所有的依靠文件都已经是最新的了),所以既使用户明 确命令 make 重新产生它,也不会有任何事情发生。解决方法是标 明所有的假象目的(用 .PHONY),这就告诉 make 不用检查它们 是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目 的需要被更新。在 makefile 里加入下面这行包含上面规则的规则:

    .PHONY : veryclean

    就可以了。注意,这是一个特殊的 make 规则,make 知道 .PHONY 是一个特殊目的,当然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它们都是假象目的。

    2.6 函数 (Functions)

    makefile 里的函数跟它的变量很相似——使用的时候,你用一个 $ 符号跟开括号,函数名,空格后跟一列由逗号分隔的参数,最后 用关括号结束。例如,在 GNU Make 里有一个叫 'wildcard' 的函 数,它有一个参数,功能是展开成一列所有符合由其参数描述的文 件名,文件间以空格间隔。你可以像下面所示使用这个命令:

    SOURCES = $(wildcard *.c)

    这行会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。

    另一个有用的函数是 patsubst ( patten substitude, 匹配替 换的缩写)函数。它需要3个参数——第一个是一个需要匹配的 式样,第二个表示用什么来替换它,第三个是一个需要被处理的 由空格分隔的字列。例如,处理那个经过上面定义后的变量,

    OBJS = $(patsubst %.c,%.o,$(SOURCES))

    这行将处理所有在 SOURCES 字列中的字(一列文件名),如果它的 结尾是 '.c' ,就用 '.o' 把 '.c' 取代。注意这里的 % 符号将匹 配一个或多个字符,而它每次所匹配的字串叫做一个‘柄’(stem) 。 在第二个参数里, % 被解读成用第一参数所匹配的那个柄。

    2.7 一个比较有效的 makefile

    利用我们现在所学的,我们可以建立一个相当有效的 makefile 。 这个 makefile 可以完成大部分我们需要的依靠检查,不用做太大 的改变就可直接用在大多数的项目里。

    首先我们需要一个基本的 makefile 来建我们的程序。我们可以让 它搜索当前目录,找到源码文件,并且假设它们都是属于我们的项 目的,放进一个叫 SOURCES 的变量。这里如果也包含所有的 *.cc 文件,也许会更保险,因为源码文件可能是 C++ 码的。

    SOURCES = $(wildcard *.c *.cc)

    利用 patsubst ,我们可以由源码文件名产生目标文件名,我们需 要编译出这些目标文件。如果我们的源码文件既有 .c 文件,也有 .cc 文件,我们需要使用相嵌的 patsubst 函数呼叫:

    OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))

    最里面一层 patsubst 的呼叫会对 .cc 文件进行后缀替代,产生的结 果被外层的 patsubst 呼叫处理,进行对 .c 文件后缀的替代。

    现在我们可以设立一个规则来建可执行文件:

    myprog : $(OBJS)
      gcc -o myprog $(OBJS)

    进一步的规则不一定需要, gcc 已经知道怎么去生成目标文件 (object files) 。下面我们可以设定产生依靠信息的规则:

    depends : $(SOURCES)
      gcc -M $(SOURCES) > depends

    在这里如果一个叫 'depends' 的文件不存在,或任何一个源码文件 比一个已存在的 depends 文件新,那么一个 depends 文件会被生 成。depends 文件将会含有由 gcc 产生的关于源码文件的规则(注 意 -M 开关)。现在我们要让 make 把这些规则当做 makefile 档 的一部分。这里使用的技巧很像 C 语言中的 #include 系统——我 们要求 make 把这个文件 include 到 makefile 里,如下:

    include depends

    GNU Make 看到这个,检查 'depends' 目的是否更新了,如果没有, 它用我们给它的命令重新产生 depends 档。然后它会把这组(新) 规则包含进来,继续处理最终目标 'myprog' 。当看到有关 myprog 的规则,它会检查所有的目标文件是否更新——利用 depends 文件 里的规则,当然这些规则现在已经是更新过的了。

    这个系统其实效率很低,因为每当一个源码文件被改动,所有的源码 文件都要被预处理以产生一个新的 'depends' 文件。而且它也不是 100% 的安全,这是因为当一个 header 档被改动,依靠信息并不会 被更新。但就基本工作来说,它也算相当有用的了。

    2.8 一个更好的 makefile

    这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修 改的用在大部分项目里。我主要把它用在 djgpp 上,那是一个 DOS 版的 gcc 编译器。因此你可以看到执行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。

    === makefile 开始 ===

    ######################################
    #
    # Generic makefile
    #
    # by George Foot
    # email: george.foot@merton.ox.ac.uk
    #
    # Copyright (c) 1997 George Foot
    # All rights reserved.
    # 保留所有版权
    #
    # No warranty, no liability;
    # you use this at your own risk.
    # 没保险,不负责
    # 你要用这个,你自己担风险
    #
    # You are free to modify and
    # distribute this without giving
    # credit to the original author.
    # 你可以随便更改和散发这个文件
    # 而不需要给原作者什么荣誉。
    # (你好意思?)
    #
    ######################################

    ### Customising
    # 用户设定
    #
    # Adjust the following if necessary; EXECUTABLE is the target
    # executable's filename, and LIBS is a list of libraries to link in
    # (e.g. alleg, stdcx, iostr, etc). You can override these on make's
    # command line of course, if you prefer to do it that way.
    #
    # 如果需要,调整下面的东西。 EXECUTABLE 是目标的可执行文件名, LIBS
    # 是一个需要连接的程序包列表(例如 alleg, stdcx, iostr 等等)。当然你
    # 可以在 make 的命令行覆盖它们,你愿意就没问题。
    #

    EXECUTABLE := mushroom.exe
    LIBS := alleg

    # Now alter any implicit rules' variables if you like, e.g.:
    #
    # 现在来改变任何你想改动的隐含规则中的变量,例如

    CFLAGS := -g -Wall -O3 -m486
    CXXFLAGS := $(CFLAGS)

    # The next bit checks to see whether rm is in your djgpp bin
    # directory; if not it uses del instead, but this can cause (harmless)
    # `File not found' error messages. If you are not using DOS at all,
    # set the variable to something which will unquestioningly remove
    # files.
    #
    # 下面先检查你的 djgpp 命令目录下有没有 rm 命令,如果没有,我们使用
    # del 命令来代替,但有可能给我们 'File not found' 这个错误信息,这没
    # 什么大碍。如果你不是用 DOS ,把它设定成一个删文件而不废话的命令。
    # (其实这一步在 UNIX 类的系统上是多余的,只是方便 DOS 用户。 UNIX
    # 用户可以删除这5行命令。)

    ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)
    RM-F := rm -f
    else
    RM-F := del
    endif

    # You shouldn't need to change anything below this point.
    #
    # 从这里开始,你应该不需要改动任何东西。(我是不太相信,太NB了!)

    SOURCE := $(wildcard *.c) $(wildcard *.cc)
    OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))
    DEPS := $(patsubst %.o,%.d,$(OBJS))
    MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
    MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) /
    $(patsubst %.d,%.cc,$(MISSING_DEPS)))
    CPPFLAGS += -MD

    .PHONY : everything deps objs clean veryclean rebuild

    everything : $(EXECUTABLE)

    deps : $(DEPS)

    objs : $(OBJS)

    clean :
      @$(RM-F) *.o
      @$(RM-F) *.d

    veryclean: clean
      @$(RM-F) $(EXECUTABLE)

    rebuild: veryclean everything

    ifneq ($(MISSING_DEPS),)
    $(MISSING_DEPS) :
      @$(RM-F) $(patsubst %.d,%.o,$@)
    endif

    -include $(DEPS)

    $(EXECUTABLE) : $(OBJS)
      gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))

    === makefile 结束 ===

    有几个地方值得解释一下的。首先,我在定义大部分变量的时候使 用的是 := 而不是 = 符号。它的作用是立即把定义中参考到的函 数和变量都展开了。如果使用 = 的话,函数和变量参考会留在那 儿,就是说改变一个变量的值会导致其它变量的值也被改变。例 如:

    A = foo
    B = $(A)
    # 现在 B 是 $(A) ,而 $(A) 是 'foo' 。
    A = bar
    # 现在 B 仍然是 $(A) ,但它的值已随着变成 'bar' 了。
    B := $(A)
    # 现在 B 的值是 'bar' 。
    A = foo
    # B 的值仍然是 'bar' 。

    make 会忽略在 # 符号后面直到那一行结束的所有文字。

    ifneg...else...endif 系统是 makefile 里让某一部分码有条件的 失效/有效的工具。 ifeq 使用两个参数,如果它们相同,它把直 到 else (或者 endif ,如果没有 else 的话)的一段码加进 makefile 里;如果不同,把 else 到 endif 间的一段码加入 makefile (如果有 else )。 ifneq 的用法刚好相反。

    'filter-out' 函数使用两个用空格分开的列表,它把第二列表中所 有的存在于第一列表中的项目删除。我用它来处理 DEPS 列表,把所 有已经存在的项目都删除,而只保留缺少的那些。

    我前面说过, CPPFLAGS 存有用于隐含规则中传给预处理器的一些 旗标。而 -MD 开关类似 -M 开关,但是从源码文件 .c 或 .cc 中 形成的文件名是使用后缀 .d 的(这就解释了我形成 DEPS 变量的 步骤)。DEPS 里提到的文件后来用 '-include' 加进了 makefile 里,它隐藏了所有因文件不存在而产生的错误信息。

    如果任何依靠文件不存在, makefile 会把相应的 .o 文件从磁碟 上删除,从而使得 make 重建它。因为 CPPFLAGS 指定了 -MD , 它的 .d 文件也被重新产生。

    最后, 'addprefix' 函数把第二个参数列表的每一项前缀上第一 个参数值。

    这个 makefile 的那些目的是(这些目的可以传给 make 的命令行 来直接选用):

    everything:(预设) 更新主要的可执行程序,并且为每一个 源码文件生成或更新一个 '.d' 文件和一个 '.o' 文件。

    deps: 只是为每一个源码程序产生或更新一个 '.d' 文件。

    objs: 为每一个源码程序生成或更新 '.d' 文件和目标文件。

    clean: 删除所有中介/依靠文件( *.d 和 *.o )。

    veryclean: 做 `clean' 和删除可执行文件。

    rebuild: 先做 `veryclean' 然后 `everything' ;既完全重建。

    除了预设的 everything 以外,这里头只有 clean , veryclean , 和 rebuild 对用户是有意义的。

    我还没有发现当给出一个源码文件的目录,这个 makefile 会失败的 情况,除非依靠文件被弄乱。如果这种弄乱的情况发生了,只要输入 `make clean' ,所有的目标文件和依靠文件会被删除,问题就应该 被解决了。当然,最好不要把它们弄乱。如果你发现在某种情况下这 个 makefile 文件不能完成它的工作,请告诉我,我会把它整好的。

3 总结
~~~~~~~~~~~~~~~

我希望这篇文章足够详细的解释了多文件项目是怎么运作的,也说明了 怎样安全而合理的使用它。到此,你应该可以轻松的利用 GNU Make 工 具来管理小型的项目,如果你完全理解了后面几个部分的话,这些对于 你来说应该没什么困难。

GNU Make 是一件强大的工具,虽然它主要是用来建立程序,它还有很多 别的用处。如果想要知道更多有关这个工具的知识,它的句法,函数, 和许多别的特点,你应该参看它的参考文件 (info pages, 别的 GNU 工具也一样,看它们的 info pages. )。

- 作者: bobodong 2005年09月27日, 星期二 14:17  回复(0) |  引用(0) 加入博采

编译你自己的Linux内核(Kernel

编译你自己的Linux内核(Kernel)

[illustratie]

摘要:

你马上就会发现,你也可以获得(get),配置(configure),编译(compile)和安装(install)属于你自己的Linux内核(Kernel)。



 

引言

为什么你想要编译并安装一个完全属于你自己的内核(Kernel)? 可能是下列原因中的一种吧:
  • 新内核提供更好的硬件支持能力。
  • 新内核有某些特殊的优点,例如,对多处理器(SMP)更好的支持, 或者是支持USB。这些特性已经被加入到2.4.x内核中。
  • 新内核修正了一些BUG。
  • 你自己定制的内核去除了多余的元素,因此它更快,更稳定。
编译(或说“轧制”)你自己的内核需要对计算机(译者注:不仅要对机器本身有了解,还要对Linux系统有一定的了解)有一定的理解——这是一个难题。 因此一个Linux新手是根本不会尝试编译内核的。这篇文章中展示了一些在使用“make xconfig”命令编译内核过程中的截图。 使用这个命令(“make xconfig”),用户可以通过图形用户界面(GUI)和鼠标来对内核进行处理。 这里有差不多40张截图,它们解释了在某种特定的情况下为什么你需要或不能选择某个特定的内核选项(options)。 将这40幅截图逐一讨论看上去似乎有些夸张,但这的确是解释内核的内部工作机理(internal workings)以及特定内核选项存在的原因和工作原理的比较好的方法。 这些截图是kernel-2.4.6的。最新的内核是2.4.19,但是除去在菜单中有一些新的项目(e.g.对新硬件的支持)以外, 屏幕的显示以及编译的过程应该是一样的。建议你在开始编译前把这篇文章打印出来,这样你就可以随时查找必要的信息!

本文的结构如下。首先讨论你在Internet的什么地方能够找到内核的源码以及如何安装这些源码, 然后通过屏幕截图讨论使用图形界面配置内核。 一旦内核配置完毕,它就需要被编译,但是一个新编译的内核并不能直接使用。 首先,新内核需要和引导程序“lilo”一起安装,然后,在使用lilo前你必须写配置文件“/etc/lilo.conf”。 另外,还有需要指出一些特殊的要点(specific points),比如笔记本电脑需要的PCMCIA支持。 看上去象信用卡的PCMCIA,小型板卡(small inserts)通常负责联网任务,在2.4.x的内核中已经可以直接提供支持。 但之前的内核需要重新编译安装后才能提供PCMCIA支持。SuSE Linux还有一个问题,那就是ALSA声卡驱动程序的音效支持。 这些驱动程序并不是内核的一部分,它们需要被编译进去然后重新安装,因为原始的驱动程序通常没有工作。 更麻烦的是,从一个系列的内核转到另一个系列的内核,比如从2.2.x系列转到2.4.x系列, 可能会由于使用某种(某些)内核工具(kernel utilities)而产生一些问题。 这就是所谓的“modutils”(译者注:可以看为“Modules Utilities”的所写,这样就不会对这个名称感到费解了),包括代码(code)需要载入(load)的内核模块(kernel module):图3解释了什么是内核模块。 有时新内核不知道如何处理老的modutils,所以你必须编译和安装更新版本的modutils。 这样的问题虽然不多但是它们的确存在,而且目前只能预先指出它们。

但是如果你完全按照本文的步骤进行,几乎不会发生错误。新内核被添加到lilo,或者被复制(copy)到loadlin所在的分区。 因此,在紧急情况下,你仍然可以通过老内核启动机器。然后可以在老内核下试着解决新内核的问题。 即使你遇到modutils的问题,你也可以从老内核重新启动计算机,然后通过将它们(内核和modutils)分别编译和安装来修复(fix)这个问题: 所有新版本的modutils都对老内核向下兼容,所以新modutils在老内核下可以很好的工作。

 

安装内核源码

你在这一节所进行的所有工作都需要root权限,所以你必须以root登录(login)。 首先,也是最重要的,你必须安装内核源码,比如从安装盘上。 在SuSE中,源码是被放在“d”类(为开发准备的文档)的“lx_kernel”包中。 安装适合你的发行版(distribution)的内核是比较适当的,因为不同的GUI在那时都会被自动安装。 一旦这件事情完成,最新版本的Linux内核的tar包,比如linux-2.4.6.tar.bz2可以从 http://www.kernel.org/pub/linux/utils/kernel/modutils/v2.4/。 注意modutils的版本号和内核的版本号可以不完全吻合:只需要下载并安装最新的版本就行。 编译并安装modutils将在后面的“安装modutils”讨论,现在我们先讨论内核的安装。

你机器上的内核(译者注:指的是你的老内核)的源码被放在/usr/src/linux/目录下。 比较明智的做法是保证源码的安全,比如通过更名(rename)linux目录:

cd /usr/src
mv linux linux-2.2.19 (如果原来的内核源码是2.2.19.的)


只有当你安全的保存了原始内核后,你才可以解包新的内核:你将会看见linux-2.4.6.tar.bz2默认将所有东西解包到“linux”目录, 如果这个目录已经存在,它将重写这个目录,然后,你将会遇到一个麻烦:你无法重新编译老内核,你已经没有它的配置和其他东西。 在这个例子中,我在解包linux-2.4.6.的源码后立即将“linux”更名(rename)为“linux-2.4.6”,并且创建一个新的符号链接“linux”到“linux-2.4.6”。 这样的优点是你可以马上知道系统现在使用的内核的版本。 另外,还可以很容易的安装一个内核补丁(kernel upgrade)。 命令是:(以root身份执行,切记!)
cd /usr/src
cp ~/linux-2.4.6.tar.bz2 (加入tar包已经下载)
(进入你的家目录(home-directory ),“~”)
bzip2 -d linux-2.4.6.tar.bz2 (这将花费一段时间 )
tar -xvf linux-2.4.6.tar
mv linux linux-2.4.6
ln -s /usr/src/linux-2.4.6 /usr/src/linux


当你完成这个步骤后,你到kernel的目录下,然后执行:
cd /usr/src/linux
make xconfig (见图1)

The graphical interface for defining your Linux kernel after the command 'make xconfig'.
图1:执行“make xconfig”命令后的用于定义(define)你的内核的图形界面。

这是用于定义(define)内核的主菜单。你需要点选(click on)不同的选项。然后点击“Save and Exit”保存你的选择, 然后你就可以编译和安装你的内核了(见图40)。 但是我们还没有到那一步。(才刚开始呢!)

 

配置内核(Configuring the kernel)

下面,我展示了配置内核的过程中的一些图片。 每幅图片都有为什么会选择特定的选项的说明。 仔细阅读这些例子,你会知道我这样选择的原因,你也可以更好的了解那些选项是适合你的。 “help”(帮助)提供了相同的信息。 你make xconfig自己的Linux发行版(distribution)时可以参考“help”。点击“Help”,帮助文档通常会建议你应该选择某个选项。

这些例子当然不可能讨论你所有的硬件设备。 但是它们能够说明你应该如何处理自己的硬件,如何查找内核是否支持你的硬件。

Selection of the 'code maturity level options'.

图2:“code maturity level options”选项
在这部分,你可以允许内核的一些试验选项(experimental options)。 有时,这些选项是必需的,比如,需要支持新的显卡。 但是,多数情况下,如果这些试验选项(experimental options)导致内核不稳定的话,最好撤销这些选项。 在图1中,你可以看见“IEEE 1394 (FireWire) support”和“Bluetooth support”选项处于灰色状态。 它们对应的代码正处于试验阶段,因此你暂时不能选择它们。

图三: 可加载模块支持.
(从现在起,截图都在超链接里,你可以自己在新的窗口里浏览它们)
可加载模块是指内核代码(kernel code)的一些片断,比如驱动程序,当编译内核的时候它们也被单独编译。 因此,这些代码不是内核的一部分,但是当你需要它的时候,它可以被加载并使用。 通常的建议是如果可能,将内核代码编译成可加载模块,因为这样可以使内核更小,而且更稳定。 警告!千万不要将文件系统(File System)部分的代码编译为可加载模块,见图32。 如果你犯了这个错误,将文件系统(File System)部分的代码编译为可加载模块,结果将是内核无法读取它自己的文件系统。 然后内核无法加载它自己的配置文件——一些很明显是在正常启动Linux时所必需的东西。 你将会发现我很少使用可加载模块:我喜欢我的内核能够直接和硬件对话,但这只是我自己的偏好。

图4:选择处理器类型(processor type)和特征(features).
在这里,你可以选择你的处理器(Processor)的类型,决定是否选择不同的选项。 通常“/dev/cpu”选项更高级,多数用户并不需要选择它。 “High Memory Support”只有当你的计算机有超过1GB内存(不是磁盘空间)时才是必须的。 多数计算机的内存从64到512MB(并且拥有8到60GB硬盘空间),因此“High Memory Support”通常并不使用。 如果你的计算机是386或486SX的话,你必须打开“Math Emulation”选项。 这些老系统没有Linux需要的浮点运算单元(Math Emulation,也称数学协处理器),所以必须选择“Math Emulation”选项。 实际上现在所有的处理器都集成了浮点运算单元(译者注:从586级开始所有CPU集成了浮点运算单元),所以通常你可以不选择这个选项。 “MTRR”选项允许在PCI或者AGP总线众进行更快速的通讯。 由于现在所有系统都将它们的显卡接在PCI或AGP总线上,你通常需要选择“MTRR”:无论如何,打开这个选项通常都是安全的——即使你的机器没有使用PCI或AGP总线的显卡。 对称多处理器(SMP)需要能够支持超过一个处理器的主板,比如一块支持双Pentium II处理器的主板。 “Symmetric multi-processing”选项保证内核能够以最佳方式加载双处理器。 最后一个选项(“APIC”选项)通常也需要多处理器,但它通常是关闭的。

图5: 常规内核选项.
在这里,你可以制定特定的内核常规选项。 通常每个人都会选择“Networking support”,因为你通常需要它,比如Internel。 Linux有很重的Internet偏向,而且它在没有联网的情况下无法充分发挥优势。 另外,许多其他的操作也网络支持——即使它们看上去跟网络没多大关系。 内核在没有网络支持选项的情况下甚至无法编译。简而言之:一定要打开“Networking support”选项。 现在的所有系统都使用PCI总线,所以你应该选择相关选项。 “PCMCIA/CardBus support”选项处于灰色不可用状态,因为你刚才已经选择你不虚妄使用试验代码(见图2)。 如果你使用笔记本电脑,你当然需要内核支持PCMCIA/CardBus以便使用网卡或者调制解调器(modem,即通常所说的“猫”)。(请参考“PCMCIA支持(笔记本电脑)”) “System V IPC”允许程序通信和同步,“BSD process accounting”保持诸如进程结束时产生的错误代码的东西,“Sysctl support”允许程序修改某些内核选项而不需要重新编译内核或者重新启动计算机。 这些选项通常是打开的。 现在的Linux发行版(distributions)以ELF格式作为它们的“内核核心格式”(kernel core (/proc/kcore/) format): 这是不同系统库(system libraries)的标准格式,比如那种格式的提供给程序使用的系统代码片断。 “ELF”是已淘汰的“a.out”格式的继承者,和Microsoft Windows的.dll文件。 现在,所有Linux程序都使用ELF库(ELF library),不幸的是有些老的程序仍然需要a.out格式支持。 一个典型的例子是“Word Perfect 8 for XWindow”:这个应用程序只有在a.out格式下可用,所以“xwp”无法在没有a.out格式支持的情况下运行。 如果你想用“xwp”,将“a.out”作为一个可加载模块。 我还把“MISC”作为一个可加载模块。 原则上,我并不使用它,但是如果你经常使用java,python或者DOS模拟器“DOSEMU”,这样可能更方便。 我还打开了“Power Management support”和“Advanced Power Management BIOS support”选项(在图5中没有显示)。 ATX主板需要这两个选项来自动关闭计算机(turn off the computer,包括关闭电源)。 另外的电源管理功能被关闭是因为它们在XWindow下无法正常工作,而我通常在XWindow下使用Linux。 KDE和Gnome有它们自己的标准电源管理功能。

图6:配置存储设备(Memory Technology Devices).
你需要这个选项来使Linux可以读取闪存卡(Flash Card)之类的存储器。 闪存卡通常用于数码相机。 通过这个选项,Linux可以读取闪存卡(从特殊的设备中,比如读卡机),并且将图片保存为.jpg格式。 除非你确定你需要它,否则不必打开它:如果你发现自己需要,你可以在以后加上。

图7:配置并口(parallel port).
在USB技术出现以前,并口是最常用的连接计算机和打印机、扫描仪的方式。 我的打印机使用了一个并口,所以我希望这个端口在Linux下可用。 注意 ,配置并口和配置打印机不同:这将在后面的图28讨论。

图8:配置即插即用(PnP)设备.
几乎所有人都有即插即用设备,因此需要这个选项的支持。 打开这个选项使内核能够自动配置即插即用设备并且使它们在系统中能够使用。 有时需要在BIOS中打开“Plug & Play OS”,否则Linux(当然Windows也一样)无法配置即插即用设备。 “ISA Plug & Play support”选项需要以即插即用模式运行的ISA卡(ISA Card)。 例如AWE64声卡。ISA总线(ISA bus)从来都没有即插即用标准,这增加了配置这些板卡的难度。 在2.4.x内核之前的早些时候,Linux用户在启动时必须呼叫(call)“isapnp”程序(isapnptools包,使用rpm qil isapnptools查看包含的所有文件)。 “isapnp”读取“/etc/isapnp.conf”文件。 这个文件包含不同板卡使用的所有端口,地址和中断。 如果/etc/isapnp.conf中的信息不正确,或者“isapnp”没有被激活,即插即用设备就无法使用,甚至网卡、modem、声卡也无法工作。 打开“ISA Plug & Play support”选项来代替以前的工序:不再需要/etc/isapnp.conf文件。 检测和配置工作都自动进行。 在SuSE 7.1中,在编译2.4.x内核后我必须重命名/etc/isapnp.conf为“/etc/isapnp.conf.old”之类的名称。因为内核和“isapnp”使用相同的资源,造成很惨的后果。 问题在于SuSE 7.1(以及更老的版本)在启动时自动激活“isapnp”,即使内核已经包括了对即插即用的必要支持。 这只是对于老Linux系统而言,新的系统默认不使用“isapnp”

图9: 配置块设备(block devices).
事实上,每个人都要使用软盘(floppy),所以这个选项要打开(或者,象我这样,作为一个可加载模块)。 倘若/etc/modules.conf或者/etc/conf.modules文件在你的Linux发行版中已经被适当配置了,当需要访问软盘的时候,内核会自动加载必须的模块。 如果你选择了图3中适当的选项,你的使用应该没有问题。 访问软盘当然内核必须能够读取软盘的文件系统。 因此你必须正确完成图32的工作(配置文件系统)。 其他的选项在你使用并口链接IDE存储设备的时候是必需的,但是它们通常是关闭的。 “loopback device support”选项可能例外。 在Linux下,刻录(burn)光盘之前你通常需要制作一个光盘镜像,在查看镜像文件的内容时需要“loopback device”。 我选择了这个选项(第五行)作为可加载模块(在图9中没有显示出来)。

图10: 配置多驱动器(multiple devices).
普通Linux用户通常不需要RAID(廉价冗余磁盘阵列)或者LVM支持。 “RAID”的意思是系统使用两块或两块以上硬盘存储并行信息。 当一块磁盘出现问题的时候另一块可以继续工作,系统不停顿。 LVM让使用增加一块硬盘来扩展一个分区成为可能。。 在实际应用中,这意味着你不必重新分区或将一个小的分区复制到一个大分区中。 路径名也不会改变。 这可能会十分方便,但是普通用户并不需要它。

图11: 网络配置选项.
你需要“Packet Socket”选项来与网卡进行通信而不需要在内核中实现网络协议。 在这里我可以简捷一些:一般情况下,打开这个选项。 大多数选项是关闭的,除非你需要特殊的支持。 比如,我选择了“Network packet filtering (replaces ipchains)”,因为我使用了SuSE的标准防火墙(Standard Firewall)。 防火墙可以保护你的机器不受来自外部Internet的攻击——至少当你正确配置了防火墙的时候是如此。 内核级的防火墙保护显然是十分优越的。 图12展示了在“network packet filtering”选项中的高级选项(Further choices)。 你需要“Unix domain sockets”来进行网络链接,但也可以使用其他的东西: XWindow要自动使用Unix sockets,所以如果没有打开这个选项,XWindow将无法启动。 通常这个选项是打开的。 “TCP/IP networking”选项包括了Internet和内部网络所需要的协议。 通常你会希望激活TCP/IP支持。 如果你不确定是否应该打开一个选项,参考一下帮助文档。 如果你还是不知道是否应该打开它,一般来说,就把它打开,然后通过试用来决定是否保留。 将某些特定的选项编译为模块也是一个很好的办法。

图12:配置IP过滤(防火墙).
为了使防火墙能够正常工作,SuSE Linux需要对ipchains的兼容支持(Backwards Support)。 因此,我为我的SuSE Linux打开了这个选项。 如果你也在其他发行版中使用了防火墙,请参考它们的手册。

图13: 配置电话技术支持(Telephony Support).
这个选项只有当你在计算机中使用电话卡的时候才有用,比如在Internet上打电话。 大多数普通用户没有电话卡,因此不需要这个选项。

图14:配置对ATA,IDE,MFM和RLL的支持(硬盘的通讯协议).
几乎每个人都需要这些协议,除非你的机器只有SCSI硬盘而没有其他任何类型的硬盘。 因此,大多数用户都应该把它打开。 点击它下面的那一栏将会出现一个有更详细配置选项的菜单。 这些将在下面讨论。 因为它们很重要,因此这里有三幅截图。仔细填写它们:它们极为重要!

图15:配置对ATA,IDE,MFM和RLL的支持:截图1.
最上面的选项是每个使用IDE/ATAPI界面的人都需要的。 包括硬盘,磁带机,ZIP盘,光驱(CDROM)和刻录机(CD-R(W))。 基本上现在的所有计算机(译者注:这里指的是PC)都使用IDE/ATAPI界面,因此这个选项必须打开。 “include IDE/ATA-2 DISK support”选项在支持硬盘的时候需要。 因此这个选项也必须打开,除非你的机器是一个纯粹的SCSI系统。

图16:配置对ATA,IDE,MFM和RLL的支持:截图2.
如果你有ATAPI CDROM的话,应该打开“include IDE/ATAPI CDROM support”选项。 ATAPI的CD-R(W)需要仿真为SCSI的才能被访问。SCSI仿真可以用来访问CDROM和CD-R(W)。 如果你使用仿真SCSI的方式来访问CDROM,你将会遇到问题,比如当你挂载(mount)CD的时候出现错误信息(Error Message),或者使用CDROM播放CD唱片(Audio CD) 最好的办法是象图16这样,将“include IDE/ATAPI CDROM support”和“SCSI emulation support”选项都打开。 需要SCSI仿真的设备通常是CD-R(W),可以在/etc/lilo.conf中加入“append="hdd=ide-scsi":”行进行定义,这将在“配置lilo”中进行讨论。 因为我有一个使用ATAPI界面的内置ZIP驱动器,所以我把“include IDE/ATAPI FLOPPY support”选项打开。 如果你有软盘类的驱动器(译者注:包括ZIP盘、LS-120等大容软驱,但不包括MO磁光盘)。 大部分主板使用“PCI IDE”(译者注:就是指通过占用PCI总线带宽进行通信的IDE接口)对硬盘、CDROM和软驱进行访问,因此这个选项必须打开。 同样的有两种开启DMA的可能性。 DMA使你的硬件直接访问内存,而不需要处理器的干预。 因此,正如你所希望的,IDE磁盘的访问速度会加快。 “sharing PCI IDE interrupts support”是关闭的,因为你通常不需要它。 的确,某些IDE控制器允许和其他设备共享中断,比如网卡。 不幸的是,共享IDE中断会使磁盘性能下降,因此通常你不需要共享中断,除非这是解决硬件问题的唯一办法。

图17:配置对ATA,IDE,MFM和RLL的支持:截图3.
我的主板有一颗Pentium II CPU,而且使用的是Intel的芯片组,因此我希望能够对该芯片组提供特殊支持。 当你选择你自己的内核选项时,你应该看看图17没有显示的其他芯片组。

图18: 配置SCSI支持.
如果你有一块SCSI卡,你当然需要打开相关选项。这张截图只显示了当你为你的CD-R(W)选择“SCSI emulation support”(图16)时需要的选项。

图19: 配置I2O设备支持(I2O Device Support).
如果你有I2O界面,你必须选择这个选项。大多数人没有,如果你也没有的话,可以直接将它关闭。

图20: 配置网卡支持(Network Device Support).
我从来都没有在没有网卡支持的情况下将Linux内核编译成功过。 因此,你应该打开这个选项。 你还应该打开虚拟驱动(Dummy Driver),作为内核的一部分或者一个可加载模块。 当没有物理网卡的时候,Linux需要一块虚拟网卡(Dummy Network)。即使已经有了物理网卡,Linux仍然经常使用虚拟网卡。 在图21所示的菜单中,你可以选择网络类型和网卡。 注意,如果你想要通过modem来访问Internet的话,你需要进行更多的设置: 你必须打开“PPP support for async serial ports”(用于COM端口)或者“PPP support for sync tty ports”(用于通过诸如SyncLink adapter进行的高速连接)选项以提供PPP支持。 如果你没有打开它们,内核将会告诉你PPP模块(PPP Module)不存在——即使你已经编译了,错误信息(Error Message)对于查找真正问题的所在几乎没有作用。 你可以将它们都选上,这样可以避免一些问题:如果内核需要它们,就会使用它们,如果不需要,就不用。

图21:配置以太网卡(Ethernet Device).
我的以太网卡是一块使用3c509/3c529芯片的3COM百兆网卡。 因为我并不需要与网络进行物理连接(我有网卡,但是我并不通过modem来访问网络),因此我将这个趋同编译成一个可加载模块,以便万一要需要网卡的时候可以使用。 你当然需要选择与你机器的情况相符的网络类型和网卡。另外,你必须使用诸如SuSE Linux下的“yast2”之类的配置程序配置网络链接。

图22: 配置业余广播支持(Amateur Radio Support).
如果你希望使用业余广播支持(Amateur Radio Support),应该打开这个选项,并且打开相应的驱动。 多数人不需要这个选项。

图23: 配置红外线(无线)通讯支持.
如果你有无线设备,比如无线鼠标或无线键盘,你应该打开这个选项。 多数桌面机器不需要这个选项。

图24: 配置ISDN.
在这里你可以打开对ISDN卡的支持。 你必须知道自己使用的是什么板卡和芯片:你需要通过这些信息来选择正确的设备。

图25: 配置老CDROM.
在486和386机器中,CDROM并不是通过硬盘IDE(ATAPI)控制器连接的,而是通过声卡或一块特殊的板卡。 使用这些老CDROM需要选择对应的驱动。 这个选项在现在的系统中显得多余。

图26: 配置Input Core Support.
这个选项提供了2.4.x内核中最重要的特性之一的USB支持。 Input core support是处于内核与一些USB设备之间的层(Layer)。 图38显示了可供选择的USB设备,帮助文档也指明了那些设备需要“input core support”:见图38。 如果你拥有其中一种USB设备,你必须打开“input core support”选项。 现在所有的主板都有USB接口,所以,原则上你应该打开它。但是,我清楚自己并不需要USB支持,所以我关闭了它。

图27: 配置字符型设备(Character Devices):截图1.
最上面的选项(“virtual terminal”)允许在XWindow中打开xterm和使用字符界面登录。 通常这个选项是打开的。 第二个选项(“support for console on virtual terminal”)告诉内核将诸如模块错误、内核错误启动信息之类的警告信息发送到什么地方, 在XWindow下,通常设置一个专门的窗口来接收内核信息,但是在字符界面下,这些信息通常被发送到第一个虚拟终端(Virtual Terminal)(CTRL+ALT+F1)。 将这个选项打开。 你还可以选择将信息发送到串口(Serial Port)设备,比如打印机或其他的终端(第四个选项)。 如果将信息发送到打印机,你还必须使用选项三激活串口。 同样,如果你使用串口鼠标(Serial Mouse),你也必须激活串口。 再说一遍,选项三(“standard/generic (8250/16550 and compatible UARTs) serial support”)通常是打开的。 在我的系统中,我将它编译为一个可加载模块。原因是在启动的时候SuSE老抱怨“serial support”丢失,而如果确定模块的确存在,将其编译为可加载模块是避免上述抱怨的的好办法。 配置字符型设备是极为重要的工作。 如果你的配置不正确,你有可能得到一个根本不能工作的系统。 因此图28到图30讨论了更多的相关选项。

图28: 配置字符型设备(Character Devices):截图2.
如果你想使用远程使用自己机器上的xterm,比如通过telnet或者ssh,你必须打开“unix98 PTY support”选项。 桌面系统看上去似乎不需要这个选项,但是许多后台进程需要使用这个选项。 因此,无论如何,将它打开是个好主意,至少可以避免启动时显示错误信息(至少SuSE是这样)。 所有拥有并口打印机的人当然需要“Parallel printer support”。 不过,并不是所有人都需要并口:USB打印机就不需要。 内核信息可以通过打开“Support for console on line printer”选项传送到并口:通常,你并不需要它。 如果你有某些设备需要占用并口,你需要“support for user-space parallel port device drivers”选项,但是通常不必。 同样,你通常不需要“I2C support”选项:这是视频处理卡所需要的,但是如果你发现你需要它,你通常可以在以后加入到内核中——只要你觉得内核运行得好。 如果你使用鼠标和游戏杆,你需要打开相应的支持,但是并不是所有鼠标都使用这个驱动(见图29)。 现在的CD-R(W)使需要“QIC-02 Tape support”的磁带机(Tape Drives)大量闲置,因此这个选项通常是关闭的。

图29: 配置字符型设备:鼠标.
如果你使用串口鼠标,你根本不需要这个选项的任何项目,但是所有其他类型的鼠标则需要在这里进行参数配置。 如果你使用最初的总线鼠标(ORIGINAL Bus Mouse)你需要打开最上面的选项。 现在的许多计算机使用另外一种鼠标,通常(而且是错误的)称作“busmouse”或者“PS/2鼠标”。 这些鼠标通常连接到/dev/aux,并且插在一个与键盘相同的小接口中。 通常,这种鼠标通过键盘来连接到计算机。 要让这些鼠标正常工作,你必须打开如图29所示的选项,“mouse support (not serial and bus mice)”和“PS/2 mouse (aka "auxiliary device" support)”。

图30: 配置字符型设备:截图3.
这些是图28和图30中没有讨论的内核选项。 它们通常是关闭的。 “Ftape, the floppy tape device driver”选项需要对连接到软驱控制器的磁带机的支持。 甚至即使你有一个那样的磁带机,这个选项都不是必须的,至少不是首要的。 其他选项需要3D显卡。 如果你有一块连接到AGP总线(AGP Bus)的显卡,你需要打开AGP支持,还需要相应的驱动(在/dev/agpgart(AGP支持))。 注意,你可以编译一个不包含这些选项,但是能够正常工作的内核,但那没必要! 如果没有这些选项,XWindow 4.0或者更高版本(被现在的多数发行版使用)将无法工作。 我的机器有一块AGP显卡,nVidia TNT2,但是内核的相应模块并不支持这块显卡(nVidia拒绝透露开发驱动所必须的技术细节)。 很不幸,打开AGP支持对于我来说没有多大意义。 虽然有这个问题,我仍然可以在不需要内核驱动的情况下使用XWindow 4.0。 “Direct rendering support”是为XWindow 4.0提供的图形加速选项。 要想使用这个选项,你的显卡必须能够被支持,而且你必须使用XFree86 4.0及以上版本。 另外,你还需要打开“AGP support”选项。 你可以编译一个不包含这些选项的内核,它照样可以正常工作。

图31: 配置多媒体设备“Multimedia Devices”.
如果你有一块视频处理卡或者广播卡,需要打开这个选项。 跟前面一样,这个选项不是必需的。

图32: 配置文件系统(File System):截图1.
在这里,你可以指定Linux能够访问的文件系统。 或许你希望制作一个能够访问Windows分区和Windows软盘的内核,但是你必须保证内核可以访问Linux自己的ext2文件系统,或者是更新的ReiserFS文件系统。 如果你没有这样做,Linux会无法启动,因为内核无法读取它自己的启动盘(正如图3中所讨论的那样)。 要想访问DOS/Windows软盘和分区,你需要打开“DOS FAT support”选项:但是要想访问Windows NT/2000的分区,你需要另外一个只读(Read-only)驱动,这将在后面进行选择。 要想读写DOS/Windows分区和软盘,你需要打开“MSDOS fs support”选项(译者注:注意,“DOS FAT support”并不提供读写功能)。 事实上,每个人都希望这样,因此通常这个选项是开着的。 “VFAT”需要Windows95/98的长文件名支持。我的机器是一个可以通过lilo(见“配置lilo”)启动Windows 98和Linux的双重启动系统(dual boot system)。 因此,我需要激活“VFAT”。 你需要加入对ISO 9660的支持来读取标准格式的光盘(CD)。 “Joliet extensions”选项允许在ISO 9660标准中使用长文件名。 多数人都希望能够读取现在的光盘,因此这些选项通常也是打开的。 图33详细讲述了一些附加选项,其中包括包括Linux的ext2文件系统。

图33: 配置文件系统:截图2.
/proc目录中的文件包含了关于系统状态的许多重要信息,比如那些中断正在使用。 一般来说,你应该把这个选项(“/proc file system support”)打开。。 “Second extended fs support”选项针对Linux的标准文件系统(Ext2 FS) 你必须打开这个选项,并且编译进内核(不是作为可加载模块)! 图32和33没有显示“ReiserFS”选项,它也可以在这里打开:Ext2文件系统的继承者,ReiserFS能够更好的对付由于断电或者类似情况而 带来的对文件系统的破坏。 目前ReiserFS仍然处于开发阶段,因此被标志为试验代码。 即使是这样,多数发行版现在都已经支持ReiserFS,但是,虽然ReiserFS被认为会在将来取代Ext2,我现在并不推荐将它作为所有分区的文件 系统。 如果你(在Windows下)使用一个叫“packetCD”的将光盘虚拟成低速磁盘的软件,你需要打开“UDF file system support”选项 这对在与其他PC交换数据时十分方便。 在Linux下通过将这些被封包的光盘挂载(mount)为UDF文件系统也可以进行读取,比如使用命令“mount -t udf /dev/scd0 /cdrom”。 这一部分还包括“Network file systems”、“partition types”和“Native language support”选项。 你不必选择“Network file systems”,除非你的计算机处于一个需要使用“Network file systems”选项的大型网络之中,“SMB file support”选项也是一样,对于普通的单机(Standalone Computer)来说,并不需要这些选项。 “Partition Types”选项是一个很高级但对于有效的使用Linux内核来说并不必要的选项。 最好是关闭它。 图34和35更详细的解释了“Native Language Support”选项。

图34:配置“native language support”选项:截图1.
在这个菜单中,你可以选择那些编码将被Linux用来处理DOS和Windows下的文件名。 图34的编码表通常是用于DOS文件名的。图35NLS表中的是用于长文件名的。 图34中最上面的选项“Default NLS option”决定那种语言是系统的标准语言。 图35描绘和详细解释了“iso8859-15”选项。

图35:配置“native language support”:截图2.
你需要使用“NLS ISO 8859-15”选项来正确复制(reproduce)Windows的FAT(文件分配表)和光盘文件系统(CD File System)的长文件名扩展(Joliet extensions),这通常是一个好主意。 “NLS ISO 8859-15”选项对于西方语言来说是最适当的,而且它包含了欧洲字符。 因此,这个编码表通常都会被编译进去。 “NLS ISO 8859-1”表是以前的西方语言编码表,但是没有包括欧洲字符。

图36: 配置控制台驱动.
“VGA text console”选项在VGA模式下启动字符模式。 多数人都需要它,因此这个玄学爱你跟通常是开着的。 只有一些386计算机没有VGA兼容显卡,选择这个选项对于现在的多数计算机来说都没有问题。 第二个选项“video mode selection support”使启动的时候能够使用字符模式的分辨率。 如果你希望一行能够有更多的字的话,这通常会给你提供方便,不过通常你不必打开它们。 下面两个选项是试验性质的,我建议你关闭它们。

图37: 声卡配置.
在这部分,你可以配置声卡。 如果你的Linux发行版使用ALSA的声卡驱动(比如SuSE 6.3及以上版本),将“sound card support”作为可加载模块就足够了。 ALSA驱动将会在待会儿被编译并连接进来(见“SuSE和ALSA声卡驱动”)。 如果你的发行版使用的是内核的标准声卡驱动,你必须正确选择你使用的声卡。 事实上,这里列出了所有牌子的声卡,因此,理论上选择声卡不成问题。 如果你的声卡在你的发行版的标准内核下工作的很正常,你也可以使用配置程序(比如SuSE的“yast2”)来找出你的声卡使用什么驱动。 请放心,声卡并不是十分关键的:如果某些地方出错,你将会失去音效,但是内核仍然可以正常工作。

图38:配置USB支持.
我的主板有USB接口,但是我并不使用它。但是如果我关闭USB支持,SuSE在启动时就给我一个错误信息(Error Message)。 SuSE支持USB,因此它尝试读取需要的模块,因此我将“Support for USB”作为可加载模块。 虽然那些错误信息(Error Message)对我来说并没有什么,但是我通过更巧妙的编译USB驱动的方法消除了它们。 最简单的办法是将“Preliminary USB device filesystem”选项打开,然后加载一个特殊的USB驱动。 因为我的Pentium II主板已经很老了,我选择了“UHCI (Intel PIIX4, VIA, ...)”驱动作为可加载模块。 但是,如果你有一块Intel芯片组的新主板,你必须选择“UHCI Alternate Driver (JE) support”模块,而如果是Compaq的计算机,你应该选择“OHCI support”。 理论上,你只需要这三个模块中的一个,但是如果你不清楚的话,可以选择所有模块。 你的Linux发行版会自动检测出哪个模块是它需要的,然后自动加载它。

仅仅开启主板的USB端口还不够,你需要指明使用哪个USB驱动。 “USB Device Class drivers”下出现的列表有不同的选项。 所有这些都是很直观的,几乎不会出错:如果还有疑问,阅读帮助文档。

图39: 配置“kernel hacking”选项.
这很简单:不要打开! 这是一个对于希望知道系统崩溃原因和读取硬盘缓存的程序员来说很有用的选项:这个选项对于普通用户来说完全没有作用。

Save and Exit.

图40:保存并退出。
Pffft,我们已经完成了配置工作。剩下的工作就是编译和安装内核了。下面我们来讨论这个问题。


 

编译内核

点击“Save and Exit”,你就可以将你的配置选项保存在“./.config”(如果你在/usr/src/linux目录下编译,就应该在/usr/src/linux/.config)中。 另外,如果你只想进行一个小小的升级,比如从2.4.5到2.4.6,将这个文件保存并复制到新内核的目录下也很方便。 用这种方法,你可以(通常)保存你所有老的选项,这将节省很多时间。 同样,你可以使用你的发行版的标准配置文件,通常是保存在/boot/config(复制到./.config来使用它)。 但是如果你升级你的内核源码,并且在编译内核的时候遇到了奇怪的问题,这个文件(老的配置文件)是你首先应该移除的! 当然你为了安全起见需要修改,写下并且小心保存正常工作的内核的配置。 编译内核的步骤如下:
make dep
make clean (针对老内核)
make bzImage
make modules
make modules_install
图40已经显示了需要运行“make dep”。 你当然需要在Linux源码目录下运行这些命令,通常是在/usrsrc/linux下。 2.0.x系列或者更老的内核还需要在编译新内核前运行“make clean”以移除以前的文件。 “make clean”可以防止一些很难对付的,但根据推测是由于没有被清除的老的目标文件(Object File,“*.o”)导致的古怪错误信息(Error Message), 命令“make bzImage”编译新内核,但是并没有安装它。 你还可以使用“make”命令来编译内核,比如“make bzlilo”或者“make zImage”,但是这些命令可能导致以外的问题。 多数内核对于“make zImage”来说太大了:你在编译的时候将会看见一个错误信息(Error Message)。 要使用“make bzlilo”命令,必须事先将所有配置保存在/etc/lilo.conf之类的文件 中,但通常并不是这种情况。 因此最好不要使用这些命令。 “make modules”编译可加载模块:它们可以用“make modules_install”命令安装。 如果当前的内核版本是2.4.6,这个命令将可加载模块放在/lib/modules/2.4.6/目录下:当你编译另外版本的内核时,目录也随之改变。 这样,对应特定版本内核的可加载模块就自动的放在单独的目录中,以此来避免可加载模块之间的冲突或者类似问题。 在启动的时候,内核知道它应该在哪个目录里才能找到正确的可加载模块。 但是如果你在编译2.4.6的内核后又将它重新编译,/lib/modules/2.4.6目录中的文件被重写,而一些老的可加载模块遗留下来。 那么,虽然这些老的可加载模块并不被新内核使用,它们仍然在那里。 通常,这并不是问题,但是在安装新的可加载模块之前将老的移除总是一个好主意。

为了避免在安装内核的过程中出现问题,你必须确定lilo的配置文件/etc/lilo.conf是正确的,并且你必须复制内核和文件“System.map”到正确的地方。 最后,你必须运行“lilo”命令。 也可以在DOS/Windows下使用“loadlin”代替。 这两种方法都会在下面讨论。

 

配置lilo

通常你可以在/etc目录找到lilo的配置文件/etc/lilo.conf。 用文本编辑器(ASCII Editor)打开它:你或许安装了一个完整的编辑器,比如XEmacs(运行“xemacs /etc/lilo.conf &”),一个简单的全屏编辑器,比如kedit或者gedit(运行“k(g)edit /etc/lilo.conf”),或者使用行编辑器,比如pico或者nano(运行“pico /etc/lilo.conf”)。 lilo.conf看上去大概象这个样子:

boot = /dev/hda
vga = normal
read-only
menu-scheme = Wg:kw:Wg:Wg
lba32
prompt
timeout = 300
message = /boot/message

other = /dev/hda1
label = win98

image = /boot/bzImage
label = linux-2.4.6
root = /dev/hda3
append = "parport=0x378,7 hdd=ide-scsi"

image = /boot/vmlinuz.suse
label = suse
root = /dev/hda3
append = "hdd=ide-scsi"
initrd = /boot/initrd.suse

每个系统和发行版的lilo.conf的详细内容或许和上面有些不同。 因此我将为你解释这些这个文件。 前8行是已经写好了的,通常你不必去更改它们。 第一行的命令“boot”表明应该从哪个物理硬盘启动,也就是说,“boot”指出了主启动记录(Master Boot Record)的位置。 对于我来说,从/dev/hda,第一块物理硬盘启动。 “vga”选项指出启动的时候使用标准VGA文本模式,80x25个字母。 “read-only”选项的意思是启动进程首先将Linux分区以只读(read-only)方式挂载(mount)。 在Linux启动期间,将检查分区是否有错误:待会儿它们将被以读写(read and write)方式重新挂载(mount)。 “menu-scheme”行设置了lilo启动菜单所使用的颜色。 使用“lda32”使操作系统可以从1024柱面以后的扇区启动,如果BIOS支持的话。 现在所有系统都支持LBA32(大硬盘模式)。 你可以通过BIOS升级来解决这个问题,有些对于大硬盘来说是必需的东西现在已经可用了。 “prompt”命令强制lilo为用户显示一个提示,使用户可以选择自己想用的操作系统。 “timeout”选项给出了当选择提示给出后,启动默认操作系统前,lilo等待输入的时间(毫秒),启动进程将启动第一个操作系统。 在我这里是Windows 98,因此不会使用Linux的人最后将会进入Windows环境。 “message”选项规定了lilo启动的时候显示的信息。 在SuSE中是Tux,Linux可爱的企鹅吉祥物,以及(当然会有)“SuSE Linux 7.1”。 你可以通过输入“xv /boot/message”或者“gv /boot/message”(甚至有时还可以输入“gimp /boot/message”)看见这个信息:“xv”和“gv”(ghostview)是两款可以用来查看不同种类图片的共享软件 请注意,如果系统中没有图形登录界面(比如在老的发行版中),/boot/message将不存在,这样,启动信息将仅仅是文本信息。 理论上它将在启动时显示你自己选择的图片,但是我还没有实验成功过。 lilo的所有选项当然都在man page里面有介绍,你可以通过“man lilo”或者“man lilo.conf”访问。

其他的选项是用来引导其他操作系统的。 你最多可以引导16个操作系统。 通常情况下,这足够了。 你可以在“label=”行选择操作系统。 默认的Windows 98安装在(还有更老版本的Windows和DOS,但不包括Windows NT和Windows 2000),就将它放在第一个主分区。 这样,就只需要一行“other”和一行“label”。 第二个选项,“image=/boot/bzImage”,标签(label)是“linux-2.4.6”。 我的Linux根目录是“/dev/hda3”。 “append= "parport=0x378,7 hdd=ide-scsi"”行高速内核并口(Parallel Port)的地址和中断号(端口0x378,中断号7),并且标明我的刻录机“hdd”必须从虚拟SCSI(SCSI-emulation)寻址。 CD的名字由系统决定:在我的系统中,它叫“hdd”,但是在你的系统里,它可能叫其他名字。 使用中断号通常是个人偏好的问题。 中断使打印的速度加快,但是如果你的并口打印机由合适的中断,你可以不写这一行。 Linux默认使用一种被称作“登记(Polling)”的速度比较慢的方式,它允许内核直接使用并口而不需要中断号。 最后一个选项,由“image = /boot/vmlinuz.suse”开始,包含了SuSE配置过程中对lilo的配置:我手工加入了“append="hdd=ide-scsi” 行。 “boot/vmlinuz.suse”文件是发行版的标准内核。 最好将这个内核保留,以应付紧急情况。 “initrd = /boot/initrd.suse”行只用于标准的安装内核(installation kernel):它规定了读取一个被称作“ramdisk”的镜像,那是读入内存的一张虚拟磁盘。 “ramdisk”包括了正常启动Linux所需要的模块:一个发行版的内核当然必须访问大量不同的硬件,那只能通过使用许多可加载模块来实现。

希望你现在能够知道运行lilo前,你必须在什么地方存放你的Linux内核。 在这个例子中,正确的命令应该是:

cp /usr/src/linux/arch/i386/boot/bzImage /boot
cp /usr/src/linux/System.map /boot/System.map-2.4.6
lilo
在SuSE 7.3中你还可以象这样再复制一份:
cp /usr/src/linux/System.map /boot

(之前已经更名了原来的Systemp.map文件)
如果你已经有了一个名为“bzImage”的内核,而且你想保留它,你可以将新内核复制为“/boot/bzImage-2.4.6”,并且在 “/etc/lilo.conf”中做相应的更改:将/boot/bzImage改为/boot/bzImage-2.4.6。 编译过程将会重新创建System.map文件,它包含了内核的一些重要参数。 命令“depmod -a”创建(create)了一个包含所有内核可加载模块的依赖关系的文件。 也就是说,内核与可加载模块之间的关系、可加载模块之间的关系,还有/etc/modules.conf中所定义的关系。 在多数Linux发行版中,包括SuSE,都会在启动的时候运行“depend -a”,但是确定/boot/System.map文件存在并且符合当前内核的版本是比较明智的。 命令“lilo”将会安装一个老内核的新配置文件,或者一个新内核。 如果你已经用新编译的内核覆盖了原来的/boot/bzImage,但是没有运行内核,你将会得到一个无法工作的新内核。 那个你保存起来的发行版自己的内核却仍然能够工作,因为它没有被覆盖。 将老内核保存起来,并且认真测试你的新内核。  

使用loadlin

如果你使用“loadlin”来启动Linux,毫无疑问你必须知道在那里找到这个方便的程序,也就是说,在C:/loadlin。 事实上,所有的Linux发行版的第一张CD上“dosutils”目录中都有“loadlin”。 你还必须将新的Linux内核复制到“C:/loadlin”(或者是另外的名字)所在的磁盘。 在启动Windows 98前,在DOS模式(译者注:这里指的是DOS实模式,也就是开机按F8选择第五项“RealDOS mode”)下,你可以使用这个命令启动Linux:
loadlin bzImage
在通常情况下,当你编译了自己的内核后,内核中储存的大多数配置参数,比如根目录的定位等,都是正确的,如果这样的话,使用上面的命令来启动Linux应 该是没有问题的。 输入“loadlin | more”(在DOS下),你可以得到帮助信息,还包含一个Internet上“loadlin-HOTO”的链接。 你可以使用“loadlin”来测试一个新内核,骗过lilo:即“loadlin”和“bzImage”一起放在一张1.44MB的软盘上。 如果你使用启动软盘启动DOS(带EMM386支持,译者注:EMM386.EXEshi DOS 5.0以上版本提供的扩展内存管理软件,使DOS可以使用640k以上的内存空间),绝对没有问题,然后你就可以通过“loadlin”和 “bzImage”来启动Linux。 但是,你当然必须有一块DOS启动软盘。  

SuSE和ALSA声卡驱动程序

SuSE使用标准的ALSA(Advanced Linux Sound Architecture,高级Linux音效体系)声卡驱动程序。 这些驱动程序比内核使用的OSS计划(Open Sound System Project)的音效更好。 如果你对Linux下的声音要求比较严格,你绝对应该使用ALSA声卡驱动。 这些驱动并不是(以前的)内核源码的一部分,这意味着这些驱动必须被单独编译和安装。 ALSA驱动的源码放在SuSE发行版的“zq”部分:如果你不知道这是什么,请继续阅读。 用YaST或者YaST2安装ALSA以使源码被存放到“/usr/src/packages/”。 要编译和安装ALSA驱动,你需要按照下面的步骤进行:
rpm -bb /usr/src/packages/SPECS/alsa.spec
cd /usr/src/packages/BUILD/alsa/alsa-driver-<version number>/
./configure
make install
第一行安装源码,包括驱动的源码,在“/usr/src/packages/BUILD/”目录中。 另外,ALSA库和工具(ALSA Libraries and Utilities)被直接编译为了rpm包。 不幸的是,ALSA驱动默认没有被编译。 你必须手动输入上面两行来安装它们。 命令“./configure”在你的系统中查找必要的配置和文件,然后将它们放在一个配置文件里。 “make install”命令编译所有ALSA驱动,同时将它们到/lib/modules/2.4.6/misc/目录,提供给内核使用。 现在,当启动SuSE的时候驱动程序将自动安装。 我承认这些步骤有那么点讨厌,但是,当你寻找必需的声卡驱动的时候,你必须准确的知道自己在干什么并且将声卡支持更直接的加入到新内核中。

如果你不使用SuSE,或者如果你希望你希望使用新版本的ALSA驱动,你可以到http://www.alsa-project.org下载驱动和相应的库文件以及工具。 这个站点的首页张贴了ALSA计划的最新消息(比如2002年4月,将ALSA驱动整合到2.5系列内核的代码树中)以及不同文件的下载链接。 下面我将展示怎么编译ALSA驱动:你可以使用类似的步骤编译库文件和工具。 将驱动解包到一个比较方便的目录,比如/usr/local。 进入这个目录,在这里是/usr/local/alsa-driver-<version-number>/,运行上面的命令,从“./configure”开始。 如果你的发行版没有将ALSA驱动作为标准声卡驱动的话,或许你会进行一些其他的步骤来使它工作。 很不幸,这不是这篇内容十分广泛的文章所讨论的内容,但是你可以从下载的ALSA FAQ(Frequently Asked Questions)中得到更多的帮助。

 

PCMCIA支持(笔记本电脑)

在2.4.x之前,对PCMCIA的支持就已经是内核的标准内容。 无论如何,官方的PCMCIA HOWTO讨论了那些版本内核的PCMCIA支持最好不使用。 现在,PCMCIA源码(Pcmcia Source),包括在所有系列的内核——2.0,2.2,2.4下都可以使用的脚本和驱动。 我的经验是,PCMCIA驱动在重新编译内核后就无法工作了。 根据你的发行版,有两种解决方法。 第一种是使用发行版自带的源码。将这些源码安装并编译到一个rpm包中,然后你可以安装它。 另外一种是从 http://sourceforge.net/projects/pcmcia-cs/)下载,解包,编译,安装最新版本的PCMCIA驱动,就像这样:
cp /etc/rc.d/pcmcia /etc/rc.d/pcmcia.SuSE
cp ~/pcmcia-cs-3.1.?.tar.gz /usr/src
cd /usr/src
tar -zxf ./pcmcia-cs-3.1.?.tar.gz
make config
make all
make install
cp /etc/rc.d/pcmcia.SuSE /etc/rc.d/pcmcia

第一行和最后一行是用来解决一个SuSE特有的问题的。 SuSE的PCMCIA初始化脚本(Pcmcia-initialization Script)/etc/rc.d/pcmcia在运行“make install”命令的时候被覆盖了。 如果你不小心将SuSE的原始脚本覆盖了,你必须从“a1”部分重新安装pcmcia包,将原始脚本复制到另外一个文件中,重新运行“make install”,最后将原始脚本复制过来。

在SuSE下安装PCMCIA支持的新rpm包应该想下面这样:

rpm -i /cdrom/suse/zq1/pcmcia-3.1.?.spm
cd /usr/src/packages
rpm -bb ./SPECS/pcmcia-3.1.?.spec
cd /RPMS/i386/
rpm -i --force ./pcmcia-3.1.?.rpm
SuSEconfig

第一行中,我假定你已经挂载(mount)到/cdrom并将从第6或者第7张CD安装PCMCIA驱动, “rpm -i”命令安装源码,“rpm -bb”编译pcmcia rpm包。 然后,你可以象安装其他rpm包一样安装它。 注意,你必须使用“--force”选项,否则rpm程序将告诉你“pcmcia”已经安装了,然后会忽略新文件。 通常,当你在SuSE下手动安装了rpm包后,你必须通过运行SuSEconfig程序(注意大小写)来激活更改。 当SuSE的设置程序YaST或者YaST2安装或修改一个新的包后,它们通常会自动进行设置。 你不必手动激活。

要想真正使用PCMCIA支持,你还必须在编译时打开“network support”选项,但是你 必须关闭其他所有网卡的驱动。 然后,正如图11所讨论的,如果你想使用Internet,你当然还必须打开“TCP/IP support”

 

安装modutils

正如上面已经提到的,内核使用一些被称作“modutils”的程序来管理内核。 这些程序包括:
insmod (用来安装可加载模块),
rmmod (用来移除可加载模块)
lsmod (显示正在使用的所有可加载模块),

还有很多其它的。输入“man lsmod”命令,你可以找到如何使用这些不同的命令,有些是我在这里没有提到的。

编译和安装“modutils”很简单:

cd /usr/src
cp ~/modutils-2.4.6.tar.bz2 . (假如那些文件在你的家目录(home directory),“~”里)
bzip2 -d modutils-2.4.6.tar.bz2 (unzip:这会花费一定时间 )
tar -xvf modutils-2.4.6.tar
cd modutils-2.4.6 ( 进入“modutils”解包的目录 )
./configure (进行系统特殊配置(system-specific configurations) )
make (编译“modutils”:因为这仅仅是一个小小的编译,因此速度惊人的快 )
make install (将“modutils”安装到“/sbin/” )

这就是你安装“modutils”时所要做的全部事情。 注意,在这个例子中“modutils”碰巧跟内核时相同的版本号,但并不总是这样。  

内核是否正常运行?

新内核已经被配置,编译,而且大概还被安装到lilo里了。 重新启动计算机,问自己:我怎样才能判断我的新内核是否正常工作呢? 当你尝试做某些事情的时候,你可以很容易的找出没有工作的硬件设备,另外,在启动的时候,甚至在你进行图形登录之前,内核会在屏幕上显示很多有用的信息。 这些信息不仅包括配置自动诊测到的设备(端口、中断请求……),而且还包括驱动程序不能正确初始化的错误信息——不管是内核的一部分还是可加载模块。 阅读这些信息可以及早得到某些特定问题的警告。对于SuSE Linux来说,这很简单:内核的每一部分如果正常启动,将在屏幕的右边显示绿色的“done”,如果出现问题,就显示“failed”。 因为信息可以在屏幕上滚动,所有的错误信息都在登录提示符的上边。 如果你默认使用的图形界面登录,你可以使用“<Ctrl>+<Alt>+F1”来找到登录提示符(字符界面)。 这样,如果错误信息的确跟你有关,你至少可以对那里出现了问题有一点线索。 类似“Cannot find module”或者“Cannot load module”的错误信息表明你没有将某些你不应该丢弃的部分加入到内核中。 更改内核的配置,然后重新编译通常可以解决这些问题。 注意,不必重新编译整个内核。 如果你只是忘记一些可加载模块,重新执行“make modules”和“make modules_install”就足够了。 其实,即使你重新编译整个内核也不是什么大问题。 大多数内核代码已经编译,仅仅需要重新编译丢失的部分。 简单的说,相对于初次编译内核来说,对内核做小小的修改然后重新编译的速度是非常快的。 你可以喝一些咖啡,来等待编译完成——一切都会很顺利。

另外一种查看内核启动信息的方式是使用“dmesg”命令。 直接运行这个命令(不带参数)会将刚才的启动信息调出来。 将它们定向到一个文件,使用“dmesg > temp”,这样你可以使用“more temp”或者使用自己喜欢的编辑器查看信息。(译者注,还可以使用“demsg | more”)

 

总结

有了这份指南,你现在就可以开始你编译内核的试验。玩内核的最低要求恐怕就是你想玩。 你的大多数时间将会花在决定内核的正确配置上,而在编译的时候,你可以玩玩“freecell”或者做其他事情。

如果你还有一些问题,并且不知道改怎么办,你可以去与Linux有关的邮件列表(Linux-related Mailing Lists)或者网站问问这些问题。 这些(站点和邮件列表)在几乎所有语言中都有,而不仅仅是英语。 通常很短的一段时间之后,你就可以得到相当有用的帮助来解决问题。 找到这些邮件列表和网站的最好办法就是使用搜索引擎。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值