Linux内核及内核编程之四Linux内核的编译及加载

3.4 Linux内核的编译及加载

3.4.1 Linux内核的编译

    Linux驱动开发者需要掌握Linux内核的编译方法以为嵌入式系统构建可运行的Linux操作系统映像。在编译内核时,需要配置内核,可以使用下面命令中的一个:

#make config(基于文本的最为传统的配置界面,不推荐使用)
#make menuconfig(基于文本菜单的配置界面)
#make xconfig(要求QT被安装)

#make gconfig(要求GTK+被安装)

在配置Linux内核所使用的make config、make menuconfig、make xconfig和make gconfig这4种方式中,最值得推荐的是make menuconfig,它不依赖于QT或GTK+,且非常直观。例如:输入make ARCH=arm64 menuconfig,呈现的界面如图3.9所示。

图3.9 Linux内核编译配置

    内核配置包含的条目相当多,arch/arm64/configs/xxx_defconfig文件包含了许多电路板的默认配置。只需要运行make ARCH=arm64 xxx_defconfig就可以为xxx开发板配置内核。

    编译内核和模块的方法是:

make ARCH=arm64 zImage

make ARCH=arm64 modules

上述命令中,如果ARCH=arm64已经作为环境变量导出,则不再需要在make命令后书写该选项。执行完上述命令后,在源代码的根目录下会得到未压缩的内核映像vmlinux和内核符号表文件System.map,在arch/arm64/boot/目录下会得到压缩的内核映像zImage,在内核各对应目录内得到选中的内核模块。

Linux内核的配置系统由以下3个部分组成。

Makefile:分布在Linux内核源代码中,定义Linux内核的编译规则。

配置文件(Kconfig):给用户提供配置选择的功能。

配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供字符界面和图形界面)。这些配置工具使用的都是脚本语言,如用Tcl/TK、Perl等。

    使用make ARCH=arm64 menuconfig等命令后,会生成一个.config配置文件,记录哪些部分被编译入内核、哪些部分被编译为内核模块

    运行make ARCH=arm64 menuconfig时,配置工具首先分析与体系结构对应的/arch/xxx/Kconfig文件(xxx即为传入
的ARCH参数),/arch/xxx/Kconfig文件中除本身包含一些与体系结构相关的配置项和配置菜单以外,还通过source语句引入(包含)了一系列Kconfig文件,而这些Kconfig又可能再次通过source引入(包含)下一层的Kconfig,配置工具依据Kconfig包含的菜单和条目即可描绘出一个如图3.9所示的分层结构。

3.4.2 Kconfig和Makefile

在Linux内核中增加程序需要完成以下3项工作。

将编写的源代码复制到Linux内核源代码的相应目录中。

在目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项。

在目录的Makefile文件中增加对新源代码的编译条目。

1.实例引导:TTY_PRINTK字符设备

    在讲解Kconfig和Makefile的语法之前,先利用两个简单的实例引导读者对其建立对其初步的认识。

    首先,在drivers/char目录中包含了TTY_PRINTK设备驱动的源代码drivers/char/ttyprintk.c。而在该目录的Kconfig文件中包含关于TTY_PRINTK的配置项:

config TTY_PRINTK
       tristate "TTY driver to output user messages via printk"
       
depends on EXPERT && TTY
       default n
       ---help---
        If you say Y here, the support for writing user messages (i.e.
        console messages) via printk is available.
        The feature is useful to inline user messages with kernel
        messages.
        In order to use this feature, you should output user messages
        to /dev/ttyprintk or redirect console to this TTY.
        If unsure, say N.

