kbuild:the linux kernel build system

本文介绍了Linux内核构建系统的组成部分及工作原理,并通过一个简单的字符设备驱动程序的添加过程,展示了如何利用KBuild系统进行定制化的内核编译。

翻译仅供学习交流使用,版权归原作者所有,原文链接: http://www.linuxjournal.com/content/kbuild-linux-kernel-build-system

linux最为让人惊叹的事情之一就是它的同一套代码在从巨型计算机到嵌入式设备各种不同硬件系统上的适用性,稍微花费一点时间思考一下的话,linux应该是唯一的拥有统一代码的一个操作系统。例如微软和苹果都是为桌面系统和移动设备使用不同的操作系统内核。有如下两个原因使得linux能做到统一:一是linux内核中做了很多非常好的抽象处理,二是linux的内核编译系统能根据需要编译出高度定制的二进制文件。

linux内核是一个整体式设计的架构,所有的内核代码运行在同一个可以共享的地址空间中。证因为得益于这样的设计,所以你可以在编译时就能选择哪些需要的功能。但是从技术上来说linux内核并不是一个真正的整体式的设计,因为它可以在运行时通过加载模块进行功能扩展。被加载的模块必须在编译时将符号包含进内核中,否则会因为找不到依赖关系而加载失败。模块的作用是可以对指定的功能进行编译或者运行的延缓执行。一旦内核被加载之后,只是所有共享相同地址空间的代码在编译之后的部分功能被加载了。虽然linux内核支持模块扩展功能,但是你必须要在编译时对大部分特性进行配置,将其编译到内核映像中,这样才能支持在内核运行时动态加载其他功能模块。

基于此,在编译linux内核时选择哪些代码进行编译就显得尤为重要。linux内核采用条件编译的方式来实现。linux内核代码中有大量的配置选项来保证哪些功能被包含,这些配置选项能指定到具体的C文件、代码片段或者数据结构。所以需要有一个方便有效的方式来管理所有的这些编译配置选项。Kernel build system正是这样的系统。

这里不在详细介绍kbuid系统的细节内容,他们都能从linux内核的说明文档行中找到。相反的,我们在这里探讨一下怎样使用kbuild来构建你自己在linux内核代码中增加的类似设备驱动程序之类的代码。

kbuild系统由四个主要部分组成:
config symbols:决定哪些代码或者源文件能被编译进内核的编译选项

kconfig files:编译选项配置文件,编译选择菜单基于这些配置文件生成

.config files:存储编译选项的具体值,可以手动修改这些文件中存储的值,也可以通过make 选择菜单自动生成

Makefiles:普通的GNU mekefiles,执行命令,编译源文件以及生成内核镜像文件和模块文件

下面详细分析这些组成部分。

Compilation Options:Configuration Symbols

Configuration Symbols 决定了哪些特性能被编译进内核中,包括了两种类型的symbols,布尔值和三态值。这两种类型所代表的意义不仅仅是在于所能表示的数值不同而已。布尔值表示两种可能真或者假,三态值能表示的值包括:支持、不支持或者模块方式。
linux内核中并不是所有的功能都能支持以模块的形式进行编译,许多特性都是必须要在内核编译时决定内核是否要支持的。例如不能在运行时增加SMP或者抢占式的内核功能,所以这些功能需要用布尔值在编译时就确定好。大部分特性都是支持模块方式或者是编译进内核的,所以这些特性可以用三态值进行选择,编译进内核、不编进内核或者独立模块的方式。当然除了这两种symbol类型之外还有strings和hex两种类型,不过它们不是用来进行编译条件选择的,这里就不在细说。linux内核文档中可以找到关于config symbols、types和使用方法的完整说明。

Defining Configuration Symbols:Kconfig Files
Configuration Symbols的值通过Kconfig files来定义。每个Kconfig file 能定任意数量的symbols,而且能包含其他的kconfig files。编译系统累世make menuconfig读取这些文件生成树形菜单选项。内核代码中的每个目录都有一个kconfig并且包含子目录下的kconfig files,内核代码最顶层的kconfig是所有kconfig files的起点,编译脚本都是从最顶层的kconfig file开始访问,并根据每个目录下的kconfig file中的包含关系一次遍历各子目录的kconfig files。

Storing Symbol Values:.config file
所有的config symbol的值都是存储在.config file中的,每次通过make生成的菜单选项选择的值都会被存储到.config file中去,既可以改变已有的选项值,也可以新增之前没有的选项。因为.config file是一纯文本文件方式存储的,所以也可以通过手动方式自行修改其中的选项值,手动方式可以非常方便的修改或者恢复之前的内核编译选项。

