1、Makefile文件
# ===================================================================
# 文件名:Makefile.txt
# 作用:顶层构建调度器,负责遍历所有模块子目录并递归调用 make
# 一、规则不同
# 1.在 Testlib: 规则下:
# 这是"默认构建规则"。当你直接输入 make 或 make Testlib 时,Make 会执行这个规则。
# 它的目的是编译生成最终的库/可执行文件。
# 2.在 %: 通配规则下:
# 这是一个"通配规则",匹配所有未在 Makefile 中显式定义的目标名。当
# 你输入 make clean、make install、make distclean 或 make foo 时,Make 会触发这个规则。
# 它的目的是将你敲的命令(比如 clean)传递给所有子模块。
# 二、传递目标参数不同
# 1.在 Testlib 规则中:$@ 的值是固定的 Testlib。
# 所以实际执行的是:make -C SUBA Testlib,告诉 SUBA 子模块去构建 Testlib 这个目标。
# 2.在 % 规则中:$@ 的值是你输入的命令。
# 比如你敲 make clean,那么 % 匹配到 clean,$@ 就是 clean,
# 实际执行的是:make -C SUBA clean,告诉 SUBA 子模块去执行清理操作。
# 三、为什么写2遍类似文本?
# 如果只写 Testlib 规则,不写 % 规则:你执行 make clean 时,Make 找不到匹配 clean 的规则,就会报错
# 如果只写 % 规则,不写 Testlib 规则:你执行 make(默认目标)时,由于 all 依赖 Testlib,Make 会去查找 Testlib 目标。
# =》总结:这2行命令(文本相同,但使命不同):
# Testlib 里的作用是:"构建"时,让 SUBA 模块编译主目标。
# % 里的作用是:"维护"时(如清理、安装),让 SUBA 模块执行对应的辅助操作。
# ===================================================================
# make 找到默认目标 all,它依赖 swslib,所以开始构建 swslib;
# 在 swslib 规则中,按书写顺序依次调用子目录;
# 即执行 make 时,默认构建 Testlib 目标。
all: Testlib
# -------------------------------------------------------------------
# 目标 Testlib:构建主库/主程序(遍历所有基础模块)
# -------------------------------------------------------------------
Testlib:
# 分号; 表示这是三条独立的 Shell 命令按顺序执行。
# 设置环境变量 VAR=SUBA,导出,然后进入 SUBA 子目录执行 make,
# 目标为当前目标名(即 Testlib)。子目录 Makefile 会根据 VAR 值编译对应模块。
# export VAR作用:export VAR 之后,子进程确实能看到这个变量。
# 在 Linux 中,每个进程(比如你当前的 Shell)都有一块专属的内存空间,里面存放着环境变量表
# (就是一个 key=value 的字符串数组,比如 PATH=/usr/bin、HOME=/root。
# 环境变量是父进程留给子进程的"遗产",只有被 export 标记过的遗产,法律(操作系统)才允许传
#给后代;没标记的,只能父进程自己用,子进程无权继承。这就是为什么在递归 Makefile 中,export 是必不可少的"传话筒"。
@VAR=SUBA;export VAR;$(MAKE) -C SUBA $@
# VAR=SUBB,进入 SUBB 目录编译
@VAR=SUBB;export VAR;$(MAKE) -C SUBB $@
# -------------------------------------------------------------------
# 通配目标 %:匹配任何未在 above 中显式指定的目标(如 make clean)
# -------------------------------------------------------------------
%:
@VAR=SUBA;export VAR;$(MAKE) -C SUBA $@
@VAR=SUBB;export VAR;$(MAKE) -C SUBB $@
2、注解
export VAR的核心作用是:将当前 Shell 中的变量 VAR提升为"环境变量",子进程就能看到吗?环境变量和子进程有什么关联?
你的理解完全正确!export VAR 之后,子进程确实能看到这个变量。
为了让你彻底搞懂环境变量和子进程之间的底层关联,我用"传纸条"和"复印机"来给你打个比方,再配合 Linux 内核的底层机制讲解:
1. 环境变量的本质("传纸条")
在 Linux 中,每个进程(比如你当前的 Shell)都有一块专属的内存空间,里面存放着环境变量表(就是一个 key=value 的字符串数组,比如 PATH=/usr/bin、HOME=/root)。
如果你没有 export(只写了 VAR=SUBA):这个变量只写在了 Shell 的"内部草稿纸"上,不属于"环境变量表"。
如果你执行了 export:Shell 会把这个 VAR=SUBA抄写到那张正式的"环境变量表(传纸条)"里。
2. 进程创建的底层机制("复印机与遗传")
当你在 Shell 里输入 make 并敲回车时,操作系统(Linux内核)会通过两个步骤创建子进程:
fork()(复印):内核会把当前 Shell 进程的所有资源(包括代码、数据、以及那张正式的环境变量表)完整地克隆一份,产生一个完全一样的新进程(子进程)。
execve()(替换):子进程发现自己是 make 程序,于是把自己的代码替换成 make 的代码,但保留了从父进程克隆过来的那张环境变量表。
关键点来了:execve 系统调用有一个参数就是envp\[\](环境变量指针)。只有被父进程 export 并挂在"环境变量表"上的变量,才会被 fork 复制,进而被 execve 保留下来。
如果你没 export,VAR=SUBA只存在于 Shell 的"内部草稿纸"上,fork 时根本没机会被复印到子进程中。子进程启动后,在自己的内存里完全找不到 VAR 这个变量,所以子 Makefile 里的 $(VAR) 就是空的。
3. 为什么 Makefile 里特别依赖这个机制?
Makefile 的执行逻辑并不是一次性读完的,它是一个递归解析的过程。
父 Make 在执行到 $(MAKE) -C SUBA时,会先启动一个 Shell 子进程来解析这行命令。
这个 Shell 子进程如果发现 VAR 是 export 的,就会把它复印给 make 子进程。
make 子进程拿到这个环境变量后,会把它自动转换成 Makefile 内部的变量 $(VAR)。
如果没有 export,父 Make 虽然知道 VAR=SUBA,但当它去执行 -C SUBA 的子 Make 时,子 Make 根本收不到这条信息,导致路径拼接失败(找不到 label.c)。
补充一个容易混淆的坑
你可能注意到你在 Makefile.txt 中写的是:
makefile
@VAR=SUBA;export VAR;$(MAKE) -C SUBA $@
这里有一个细节:分号 ; 表示这是三条 独立的 Shell命令按顺序执行 。
如果不加 export,第二条命令 (MAKE) 启动时,VAR 仅仅是当前 Shell 进程的局部变量。加上 export 后,当前 Shell 会把它放进自己的环境表,这样当 (MAKE) 作为子进程被启动时,才能继承过去。
**总结一句话:**环境变量是父进程留给子进程的"遗产",只有被 export 标记过的遗产,法律(操作系统)才允许传给后代;没标记的,只能父进程自己用,子进程无权继承。这就是为什么在递归 Makefile 中,export 是必不可少的"传话筒"。