Linux内核配置编译及基本调试方法

时间:2022-10-25 12:28:49

一、Linux内核配置编译

 

1. 交叉编译设置:make ARCH=arm CROSS_COMPILE=arm-linux-

注:也可以直接修改顶层Makefile

ARCH        ?= arm
CROSS_COMPILE   ?= arm-linux-

 

2. 加载默认设置:make mini2440_defconfig

make mini2440_config: 将arch/arm/configs目录下的mini2440_defconfig文件复制为.config

make menuconfig: 对内核默认配置进行调整

 

3. 编译内核

make zImage

make modules

make uImage(uImage是在zImage基础上加了64Bytes的头信息)

make bzImage(内核最终镜像大于512KB)

 

4. 安装内核:

make install

make modules_install

 

5. 内核配置系统由以下3部分组成

① Makefile:

② Kconfig:

③ 配置工具:

注:使用make config、make menuconfig等命令后,内核顶层目录生成一个“.config‘’配置文件,该文件记录模块是否编译进内核、或者编译成模块

 

6. 运行make menuconfig时,配置工具首先分析与体系结构对应的arch/<arch>/Kconfig文件,该文件除本身包含一些与体系结构相关的配置项和配置菜单外,还通过sourse语句引入下一层的Kconfig文件

 

7. 在make menuconfig界面中选择“config ARCH_S3C2410”选项后内核会在配置文件".config"中添加

CONFIG_ARCH_S3C2440=y

同时在include/config目录中生成由.config得来的配置文件auto.conf,以及在include/linux目录中以C语言头文件给出的配置文件autoconf.h

 

8. 执行make zImage等生成内核镜像命令时,首先执行顶层Makefile,顶层Makefile又通过include指令导入特定体系架构或子目录中的Makefile

① 顶层Makefile:

include $(srctree)/arch/$(SRCARCH)/Makefile

② arch/arm/Makefile:

machine-$(CONFIG_ARCH_S3C2410) := s3c2410 s3c2400 s3c2412 s3c2440 s3c2442 s3c2443

③ machine-y用于定义machdirs变量

machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y))

④ machdirs通过下面语句添加到core-y中(cory-y表示内核需要编译的核心文件)

cory-y += $(machdirs) $(platdirs)

 

9. 向内核中添加新的功能的步骤

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

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

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

 

10. 向内核中添加RTC驱动:

① 在drivers/rtc中包含S3C24XX处理器的RTC驱动源代码rtc-s3c.c

② 在drivers/rtc目录的Kconfig文件中包含RTC_DRV_S3C的配置项目

config RTC_DRV_S3C
    tristate "Samsung S3C series SoC RTC"
    depends on ARCH_S3C2410
    help
      RTC (Realtime Clock) driver for the clock inbuilt into the
      Samsung S3C24XX series of SoCs. This can provide periodic
      interrupt rates from 1Hz to 64Hz for user programs, and
      wakeup from Alarm.

      The driver currently supports the common features on all the
      S3C24XX range, such as the S3C2410, S3C2412, S3C2413, S3C2440
      and S3C2442.

      This driver can also be build as a module. If so, the module
      will be called rtc-s3c.

③ 在drivers/rtc目录的Makefile文件中添加关于RTC_DRV_S3C的条目

obj-$(CONFIG_RTC_DRV_S3C)    += rtc-s3c.o

 

11. 内核中的Makefile

① 目标定义:目标定义用来定义哪些内容要作为模块编译,哪些要编译进内核

obj-y += foo.o

更常见的做法是根据auto.conf文件的CONFIG_变量来决定文件的编译方式,例如

obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o

② 多文件模块的定义:如果一个模块由多个文件组成,就应该采用模块名加-objs后缀或者-y后缀的形式来定义模块的组成文件。

obj-$(CONFIG_RTC_CLASS)  += rtc-core.o
rtc-core-y         := class.o interface.o

多文件模块定义也可以按照下面的格式,

obj-$(CONFIG_ISDN)   += isdn.o
isdn-objs            := isdn_net_lib.o isdn_v110.o isdn_common.o

③ 目录层次迭代:当CONFIG_EXT3_FS的值为y或m时,内核构建系统会将ext3目录列入向下迭代的目标中

obj-$(CONFIG_EXT3_FS) += ext3/

 

12. 内核中的Kconfig

① Kconfig的语法比较丰富,按功能可分为配置选项描述语句和菜单结构描述语句

② 配置选项描述:

(1)“config”关键字定义新的配置选项,之后的几行定义该配置选项的属性

(2) 配置选项的属性包括类型、数据范围、输入提示、依赖关系、帮助信息和默认值

(3) 每个配置选项都必须指定类型,类型包括bool、tristate、string、hex和int。其中,tristate和string是两种基本类型,其他都是基于这两种基本类型。

