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. 必学核心知识
  1. 基本结构
    • 目标(Target)、依赖(Dependency)、命令(Command)三要素。
    • 伪目标(.PHONY)的作用。
  2. 变量与函数
    • 变量赋值(:==?=+=)。
    • 常用函数(substwildcardforeach)。
  3. 条件与流程控制
    • ifdefifeqifneq
    • 条件函数 $(if)
  4. 模式规则
    • 通配符 % 的使用。
    • 隐式规则(如自动推导 .c.o)。
  5. 调试技巧
    • 使用 $(info ...)$(warning ...) 输出变量值。
    • 运行 make -n 查看命令但不执行。
2. 推荐学习资源
3. 学习策略
  1. 从简单项目入手

    • 编写一个编译多文件 C 项目的 Makefile,逐步添加变量和条件。

    • 示例:

      CC = gcc
      CFLAGS = -Wall
      OBJ = main.o utils.o
      
      app: $(OBJ)
          $(CC) $(CFLAGS) -o $@ $^
      %.o: %.c
          $(CC) $(CFLAGS) -c $<
      
  2. 分析现有 Makefile

    • 阅读开源项目(如 Linux 内核、BusyBox)的 Makefile,理解其设计模式。
  3. 动手调试

    • 在 Makefile 中插入 $(info VAR=$(VAR)) 打印变量值。
    • 使用 make -p 查看内置规则和变量。
4. 常见陷阱
  • Tab 与空格:命令必须用 Tab 缩进,不能用空格。
  • 变量作用域:全局变量与目标级变量的优先级。
  • 路径处理:使用 abspathrealpath 避免相对路径问题。

三、Makefile.lkm 中关键代码段解析

# 生成符号链接到 build 目录
link_file_local:
    $(foreach i,$(MODULE_SRC), \
        $(if $(wildcard $(dir $(BUILD_DIR)/...)),, \
            $(shell mkdir -p ...)) \
        $(shell ln -s $i ...))
  • 作用:为每个源文件在 build 目录中创建符号链接,保持与源码相同的目录结构。
  • 解析
    1. $(foreach i,...) 遍历 MODULE_SRC 列表中的每个文件路径 i
    2. $(if $(wildcard ...)) 检查目标目录是否存在,若不存在则执行 mkdir -p
    3. $(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_PATHROOT_PATH 等变量与实际源码路径匹配。
  • 内核配置:若修改内核选项(如添加驱动支持),需重新执行 make menuconfig 并编译内核。
  • 版本匹配:模块与运行中的内核版本必须一致(通过 uname -r 查看)。

通过以上步骤,可高效管理内核模块的构建、安装与维护。

posted @ 2025-04-20 09:42  Jiangson  阅读(268)  评论(0)    收藏  举报