01-内核编译与配置.md 18 KB


title: "Linux内核编译与配置" tags:

  • 嵌入式Linux
  • 内核编译
  • 内核配置
  • Kconfig
  • Makefile
  • I.MX6ULL created: 2026-09-17 updated: 2026-09-17 pdf_ref: "正点原子I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第三十五章 Linux内核顶层Makefile详解" ---

一、Linux内核概述

1.1 内核版本

Linux内核版本号由三个数字组成:主版本.次版本.修订号。例如Linux 4.1.15,其中:

  • 4:主版本号,重大架构变更时递增
  • 1:次版本号,奇数表示开发版,偶数表示稳定版
  • 15:修订号,bug修复和小功能更新

查看内核版本:

uname -r
# 输出示例:4.1.15-gb39c1d0

1.2 内核源码结构

Linux内核源码主要目录结构如下:

linux-4.1.15/
├── arch/           # 架构相关代码(ARM、x86等)
├── block/          # 块设备驱动
├── crypto/         # 加密算法
├── Documentation/  # 内核文档
├── drivers/        # 设备驱动
├── fs/             # 文件系统
├── include/        # 头文件
├── init/           # 内核初始化
├── ipc/            # 进程间通信
├── kernel/         # 内核核心
├── lib/            # 库函数
├── mm/             # 内存管理
├── net/            # 网络协议栈
├── scripts/        # 构建脚本
├── security/       # 安全模块
├── sound/          # 音频驱动
└── Makefile        # 顶层Makefile

1.3 内核配置系统

Linux内核使用Kconfig配置系统,主要文件包括:

  • Kconfig:配置项定义
  • Makefile:编译规则
  • defconfig:默认配置文件

配置流程:Kconfigmenuconfig.config → 编译

二、内核配置系统

2.1 Kconfig语法

Kconfig是Linux内核的配置描述语言,定义在各子目录的Kconfig文件中。

基本语法结构

config 选项名称
    类型(bool/tristate/int/hex/string)
    "提示信息"
    默认值
    依赖关系
    帮助文本

配置项类型

类型 说明 取值
bool 布尔类型 y或n
tristate 三态类型 y、n或m(模块)
int 整数类型 数字
hex 十六进制 0x格式数字
string 字符串 任意字符串

常用关键字

# 定义配置项
config LED_DRIVER
    bool "LED Driver"
    default y

# 三态类型(可编译为模块)
config USB_STORAGE
    tristate "USB storage support"
    default m

# 整数类型
config NR_CPUS
    int "Maximum number of CPUs"
    range 2 8
    default 4

# 依赖关系
config HIGHMEM
    bool "High Memory Support"
    depends on ARM

# 反向依赖
config FB
    bool "Support for frame buffer"
    depends on !S390

# 选择依赖
config SND_SOC
    tristate "ALSA SoC support"
    select SND_PCM

# 多选依赖
config EXT4_FS
    tristate "Ext4 file system"
    depends on BLOCK

菜单结构

# 创建菜单
menu "Device Drivers"
    source "drivers/Kconfig"
endmenu

# 创建子菜单
menu "Network device support"
    menu "Ethernet driver support"
        source "drivers/net/ethernet/Kconfig"
    endmenu
endmenu

# 条件菜单
if ARCH_ARM
    menu "ARM specific configuration"
    endmenu
endif

2.2 配置项依赖关系

依赖关系是Kconfig的核心机制,确保配置选项的逻辑正确性。

# depends on - 必须满足
config MODULE_UNLOAD
    bool "Module unloading"
    depends on MODULES

# ! 取反
config LOCALVERSION
    string "Local version - append to kernel release"
    depends on !COMPILE_TEST

# || 或
config SND
    tristate "Sound card support"
    depends on AUDIT || !AUDIT

# && 与
config X86_64
    bool "64-bit kernel"
    depends on 64BIT && X86

# 选择依赖(自动选择被依赖项)
config SND_SOC
    tristate "ALSA SoC support"
    select SND_PCM

# 反向选择
config SND_SOC_I2S
    tristate "Generic I2S driver"
    select SND_SOC

2.3 menuconfig使用

make menuconfig是基于ncurses的图形配置界面。

# 基本使用
make menuconfig

# 交叉编译配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 使用外部分支配置
make ARCH=arm menuconfig O=out/

配置界面操作:

  • y:编译进内核
  • m:编译为模块
  • n:不编译
  • ?:查看帮助
  • /:搜索配置项
  • Esc Esc:返回上级

三、顶层Makefile详解

3.1 版本信息

顶层Makefile开头定义内核版本号:

# 版本定义
VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 15
EXTRAVERSION =

# 自动生成版本信息
KERNELVERSION = $(VERSION)$(PATCHLEVEL)$(SUBLEVEL)$(EXTRAVERSION)

3.2 MAKEFLAGS设置

# 递归编译标志
MAKEFLAGS += -rR --include-dir=$(CURDIR)

