您的位置:首页 > 运维架构 > Linux

Kbuild: the Linux Kernel Build System

2016-04-21 19:12 656 查看

Kbuild:Linux内核构建系统

Linux使用一样的基础代码却可以被上至超级计算机下至小型嵌入式设备的不同设备使用着实让我们惊讶。静下来想想其实Linux应该是唯一一款操作系统使用统一的代码基础的,相比之下微软的Windows NT和Windows CE和Apple的OSX和IOS在桌面和移动设备上使用了不同的内核。其中的原因我想大概是Linux内核有许多的抽象层和间接层次结构,并且Linux提供了允许高度定制的二进制内核文件。

由于Linux的内核有庞大但统一的体系结构,全部的内核程序都会在内核空间中运行并享有相同的地址空间。因为这种特殊的系统结构,人们必须在内核编译前就确定好内核的功能。但是严格意义上来讲,Linux内核并非完全统一的内核,因为它仍然可以在运行时一定程度上扩展内核模块。为什么说是一定程度上呢?因为实际上在运行时扩展的新模块仍然需要调用已经编译好的内核模块的接口来实现。如果这些调用的接口没有一开始就写进内核中并被编译,那么实际上在加载新模块的时候就会出现找不到依赖的错误,所以后来加载的新模块只是内核功能的一种特殊实现方式。一旦内核模块被装载,那么这个模块就和原来编译好的内核共享一片相同的地址空间。由此看来,就算Linux支持添加新模块,我们仍需要在内核编译前就确定好内核所需要的大部分功能,这些功能将被写入内核镜像中用来在系统运行时加载新的内核模块。

因为上述原因,选择哪些代码在内核编译的时候被编译与否是非常重要的。为了实践这种思想,我们就需要可选择配置的编译,成吨的选项支持我们可以选择我们需要的功能,这些配置选项可以决定编译器在编译的时候是否将特定的C文件、代码段或者数据结构编译进内核镜像和内核模块中。

所以,需要一个简单高效的管理方法来配置这些选项。这个管理这些配置的基础程序就是Linux内核构建系统(kbuild)。

Kbuild的组成

Config symbols:一些可以决定哪些代码或数据结构应该被包含到内核映像或模块中的选项。

Kconfig文件:定义每个配置选项和它的属性,比如说它的类型、描述和依赖。构建选项菜单树的程序(比如说
make menuconfig
)会根据这些文件加载整个选项菜单。

.config文件:存储每个配置选项的选择。你可以手工编辑这些文件或者使用配置程序(比如说
menuconfig
xonfig
)更新这些文件。

Makefiles:标准的GNU
Makefile文件
描述了各个源文件的关系和一些指向各个
make
目标的命令,比如说内核映像和模块。

各个组成部分的详细说明

Compilation Options: Configuration Symbols

配置选项用来决定那些功能需要被包含到Linux内核映像中。两个主要的选项标志是:
boolean选项
tristate选项
。这两种选项的区别仅仅是选择个数的区别,其中
boolean选项
选项毫无疑问是只有
true
false
两个选项,而
tristate选项
则有
yes
no
module
三个选项。

并不是所有的在内核里面的东西都可以被编译成一个模块。许多功能具有干扰性了,你需要在配置时决定是否让你的内核支持这些功能。比如说,你不能添加
Symmetric Multi- Processing (SMP)
或者
Kernel preemption support
到一个正在运行的内核中。所以这个时候我们就需要用到
boolean选项
来明确这些功能。但仍然有大部分的模块是可以在编译完后的内核中添加的,这就是
tristate选项
存在的原因———来决定你是将这些可以后期添加的东西现在就编译到内核中
(y)
,作为模块编译在运行内核的时候加载
(m)
还是根本就不编译
(n)


除了上述两种形式,配置选择标志还有一些其他的形式比如说
strings选项
hex选项
。但因为这并不是用来条件编译的,所以我并不打算在这里多讲。如果需要了解,可以阅读Linux内核的官方文档

Defining Configuration Symbols: Kconfig Files