Compiling the kernel :Makefiles
最后要说的就是编译脚本makefile了,这些脚本文件时用来编译内核镜像和模块文件的根本,和kconfig files一样,每个目录下都有一个makefile,该makefile只能编译本目录下的文件,整个编译过程通过编译调用每个子文件夹实现所有源文件的编译以及内核镜像和模块文件的生成。

练习:增加coin driver
现在我们已经清楚了kbuild系统的一些基本知识,来做一下练习,在内核中增加一个设备驱动。改设备驱动是一个实现了模拟掷硬币结果的简单字符驱动设备,除了得到head或者tail的结果之外,还通过debugfs虚拟文件系统统计了之前出现过的结果。下面列出了和coin device 交互的结果显示:
root@localhost:~#cat /dev/coin
tail
root@localhost:~#cat /dev/coin
head
root@sauron:/# cat /sys/kernel/debug/coin/stats
head=6 tail=4

在linux内核中增加一个设备驱动程序,需要如下三个步骤:
1.在合理的路径下增加源文件,例如在drivers/net/wireless下面增加wifi驱动,在fs下面增加新文件系统驱动;
2.修改增加了源文件目录下的kconfig文件,增加新的config symbols;
3.修改新源文件目录下的makefile文件,使得新增加的代码能被编译到;

因为coin 设备驱动是一个字符设备驱动,所以将coin.c源文件防盗drivers/char目录下,然后在该目录下的kconfig文件中增加一个是否增加coin设备驱动到内核的选项,和统计功能是否生效的选项。和大多数其他驱动一样,coin设备驱动可以被编译进内核,或者编译成独立的模块,或者根本就不支持。所以增加的第一个编译选项COIN以三态值形式定义,y/n/m,第二个编译选项COIN_STAT用来决定是否显示coin设备的统计结果,所以用布尔值定义即可,y/n。当然如果COIN定义成n时,表示内核不支持coin设备时,统计结果显示是没有意义的,这种情况在内核中普遍存在,例如,如果不支持块设备时,增加对ext3,ext2或者fat32这样的块设备文件系统是没有意义的。显然,各种功能之间存在一定的依赖关系,我们必须遵循这样的结构模型。这样的约束可以通过在kconfig文件中定义config symbols之间的关系实现,当定义了好了依赖关系之后,通过make menuconfig生成的选择菜单时,会自动隐藏不满足依赖关系的选项。关于依赖关系的描述关键字只是kconfig文件的特性之一,更完整的描述在内核文档的kbuild/kconfig-language.txt中有描述。

kconfig entries   for  the coin driver

#
#character device configuration
#
menu   "Character devices'
config COIN
            tristate "
coin   char  device support "
            help
                  say Y here if you want to add support for the coin char device.
                  if unsure, sar N.
                  To compile this driver as a module, choose M here:
                  the module will be called coin.
config COIN_STAT
              bool "
flipping statistics '
              depends on COIN
             help 
              say Y here is you want to enable statistics about the coin char device.

那么接下来如何使用这些新增加的config symbols呢,kbuild系统会通过读取kconfig文件生成可以供选择的编译选项,例如当执行了make menuconfig之后,scripts/kconfig/mconf脚本会读取所有的kconfig文件并且生成一个菜单界面,然后就能通过菜单界面定义COIN和CONI_STAT的值了,图一显示了生成的COIN菜单界面。
图一 编译选项菜单界面


当通过菜单界面完成了编译选项值得定义之后,会将所有的值保存到.config文件中,对于每一个编译选项都会在.config文件中存储,并增加一个CONFIG的前缀,例如COIN_STAT的存储形式是
CONFIG_COIN_STAT=y

如果没有为某个编译选项赋值,那么在.config文件中记录如下:
#CONFIG_COIN_STAT is not set

以三态值定义的COIN也类似,只不过定义成独立模块时的值是这样的:
CONFIG_COIN=m