-r禁止内置规则,-R禁止内置变量,提高编译效率。

3.3 输出控制

# V=1 显示详细编译命令
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif

export quiet Q KBUILD_VERBOSE

使用方法:

  • make V=0:静默输出(默认)
  • make V=1:显示完整编译命令

3.4 静默编译

# make -s 静默模式
ifneq ($(filter 4.%,$(MAKE_VERSION)),)  # make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
quiet=silent_
endif
else  # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
quiet=silent_
endif
endif

3.5 输出目录配置

# O=xxx 指定输出目录
ifeq ($(KBUILD_SRC),)
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
endif

使用方法:

make O=out/linux    # 编译输出到out/linux目录

3.6 代码检查

# C=1 检查需要重新编译的文件
# C=2 检查所有源文件
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif

3.7 外部模块编译

# M=dir 编译外部模块
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif

# 外部模块时,目标为modules
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

使用方法:

make M=/path/to/module    # 编译外部模块

3.8 目录架构与源码目录

ifeq ($(KBUILD_SRC),)
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
objtree := .
src := $(srctree)
obj := $(objtree)

VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

export srctree objtree VPATH

3.9 架构与交叉编译配置

# 架构类型
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)

实际使用时需要修改为:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-

或者通过命令行指定:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xxx_defconfig

3.10 工具链变量

AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

3.11 头文件路径

USERINCLUDE := \
    -I$(srctree)/arch/$(hdr-arch)/include/uapi \
    -Iarch/$(hdr-arch)/include/generated/uapi \
    -I$(srctree)/include/uapi \
    -Iinclude/generated/uapi \
    -include $(srctree)/include/linux/kconfig.h

LINUXINCLUDE := \
    -I$(srctree)/arch/$(hdr-arch)/include \
    -Iarch/$(hdr-arch)/include/generated/uapi \
    -Iarch/$(hdr-arch)/include/generated \
    $(if $(KBUILD_SRC), -I$(srctree)/include) \
    -Iinclude \
    $(USERINCLUDE)

3.12 导出变量

export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP
export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
export KBUILD_ARFLAGS

四、内核配置方法

4.1 make xxx_defconfig

首次编译前需要使用make xxx_defconfig配置内核:

# 使用默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

# 配置目标匹配
%config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

执行流程:

make xxx_defconfig
    ↓
scripts_basic (编译fixdep、bin2c工具)
    ↓
scripts/kconfig/conf --defconfig=arch/arm/configs/xxx_defconfig Kconfig
    ↓
生成.config文件

4.2 make menuconfig

图形化配置界面:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

配置文件位置:

  • 输入:arch/arm/configs/xxx_defconfig
  • 输出:.config

4.3 默认配置

# 使用架构默认配置
make ARCH=arm defconfig

# 使用厂商提供的配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

4.4 自定义配置

# 方式1:基于现有配置修改
make menuconfig
# 在菜单中修改配置,保存后生成.config

# 方式2:手动编辑.config
vim .config
make olddefconfig  # 更新配置

# 方式3:备份和恢复配置
cp .config .config.backup
make defconfig
cp .config.backup .config
make olddefconfig

五、内核编译

5.1 编译命令

# 编译zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage

# 编译所有(包括模块)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all

# 使用16线程并行编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

5.2 编译目标生成

# make all 目标
all: vmlinux

# BOOT_TARGETS定义
BOOT_TARGETS = zImage Image xipImage bootpImage uImage

$(BOOT_TARGETS): vmlinux
    $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

5.3 vmlinux生成流程

# vmlinux依赖
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

# 链接命令
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)

vmlinux组成部分:

组成 说明 路径
head-y 启动代码 arch/arm/kernel/head.o
init-y 初始化代码 init/built-in.o
core-y 核心代码 kernel/mm/fs/ipc等
libs-y 库函数 lib/lib.a lib/built-in.o
drivers-y 设备驱动 drivers/sound/firmware
net-y 网络协议 net/built-in.o

5.4 内核镜像类型

镜像 大小 说明
vmlinux ~16MB ELF格式,未压缩
Image ~12MB 去除调试信息
zImage ~6MB gzip压缩
uImage ~6MB U-Boot专用格式

转换关系:

vmlinux → objcopy → Image → gzip → zImage → mkimage → uImage

5.5 增量编译

# 编译后修改代码,直接执行
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

# 清理编译产物
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean

# 完全清理(包括.config)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mrproper

六、内核模块编译

6.1 内置模块

在Kconfig中使用obj-y编译进内核:

# drivers/leds/Kconfig
config LEDS_GPIO
    tristate "GPIO LED driver"
    depends on LEDS_CLASS
    default y

# drivers/leds/Makefile
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o

6.2 外部模块

# 编译外部模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=/path/to/module modules

# 安装模块到指定目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=/path/to/module \
    INSTALL_MOD_PATH=/path/to/rootfs modules_install

6.3 模块Makefile示例

# 外部模块Makefile
obj-m := mydriver.o
mydriver-objs := main.o init.o

