嵌入式 Linux 驱动开发 - Makefile目录嵌入式 Linux 驱动开发 - Makefile1. Makefile 基础1.1 什么是 Makefile1.2 Makefile 基本结构2. 当前项目解析2.1 项目结构2.2 Makefile 逐行解析2.3 自动变量详解3. Makefile 核心语法3.1 变量定义3.2 模式规则3.3 函数使用3.4 条件判断3.5 多文件项目示例4. 内核模块 Makefile4.1 最简单的内核模块 Makefile4.2 参数解析4.3 多文件内核模块4.4 交叉编译嵌入式开发5. 设备驱动开发实战5.1 字符设备驱动示例5.2 驱动 Makefile5.3 使用流程6. 常用技巧与最佳实践6.1 目录结构组织6.2 带子目录的 Makefile6.3 条件编译6.4 常用内核编译变量6.5 完整项目模板6.6 学习建议6.7 常用命令参考1. Makefile 基础1.1 什么是 MakefileMakefile 是一个文本文件包含一系列规则用于告诉 make 工具如何编译和链接程序。1.2 Makefile 基本结构makefile目标依赖 命令必须是 Tab 缩进 target: dependencies command1.3 运行 Makemake # 执行第一个目标 make clean # 执行 clean 目标 make -n # 预览命令不执行 make -j4 # 并行编译4 个任务2. 当前项目解析2.1 项目结构makefile_test/ └── 1-1/ ├── Makefile # 编译规则 ├── hello.c # 源代码 ├── hello.o # 目标文件编译生成 └── hello # 可执行文件链接生成2.2 Makefile 逐行解析第 1 行定义编译器CC gcc第 2 行定义编译选项-Wall 开启所有警告 -g 生成调试信息 CFLAGS -Wall -g第 3 行定义目标文件名TARGET hello第 4 行定义目标文件OBJS hello.o第 6-7 行链接规则$ 代表目标 (hello) $^ 代表所有依赖 (hello.o) $(TARGET): $(OBJS) $(CC) -o $ $^第 9-10 行模式规则通配符规则%.o 匹配所有 .o 文件 %.c 匹配对应的 .c 文件 $ 代表第一个依赖 %.o: %.c $(CC) $(CFLAGS) -c $ -o $第 12-13 行清理目标clean: rm -f $(TARGET) $(OBJS) 第 15 行声明伪目标不是真实文件 .PHONY: clean2.3 自动变量详解| 变量 | 含义 | 示例 ||------|------|------|| $ | 目标文件名 | hello || $^ | 所有依赖文件 | hello.o || $ | 第一个依赖文件 | hello.c || $? | 比目标新的依赖 | - || $* | 匹配的模式部分 | hello (从 hello.o) |3. Makefile 核心语法3.1 变量定义基本赋值VAR value # 延迟赋值使用时展开 VAR : value # 立即赋值定义时展开 VAR ? value # 条件赋值未定义时才赋值 VAR value # 追加赋值使用变量$(VAR) 或 ${VAR}3.2 模式规则匹配所有 .c 文件编译为 .o%.o: %.c $(CC) -c $ -o $多文件编译SRCS main.c func1.c func2.c OBJS $(SRCS:.c.o) # 替换扩展名3.3 函数使用获取所有源文件SRCS : $(wildcard *.c)替换扩展名OBJS : $(patsubst %.c,%.o,$(SRCS))查找文件DIRS : $(shell find . -type d)字符串操作NAMES : $(notdir $(SRCS)) # 去掉目录 BASES : $(basename $(SRCS)) # 去掉扩展名3.4 条件判断判断变量ifeq ($(CC),gcc) CFLAGS -DGCC_COMPILER endif判断文件是否存在ifneq ($(wildcard config.h),) CFLAGS -DHAVE_CONFIG endif # if-else ifdef DEBUG CFLAGS -g -DDEBUG else CFLAGS -O2 endif3.5 多文件项目示例CC gcc CFLAGS -Wall -g # 自动查找所有源文件 SRCS : $(wildcard *.c) OBJS : $(SRCS:.c.o) TARGET myapp $(TARGET): $(OBJS) $(CC) -o $ $^ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(TARGET) $(OBJS) .PHONY: clean4. 内核模块 Makefile4.1 最简单的内核模块 Makefileobj-m : hello.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean .PHONY: all clean4.2 参数解析| 参数 | 含义 ||------|------|| obj-m | 指定要编译的内核模块.o 文件 || KDIR | 内核源码目录 || M$(PWD) | 外部模块目录 || modules | 内核编译目标 |4.3 多文件内核模块# 模块由多个文件组成 obj-m : mydriver.o mydriver-objs : main.o ops.o utils.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean .PHONY: all clean4.4 交叉编译嵌入式开发obj-m : mydriver.o # 交叉编译工具链 CROSS_COMPILE : arm-linux-gnueabihf- ARCH : arm KDIR : /opt/kernel/linux-5.10 PWD : $(shell pwd) all: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) \ -C $(KDIR) M$(PWD) modules clean: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) \ -C $(KDIR) M$(PWD) clean .PHONY: all clean5. 设备驱动开发实战5.1 字符设备驱动示例创建 hello_driver.c:#include linux/module.h #include linux/kernel.h #include linux/fs.h #include linux/uaccess.h #define DEVICE_NAME hello #define BUFFER_SIZE 256 static char message[BUFFER_SIZE]; static int major_number; static int device_open(struct inode *inode, struct file *file) { printk(KERN_INFO hello: Device opened\n); return 0; } static int device_release(struct inode *inode, struct file *file) { printk(KERN_INFO hello: Device closed\n); return 0; } static ssize_t device_read(struct file *file, char __user *buf, size_t count, loff_t *offset) { int bytes_read 0; // 实现读取逻辑 return bytes_read; } static ssize_t device_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) { // 实现写入逻辑 return count; } static struct file_operations fops { .open device_open, .release device_release, .read device_read, .write device_write, }; static int __init hello_init(void) { major_number register_chrdev(0, DEVICE_NAME, fops); if (major_number 0) { printk(KERN_ALERT hello: Failed to register device\n); return major_number; } printk(KERN_INFO hello: Registered with major number %d\n, major_number); return 0; } static void __exit hello_exit(void) { unregister_chrdev(major_number, DEVICE_NAME); printk(KERN_INFO hello: Unregistered\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(Hello World Driver);5.2 驱动 Makefileobj-m : hello_driver.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) # 默认目标 all: modules modules: echo Building kernel module... $(MAKE) -C $(KDIR) M$(PWD) modules echo Build complete: $(PWD)/hello_driver.ko clean: $(MAKE) -C $(KDIR) M$(PWD) clean rm -f *.o *.ko *.mod.c *.mod *.order *.symvers install: $(MAKE) -C $(KDIR) M$(PWD) modules_install depmod -a load: insmod hello_driver.ko unload: rmmod hello_driver dmesg: dmesg | tail -20 help: echo Makefile targets: echo all - Build module (default) echo clean - Remove build files echo install - Install module echo load - Load module echo unload - Unload module echo dmesg - Show kernel messages .PHONY: all modules clean install load unload dmesg help5.3 使用流程# 1. 编译模块make# 2. 加载模块sudo make load# 或 sudo insmod hello_driver.ko# 3. 查看设备ls -l /dev/hello# 4. 查看内核日志make dmesg# 或 dmesg | tail# 5. 卸载模块sudo make unload# 或 sudo rmmod hello_driver# 6. 清理make clean6. 常用技巧与最佳实践6.1 目录结构组织driver/├── src/ # 源代码│ ├── main.c│ ├── ops.c│ └── utils.c├── include/ # 头文件│ └── driver.h├── Makefile # 编译规则└── README.md # 说明文档6.2 带子目录的 Makefilemakefileobj-m : mydriver.omydriver-objs : src/main.o src/ops.occflags-y : -I$(src)/includeKDIR : /lib/modules/$(shell uname -r)/buildPWD : $(shell pwd)all:$(MAKE) -C $(KDIR) M$(PWD) modulesclean:$(MAKE) -C $(KDIR) M$(PWD) clean.PHONY: all clean6.3 条件编译obj-m : mydriver.o # 调试版本 ifdef DEBUG ccflags-y -DDEBUG -g endif # 架构相关 ifeq ($(ARCH),arm) ccflags-y -DCONFIG_ARM endif KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules .PHONY: all6.4 常用内核编译变量| 变量 | 说明 ||------|------|| ccflags-y | 添加到 C 编译器的选项 || asflags-y | 添加到汇编器的选项 || ldflags-y | 添加到链接器的选项 || subdir-m | 进入子目录编译 || obj-y | 编译进内核的目标 || obj-m | 编译为模块的目标 |6.5 完整项目模板makefile## 模块配置#MODULE_NAME : mydriverobj-m : $(MODULE_NAME).o$(MODULE_NAME)-objs : main.o ops.o utils.o## 编译配置#ARCH ? $(shell uname -m)ifeq ($(ARCH),x86_64)ARCH : x86endifKDIR ? /lib/modules/$(shell uname -r)/buildPWD : $(shell pwd)# 调试模式make DEBUG1ifdef DEBUGccflags-y -DDEBUG -g -O0elseccflags-y -O2endif## 目标#all: modulesmodules:echo echo Building $(MODULE_NAME).koecho Architecture: $(ARCH)echo Kernel: $(shell uname -r)echo $(MAKE) ARCH$(ARCH) -C $(KDIR) M$(PWD) modulesecho Build complete!clean:echo Cleaning...$(MAKE) ARCH$(ARCH) -C $(KDIR) M$(PWD) cleanrm -f *.o *.ko *.mod.c *.mod *.order *.symversrm -f .*.cmd .*.flags .tmp_versions/*install: modulesecho Installing module...$(MAKE) ARCH$(ARCH) -C $(KDIR) M$(PWD) modules_installdepmod -aecho Installation complete!load:echo Loading module...insmod $(MODULE_NAME).kounload:echo Unloading module...rmmod $(MODULE_NAME)reload: unload loadtest:echo Running tests...# 添加测试命令help:echo $(MODULE_NAME) Makefileecho echo Targets:echo all - Build module (default)echo clean - Remove build filesecho install - Install module to systemecho load - Load moduleecho unload - Unload moduleecho reload - Reload moduleecho test - Run testsecho help - Show this helpecho echo Variables:echo DEBUG1 - Build with debug symbolsecho ARCHarm - Cross-compile for ARMecho KDIR... - Kernel source directory.PHONY: all modules clean install load unload reload test help6.6 学习建议1. **从简单开始**: 先理解当前项目的 Makefile2. **动手实践**: 修改参数观察变化3. **阅读文档**: make --help 和 man make4. **查看内核源码**: 学习内核自带的 Makefile5. **循序渐进**: 用户空间 → 内核模块 → 完整驱动6.7 常用命令参考bash# Make 相关make -n # 预览命令make -B # 强制重新编译make -j$(nproc) # 并行编译make -C dir # 在指定目录执行# 内核模块相关modinfo xxx.ko # 查看模块信息lsmod # 列出已加载模块dmesg -w # 实时查看内核日志---