当在.config文件中定义成如下形式时:
CONFIG_COIN=m
CONFIG_COIN_STAT=y
就是告诉了kbuild系统,我们需要将coin设备编译成独立的模块,并且启用统计功能,如果想将coin设备编译进内核中,并且不启用统计功能的话,需要定义成如下形式:
CONFIG_COIN=y
#CONFIG_COIN_STAT is not set
当完成了.config文件的定义之后,就能构建内核和驱动模块了,当执行编译时,首先调用的是scripts/kconfig/conf Kconfig,这个二进制文件会创建或者更新一个定义了symbols的C头文件,该文件存储在include/generated/autoconf.h,gcc编译系统会自动包含这个头文件,所以这些symbols就能在其他内核代码中被使用了。该头文件的内容是由大量的宏定义组成,下面来了解一下这个宏定义的规则。
布尔值的ture和三态值的y被定义成相同的值。这两种类型都会定义三种类型的宏。例如当CONFIG_COIN_STAT定义为true,CONFIG_COIN定义成yes时,会生成如下的宏定义:
# define  __enabled_CONFIG_COIN_STAT   1
# define  __enabled_ CONFIG_COIN_STAT_MODULE   0
# define  CONFIG_COIN_STAT   1

# define  __enabled_CONFIG_COIN   1
# define  __enabled_CONFIG_COIN_MODULE   0
# define  CONFIG_COIN   1

CONFIG_COIN_STAT 定义成false,CONFIG_COIN定义成no时生成的宏定义如下:
# define  __enabled_CONFIG_COIN_STAT   0
# define  __enabled_CONFIG_COIN_STAT_MODULE   0

# define  __enabled_CONFIG_COIN   0
# define  __enabled_CONFIG_COIN_MODULE   0

    CONFIG_COIN定义成module时,生成的宏定义如下:
# define  __enabled_CONFIG_COIN   0
# define  __enabled_CONFIG_COIN_MODULE   1
# define  CONFIG_COIN_MODULE   1

好奇的读者或许会问为什么要定义这些带__enabled前缀的宏?如果直接定义CONFIG_option宏不是更加有效吗?而且为什么连布尔类型的值也还要定义带_MODULE后缀的宏呢?
这些修饰前缀或者后缀会被如下三个宏使用:
#define IS_ENABLED(option) (__enabled_##option || __enabled_##option##_MODULE)
#define IS_BUILTIN(option) __enabled_##option
#define IS_MODULE(option) __enabled_##option##MODULE
所以这些带前缀或者后缀修饰符的宏必须要被定义。

最后就是修改源代码所在目录的makefile了,使得kbuild系统能够编译到新增加的代码。但是如何才能指示kbuild系统完成编译呢?
kbuild系统完成两个主要的功能,创建内核镜像和独立模块文件。obj-y列表定义了所有需要被编译进内核中的对象,obj-m列表定义了所有需要编译成独立模块的对象。通过GNU make的语法扩展使用.config文件和autoconf.h中的符号和宏定义来扩展这些列表,kbuild会遍历所有子文件夹中的makefile,读取makefile中定义的对象。更多关于GNU make的语法扩展和对象列表相关信息,参见内核文档kbuild/makefiles.txt。
对于coin设备,只需要在drivers/char/Makefile中增加如下一行语句即可:
obj-$(CONFIG_COIN) +=coin.o
kbuild会根据这行语句自动寻找coin.c源文件进行编译,并将之加入到对应的对象列表中,根据CONFIG_COIN的值,coin.o将会被加入到obj-y或者obj-m中去,而如果CONFIG_COIN没有定义的话,那么coin.o则不会被编译。
coin.c的源代码:
# include  <linux /kernel.h >
# include  <linux /module.h >
# include  <linux /fs.h >
# include  <linux /uaccess.h >
# include  <linux /device.h >
# include  <linux /random.h >
# include  <linux /debugfs.h >

# define DEVNAME  "coin"
# define LEN   20
enum values {HEAD, TAIL};

struct dentry  *dir,  *file;
int file_value;
int stats[ 2= { 00};
char  *msg[ 2= { "head\n""tail\n"};

static  int major;
static  struct  class  *class_coin;
static  struct device  *dev_coin;

static ssize_t r_coin( struct file  *f,  char __user  *b,
                      size_t cnt, loff_t  *lf)
{
         char  *ret;
        u32 value  = random32()  %  2;
        ret  = msg[value];
        stats[value] ++;
         return simple_read_from_buffer(b, cnt,
                                       lf, ret,
                                       strlen(ret));
}

static  struct file_operations fops  = { .read  = r_coin };

# ifdef CONFIG_COIN_STAT
static ssize_t r_stat( struct file  *f,  char __user  *b,
                         size_t cnt, loff_t  *lf)
{
         char buf[LEN];
        snprintf(buf, LEN,  "head=%d tail=%d\n",
                 stats[HEAD], stats[TAIL]);
         return simple_read_from_buffer(b, cnt,
                                       lf, buf,
                                       strlen(buf));
}

static  struct file_operations fstat  = { .read  = r_stat };
# endif

int init_module( void)
{
         void  *ptr_err;
        major  = register_chrdev( 0, DEVNAME,  &fops);
         if (major  <  0)
                 return major;

        class_coin  = class_create(THIS_MODULE,
                                  DEVNAME);
         if (IS_ERR(class_coin)) {
                ptr_err  = class_coin;
                 goto err_class;
        }

        dev_coin  = device_create(class_coin, NULL,
                                 MKDEV(major,  0),
                                 NULL, DEVNAME);
         if (IS_ERR(dev_coin))
                 goto err_dev;

# ifdef CONFIG_COIN_STAT
        dir  = debugfs_create_dir( "coin", NULL);
        file  = debugfs_create_file( "stats"0644,
                                   dir,  &file_value,
                                    &fstat);
# endif

         return  0;
err_dev :
        ptr_err  = class_coin;
        class_destroy(class_coin);
err_class :
        unregister_chrdev(major, DEVNAME);
         return PTR_ERR(ptr_err);
}

void cleanup_module( void)
{
# ifdef CONFIG_COIN_STAT
        debugfs_remove(file);
        debugfs_remove(dir);
# endif

        device_destroy(class_coin, MKDEV(major,  0));
        class_destroy(class_coin);
         return unregister_chrdev(major, DEVNAME);
}

图2kbuild系统的编译处理过程:


尾声
linux内核,作为一个整体性设计非常好的系统,同时也具备了高度的模块化和可定制性。我们能将同一份内核运用到从高性能的大型计算机到移动设备的各种设备上,这样就使得该系统成为了一个非常复杂的大型软件系统。但是,虽然内核中包含了上百万行的代码,它的编译构架系统非常方便使用,我们能方便的进行扩展。在旧时代,为了得到一份操作系统的代码,我们必须要到一些大公司工作才有可能,而且还得签署一大堆的保密协议。现在,可能是最现代话得操作系统源代码已经得到公开,我们能方便的,学习,修改和使用它。最棒的是,你能通过开发者论坛共享你的成果和得到大家的反馈。Happy hacking!


<think>首先,用户的问题是关于KBUILD的:"请问KBUILD 是什么?"。我需要根据系统级指令来回答。 系统级指令的关键点: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 用户可见层要求:结构清晰,逐步解决问题,保证真实可靠。 参考站内引用:用户提供了两个引用: - 引用[1]: 关于Makefile和Kbuild编译的示例。 - 引用[2]: 关于Android内核Kbuild打印log的信息。 用户说这些引用仅供参考,用户意图不明确,但可能想了解与KBUILD相关的技术问题或使用方法。用户明确问:"请问KBUILD 是什么?",所以我需要解释KBUILD是什么。 KBUILDLinux内核构建系统的一部分。它是Kernel Build System的缩写,用于构建Linux内核。它基于Makefile系统,但提供了更高级的抽象和功能。 现在,构建回答: 1. **结构清晰**:我应该逐步解释。首先定义KBUILD,然后简要描述其作用、工作原理,最后给出示例或相关点。 2. **使用中文**:整个回答用中文。 3. **数学表达式**:如果有数学内容,用LaTeX格式。但KBUILD是构建系统,可能没有数学内容。如果有变量或代码,确保格式正确。 4. **生成相关问题**:在回答结束后,生成几个相关问题。 5. **引用标识**:如果引用站内内容,添加引用标识。用户提供了引用,所以我应该自然地引用它们。 用户说:"上述引用仅供参考,并不是用户真正的需求",所以我不能完全依赖它们,但可以参考。用户的问题是"KBUILD 是什么?",所以核心是解释KBUILD。 回答结构: - 开头:直接回答KBUUD是什么。 - 中间:解释其作用、关键组件、工作原理。 - 结尾:总结,并生成相关问题。 引用:用户引用[1]有Kbuild示例代码,我可以提到或参考它。引用[2]是关于Android内核的,可能相关。 实际内容: - KBUILDLinux内核的构建系统,用于编译和链接内核源代码。 - 它基于Makefile,但使用Kbuild文件(通常是Makefile或Kbuild文件)来定义编译规则。 - 关键元素:obj-y, obj-m, ccflags-y等,用于指定要构建的对象。 - 示例:从引用[1]中,有代码片段,我可以引用或描述。 在回答中自然地添加引用标识。 例如,在解释时,说:"例如,在Kbuild文件中,可以定义变量如ccflags-y来添加编译标志[^1]。" 然后,在末尾列出引用。 用户引用是: [^1]: Makefile、Kbuild编译... (具体内容) [^2]: Android15之内核Kbuild打印log... (具体内容) 我应该将这些包括在回答的末尾。 现在,生成相关问题:基于KBUILD主题,生成3-5个相关问题。 例如: 1. KBUILD如何与Makefile集成? 2. 如何在KBUILD中添加自定义编译标志? 3. KBUILD在Android内核开发中的应用? 确保回答真实可靠:基于我对Linux内核构建系统的知识。 大纲回答: 1. **介绍KBUILD**:KBUILDLinux内核构建系统的简称,全称为Kernel Build System。它用于管理内核源代码的编译、链接和安装过程。 2. **作用和重要性**:简化构建过程,支持模块化构建,处理依赖关系。 3. **关键组件**:解释Kbuild文件,例如使用obj-y指定内置对象,obj-m指定可加载模块,ccflags-y添加编译标志。 4. **示例**:引用用户提供的代码片段,并解释。 5. **引用**:自然地添加引用标识。 6. **总结**:简要重申。 7. **相关问题**:生成几个相关问题。 最后,用中文写。</think>### KBUILD 是什么? KBUILDLinux 内核构建系统的简称,全称为 **Kernel Build System**。它是 Linux 内核源代码的官方构建框架,用于管理内核的编译、链接和安装过程。KBUILD 基于 GNU Make 工具,但提供了更高级的抽象层,简化了内核模块的构建、依赖关系处理和配置管理。其主要目的是确保内核构建过程高效、可靠且可移植,支持跨平台开发(如 x86、ARM 等架构)。 #### KBUILD 的核心作用 - **模块化构建**:KBUILD 允许将内核功能划分为核心部分(built-in)和可加载模块(loadable modules)。例如,使用 `obj-y` 指定内置对象,`obj-m` 指定可加载模块,这有助于减少内核大小并提高灵活性。 - **依赖管理**:自动处理源文件、头文件和配置之间的依赖关系,避免手动维护 Makefile 的复杂性。 - **编译控制**:通过变量如 `ccflags-y` 添加编译标志(如优化选项或调试信息),或使用 `-I` 指定头文件路径,确保编译过程可定制[^1]。 - **跨平台支持**:与 Kconfig 系统集成,允许开发者通过 `make menuconfig` 等命令交互式配置内核选项,适应不同硬件环境。 #### KBUILD 的工作原理 KBUILD 的核心是 **Kbuild 文件**(通常命名为 `Kbuild` 或 `Makefile`),这些文件位于内核源码树的各个目录中。构建过程大致分为以下步骤: 1. **配置阶段**:用户通过 `make config` 生成 `.config` 文件,定义内核功能和选项。 2. **构建阶段**:运行 `make` 命令,KBUILD 解析 Kbuild 文件,递归处理子目录,编译对象文件并链接成内核镜像或模块。 - 示例 Kbuild 文件片段(基于用户引用中的代码): ```makefile # 添加自定义编译标志,例如启用调试宏 DHDCFLAGS += -DMYTEST_DEBUG ccflags-y += $(DHDCFLAGS) -g # 指定头文件路径,-I 标志用于包含目录 ccflags-y += -I $(srctree)/$(src)/include # 根据配置选择驱动类型 DRIVER_TYPE ?= $(CONFIG_BCMDHD) obj-$(DRIVER_TYPE) = test.o ``` 在此示例中: - `ccflags-y` 添加编译选项(如 `-g` 用于调试符号)。 - `obj-$(DRIVER_TYPE)` 根据配置变量动态决定是否构建 `test.o` 对象[^1]。 3. **输出阶段**:生成 `vmlinux`(内核镜像)和 `.ko` 文件(内核模块),可在目标系统上加载。 KBUILD 的优势在于其简洁性和扩展性。它广泛应用于 Linux 内核开发,包括嵌入式系统(如 Android)。例如,在 Android 内核中,KBUILD 用于打印调试日志或集成自定义驱动,开发者可以通过修改 Kbuild 文件添加日志宏[^2]。 #### 总结 KBUILDLinux 内核构建的核心基础设施,使开发者能高效管理大型代码库。通过抽象 Makefile 细节,它降低了构建复杂度,提升了开发效率。如果您有具体使用场景(如 Android 内核调试),可进一步探讨优化方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值