KERNEL_DIR := /path/to/linux-4.1.15
CROSS_COMPILE := arm-linux-gnueabihf-
ARCH := arm

all:
    make -C $(KERNEL_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
        M=$(PWD) modules

clean:
    make -C $(KERNEL_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
        M=$(PWD) clean

6.4 模块安装

# 将模块复制到内核模块目录
cp mydriver.ko /lib/modules/4.1.15/kernel/drivers/

# 生成模块依赖
depmod -a

# 加载模块
insmod mydriver.ko

# 查看已加载模块
lsmod

七、跨平台对比

7.1 I.MX6ULL vs STM32 vs RK3568内核差异

对比项 I.MX6ULL STM32MP1 RK3568
内核版本 4.1.15 5.4 5.10
架构 ARM Cortex-A7 ARM Cortex-A7 ARM Cortex-A55
位数 32位 32位 64位
配置文件 imx_v7_defconfig stm32mp1_defconfig rockchip_linux_defconfig
设备树 imx6ull-14x14-evk.dts stm32mp157c-ev1.dts rk3568-evb1-v10.dts
内核大小 ~5MB ~6MB ~8MB
启动方式 U-Boot+zImage U-Boot+zImage U-Boot+zImage

7.2 跨平台编译注意事项

# I.MX6ULL (ARM 32位)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

# STM32MP1 (ARM 32位)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- stm32mp1_defconfig

# RK3568 (ARM 64位)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_linux_defconfig

7.3 驱动差异

驱动类型 I.MX6ULL STM32MP1 RK3568
GPIO gpio-mxc gpio-stm32 gpio-rockchip
I2C i2c-imx i2c-stm32 i2c-rk3x
SPI spi-imx spi-stm32 spi-rockchip
UART serial-imx serial-stm32 serial-rockchip

八、面试精选

Q1: 请描述Linux内核编译的完整流程

答案:

  1. 配置阶段:使用make xxx_defconfigmake menuconfig生成.config文件
  2. 准备阶段:编译scripts/basic工具(fixdep、bin2c)
  3. 架构配置:读取arch/arm/Makefile设置架构相关变量
  4. 编译源码:递归编译各子目录,生成built-in.o和模块
  5. 链接阶段:使用scripts/link-vmlinux.sh链接生成vmlinux
  6. 镜像生成:将vmlinux转换为Image、zImage等可引导镜像

Q2: Kconfig中的depends on和select有什么区别?

答案:

  • depends on:配置项被依赖,必须满足条件才能启用

    config HIGHMEM
      bool "High Memory Support"
      depends on ARM  # 只有ARM架构才能选择
    
  • select:配置项被选中时自动选择被依赖项

    config SND_SOC
      tristate "ALSA SoC support"
      select SND_PCM  # 选中时自动选中SND_PCM
    

Q3: vmlinux是如何生成的?涉及哪些关键变量?

答案: vmlinux通过链接脚本和链接命令生成:

  • KBUILD_VMLINUX_INIT:启动代码和初始化代码
    • head-y:arch/arm/kernel/head.o
    • init-y:init/built-in.o
  • KBUILD_VMLINUX_MAIN:核心功能模块
    • core-y:kernel/mm/fs/ipc等
    • libs-y:lib/lib.a lib/built-in.o
    • drivers-y:drivers/sound/firmware
    • net-y:net/built-in.o
  • 链接脚本:arch/arm/kernel/vmlinux.lds
  • 链接命令:scripts/link-vmlinux.sh调用LD完成链接

Q4: make menuconfig和make xxx_defconfig有什么区别?

答案:

  • make xxx_defconfig:使用预定义的默认配置文件快速配置,适合初始配置

    make ARCH=arm imx_v7_defconfig  # 使用IMX6ULL默认配置
    
  • make menuconfig:提供图形界面手动修改配置,适合定制化需求

    make ARCH=arm menuconfig  # 打开配置菜单
    
  • 推荐流程:先用defconfig快速配置,再用menuconfig精细调整

Q5: 如何交叉编译Linux内核并烧录到开发板?

答案:

# 1. 进入内核源码目录
cd /path/to/linux-4.1.15

# 2. 配置内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

# 3. 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

# 4. 编译模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

# 5. 安装模块到rootfs
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
    INSTALL_MOD_PATH=/path/to/rootfs modules_install

# 6. 复制zImage到TFTP目录
cp arch/arm/boot/zImage /tftpboot/

# 7. 复制设备树到TFTP目录
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /tftpboot/

# 8. 在U-Boot中设置启动参数
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait'
setenv bootcmd 'tftp ${loadaddr} zImage; tftp ${fdt_addr} imx6ull-14x14-evk.dtb; bootz ${loadaddr} - ${fdt_addr}'
saveenv
run bootcmd

相关知识

  • [[02-Linux内核启动流程]]
  • [[03-设备树详解]]
  • [[04-内核模块开发]]
  • [[05-Bootloader配置]]

参考资料