定义配置选项的文件被称为
Kconfig文件
。每个
Kconfig文件
可以描述一系列配置,也可以包括一些其他的
Kconfig文件
的源码。构建选项菜单树的程序(比如说
make menuconfig
)会根据这些文件加载整个选项菜单树。

内核文件中的每一个目录都有一个
Kconfig
,里面包含了所有
Kconfig文件
和它的子目录。在最外层内核源代码目录,有一个
Kconfig文件
是整个树状结构的根。
menuconfig
gconfig
和其他编译目标组合程序从这个根
Kconfig文件
开始搜索子目录来构建配置选项菜单树。选择哪个子目录去访问不仅决定于每个上层目录的
Kconfig文件
,还会根据配置过程中用户的选项。

Storing Symbol Values: .config File

存储每个配置选项的选择。每次你想要改变内核编译的配置选项都要使用配置程序(比如说
menuconfig
xonfig
)更新这些文件。当然,这些工具不仅可以更新你的配置选项,还可以建新的
.config文件
如果文件不存在。

因为
.confi文件
是一个纯文本文件,所以你也可以不用特殊的程序,手动改变这些文件。这也不失为一种方便的保存配置选项的方法。

Compiling the Kernel: Makefiles

最后一个
kbuild系统
的组件就是
Makefiles
,它们用来编译内核映像和模块。就像
Kconfig文件
一样,内核源码包的每一层子目录都有一个
Makefile
用来编译这个文件夹下的文件。上一层
Makefile
会向下找各个子目录的
Makefile
的位置并且会编译它们。最后,这些被编译好的文件会成为Linux内核映像。

怎么将上述组件组合起来

想要在Linux内核中添加一个功能,需要做三件事:

将源代码文件放在相应目录下,比如说把
WI-FI设备
放在
drivers/net/wireless
目录下。

在你放置源代码的目录下为每一个子目录更新每一个
Kconfig文件
来让用户可以选择是否添加该功能。

在你放置源代码的目录下为每一个子目录更新
Makefile
文件,确保你的构建系统可以条件编译你的代码。

用Scull设备驱动程序作为范例

因为这个设备是一个字符型设备,所以把源代码放置到
drivers/char
目录下。

接着就是让用户可以选择是否编译这个设备驱动,为了完成这一步我们需要为设备添加一个
Kconfig文件
——
drivers/char/Kconfig  file


就像大部分的设备一样,
scull设备
可以编译到内核映像里、模块里或者根本就不编译。所以这个配置选项应该叫
SCULL
,是一个
tristate选项
,具有
(y/n/m)
三个选项。

Kconfig Entries for the SCULL Driver

#
# Character device configuration
#
menu "Character devices"
config SCULL
tristate "Coin char device support"
help
Say Y here if you want to add support for the coin char device.
If unsure, say N.
To compile this driver as a module, choose M here: the module will be called coin.


那么怎么使用最新添加的这个配置呢?

就像前面说的一样,构建配置选项菜单树的程序会用到这个配置选项,所以你可以选择让什么编译到你的内核里面。比如说,当我们运行命令
make menuconfig
时,命令行程序会开始读取所有
Kconfig文件
来构建基础菜单接口。接着你就可以更新我们对
SCULL
这个设备的配置选项了。将导航按照
Drivers→Character devices
就可以看到我们对于
SCULL
设备的选项了,再接着我们就可以根据我们的需要修改这个选项了。

一旦你完成了整个编译配置过程,退出菜单程序,如果配置过程中跟以前的配置发生了改变,那么程序会问你是否应用新的配置。这将会这些配置的选择到
.config文件
。对于每一个配置选项都会添加一个
CONFIG_ prefix
.config文件
中。比如说,如果上述设备配置选项是
boolean
形式而且我们选择了
yes
的话,那么在相应的
.config文件
中就会添加对应条目
CONFIG_SCULL=y
。如果你并没有为这个配置选项选择,那么在
.config文件
里将会对应一条
# CONFIG_SCULL is not set


tristate
形式的选项和
boolean
形式的选项一样,都有
yes
no
两种情况,也有没有选择的情况,当然别忘记了这种形式下还有一个选项
module
。如果上述设备使用的是这个形式的选项并且选择了
module
,那么在
.config文件
中就会添加一行
CONFIG_SCULL=m