上述Kconfig文件的这段脚本意味着只有在EXPERT和TTY被配置的情况下,才会出现TTY_PRINTK配置项,这个配置项为三态(可编译入内核,可不编译,也可编译为内核模块,选项分别为“Y”、“N”和“M”),菜单上显示的字符串为“TTY driver to output user messages via printk”,“help”后面的内容为帮助信息。图3.10显示了TTY_PRINTK菜单以及help在运行make ARCH=arm64 menuconfig时的情况。


    图3.10 Kconfig菜单项与help信息

    除了布尔(bool)配置项外,还存在一种布尔配置选项,它意味着要么编译入内核,要么不编译,选项为“Y”或“N”。

    在目录的Makefile中关于TTY_PRINTK的编译项为:obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o

    上述脚本意味着如果TTY_PRINTK配置选项被选择为“Y”或“M”,即obj-$(CONFIG_TTY_PRINTK)等同于obj-y或obj-m,则编译ttyprintk.c,选“Y”时会直接将生成的目标代码连接到内核,选“M”时则会生成模块ttyprintk.ko;如果TTY_PRINTK配置选项被选择为“N”,即obj-$(CONFIG_TTY_PRINTK)等同于obj-n,则不编译ttyprintk.c。

    驱动开发者会在内核源代码的drivers目录内的相应子目录中增加新设备驱动的源代码或者在arch/arm/mach-xxx下新增加板级支持的代码,同时增加或修改Kconfig配置脚本和Makefile脚本,具体执行完全仿照上述过程即可。

2.Makefile

    主要对内核源代码各级子目录中的kbuild(内核的编译系统)Makefile进行简单介绍,这部分是内核模块或设备驱动开发者最常接触到的。

Makefile的语法包括如下几个方面。

(1)目标定义

目标定义就是用来定义哪些内容要作为模块编译,哪些要编译并链接进内核。

例如:obj-y += foo.o

表示要由foo.c C文件或者foo.s汇编文件编译得到foo.o并链接进内核(无条件编译,不需要Kconfig配置选项),而obj-m则表示该文件要作为模块编译。obj-n形式的目标不会被编译。

更常见的做法是根据make ARCH=arm64 menuconfig后生成的config文件的CONFIG_变量来决定文件的编译方式,如:

obj-$(CONfiG_ISDN) += isdn.o

