makefile学习及内核模块编译
Makefile.lkm 语法解析与学习指南
一、Makefile.lkm 涉及的语法与符号详解
1. 变量定义与赋值
-
:=
(立即赋值)
定义变量并立即展开右侧表达式,例如:BUILD_DIR := build # BUILD_DIR 值为 "build"
-
+=
(追加赋值)
向变量追加新值,例如:RTSDK_CFLAGS += -D__KERNEL__ # 在原有 CFLAGS 后添加宏定义
-
=
(延迟赋值)
变量值在引用时才会展开,例如:VAR = $(shell date) # 每次引用 VAR 时才会执行 date 命令
2. 特殊变量与函数
-
$(subst from,to,text)
字符串替换函数,例如:$(subst $(ROOT_PATH)/,,src/root/module.c) # 输出 "src/module.c"
-
$(addprefix prefix,names)
为每个名称添加前缀,例如:$(addprefix build/, a.o b.o) # 输出 "build/a.o build/b.o"
-
$(wildcard pattern)
通配符匹配文件,例如:$(wildcard *.c) # 返回所有 .c 文件的列表
-
$(shell command)
执行 Shell 命令并返回结果,例如:PWD := $(shell pwd) # 获取当前目录路径
3. 条件判断
-
ifneq (a,b)
/ifeq (a,b)
比较两个值是否不等/相等,例如:ifneq ($(KERNELRELEASE),) # 如果 KERNELRELEASE 非空
-
$(if condition,then-part,else-part)
条件函数,例如:$(if $(wildcard dir),,mkdir dir) # 目录不存在则创建
4. 规则与命令
-
目标-依赖规则
target: dependency command # 命令必须以 Tab 开头
示例:
modules: link_file_local $(MAKE) -C ... # 构建模块前先执行 link_file_local
-
.PHONY
伪目标
声明目标不代表实际文件,例如:.PHONY: install clean # install 和 clean 是动作而非文件
5. 通配符与自动变量
-
%
(通配符)
模式匹配,例如:%.o: %.c # 所有 .o 文件依赖对应的 .c 文件
-
$@
、$<
、$^
(自动变量)$@
:当前目标名(如install
)。$<
:第一个依赖文件名。$^
:所有依赖文件列表。
6. 循环与多行命令
-
$(foreach var,list,text)
遍历列表并执行操作,例如:$(foreach i,$(MODULE_SRC), ln -s $i ...) # 为每个源文件创建符号链接
-
多行命令
使用\
换行或直接换行(需保持缩进),例如:link_file_local: $(foreach i,$(MODULE_SRC), \ $(shell ln -s ...)) # 使用 \ 换行
二、快速学习 Makefile 的路径
1. 必学核心知识
- 基本结构
- 目标(Target)、依赖(Dependency)、命令(Command)三要素。
- 伪目标(
.PHONY
)的作用。
- 变量与函数
- 变量赋值(
:=
、=
、?=
、+=
)。 - 常用函数(
subst
、wildcard
、foreach
)。
- 变量赋值(
- 条件与流程控制
ifdef
、ifeq
、ifneq
。- 条件函数
$(if)
。
- 模式规则
- 通配符
%
的使用。 - 隐式规则(如自动推导
.c
→.o
)。
- 通配符
- 调试技巧
- 使用
$(info ...)
或$(warning ...)
输出变量值。 - 运行
make -n
查看命令但不执行。
- 使用
2. 推荐学习资源
- 官方手册
GNU Make Manual:权威且全面。 - 速查表
Makefile 快速参考:常用语法一页总结。 - 实战教程
3. 学习策略
-
从简单项目入手
-
编写一个编译多文件 C 项目的 Makefile,逐步添加变量和条件。
-
示例:
CC = gcc CFLAGS = -Wall OBJ = main.o utils.o app: $(OBJ) $(CC) $(CFLAGS) -o $@ $^ %.o: %.c $(CC) $(CFLAGS) -c $<
-
-
分析现有 Makefile
- 阅读开源项目(如 Linux 内核、BusyBox)的 Makefile,理解其设计模式。
-
动手调试
- 在 Makefile 中插入
$(info VAR=$(VAR))
打印变量值。 - 使用
make -p
查看内置规则和变量。
- 在 Makefile 中插入
4. 常见陷阱
- Tab 与空格:命令必须用 Tab 缩进,不能用空格。
- 变量作用域:全局变量与目标级变量的优先级。
- 路径处理:使用
abspath
或realpath
避免相对路径问题。
三、Makefile.lkm 中关键代码段解析
# 生成符号链接到 build 目录
link_file_local:
$(foreach i,$(MODULE_SRC), \
$(if $(wildcard $(dir $(BUILD_DIR)/...)),, \
$(shell mkdir -p ...)) \
$(shell ln -s $i ...))
- 作用:为每个源文件在
build
目录中创建符号链接,保持与源码相同的目录结构。 - 解析:
$(foreach i,...)
遍历MODULE_SRC
列表中的每个文件路径i
。$(if $(wildcard ...))
检查目标目录是否存在,若不存在则执行mkdir -p
。$(shell ln -s ...)
创建软链接,使编译时能正确找到源文件。
四、总结
- Makefile 本质:通过定义规则和依赖关系自动化构建流程。
- 学习优先级:变量 > 规则 > 函数 > 条件/循环。
- 实践建议:从单一文件编译开始,逐步扩展到多目录、多条件项目。
################################################################################
#
#
# Rules for building Linux Kernel Module
################################################################################
BUILD_DIR := build
LKM_INSTALL_PATH := $(ROOTFS_PATH)/lib/modules
RTSDK_CFLAGS += -D__KERNEL__
OUTPUT_MODULE_OBJ := $(addprefix $(BUILD_DIR)/,$(subst $(ROOT_PATH)/,,$(MODULE_OBJ)))
EXTRA_CFLAGS += $(RTSDK_CFLAGS)
EXTRA_LDFLAGS += $(RTSDK_LDFLAGS)
ifneq ($(KERNELRELEASE),)
obj-m := $(MODULE_NAME).o
$(MODULE_NAME)-objs := $(OUTPUT_MODULE_OBJ)
else
PWD := $(shell pwd)
modules: link_file_local
$(MAKE) -C $(OSS_LINUX_PATH) $(MAKEOPT) M=$(PWD) ARCH=$(RTSDK_ARCH) "CROSS_COMPILE=$(CROSS_COMPILE)" "CC=$(CC)" "LD=$(LD)" "AR=$(AR)" modules
endif
.PHONY: install
install:
ifeq ($(MODULE_NAME).ko, $(wildcard $(MODULE_NAME).ko))
$(if $(wildcard $(LKM_INSTALL_PATH)),,mkdir -p $(LKM_INSTALL_PATH))
$(STRIP) --strip-debug -R .note -R .comment $(MODULE_NAME).ko
cp $(MODULE_NAME).ko $(LKM_INSTALL_PATH)
@echo "$(MODULE_NAME).ko installed"
endif
clean distclean clean_all:
if [ -d $(BUILD_DIR) -a -d $(OSS_LINUX_PATH) ]; then $(MAKE) -C $(OSS_LINUX_PATH) M=$(PWD) "CC=$(CC)" "LD=$(LD)" "AR=$(AR)" clean; fi
@find \( -name '*.[oas]' -o -name 'Module.symvers' -o -name 'modules.order' \) -type f -print | xargs rm -rf
rm -rf $(BUILD_DIR)
link_file_local:
$(foreach i,$(MODULE_SRC),$(if $(wildcard $(dir $(BUILD_DIR)/$(subst $(ROOT_PATH),,$i))),,$(shell mkdir -p $(dir $(BUILD_DIR)/$(subst $(ROOT_PATH),,$i)))))
$(foreach i,$(MODULE_SRC),$(if $(wildcard $(BUILD_DIR)/$(subst $(ROOT_PATH),,$i)),,$(shell ln -s $i $(BUILD_DIR)/$(subst $(ROOT_PATH),,$i))))
总结:Makefile.lkm 内容与 Linux 内核模块编译流程
一、Makefile.lkm 核心内容解析
1. 变量定义
-
路径与编译选项:
BUILD_DIR := build # 构建输出目录 LKM_INSTALL_PATH := $(ROOTFS_PATH)/lib/modules # 模块安装路径 RTSDK_CFLAGS += -D__KERNEL__ # 内核模块编译必选宏 OUTPUT_MODULE_OBJ := $(addprefix $(BUILD_DIR)/,$(subst $(ROOT_PATH)/,,$(MODULE_OBJ))) # 对象文件路径映射
2. 内核模块构建规则
-
条件分支:
ifneq ($(KERNELRELEASE),) # 在内核构建系统中被调用时 obj-m := $(MODULE_NAME).o $(MODULE_NAME)-objs := $(OUTPUT_MODULE_OBJ) else # 用户态执行时 modules: link_file_local # 依赖符号链接创建 $(MAKE) -C $(OSS_LINUX_PATH) M=$(PWD) ... modules # 调用内核构建系统 endif
3. 符号链接与目录管理
-
link_file_local
目标:link_file_local: # 创建构建目录结构,并为源文件创建符号链接到 build/ 目录 $(foreach i,$(MODULE_SRC), mkdir -p ... && ln -s ...)
4. 安装与清理规则
-
安装:
install: # 安装内核模块到根文件系统 $(STRIP) --strip-debug ... # 剥离调试符号 cp $(MODULE_NAME).ko $(LKM_INSTALL_PATH)
-
清理:
clean distclean clean_all: # 删除构建产物 make -C $(OSS_LINUX_PATH) clean # 清理内核构建中间文件 rm -rf $(BUILD_DIR) ... # 删除本地构建目录
二、Linux 内核模块编译流程
1. 准备环境
-
设置变量(在 Makefile 或命令行中):
# 示例:Realtek SDK 环境变量 export OSS_LINUX_PATH=/home/jiangzhiguo/SDK4_V4.4.1.55661/build/oss/linux-4.4.153 export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu-
2. 编译模块
# 生成符号链接并调用内核构建系统
make -f Makefile.lkm modules
3. 安装模块
# 安装到根文件系统(需提前设置 ROOTFS_PATH)
make -f Makefile.lkm install
4. 清理构建文件
make -f Makefile.lkm clean # 常规清理
make -f Makefile.lkm distclean # 深度清理(删除配置)
三、常见问题与解决
问题现象 | 原因与解决方案 |
---|---|
No such file or directory |
OSS_LINUX_PATH 未定义或路径错误:在 Makefile 或命令行中指定正确路径。 |
模块编译后无法加载 | 内核版本与模块不匹配:确保 OSS_LINUX_PATH 指向与目标内核一致的源码目录。 |
符号链接未正确生成 | link_file_local 执行失败:检查 MODULE_SRC 变量是否包含有效源文件路径。 |
权限不足导致安装失败 | 使用 sudo 或确保 LKM_INSTALL_PATH 对当前用户可写。 |
四、完整编译流程图
1. 设置环境变量
├─ OSS_LINUX_PATH:内核源码路径
├─ ARCH:目标架构(如 arm64)
└─ CROSS_COMPILE:交叉编译工具链
2. 生成符号链接
└─ make -f Makefile.lkm modules → 触发 link_file_local
3. 调用内核构建系统
└─ 生成 .ko 文件到当前目录
4. 安装模块
└─ make -f Makefile.lkm install → 复制到 ROOTFS_PATH/lib/modules
5. 清理
└─ make -f Makefile.lkm clean 或 distclean
五、关键注意事项
- 路径一致性:确保
OSS_LINUX_PATH
、ROOT_PATH
等变量与实际源码路径匹配。 - 内核配置:若修改内核选项(如添加驱动支持),需重新执行
make menuconfig
并编译内核。 - 版本匹配:模块与运行中的内核版本必须一致(通过
uname -r
查看)。
通过以上步骤,可高效管理内核模块的构建、安装与维护。