在这里我们可以让我们的设备驱动在编译内核的时候就编译到内核映像中或者选择在编译完内核之后在运行的时候再装载这个模块,对应的两种在
.config文件
中的指令就是
CONFIG_SCULL=m
CONFIG_SCULL=y


一旦你已经生成好了
.config文件
,就说明你已经准备好编译内核和内核模块。当你执行编译命令来编译内核以及模块的时候,计算机会先执行一个二进制程序来读取所有
Kconfig文件
.config文件
$ scripts/kconfig/conf Kconfig


这个二进制程序更新(或者新建)了一个内有你对所有配置的选择的C语言头文件——
include/generated/autoconf.h
,并且所有
GCC编译器
都会包含这个头文件,所以这些配置选项对于所有内核文件的编译都生效。这个文件定义了数以千计用以描述配置选项的选择的
#define
宏定义。

下面我们来看看这些宏定义是什么样的。

boolean
型选项的
true
tristate
型选项的
yes
是等价的,拿
SCULL设备
做例子,对于这个选项,会生成三个宏定义如下:

#define __enabled_CONFIG_SCULL 1
#define __enabled_CONFIG_SCULL_MODULE 0
#define CONFIG_SCULL 1


同样的,对于
boolean
型选项的
false
tristate
型选项的
no
也是等价的,还是
SCULL设备
做例子,对于这个选项,会生成宏定义如下:

#define __enabled_CONFIG_SCULL 0
#define __enabled_CONFIG_SCULL_MODULE 0


当然别忘了
tristate
型选项还有一个选择
module
,继续拿
SCULL设备
做例子,对于这个选项,会生成宏定义如下:

#define __enabled_CONFIG_SCULL 0
#define __enabled_CONFIG_SCULL_MODULE 1
#define     CONFIG_SCULL_MODULE 1
#define IS_ENABLED(option) \
(__enabled_ ## option || __enabled_ ## option ## _MODULE)
#define IS_BUILTIN(option) __enabled_ ## option
#define IS_MODULE(option) __enabled_ ## option ## _MODULE


更新Makefiles。

最后一步就是为我们这个设备的子目录更新
Makefiles


好让
kbuild
可以为我们编译这个设备的驱动。但是我们怎么让
kbuild
来条件编译我们的内核源文件呢?

内核编译系统
Kbuild
有两个主要的任务:创造二进制的内核映像和内核模块。为了做到这两个任务,
kbuild
将会分别生成一系列的两种文件:
obj-y
obj-m
。前者是一系列和构建内核映像有关的对象,而后者则是一系列将会被编译成内核模块的对象。

.config文件
aotuconf.h
中的配置选项及其选择被用来根据
GUN``make
的语法完成以上一系列对象的创造。
Kbuild
将会递归的进入到各个文件夹
Makefile
文件中并构建列出的一系列对象。更多的信息关于
make
语法和对象列表的描述可以读取源码包中的
Documentation/kbuild/makefiles.txt


对于我们这个
SCULL设备
,只需要添加一行
obj-$(CONFIG_SCULL) += scull.o
drivers/char/Makefile
中去就可以了。这句话的意思就是告诉
kbuild
去根据
scull.c
源文件创造一个对象并把它加入到构建对象的列表里面去。因为
CONFIG_SCULL
的选择可能是
y
或者
m
scull.o
对象文件将会根据选项被添加到
obj-y
obj-m
列表当中去,接着这个源文件就会被编译成
内核
或者
模块
。如果我们没有选择
CONFIG_SCULL
的值,那么整个
scull.o
对象文件将根本不会被编译。

现在你应该了解怎么让源文件有条件地包含到源码包中。最后一个难题就是怎么有条件地编译内核源文件,通过使用
autoconfig.h
中的宏定义,这个难题将会被很轻松的解决。

作业

把我们自己编写的驱动程序加入Linux的源码树。

翻译信息

原文档:<北京交通大学 杨武杰老师课件>

原作者:杨武杰

译者:刘瀚文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: