浅析Linux内核Kbuild机制

文章目录

概述

Kbuild是Linux系统提供的专门用于编译内核源码的工具,它的底层仍然是基于Makefile机制,但引入了一些扩展功能,例如用于配置内核特性的Kconfig,以及针对于内核功能模块的目标定义语法等,所有这些功能一起组成了完整的Kbuild机制。

Kconfig

Kconfig用于管理内核配置,我们编译内核前通常会使用make menuconfig来配置所需要的内核特性以及进行功能裁剪,其底层就是利用Kconfig进行管理。

Kconfig的结构元素

执行make menuconfig之后会进入如下界面,从中可以看到Kconfig管理的基本层次结构。

上图展示的界面中展示了Kconfig的几个基本元素:

  • 主目录:顶层的配置目录,全局唯一,通过关键字mainmenu进行配置;
  • 目录:用于组织内核的特性或功能,通过关键字menu进行配置;
  • 目录配置选项:可进行配置的目录,通过关键字menuconfig进行配置;
  • 配置选项:对应具体的内核特性或功能,通过关键字config进行配置。

Kconfig语法

Kconfig语法支持的主要关键字如下:

  • mainmenu
  • menu/endmenu
  • config
  • menuconfig
  • choice/endchoice
  • if/endif
  • comment
  • source

mainmenu,只能出现在配置层次结构的顶部(且只能出现一次),用于为整个层次结构指定一个标题。因而该项只用于arch/arch/Kconfig中,因为这些文件表示配置层次结构的起始点。

目录使用以下命令指定:

复制代码
menu "string" 
    <attributes> 

<configuration options> 

endmenu 

其中string是菜单的名称。menu和endmenu之间所有项都解释为该菜单的菜单项,自动地从菜单继承了依赖关系。

config

配置选项由关键字config开头,必须后接一个配置符号。

复制代码
config <symbol> 
    <type-name> "Description" 
    <attributes> 

类型名()表示选项的类型。如前所述,

  • tristate类型的值为以下一种状态:y、n或m。其他的选项类型如下所示:
  • bool用于返回y或者n的布尔查询,即是否选中该项。
  • string查询一个字符串。

可以使用下列语法:

复制代码
config <symbol> 
    <type-name> 
    prompt "Description" 

关键字menuconfig用于定义一个配置符号和一个子菜单。

comment

comment在配置选项列表中创建一个注释。注释的文本会显示,但用户不能进行选择。

source

通过source,可以将更多的配置文件关联进来。这些配置文件的文本内容,将直接包含到嵌入的配置文件中进行解释。

Kernel Makefile

Kernel Makefile结构

完整的Linux内核Makefile包含以下5个部分:

  • .config:内核配置文件;
  • 顶层Makefile:位于内核源码顶层目录的Makefile;
  • arch/$(SRCARCH)/Makefile:体系结构相关的Makefile;
  • scripts/Makefile.* :所有kbuild Makefile都使用的公共规则;
  • kbuild Makefiles:位于各个子目录下的Makefile。
顶层Makefile

Makefile,通过根据配置递归地编译子目录,并将编译结果合并到最终产品中,来生成内核本身和模块。

体系结构相关Makefile

体系结构相关的Makefile,在arch/arch/Makefile中,负责在编译期间必须遵守的与处理器相关的微妙之处,如特别的编译优化选项。该文件还实现了所有体系结构相关的make目标,此前在讨论help时提到过这些目标。

scripts/Makefile

scripts/Makefile.*包含了与一般编译、模块生成、各种实用程序的编译、从内核树删除目标文件和临时文件等任务相关的make规则。

驱动程序和子系统Makefile

内核源代码的各个子目录都包含了与特定驱动程序或子系统相关的Makefile(也采用了标准的语法)。

驱动程序和子系统目录中的Makefile用于根据.config中的配置来编译正确的文件,并将编译的流程导向到所要求的子目录中。

编译目标定义

复制代码
obj-$(CONFIG_FOO) += foo.o

通常CONFIG_FOO有三种取值:y/m/n。

  • obj-y:内置对象目标,这种情况下,目标文件会编译到内核镜像中;
  • obj-m:可加载模块目标,最后会编译成内核模块;
  • obj-n:目标不参与编译。

如果一个内核模块是从多个源文件构建的,则必须通过设置<mod_name>-y变量来告知kbuild模块构建依赖的源文件列表。

复制代码
obj-$(CONFIG_FOO) += foo.o
foo-y := foo_comm.o foo_lib.o foo_main.o

编译外部模块

Linux编译外部模块的命令语法如下:

复制代码
make -C <path_kernel_src> M=<path_external_module> [target]
  • -C <kernel_path>:指定Linux内核源码路径。make执行时会实际切换到内核源码目录进行编译,并在完成时切换回来;
  • M=<mod_path>:指定外部模块所在路径。通常都是在外部模块所在目录执行编译,因此这里基本都是使用当前目录;
  • target:指定编译目标,默认为modules。

目前Kbuild支持的target如下:

  • modules:编译外部模块,为默认的编译目标,可以不进行指定;
  • modules_install:安装外部模块,默认安装路径为/lib/modules/<kernel_release>/extra/;;
  • clean:清理外部编译的编译生成文件;
  • help:列出外部模块编译可以使用的target。

导出内核头文件

Linux内核导出的头文件用于向用户空间程序描述内核提供的服务接口,这些头文件会被系统C程序库(例如glibc)包含来定义系统调用以及相关的数据结构。对于系统C程序库提供的头文件通常会放置在/usr/include目录下,而内核头文件则习惯性放在/usr/includelinuxasm子目录下。

Linux系统通过make headers_install安装内核头文件到合适的用户目录中,如下:

复制代码
make headers_install ARCH=i386 INSTALL_HDR_PATH=/usr
  • ARCH:指定目标体系架构;
  • INSTALL_HDR_PATH:指定头文件安装路径。

相关参考