(4) 输入提示的一般格式:

prompt <prompt> [if <expr>]

(5) 默认值的一般格式:

default <expr> [if <expr>]

(6) 依赖关系的格式:

depend on(requires) <expr>

(7) 选择(反向依赖)的格式:

select <symbol> [if <expr>]

(8) 数据范围的格式:

range <symbol> <symbol> [if <expr>]

(9) 帮助信息格式如下:

help(---help---)

(10) 一个简单的配置选项示例

config MODVERSIONS
bool
prompt "Set version infomation on all module sysbols"
help
    Usually, modules have to be recompiled whenever you switch to a new kernel③

③ 菜单结构

(1)一般菜单结构由menu......endmenu描述

menu "Network device support"
depends on NET
config NETDEVICES
...
endmenu

注:所有处于“menu”和“endmenu”之间的菜单入口都会成为“Network device support”的子菜单,而且所有子菜单入口都会继承父菜单的依赖关系。

(2)通过分析依赖关系生成菜单结构

config MODULES
    bool "Enable loadable module support"

config MODVERSIONS
    bool "Set version information on all module symbols"
    depend on MODULES

(3)除此之外,Kconfig中还可能使用“choices...endchoices”、“comment”、“if...endif”这样的语法结构

 

 

二、Linux内核调试技术

 

1. 源码级别的调试接口

① BUG()和BUG_ON():当调用BUG()时,内核通过调用panic()引发OOPS,导致函数调用栈的回溯和打印错误信息。

#ifndef HAVE_ARCH_BUG
#define BUG() do { \
    printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); \
    panic("BUG!"); \
} while (0)
#endif

#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while(0)
#endif

注:内核还提供了WARN()和WARN_ON()用于警告作用。

② dump_stack():打印寄存器上下文和函数的跟踪线索。

if (!consistent_pte[0]) {
    printk(KERN_ERR "%s: not initialised\n", __func__);
    dump_stack();
    return NULL;
}

③ printk()内核格式化打印函数。

(1)几乎在任何地方、任何时候内核都可以调用它(中断上下文、进程上下文、持有锁时、多处理器处理时等)。

(2)在系统启动过程中和终端初始化之前无法调用。可以用以下方法打印调试信息:

  * 通过底层调试接口,将调试信息输出到串口等输出设备(Kernel low-level debugging functions)

  * 使用early_printk()

④ printk_ratelimit():限制输出速率

(1)printk_ratelimit()的典型用法

if(printk_ratelimit())
    printk(KERN_NOTICE "The printer if still on fire\n");

(2)printk_ratelimit()的返回值取决于两个因素,分别定义于以下两个文件

  * /proc/sys/kernel/printk_ratelimit

  * /proc/sys/kernel/printk_ratelimit_burst

  例如:printk_ratelimit = 5、 printk_ratelimit_burst = 10表示每打印10次就会有5s不会向控制台输出。

 

2. 使用printk()打印调试信息

① 日志等级

(1)printk()和printf()一个主要区别就是前者可以指定一个日志等级

#define    KERN_EMERG    "<0>"    /* system is unusable            */
#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define    KERN_CRIT     "<2>"    /* critical conditions            */
#define    KERN_ERR      "<3>"    /* error conditions            */
#define    KERN_WARNING  "<4>"    /* warning conditions            */
#define    KERN_NOTICE   "<5>"    /* normal but significant condition    */
#define    KERN_INFO     "<6>"    /* informational            */
#define    KERN_DEBUG    "<7>"    /* debug-level messages            */

注:如果调用者未将日志等级提供给printk,系统就使用默认值:KERN_WARNING "<4>"

(2)/proc/sys/kernel/printk文件定义了当前使用的日志等级,4个数字分别表示控制台日志级别、默认消息日志级别、最小控制台日志级别和默认控制台日志级别。

/Test/TestOops # cat /proc/sys/kernel/printk
4    4    1    7

注:修改printk的值可以用如下命令

echo 7 4 1 7 > /proc/sys/kernal/printk

② 日志缓冲区

(1)内核消息被保存在一个LOG_BUF_LEN大小的环形队列中。

(2)日志缓冲区的特点:

  * 消息被读出到用户空间,此消息就从环形队列中删除

  * 当消息缓冲区满时,再有printk()调用时,新消息将覆盖队列中的老消息

  * 在读写环形队列时,同步问题很容易解决

③ syslogd/klogd

④ dmesg

⑤ 注意事项

(1)虽然printk()很健壮,但效率很低

(2)printk()的临时缓存printk_buf只有1KB,所以printk()函数一次只能记录小于1KB的信息到日志缓冲区,并且使用时注意是环形缓冲区。

 

3. 使用strace跟踪系统调用

 

4. 使用OOPS调试系统故障