2024年8月17日发(作者:)
编译Linux内核
实验目的
学习重新编译Linux内核,理解、掌握Linux内核和发行版本的区别。
实验内容
重新编译内核是一件比你想像的还要简单的事情,它甚至不需要你对内核有任何的了
解,只要你具备一些基本的Linux操作系统的知识就可以进行。
本次实验,要求你在RedHat Fedora Core 5的Linux系统里,下载并重新编译其内核源
代码(版本号KERNEL-2.6.15-1.2054);然后,配置GNU的启动引导工具grub,成功运行
你刚刚编译成功的Linux内核。
实验提示
Linux是当今流行的操作系统之一。由于其源码的开放性,现代操作系统设计的思想和
技术能够不断运用于它的新版本中。因此,读懂并修改Linux内核源代码无疑是学习操作系
统设计技术的有效方法。本实验首先介绍Linux内核的特点、源码结构和重新编译内核的方
法,讲述如何通过Linux系统所提供的/proc虚拟文件系统了解操作系统运行状况的方法。
最后,对Linux编程环境中的常用工具也有简单介绍。
1.1查找并且下载一份内核源代码
我们知道,Linux受GNU通用公共许可证(GPL)保护,其内核源代码是完全开放的。
现在很多Linux的网站都提供内核代码的下载。推荐你使用Linux的官方网站:
,如图1-1。在这里你可以找到所有的内核版本。
图1-1 Linux的官方网站
由于作者安装的Fedora Core 5并不附带内核源代码,第一步首先想办法获取合适版本
的Linux内核代码。通过命令
# uname –r
2.6.15-1.2054_FC5
这就是说,RedHat Fedora Core 5采用的内核版本是2.6.15-1.2054_FC5。但是,官方网
站/pub/linux/kernel/找不到对应版本。请别着急,既然它是RedHat发布
的,RedHat的官方网站总有吧。浏览
/pub/fedora/linux/core/5/source/SRPMS,我们发现果然有文件
kernel-2.6.15-1.2054_
,这个rpm文件就是2.6.15-1.2054_FC5版的内核源代码了。
下载后保存。需要说明的是,其实还有许多网站保存此文件的;有时候以ISO压缩包的形
式出现,文件名为kernel-2.6.15-1.2054_ 。
1.2 部署内核源代码
此过程比较机械、枯燥,因而容易出错。请严格按下述步骤来操作。
首先,解开rpm包,放在/usr/src/redhat。使用操作序列:
# rpm –Uvh kernel-2.6.15-1.2054_
# cd /usr/src/redhat/SPECS
# rpmbuild -bp --target $(uname -m)
这里,命令uname –m检测CPU型号。作者的主机是i686。假如执行shell命令:
# ls /usr/src/redhat/BUILD/kernel-2.6.15/
linux-2.6.15.i686 vanilla xen xen-vanilla
可见,Linux内核源代码已经在/usr/src/redhat/BUILD/kernel-2.6.15/linux-2.6.15.i686下面
了。作者习惯了RedHat过去的部署,还是希望通过路径/usr/src/linux去访问它。这只要建
一个符号链接:
# cd /usr/src
# ln -s ./redhat/BUILD/kernel-2.6.15/linux-2.6.15.i686/ linux
1.3 配置内核
在你进行这项工作之前,不妨先看一看/usr/src/linux目录下内核源代码自带的README
文件。在这份文件中,对怎样进行内核的解压,配置,安装都进行了详细的讲解。不过,其
介绍的步骤不完全符合我们的版本,所以还是以本书为准。
在编译内核前,一般来说都需要对内核进行相应的配置。配置是精确控制新内核功能的
机会。配置过程也控制哪些需编译到内核的二进制映像中(在启动时被载入),哪些是需要时
才装入的内核模块(module)。
# cd /usr/src/linux
# cp configs/ .config
cp:overwrite „.config‟ ? y
当前目录下的Makefile有一项内容:
EXTRAVERSION = -prep
因为版本号已经变成2.6.15-1.2054_FC5了,所以,使用任何一种文本编辑工具,将它换成:
EXTRAVERSION = -1.2054_FC5
第一次编译的话,有必要将内核源代码树置于一种完整和一致的状态。因此,我们推荐执行
命令make mrproper。它将清除目录下所有配置文件和先前生成核心时产生的.o文件:
#make mrproper
然后:
#make menuconfig
make menuconfig是基于文本的选单式配置界面,作者一般使用这一配置命令。当然,
其它的配置界面也不错啊,例如:
make xconfig,使用X Windows (Qt) 界面
make gconfig,使用X Windows (Gtk) 界面
make oldconfig,使用文本界面,按照./.config文件的内容取其缺省值
make silentoldconfig,与上一个一样;不同的是,不再逐项提问了
进行配置时,大部分选项可以使用其缺省值,只有小部分需要根据用户不同的需要选择。
例如,如果硬盘分区采用ext2文件系统(或ext3文件系统),则配置项应支持ext2文件系
统(ext3文件系统)。又例如,系统如果配有SCSI总线及设备,需要在配置中选择SCSI
卡的支持。
对每一个配置选项,用户有三种选择,它们分别代表的含义如下:
“<*>”或“[*]” - 将该功能编译进内核
“[ ]” - 不将该功能编译进内核
“[M]” - 将该功能编译成可以在需要时动态插入到内核中的模块
将与核心其它部分关系较远且不经常使用的部分功能代码编译成为可加载模块,有利于
减小内核的长度,减小内核消耗的内存,简化该功能相应的环境改变时对内核的影响。许多
功能都可以这样处理,例如像上面提到的对SCSI卡的支持,等等。
1.4 编译内核和模块
编译内核,就用:
#make
编译内核需要较长的时间,具体与机器的硬件条件及内核的配置等因素有关(作者采用
VMWare虚拟机,需要约50分钟)。完成后产生的内核文件bzImage的位置在
/usr/src/linux/arch/i386/boot目录下,当然这里假设用户的CPU是Intel x86型的,并且你将
内核源代码放在/usr/src/linux目录下。
如果选择了可加载模块,编译完内核后,要对选择的模块进行编译。用下面的命令编译
模块并安装到标准的模块目录中:
#make modules
#make modules_install
1.5了解Linux内核的启动
通常,Linux在系统引导后从/boot目录下读取内核映像到内存中。因此我们如果想要使
用自己编译的内核,就必须先将/usr/src/linux/arch/i386/boot下的bzImage和拷贝
到/boot目录下。
mv /usr/src/linux/arch/i386/boot/bzImage /boot
mv /usr/src/linux/arch/i386/boot/ /boot
或者,使用命令make install也能达到此目的。
现在,编译完毕的Linux内核已经在那里了。那么,如何让它运转呢?或者说,当我们
启动电脑后,如何告诉CPU,去加载、执行这个Linux内核呢?让我们简单回忆一下BIOS
工作原理,以及单一操作系统装入、启动原理。
主机上电后,通过RESET组合电路,给CPU一个稳定的启动信号,使INTEL CPU(或
与其兼容的CPU)从地址为0xf000:0xfff0开始执行指令。加电入口地址F000:FFF0存放一
条JMP指令。通常,主机将该跳转地址安排为BIOS的初始化程序的入口地址。所以,用
户开机后,马上看到了BIOS的开机画面。
BIOS的初始化过程,将完成两部分工作:系统加电自检(Power On Self Test)和系统
自举。系统自举就是操作系统的装入和引导。不过,在此之前先做加电自检,即对电脑系统
硬件的进行一系列测试。简要而言,BIOS开机启动工作的工作流程:
检测电脑系统中的内存、显卡等关键设备能否正常工作
查找显卡的BIOS,然后调用显卡BIOS的初始化代码,由它来完成显卡的初始化。大
多数显卡在这个过程通常会在屏幕上显示出一些显卡的信息,如生产厂商、图形芯片类
型、显存容量等内容
查找其它设备的BIOS程序,找到之后同样要调用这些BIOS内部的初始化代码来初始
化这些设备。在这个步骤里,我们可以看到键盘上的NUM LOCK、CAPS LOCK、
SCROLL LOCK等指示灯都闪亮一下。另外,如果电脑正连接着一台针式打印机的话,
将会听到打印头复位的声音。除了初始化系统硬件,初始化芯片中的寄存器,这个过程
还要初始化能源管理模块,所有与电脑节能有关的寄存器、记时器等都从头开始
显示BIOS自己的启动画面,其中包括有系统BIOS的类型、序列号和版本号等内容同
时屏幕底端左下角会出现主板信息代码,包含BlOS的日期、主板芯片组型号、主板的
识别编码及厂商代码等
检测CPU的类型和工作频率,并将检测结果显示在屏幕上,这就是我们开机看到的CPU
类型和主频
开始检测系统中安装的一些标准硬件设备,这些设备包括硬盘、CD-ROM、软驱、串行
接口和并行接口等连接的设备
标准设备检测完毕后,开始检测和配置系统中安装的即插即用设备。每找到一个设备之
后,BIOS都会在屏幕上显示出设备的名称和型号等信息,同时为该设备分配中断、DMA
通道和I/O端口等资源
上面描述的步骤是电脑在打开电源开关(或按Reset键)进行冷启动时所要完成的各种初
始化工作。如果我们按Ctrl+Alt+Del组合键来进行热启动,那么前面两步将被跳过,直接从
第三步开始;另外,第五步的检测CPU和内存测试也不会再进行。无论是冷启动还是热启
动,BIOS都会重复上面的硬件检测。
加电自检完成后,即根据用户指定的启动顺序从软盘、硬盘或光驱中寻找启动设备。一
旦找到启动设备,就会将系统的控制权交给启动设备中的操作系统。BIOS读取并执行硬盘
上的主引导记录,主引导记录接着从分区表中找到第一个活动分区,然后读取并执行这个活
动分区的分区引导记录,而分区引导记录将负责读取并执行操作系统的初始化程序。
不妨以从Windows下的C盘启动为例,分区引导记录将负责读取并执行,这是
Windows最基本的系统文件。Windows中首先要初始化一些重要的系统数据,然后
就显示出我们熟悉的如蓝天白云的启动画面,在这幅画面之下,Windows将继续进行DOS
部分或GUI(图形用户界面)部分的引导和初始化工作。
任何操作系统都应首先考虑与INTEL微机的系统结构和BIOS的兼容性。按照早期
INTEL微机与DOS的约定,电源开启后,由机器的ROM BIOS(或主引导区)负责将启
动盘第一扇区(boot sector)的内容从磁盘装入起始地址为0X7C00的内存空间,然后跳
转至0X7C00位置开始执行。
如果电脑系统安装了多种操件系统,如Linux等,那么,BIOS怎么引导指定的操作系
统呢?答案是,此时我们需要一个操作系统引导工具,如grub。
通常主引导记录将被替换成该引导工具的引导代码,这些代码将允许用户选择一种操作
系统,然后读取并执行该操作系统的基本引导代码。例如,Windows的基本引导代码就是
分区引导记录。
GNU grub是一个多重操作系统启动管理器。类似的引导工具有很多,例如LILO(Linux
Loader),Windows中的NTLOADER,PowerPC架构中的yaboot。grub的一个重要特性是
灵活性,它理解文件系统和内核的可执行格式,所以我可以按自己喜欢的方式加载操作系统
而不需要记录内核的物理地址。这样,就可以通过给出内核文件名和它所在的磁盘分区、路
径来加载它。当启动grub后,可以使用命令行方式或者菜单方式启动内核。使用命令行方
式时,需要手动输入磁盘信息和内核的文件名。菜单方式下,只需要选择要启动的操作系统
就可以了。菜单显示的是我之前就已经配置好的一个文件。菜单模式下,可以选择进入命令
行模式,反之也可。甚至可以在使用之前编辑菜单入口。
grub源文件可以从其官方网站上下载/,不过,RedHat Fedora Core
5已经附带了grub工具。
开机后,INTEL CPU在实模式下(real mode)工作,只能使用低端640KB的内存
空间,且一部分空间已经被BIOS占用。核心系统一般比这大,而且开始只能装入一个扇区,
因而Linux不得不设计特殊的方法装入内核。方法之一就是经过压缩的核心模块zImage。
如果zImage还是放不下,那么就采用big zImage的加载方式,也就是我们刚刚编译生成的
bzImage。
不妨粗略看一下arch/i386/boot目录下的bootsect.S源文件,它是一个实模式下运行的汇
编程序。它们经过汇编后生成二进制代码,存放在磁盘的引导区。
arch/i386/boot/bootsect.S
18 SETUPSECS = 4 /* default nr of setup-sectors */
19 BOOTSEG = 0x07C0 /* original address of boot-sector */
20 INITSEG = DEF_INITSEG /* we move boot here - out of the way */
21 SETUPSEG = DEF_SETUPSEG /* setup starts here */
22 SYSSEG = DEF_SYSSEG /* system loaded at 0x10000 (65536) */
23 SYSSIZE = DEF_SYSSIZE /* system size: # of 16-byte clicks */
/* to be loaded */
——————————————————————————————————
这是 bootsect.S中的最初几行,将对几个段值做初始化,其中的几个宏DEF_INITSEG,
DEF_SETUPSEG, DEF_SYSSEG, DEF_SYSSIZE定义在include/asm-i386/boot.h :
#define DEF_INITSEG 0x9000
#define DEF_SYSSEG 0x1000
#define DEF_SETUPSEG 0x9020
#define DEF_SYSSIZE 0x7F00
如果仔细阅读bootsect.S,我们就会发现,正是这个属于Linux内核源代码一部分的内
核程序,将Linux内核(无论以linuz文件,还是以zImage文件,或者bzImage文件的形式
保存)最终装进了主机内存,并且启动运行。
综上所述,BIOS、grub、bootsect.S在从主机上电至Linux操作系统运行这一过程中,
各自起到了不可替代的作用。如表1-1
表1-1 系统上电启动阶段各功能模块的作用
对 象
BIOS
grub
作 用
上电后接管CPU,装入操作系统引导工具,如grub
提供配置手段,或人机交互手段,将CPU交给用户指定的操作
系统的内核
bootsect.S
从grub那里接管CPU,并且拼装、运行Linux内核初始化程序
1.6 应用grub配置启动文件
如果使用grub启动Linux,则编辑/boot/grub/文件,修改系统引导配置。例如
使用vi编辑工具:
#vi /boot/grub/
# generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE: You do not have a /boot partition. This means that
# all kernel and initrd paths are relative to /, eg.
# root (hd0,0)
# kernel /boot/vmlinuz-version ro root=/dev/sda1
# initrd /boot/
#boot=/dev/sda1
default=0
timeout=5
splashimage=(hd0,0)/boot/grub/
hiddenmenu
title Fedora Core (2.6.15-1.2054_FC5)
root (hd0,0)
kernel /boot/vmlinuz-2.6.15-1.2054_FC5 ro root=LABEL=/
initrd /boot/initrd-2.6.15-1.2054_
title Project One
root (hd0,0)
kernel /boot/bzImage ro root=LABEL=/
initrd /boot/initrd-2.6.15-1.2054_
这里,以‘#’开头的,是注释行,grub不会去解释、执行它。“default=0”命令表示,
当用户没有在规定时间内响应时,将解释、执行第1个“title”选项。“timeout=5”规定用
户有效响应时间是5。“title”定义了启动菜单里面的菜单项,一旦选中了此菜单项,那么,
只有这个“title”后跟的命令才被grub解释、执行了。“root”指定“根设备”的地址。通
过“kernel”命令,要求grub从后跟的路径,装入操作系统内核。grub将从“initrd”命令
指定的路径,装入一个ramdisk文件,并设定一些必需的参数;Linux操作系统启动时,需
要这样的文件。
好了,我们已经编译了内核bzImage,放到了指定位置/boot;我们也配置了
/boot/grub/。现在,请你重启主机系统,期待编译过的Linux操作系统内核正常运
行!
做到这一步不容易。初次接触Linux的新手,恐怕没这么顺利。
在您提交的实验报告中,要求有您的计算机上文件内容。
最后,当你完成整个实验后,要清除编译时的临时文件,使用命令:
#make clean
注意,这些临时文件是你花费1小时左右的时间,辛辛苦苦建立起来的。不到有十分的
把握,建议不要轻易删除它们。
1.7 学习Linux的常用工具
有些软件、工具,对于我们学习Linux很有帮助。用好、用通了其中一二,往往达到事
半功倍的效果。例如,我们已经看到了,grub工具帮助我们很轻松地管理多系统启动问题。
这里,再推荐虚拟机系统VMware。
1.7.1 虚拟机系统VMware(/overview/)
虚拟化技术,希望在计算机硬件与操作系统之间,插入一个抽象层,变原本的一对一关
系(一套计算机硬件,对应一个操作系统),为一对多关系(一套计算机硬件,对应多个独
立的操作系统)。而且,所对应的操作系统,可以是相同版本的,也可以是多个版本的,甚
至是完全不同的操作系统。例如,一台典型的IBM X60笔记本,安装了Windows XP,没有
Linux环境。不过,如果引入、安装了VMware Workstation,则可在它的上层,安装、运行
Linux系统。
尽管不采用虚拟化技术,我们照样能够借助grub,完成Linux的一系列实验。但是,如
果选择虚拟机,如VMware,那么至少:
在一台计算机上,方便地支持多个操作系统,支持在异构操作系统环境运行的多种应用
程序
在虚拟机上运行的应用程序不必考虑与其它操作系统的资源共享问题。它似乎独占了计
算机资源
虚拟机之间,虚拟机与主机之间完全隔离。任一台虚拟机死机,不会影响其它机器
虚拟机上的数据也相互隔离。数据通信只能通过网络连接进行
1.7.2 安装VMware
访问VMware官方网站/download/ws/open_,可以下
载其open source版本VMware Workstation 5。因为有60MB以上,得耐心等一会。
下载得到安装文件“VMware_Workstation_”。双击之,即开始安
装。出现画面,如图1-2。
图1-2 开始安装Vmware
如同通常的Windows应用程序安装,其安装过程没有什么例外。期间会出现如图1-3
的画面。
图1-3 安装VMware正在进行中
如果你看到了图1-4所示的画面,那么,恭喜你,成功了!
图1-4 VMware安装成功
1.7.3 使用VMware――在虚拟机上安装RedHat Fedora Core 5
在桌面上点击VMware的启动图标,然后选择新建虚拟机的操作。在“新建虚拟机向
导”的导引下,创建第1个虚拟机。创建过程中,按照你使用的主机的情况,配置系统。当
然,基本上可以采用向导提供的缺省值。
在作者的环境里,创建了准备安装RedHat Fedora Core 5的虚拟机,其就绪状态如图1-5。
图1-5 在VMware虚拟环境里安装Linux系统
参照图1-5,可以看出,我们配置了内存、硬盘、光盘、以太网、USB控制器、音频。
特别请注意,硬盘、光盘的类型,选择IDE,以便于后面安装Linux系统。如图1-6。
图1-6 在虚拟环境里配置Linux系统
光盘配置窗口中,指定其“使用ISO映象”,而且,ISO文件就是“”,
即RedHat Fedora Core 5的安装光盘。
在图1-5所示的窗口,不难找到一个栏目,栏目名称是“命令”。这里,第1个显示的
命令为“启动此虚拟机”。很好,点击它!虚拟机转入安装Linux的过程。该过程与我们在
裸机上安装Linux的过程一模一样。
1.8 使用GNU cc
GNU cc(gcc)是Gnu/Linux操作系统中的编译器套件,使用它能够编译C、C++和
Objective C编写的程序。gcc是一个交叉平台的编译器,支持在不同CPU平台上开发基于
不同体系结构硬件的软件。如同其它的编译器,gcc可以在编译时优化执行代码,而且能够
产生调试代码。
1. 简单使用
在详细介绍gcc的特有的选项功能之前,我们先通过编译上节的程序kinfo.c来看gcc
的简单使用方法。
在命令行上键入如下命令编译,运行这个程序:
#gcc kinfo.c –o kinfo
#./kinfo
其中,kinfo前的‘./’指明执行当前目录下的程序。否则,shell会按环境变量PATH
所设定的路径(缺省不包括当前目录)查找命令并提示命令kinfo不存在。
使用gcc命令对源程序kinfo.c进行编译连接,-o参数指定生成的可执行文件名为kinfo。
gcc在编译过程中可以分为预处理、编译、链接三个阶段。在预处理阶段,gcc运行预处理
程序cpp展开源程序中的宏并插入#include
处理后的源代码编译成与机器相关的目标代码;最后,链接程序 ld链接目标代码创建名为
kinfo的可执行二进制文件。程序员可以通过选项让gcc在编译的任何阶段结束后停止整个
编译过程以检查编译器在该阶段的输出信息。比如,gcc的-E选项可以使gcc在预处理后停
止编译过程:
#gcc –E kinfo.c –o
此时打开会发现头文件内容和其它预处理符号已被插入到其中。
-c选项使gcc停止在生成目标代码结束。如下命令将编译为目标代码:
#gcc –x cpp-output –c kinfo.o
其中,-x选项告诉gcc从指定的步骤(即预处理输出)开始编译。
最后,使用-o链接目标文件生成二进制代码:
#gcc kinfo.o –o kinfo
表1-2列出了gcc常用的命令行选项。全部的选项长达数页,可以通过Linux帮助命令
man查看。
表1-2 gcc常用命令行选项
选项
-o filename
-c
-DFOO=BAR
-IDIRNAME
-LDIRNAME
-static
-lFOO
-g
-ggdb
-On
-ansi
-pedantic
-pedantic -error
-w
-Wall
-werror
-v
描述
指定输出文件名,如果不指定filename缺省文件则是
只编译产生目标文件(.o文件)不链接
定义预处理宏FOO,其值为BAR
将DIRNAME路径加到头文件搜索目录中
将DIRNAME路径加到库文件搜索目录中
静态链接库文件
链接名为 libFOO的库文件
在可执行代码中包含标准调试信息
在可执行代码中包含gdb特有的调试信息
指定优化编译的级别n,n可以为1、2、3
使用ANSI/ISO C的标准语法
允许发出ANSI/ISO C标准所列出的警告
允许发出ANSI/ISO C标准所列出的错误
关闭所有警告
允许发出gcc的所有警告
编译时将警告作为错误处理
显示在编译过程中每一步用到的命令
2. 优化编译选项
优化可以改进执行文件的代码长度和执行效率。gcc支持三个级别的编译优化。-O或-O1
选项指定第一级上的优化,在这个级别上所能执行的优化是取决于目标处理器的,通常包括
线程直接跳转(thread jumps)和延迟退栈(deferred stack pops)。线程直接跳转优化的目的
是减少跳转的次数。延迟退栈则通过在嵌套的函数调用时推迟退栈的时间直到所有递归调用
完成,从而优化运行效率。
-O2优化选项包含O1级所做的优化,并调整处理器指令执行时序。优化使处理器在等
待其它指令的结果或数据延迟时仍然可以执行其它不相关指令,从而充分利用CPU资源,
但其实现与处理器是密切相关的。-O3选项则包括O2级的一切优化并使用内嵌函数、循环
展开以及其它与特定处理器特性有关的优化。另外,还可以使用-f{flag}选项来指定需要执
行的具体优化方法。比如-finline-function使用内嵌函数,-funroll-loops使用循环展开。内嵌
函数把简单的函数内嵌到调用它的地方,减少函数调用时的开销。循环展开则展开所有在编
译期间能确定重复次数的循环,产生更多的不相关代码利于优化。但这两种优化的代价使可
执行代码急剧增长。
3. 调试选项
gcc能够使用-g和-ggdb选项在可执行代码中插入调试信息以便于程序调试。-g选项后
可以附加1、2或3指定要在代码中加入多少调试信息。缺省情况下是2,它将在输出代码
中加入符号表、行号、局部和外部变量信息。1级选项仅生成函数调用时的堆栈转储和回退
信息,不包含行号和变量的调试信息。3级选项则包括所有2级的调试信息并包含所有宏定
义等。
如果使用GNU Debugger(gdb)来调试,可以使用-ggdb选项产生gdb所特有的调试信
息以方便gdb调试。但是,这样做会使程序不能被其它的调试程序调试。使用调试选项会使
产生的可执行代码变得很大,尤其是使用-ggdb选项会使代码急剧增大。gcc允许-g和-O联
用,优化代码可能改变程序的控制流,会使程序无法正常调试。因此建议在程序被调试通过
后再对其优化,并且在发布程序的可执行代码中包含标准的调试信息(即-g选项),以便
用户在遇到程序出错时能够自行调试代码发现问题。
实验思考
浏览/boot目录,你一定发现了-2.6.15-1.2054_FC5文件,以及
initrd-2.6.15-1.2054_文件。如果打开/boot/grub目录下面的文件,不难发
现命令:
initrd /boot/initrd-2.6.15-1.2054_
这两个文件分别起什么作用?你能否设计一个实验来验证你的判断?


发布评论