|
@@ -424,9 +424,328 @@ all:
|
|
|
@echo "CC = $(CC)"
|
|
@echo "CC = $(CC)"
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-## 九、完整项目示例
|
|
|
|
|
|
|
+## 九、深度实例:i.MX6ULL ARM 裸机 Makefile 逐行解析
|
|
|
|
|
|
|
|
-### 9.1 嵌入式 LED 控制项目
|
|
|
|
|
|
|
+以下是一个真实的嵌入式裸机项目 Makefile,涵盖了多目录编译、交叉编译、链接脚本、反汇编等生产级特性。
|
|
|
|
|
+
|
|
|
|
|
+### 9.1 项目目录结构
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+project/
|
|
|
|
|
+├── imx6ul/ # 芯片级头文件(寄存器定义等)
|
|
|
|
|
+│ ├── imx6ul.h
|
|
|
|
|
+│ └── fsl_common.h
|
|
|
|
|
+├── bsp/ # 板级支持包
|
|
|
|
|
+│ ├── clk/ # 时钟驱动
|
|
|
|
|
+│ │ ├── bsp_clk.h
|
|
|
|
|
+│ │ └── bsp_clk.c
|
|
|
|
|
+│ ├── led/ # LED 驱动
|
|
|
|
|
+│ │ ├── bsp_led.h
|
|
|
|
|
+│ │ └── bsp_led.c
|
|
|
|
|
+│ └── delay/ # 延时驱动
|
|
|
|
|
+│ ├── bsp_delay.h
|
|
|
|
|
+│ └── bsp_delay.c
|
|
|
|
|
+├── project/ # 应用入口
|
|
|
|
|
+│ └── main.c
|
|
|
|
|
+├── imx6ul.lds # 链接脚本(定义内存布局)
|
|
|
|
|
+├── obj/ # 编译输出目录(.o 文件)
|
|
|
|
|
+└── Makefile
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.2 完整 Makefile
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+CROSS_COMPILE ?= arm-linux-gnueabihf-
|
|
|
|
|
+TARGET ?= bsp
|
|
|
|
|
+
|
|
|
|
|
+CC := $(CROSS_COMPILE)gcc
|
|
|
|
|
+LD := $(CROSS_COMPILE)ld
|
|
|
|
|
+OBJCOPY := $(CROSS_COMPILE)objcopy
|
|
|
|
|
+OBJDUMP := $(CROSS_COMPILE)objdump
|
|
|
|
|
+
|
|
|
|
|
+INCDIRS := imx6ul \
|
|
|
|
|
+ bsp/clk \
|
|
|
|
|
+ bsp/led \
|
|
|
|
|
+ bsp/delay
|
|
|
|
|
+
|
|
|
|
|
+SRCDIRS := project \
|
|
|
|
|
+ bsp/clk \
|
|
|
|
|
+ bsp/led \
|
|
|
|
|
+ bsp/delay
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+INCLUDE := $(patsubst %, -I %, $(INCDIRS))
|
|
|
|
|
+
|
|
|
|
|
+SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
|
|
|
|
|
+CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
|
|
|
|
|
+
|
|
|
|
|
+SFILENDIR := $(notdir $(SFILES))
|
|
|
|
|
+CFILENDIR := $(notdir $(CFILES))
|
|
|
|
|
+
|
|
|
|
|
+SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
|
|
|
|
|
+COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
|
|
|
|
|
+OBJS := $(SOBJS) $(COBJS)
|
|
|
|
|
+
|
|
|
|
|
+VPATH := $(SRCDIRS)
|
|
|
|
|
+
|
|
|
|
|
+.PHONY: clean
|
|
|
|
|
+
|
|
|
|
|
+$(TARGET).bin : $(OBJS)
|
|
|
|
|
+ $(LD) -Timx6ul.lds -o $(TARGET).elf $^
|
|
|
|
|
+ $(OBJCOPY) -O binary -S $(TARGET).elf $@
|
|
|
|
|
+ $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
|
|
|
|
|
+
|
|
|
|
|
+$(SOBJS) : obj/%.o : %.S
|
|
|
|
|
+ $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
|
|
|
|
|
+
|
|
|
|
|
+$(COBJS) : obj/%.o : %.c
|
|
|
|
|
+ $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
|
|
|
|
|
+
|
|
|
|
|
+clean:
|
|
|
|
|
+ rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.3 逐段深度解析
|
|
|
|
|
+
|
|
|
|
|
+#### 第一段:交叉编译工具链定义
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+CROSS_COMPILE ?= arm-linux-gnueabihf-
|
|
|
|
|
+TARGET ?= bsp
|
|
|
|
|
+
|
|
|
|
|
+CC := $(CROSS_COMPILE)gcc
|
|
|
|
|
+LD := $(CROSS_COMPILE)ld
|
|
|
|
|
+OBJCOPY := $(CROSS_COMPILE)objcopy
|
|
|
|
|
+OBJDUMP := $(CROSS_COMPILE)objdump
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+| 变量 | 赋值方式 | 作用 |
|
|
|
|
|
+| --------------- | -------- | ---------------------------------------------------------------------------- |
|
|
|
|
|
+| `CROSS_COMPILE` | `?=` | 条件赋值,允许外部覆盖。`make CROSS_COMPILE=aarch64-linux-gnu-` 可切换工具链 |
|
|
|
|
|
+| `TARGET` | `?=` | 同上,默认目标名为 `bsp`,可外部指定 |
|
|
|
|
|
+| `CC` | `:=` | 立即展开,派生出 `arm-linux-gnueabihf-gcc` |
|
|
|
|
|
+| `LD` | `:=` | 链接器,用于将 `.o` 链接为 `.elf` |
|
|
|
|
|
+| `OBJCOPY` | `:=` | 格式转换工具,将 `.elf` 转为裸二进制 `.bin` |
|
|
|
|
|
+| `OBJDUMP` | `:=` | 反汇编工具,生成 `.dis` 文件供调试分析 |
|
|
|
|
|
+
|
|
|
|
|
+> **为什么用 `?=` 而不是 `:=`?** `?=` 允许用户在命令行覆盖,例如 `make CROSS_COMPILE=arm-linux-gnueabihf-`,方便在同一台机器上切换不同工具链。
|
|
|
|
|
+
|
|
|
|
|
+#### 第二段:多目录源文件与头文件收集
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+INCDIRS := imx6ul \
|
|
|
|
|
+ bsp/clk \
|
|
|
|
|
+ bsp/led \
|
|
|
|
|
+ bsp/delay
|
|
|
|
|
+
|
|
|
|
|
+SRCDIRS := project \
|
|
|
|
|
+ bsp/clk \
|
|
|
|
|
+ bsp/led \
|
|
|
|
|
+ bsp/delay
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`INCDIRS` 和 `SRCDIRS` 分别列出所有头文件目录和源文件目录。裸机项目通常按功能模块拆分目录,每个模块包含自己的 `.c` 和 `.h` 文件。
|
|
|
|
|
+
|
|
|
|
|
+#### 第三段:生成 `-I` 编译选项
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+INCLUDE := $(patsubst %, -I %, $(INCDIRS))
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**`patsubst` 执行过程:**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+输入: imx6ul bsp/clk bsp/led bsp/delay
|
|
|
|
|
+模式: % → -I %
|
|
|
|
|
+输出: -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+最终 `INCLUDE` 变量展开为编译器的头文件搜索路径参数。
|
|
|
|
|
+
|
|
|
|
|
+#### 第四段:递归收集所有源文件
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
|
|
|
|
|
+CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**展开过程(以 CFILES 为例):**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+SRCDIRS = project bsp/clk bsp/led bsp/delay
|
|
|
|
|
+
|
|
|
|
|
+$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
|
|
|
|
|
+
|
|
|
|
|
+→ $(wildcard project/*.c) → project/main.c
|
|
|
|
|
+ $(wildcard bsp/clk/*.c) → bsp/clk/bsp_clk.c
|
|
|
|
|
+ $(wildcard bsp/led/*.c) → bsp/led/bsp_led.c
|
|
|
|
|
+ $(wildcard bsp/delay/*.c) → bsp/delay/bsp_delay.c
|
|
|
|
|
+
|
|
|
|
|
+最终: CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`foreach` + `wildcard` 组合是多目录项目自动收集源文件的标准模式。
|
|
|
|
|
+
|
|
|
|
|
+#### 第五段:剥离路径、生成目标文件列表
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+SFILENDIR := $(notdir $(SFILES))
|
|
|
|
|
+CFILENDIR := $(notdir $(CFILES))
|
|
|
|
|
+
|
|
|
|
|
+SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
|
|
|
|
|
+COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
|
|
|
|
|
+OBJS := $(SOBJS) $(COBJS)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**逐步展开:**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
|
|
|
|
|
+
|
|
|
|
|
+① notdir: 去掉路径前缀
|
|
|
|
|
+CFILENDIR = main.c bsp_clk.c bsp_led.c bsp_delay.c
|
|
|
|
|
+
|
|
|
|
|
+② 后缀替换: .c → .o
|
|
|
|
|
+CFILENDIR:.c=.o → main.o bsp_clk.o bsp_led.o bsp_delay.o
|
|
|
|
|
+
|
|
|
|
|
+③ patsubst: 加上 obj/ 前缀
|
|
|
|
|
+COBJS = obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+所有 `.o` 文件统一输出到 `obj/` 目录,保持源码目录干净。
|
|
|
|
|
+
|
|
|
|
|
+#### 第六段:VPATH — 源文件搜索路径
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+VPATH := $(SRCDIRS)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**这是本 Makefile 最关键的设计之一。**
|
|
|
|
|
+
|
|
|
|
|
+`VPATH` 告诉 make:当在当前目录找不到某个依赖文件时,依次去 `project`、`bsp/clk`、`bsp/led`、`bsp/delay` 目录下搜索。
|
|
|
|
|
+
|
|
|
|
|
+**为什么需要 VPATH?**
|
|
|
|
|
+
|
|
|
|
|
+目标文件统一放在 `obj/` 目录(如 `obj/main.o`),但源文件分散在多个子目录。模式规则写的是:
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+$(COBJS) : obj/%.o : %.c
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+make 看到 `obj/main.o` 需要 `main.c`,但 `main.c` 不在当前目录。`VPATH` 让 make 去 `project/` 目录下找到它,然后执行:
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk ... -o obj/main.o project/main.c
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**没有 VPATH 会怎样?** make 会报错 `No rule to make target 'main.c', needed by 'obj/main.o'`。
|
|
|
|
|
+
|
|
|
|
|
+#### 第七段:静态模式规则 — 分离 .S 和 .c 编译
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+$(SOBJS) : obj/%.o : %.S
|
|
|
|
|
+ $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
|
|
|
|
|
+
|
|
|
|
|
+$(COBJS) : obj/%.o : %.c
|
|
|
|
|
+ $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**静态模式规则语法:**
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+$(targets): pattern: prereqs
|
|
|
|
|
+ command
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- `$(SOBJS)` 限定此规则只对 SOBJS 列表中的目标生效
|
|
|
|
|
+- `obj/%.o : %.S` 是模式部分,`%` 匹配文件名词干(如 `start`)
|
|
|
|
|
+- `$@` = `obj/start.o`(当前目标)
|
|
|
|
|
+- `$<` = `start.S`(第一个依赖,由 VPATH 解析)
|
|
|
|
|
+
|
|
|
|
|
+**`-nostdlib` 的含义:** 不链接标准库(libc、libgcc 等),因为裸机环境没有操作系统提供的 C 运行时库。
|
|
|
|
|
+
|
|
|
|
|
+#### 第八段:链接与输出 — 三步生成最终产物
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+$(TARGET).bin : $(OBJS)
|
|
|
|
|
+ $(LD) -Timx6ul.lds -o $(TARGET).elf $^
|
|
|
|
|
+ $(OBJCOPY) -O binary -S $(TARGET).elf $@
|
|
|
|
|
+ $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+| 步骤 | 命令 | 作用 |
|
|
|
|
|
+| ---- | -------------------------------------- | ------------------------------------------- |
|
|
|
|
|
+| 1 | `ld -Timx6ul.lds -o bsp.elf $^` | 用链接脚本将所有 `.o` 链接为 ELF 可执行文件 |
|
|
|
|
|
+| 2 | `objcopy -O binary -S bsp.elf bsp.bin` | 从 ELF 提取裸二进制,用于烧录到 Flash |
|
|
|
|
|
+| 3 | `objdump -D -m arm bsp.elf > bsp.dis` | 反汇编全部内容,生成 `.dis` 文件供调试分析 |
|
|
|
|
|
+
|
|
|
|
|
+**链接脚本 `imx6ul.lds` 的作用:** 定义内存布局——代码段放哪里、数据段放哪里、栈的起始地址等。裸机项目没有操作系统的内存管理,必须手动指定。
|
|
|
|
|
+
|
|
|
|
|
+**为什么需要 `.bin`?** ELF 是 Linux 可执行文件格式,包含元数据。烧录到裸机 Flash 时需要纯二进制 `.bin`(不含 ELF 头、段表等)。
|
|
|
|
|
+
|
|
|
|
|
+#### 第九段:清理目标
|
|
|
|
|
+
|
|
|
|
|
+```makefile
|
|
|
|
|
+clean:
|
|
|
|
|
+ rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+删除所有生成物,恢复到干净状态。
|
|
|
|
|
+
|
|
|
|
|
+### 9.4 构建流程图
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ ┌─────────────────────────────────┐
|
|
|
|
|
+ │ make 执行流程 │
|
|
|
|
|
+ └─────────────────────────────────┘
|
|
|
|
|
+ │
|
|
|
|
|
+ ┌───────────────▼───────────────┐
|
|
|
|
|
+ │ 1. 默认目标: bsp.bin │
|
|
|
|
|
+ │ 依赖: $(OBJS) │
|
|
|
|
|
+ └───────────────┬───────────────┘
|
|
|
|
|
+ │
|
|
|
|
|
+ ┌───────────────▼───────────────┐
|
|
|
|
|
+ │ 2. 检查 OBJS 中每个 .o 文件 │
|
|
|
|
|
+ │ obj/main.o → main.c (VPATH)│
|
|
|
|
|
+ │ obj/bsp_clk.o → bsp/clk/ │
|
|
|
|
|
+ │ obj/bsp_led.o → bsp/led/ │
|
|
|
|
|
+ │ ... │
|
|
|
|
|
+ └───────────────┬───────────────┘
|
|
|
|
|
+ │
|
|
|
|
|
+ ┌───────────────▼───────────────┐
|
|
|
|
|
+ │ 3. 模式规则编译 │
|
|
|
|
|
+ │ .c → obj/*.o (gcc -c) │
|
|
|
|
|
+ │ .S → obj/*.o (gcc -c) │
|
|
|
|
|
+ └───────────────┬───────────────┘
|
|
|
|
|
+ │
|
|
|
|
|
+ ┌───────────────▼───────────────┐
|
|
|
|
|
+ │ 4. 链接 │
|
|
|
|
|
+ │ ld → bsp.elf │
|
|
|
|
|
+ └───────────────┬───────────────┘
|
|
|
|
|
+ │
|
|
|
|
|
+ ┌───────────────▼───────────────┐
|
|
|
|
|
+ │ 5. 格式转换 │
|
|
|
|
|
+ │ objcopy → bsp.bin (烧录用) │
|
|
|
|
|
+ │ objdump → bsp.dis (调试用) │
|
|
|
|
|
+ └───────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 9.5 关键技巧总结
|
|
|
|
|
+
|
|
|
|
|
+| 技巧 | 本例用法 | 解决的问题 |
|
|
|
|
|
+| ---------------------- | -------------------------- | ------------------------------ |
|
|
|
|
|
+| `?=` 条件赋值 | `CROSS_COMPILE ?=` | 允许命令行覆盖工具链 |
|
|
|
|
|
+| `foreach` + `wildcard` | 自动收集多目录源文件 | 无需手动列出每个文件 |
|
|
|
|
|
+| `notdir` + `patsubst` | 生成 `obj/` 前缀的目标列表 | 统一输出目录,保持源码整洁 |
|
|
|
|
|
+| `VPATH` | 指定源文件搜索路径 | 解决目标与源文件路径分离的问题 |
|
|
|
|
|
+| 静态模式规则 | 分别处理 `.S` 和 `.c` | 汇编和 C 用不同编译选项 |
|
|
|
|
|
+| 链接脚本 `-T` | `imx6ul.lds` | 裸机项目的内存布局定义 |
|
|
|
|
|
+| `objcopy` | ELF → bin | 生成可烧录的裸二进制 |
|
|
|
|
|
+| `objdump -D` | 生成反汇编 | 调试时对照源码分析 |
|
|
|
|
|
+
|
|
|
|
|
+## 十、完整项目示例
|
|
|
|
|
+
|
|
|
|
|
+### 10.1 嵌入式 LED 控制项目
|
|
|
|
|
|
|
|
```makefile
|
|
```makefile
|
|
|
# ============================================
|
|
# ============================================
|
|
@@ -466,7 +785,7 @@ deploy: $(TARGET)
|
|
|
.PHONY: all clean deploy
|
|
.PHONY: all clean deploy
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-## 十、注意事项
|
|
|
|
|
|
|
+## 十一、注意事项
|
|
|
|
|
|
|
|
1. **Tab 缩进是强制的**:命令行前必须用 Tab,不能用空格(最常见的错误)
|
|
1. **Tab 缩进是强制的**:命令行前必须用 Tab,不能用空格(最常见的错误)
|
|
|
2. **`@` 前缀**:`@echo` 不打印命令本身,只打印输出
|
|
2. **`@` 前缀**:`@echo` 不打印命令本身,只打印输出
|
|
@@ -476,7 +795,7 @@ deploy: $(TARGET)
|
|
|
6. **`make` 默认执行第一个目标**:将 `all` 放在最前面
|
|
6. **`make` 默认执行第一个目标**:将 `all` 放在最前面
|
|
|
7. **依赖自动生成**:用 `-MMD -MP` 避免手动维护头文件依赖
|
|
7. **依赖自动生成**:用 `-MMD -MP` 避免手动维护头文件依赖
|
|
|
|
|
|
|
|
-## 十一、面试要点
|
|
|
|
|
|
|
+## 十二、面试要点
|
|
|
|
|
|
|
|
### Q1: Make 的核心机制是什么?
|
|
### Q1: Make 的核心机制是什么?
|
|
|
|
|
|