obj-$(CONfiG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

除了具有obj-形式的目标以外,还有lib-y library库、hostprogs-y主机程序等目标,但是这两类基本都应用在特定的目录和场合下。

(2)多文件模块的定义。

最简单的Makefile仅需一行代码就够了。如果一个模块由多个文件组成,会稍微复杂一些,这时候应采用模块名加-y或-objs后缀的形式来定义模块的组成文件,如下:

#
# Makefile for the linux ext2-filesystem routines.
#
obj-$(CONfiG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o fsync.o ialloc.o inode.o \
       ioctl.o namei.o super.o symlink.o
ext2-$(CONfiG_EXT2_FS_XATTR)     += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONfiG_EXT2_FS_POSIX_ACL) += acl.o
ext2-$(CONfiG_EXT2_FS_SECURITY)  += xattr_security.o
ext2-$(CONfiG_EXT2_FS_XIP)   += xip.o

模块的名字为ext2,由balloc.o、dir.o、file.o 等多个目标文件最终链接生成ext2.o直至ext2.ko文件,并且是否包括xattr.o、acl.o、......、xip.o等则取决于内核配置文件的配置情况,如果CONFIG_EXT2_FS_POSIX_ACL被选择,则编译acl.c得到acl.o并最终链接进ext2。

(3)目录层次的迭代

如下例:

obj-$(CONfiG_EXT2_FS) += ext2/

当CONFIG_EXT2_FS的值为y或m时,kbuild(内核编译系统)将会把ext2目录列入向下迭代的目标中。

3.Kconfig

内核配置脚本文件的语法也比较简单,主要包括如下几个方面。

(1)配置选项

大多数内核配置选项都对应Kconfig中的一个配置选项(config):

config MODVERSIONS
     bool "Module versioning support"
     help
         Usually, you have to use modules compiled with your kernel.
         Saying Y here makes it ...

“config”关键字定义新的配置选项,之后的几行代码定义了该配置选项的属性。配置选项的属性包括类型、数据范围、输入提示、依赖关系、选择关系及帮助信息、默认值等。

    每个配置选项都必须指定类型,类型包括bool、tristate、string、hex和int,其中tristate和string是两种基本类型,其他类型都基于这两种基本类型。类型定义后可以紧跟输入提示,下面两段脚本是等价的:

bool “Networking support”


bool
prompt "Networking support"

输入提示的一般格式为:

prompt <prompt> [if <expr>]

其中,可选的if用来表示该提示的依赖关系。

默认值的格式为:default <expr> [if <expr>]

如果用户不设置对应的选项,配置选项的值就是默认值。

依赖关系的格式为:depends on(或者requires) <expr>

如果定义了多重依赖关系,它们之间用“&&”间隔。依赖关系也可以应用到该菜单中所有的其他选项(同样接受if表达式)内,下面两段脚本是等价的:

bool "foo" if BAR
default y if BAR


depends on BAR
bool "foo"
default y

选择关系(也称为反向依赖关系)的格式为:select <symbol> [if <expr>]

A如果选择了B,则在A被选中的情况下,B自动被选中。

config VICHIP_TEST
     bool "Module versioning support test A"
     ---help---
         Usually, you have to use modules compiled with your kernel.
         Saying Y here makes it ...
 
config VICHIP_TEST1
     bool "Module versioning support test B"
select VICHIP_TEST
     ---help---
         Usually, you have to use modules compiled with your kernel.

         Saying Y here makes it ...

总结:VICHIP_TEST1配置项被选中,则VICHIP_TEST配置项自动被选中。

数据范围的格式为:range <symbol> <symbol> [if <expr>]

Kconfig中的expr(表达式)定义为:

<expr> ::= <symbol>
              <symbol> '
=' <symbol>
              <symbol> '
!=' <symbol>
             '(' <expr> ')'
             '
!' <expr>
              <expr> '
&&' <expr>
              <expr> '
||' <expr>

也就是说,expr是由symbol、两个symbol相等、两个symbol不等以及expr的赋值、非、与或运算构成。而symbol分为两类,一类是由菜单入口配置选项定义的非常数symbol,另一类是作为expr组成部分的常数symbol。比如,SHDMA_R8A73A4是一个布尔配置选项,表达式“ARCH_R8A73A4&&SH_DMAE!=n”

config VICHIP_TEST
     bool "Module versioning support test A"
     ---help---
         Usually, you have to use modules compiled with your kernel.
         Saying Y here makes it ...
 
config VICHIP_TEST1
     bool "Module versioning support test B"
     ---help---
         Usually, you have to use modules compiled with your kernel.
         Saying Y here makes it ...
 
config VICHIP_TEST2
     bool "Module versioning support test C"
depends on VICHIP_TEST && VICHIP_TEST1 != n
def_bool y
     ---help---
         Usually, you have to use modules compiled with your kernel.
         Saying Y here makes it ...

总结:只有当VICHIP_TEST 被选中且VICHIP_TEST1被选中时,才可能出现这个VICHIP_TEST2

为int和hex类型的选项设置可以接受的输入值范围,用户只能输入大于等于第一个symbol,且小于等于第二个symbol的值。

帮助信息的格式为:

help(或---help---)
   开始
   …
   结束

帮助信息完全靠文本缩进识别结束。“---help---”和“help”在作用上没有区别,设计“---help---”的初衷在于将文件中的配置逻辑与给开发人员的提示分开。

(2)菜单结构

配置选项在菜单树结构中的位置可由两种方法决定。第一种方式为:

menu "Network device support"
   depends on NET

config NETDEVICES

   …
endmenu

所有处于“menu”和“endmenu”之间的配置选项都会成为“Network device support”的子菜单,而且,所有子菜单(config)选项都会继承父菜单(menu)的依赖关系,比如,“Network device support”对“NET”的依赖会被加到配置选项NETDEVICES的依赖列表中。

    注意:menu后面跟的“Network device support”项仅仅是1个菜单,没有对应真实的配置选项,也不具备
3种不同的状态。这是它和config的区别。

    另一种方式是通过分析依赖关系生成菜单结构。如果菜单项在一定程度上依赖于前面的选项,它就能成为该选项的子菜单。

如果父选项为“n”,子选项不可见;如果父选项可见,子选项才可见。

config MODULES
   bool "Enable loadable module support"
config MODVERSIONS
   bool "Set version information on all module symbols"
   depends on MODULES
comment "module support disabled"
   depends on !MODULES

MODVERSIONS直接依赖MODULES,只有MODULES不为“n”时,该选项才可见。

除此之外,Kconfig中还可能使用“choice...endchoice”、“comment”、“if...endif”这样的语法结构。其中“choice...endchoice”的结构为:

choice
<choice options>
<choice block>
endchoice"

它定义一个选择群,其接受的选项(choice options)可以是前面描述的任何属性,例如,LDD6410的VGA输出分辨率可以是1024×768或者800×600,在drivers/video/samsung/Kconfig中就定义了如下choice:

choice
depends on FB_S3C_VGA
prompt "Select VGA Resolution for S3C Framebuffer"
default FB_S3C_VGA_1024_768
config FB_S3C_VGA_1024_768
     bool "1024*768@60Hz"
     ---help---
     TBA
config FB_S3C_VGA_640_480
     bool "640*480@60Hz"
     ---help---
     TBA

endchoice

上述例子中,prompt配合choice起到提示作用。

4.应用实例:在内核中新增驱动代码目录和子目录

假设要在内核源代码drivers目录下为ARM体系结构新增如下用于test driver的树形目录:

|--test
   |-- cpu
        | -- cpu.c
   |-- test.c
   |-- test_client.c
   |-- test_ioctl.c
   |-- test_queue.c

在内核中增加目录和子目录时,需为相应的新增目录创建Makefile和Kconfig文件,而新增目录的父目录中的Kconfig和Makefile也需修改,以便新增的Kconfig和Makefile能被引用。

在新增的test目录下,应该包含如下Kconfig文件:

#
# TEST driver configuration
#
menu "TEST Driver "
comment "TEST Driver"
config TEST
    bool "TEST support "
config TEST_USER
    tristate "TEST user-space interface"
    depends on TEST
endmenu

由于test driver对于内核来说是新功能,所以需首先创建一个菜单TEST Driver。然后,显示“TEST support”,等待用户选择;接下来判断用户是否选择了TEST support ,如果选择了(CONFIG_TEST=y),则进一步显示子功能:用户接口与CPU功能支持;由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了tristate。

为了使这个Kconfig能起作用,修改arch/arm64/Kconfig文件,增加:

source "drivers/test/Kconfig"

脚本中的source意味着引用(包含)新的Kconfig文件。

在新增的test目录下,应该包含如下Makefile文件:该脚本根据配置变量的取值,构建obj-*列表。由于test目录中包含一个子目录cpu,因此当CONFIG_TEST_CPU=y时,需要将cpu目录加入列表中。

test目录中的cpu子目录也需包含如下Makefile:

# drivers/test/cpu/Makefile
#
# Makefile for the TEST CPU
#

obj-$(CONFIG_TEST_CPU) += cpu.o

为了使得编译命令作用到能够整个test目录,test目录的父目录中Makefile也需新增如下脚本:

obj-$(CONFIG_TEST) += test/

在drivers/Makefile中加入obj-$(CONFIG_TEST)+=test/,使得用户在进行内核编译时能够进入test目录。

增加了Kconfig和Makefile之后的新test树形目录为:

test
├── cpu
│   ├── cpu.c
│   ├── Kconfig
│   └── Makefile
├── Kconfig
├── Makefile
├── test.c
├── test_client.c
├── test_ioctl.c
└── test_queue.c

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80258884