文章目录
前言
近期做了一个linux平台上WiFi工具acfg_tool的移植,借此机会对makefile和编译流程有了进一步的学习和理解,在此记录和分享。
一、Makefile学习
1.基本规则
Makefile基本规则的样式如下:
目标(target)... : 依赖(prerequiries)...
命令(command)
例如下面这条规则指以test.c
为依赖,以test
为目标,执行编译命令gcc test.c -o test
:
test:test.c
gcc test.c -o test
2.gcc
接下来看下从一个hello.c文件到可执行文件out的编译流程:
- C 源文件: hello.c
- 预处理:生成预处理后的C源文件 hello.i
- 编译:将C源文件翻译成汇编文件 hello.s
- 汇编:将汇编文件汇编成目标文件 hello.o
- 链接:将目标文件链接成可执行文件 out
makefile中如何实现上述过程呢?
gcc命令是GCC编译器里的一个前端程序,用来控制整个编译过程:分别调用预处理器、编译器和汇编器,完成编译的每一个过程,最后调用链接器,生成可执行文件。默认情况下,gcc命令会自动完成上述的整个编译过程。当然,gcc还提供了一系列参数,使用这些参数,可以让用户精准控制每一个编译过程。
-E :只做预处理,不编译
-S :只编译,将C程序编译为汇编文件
-c :只汇编,不链接。
-o :指定输出的文件名
-C :指定包含宏定义或其他预处理指令的头文件:gcc -C myheader.h -o myheader.i
-L :指定库文件的搜索路径
-l :指定要链接的库名称:gcc -L/usr/local/lib -o myprogram main.o -lmystaticlibrary
-D :定义一个宏:gcc -DDEBUG=1 -o myprogram main.c
-I :指定头文件的搜索路径:gcc -I/usr/local/include -o myprogram main.c
注:除了用-C
参数指定头文件,makefile中.h文件也可以放在依赖处:
main: main.c main.h
gcc main.c -o main
3.ar和静态库
在Makefile中,ar
指令用于创建静态库(.a文件)。同样ar也提供了众多参数对创建静态库的过程进行控制:
参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。
参数c:创建一个库。不管库是否存在,都将创建。
参数s:创建目标文件索引,这在创建较大的库时能加快时间。(补充:如果不需要创建索引,可改成大写S参数;如果.a文件缺少索引,可以使用ranlib命令添加)。
参数q:在文件包尾加入文件。
参数v:将文件插入库文件中。
下面给出两个创建静态库的示例:
示例1:
1、编译源代码生成.o 文件: gcc -c add.c -o add.o
2、制作静态库: ar rcs libname.a file1.o file2.o …
3、编译静态库到可执行文件中: gcc test.c libname.a -o test
示例2:
有源文件test.c和test.h:
1、编译test.c生成目标文件: gcc -o test.o -c test.c;
2、将目标文件加入到静态库中: ar rcv libmytest.a test.o;
3、连接(如果库不在默认路径,需要用-L表明路径): gcc -o test test.c -lmytest
4. $< $@ $^
- $@ 表示目标文件
- $^ 表示所有的依赖文件
- $< 表示第一个依赖文件
- $? 表示比目标还要新的依赖文件列表
示例:
链接文件main.o,stack.o,maze.o生成main:
main: main.o stack.o maze.o
gcc $^ -o $@
编译所有.c文件生成对应.o文件:
%.o : %.c
gcc -c $< -o $@
5.动态库
在GCC编译器中,-shared 选项用于生成共享库(动态库,.so文件)。
示例1:
gcc test_a.c test_b.c test_c.c -shared -o libtest.so
示例2:
1、将.c 文件生成.o 文件 (生成与位置无关的代码 -fPLC): gcc -c add.c -o add.o -fPIC
2、使用 gcc -shared 制作动态库: gcc -shared -o libmymath.so add.o sub.o div.o
3、编译可执行程序时指定所使用的动态库: gcc test.c -o a.out -l mymath -L ./lib
静态库和动态库的区别:
- 静态库: 编译到可执行程序中的库
- 动态库: 在程序运行时被加载到内存中,以文件形式独立存在
6.make的执行
make
命令会默认查找目录下名称为Makefile的文件进行执行,如果想执行特定的Makefile文件,可用命令:
make -f 文件名 目标
若没有指定make
的目标,make
的默认行为是执行Makefile的第一个target,该目标一般会带起多个目标的执行。目标中有=,或者-打头的不能指定为终极目标,因为这些字符的目标会被解析成命令行参数或者变量。
make的常用参数:
-n 打印命令,不执行
-q 寻找目标,找到什么都不输出,找不到打印错误error
-B 认为所有目标都需要重编译
-C <dir> 指定读取Makefile的目录,如果有多个“-C”参数,make 的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。
比如“make -C ~hchen/test -C prog”等价于“make -C ~hchen/test/prog”。
-I <dir> 指定一个被包含Makefile的搜索目标。可以使用多个“-I”参数来指定多个目录
-t 更新target的时间,组织生成目标的命令运行
示例1:以all为目标带动多个目标执行
在Makefile中,'all
’通常被定义为默认目标。这意味着如果用户在命令行中不指定任何目标,那么’all
’将被视为默认的构建目标。
例如,如果Makefile包含以下内容:
all: myprogram
myprogram: main.o myfunctions.o
gcc -o $@ $^
main.o: main.c
gcc -c -o $@ $<
myfunctions.o: myfunctions.c
gcc -c -o $@ $<
clean:
rm -f myprogram main.o myfunctions.o
当用户在命令行中运行’make
’时,Makefile会首先检查’all
’ 目标是否是最新的。由于’all
’依赖于’myprogram
’,Makefile会查看’myprogram
’是否是最新的。由于’myprogram
’依赖于’main.o
’和’myfunctions.o
’,Makefile会检查这些文件是否存在且是最新的。如果这些文件中的任何一个不是最新的,或者根本不存在,Makefile将执行相应的规则来生成或更新它们。最后,Makefile将链接’main.o
’和’myfunctions.o
’生成最终的可执行文件’myprogram
’。
通过’all
设置为默认目标,可以简化构建过程,只需运行’make
’命令即可编译和链接程序。如果需要清理构建产物,可以运’make clean
’命令。
示例2:模式规则
在Makefile中,%.o: %.c
被称为模式规则(pattern rule),而不是一个普通的目标,因为它使用模式(pattern)来匹配多个可能的源文件和目标文件,而不是指定单一的具体文件。这种规则允许你编写一组通用的指令,这些指令可以应用于以特定模式命名的所有文件。
%.o: %.c
$(CC) -c $(CFLAGS) $(LDFLAGS) $(COPTS) $< -o $@ $(Bx86_LIB)
all: $(OBJS)
$(CC) -shared -o libacfg.so $(CFLAGS) -z relro $(OBJS)
上述代码中,尽管all在%.o后面,默认目标仍是all,%.o不是目标。
示例3:伪目标
.PHONY: clean
clean:
rm a.txt
执行make clean
命令,发现即使存在clean文件,最终还是执行了Makefile中的rm
指令。声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。
二、acfg_tool代码结构和编译流程
1.工具介绍
acfg_tool
是高通的一个用户态WiFi应用,它提供了一系列用户态接口用于配置驱动。它支持命令和文件两种方式进行WiFi配置,同时也支持netlink
和ioctl
两种方式向驱动发送消息。
2.目录结构
acfg_tool
代码结构如下所示:
[qca-acfg]$ tree
.
├── acfg_app_cmn
│ ├── acfg_config_file.c
│ ├── acfg_config_file.h
│ ├── acfg_tool.h
│ └── acfg_tool_util.c
├── acfg_cli.c
├── acfg_fw_rec
│ ├── acfg_fw_rec_app.c
│ ├── acfg_fw_rec.c
│ └── Makefile
├── acfg_profile_app
│ ├── acfg_profile_tool.c
│ └── Makefile
├── acfg_test_app
│ ├── acfg_sample.conf
│ ├── acfg_test.c
│ ├── acfg_tool_fn.c
│ └── Makefile
├── acfg_wsupp_event.h
├── acfg_wsupp.h
├── acfg_wsupp_if.h
├── acfg_wsupp_nw.h
├── Doxyfile
├── include
│ ├── acfg_api_cmds.h
│ ├── acfg_api.h
│ ├── acfg_misc.h
│ ├── acfg_types.h
│ ├── acfg_wsupp_types.h
│ ├── appbr_if.h
│ └── linux
│ └── ac_shims.h
├── LicenseChoice.txt
├── Makefile
├── NOTICE
├── Notice.txt
└── src
├── acfg_api_cmds.c
├── acfg_api_event.c
├── acfg_api_event.h
├── acfg_api_pvt.c
├── acfg_api_pvt.h
├── acfg_br.c
├── acfg_br.h
├── acfg_common.conf
├── acfg_config.c
├── acfg_ctrl.c
├── acfg_log.c
├── acfg_log.h
├── acfg_security.c
├── acfg_security.h
├── acfg_wireless.h
├── acfg_wsupp_api_pvt.h
├── appbr_if.c
└── Makefile
7 directories, 48 files
其中,src
下主要包含acfg_tool
中需要的接口,编译后会生成一个.so
动态库,acfg_profile_app和acfg_test_app
下的代码会分别生成bin文件:acfg_set_profile和acfg_tool
,命令行也是主要通过这两个工具对无线驱动进行配置。
3.编译过程和输出
首先在qca-acfg
的前一级目录的Makefile
中,编译目标添加了qca-acfg/src
路径,且all
目标会依次执行make
和make installromfs
命令。
obj-y += applications/app/qca-acfg/src
all:
mkdir -p $(TARGET_ROMFS_PATH)/etc/wlan
for dir in $(obj-y) ; do $(MAKE) -C $$dir || exit 1 ; done
for dir in $(obj-y) ; do $(MAKE) -C $$dir installromfs || exit 1 ; done
qca-acfg
的编译入口为src
目录下的Makefile
文件(并非qcs-acfg
根目录下的Makefile
,qcs-acfg
根目录下的Makefile
和.c
文件不参与编译):
all: $(OBJS)
...
$(CC) -shared -o libacfg.so $(CFLAGS) -z relro $(OBJS)
$(MAKE) -C ../acfg_profile_app
$(MAKE) -C ../acfg_test_app
编译生成动态库libacfg.so
后,分别进入acfg_profile_app
和acfg_test_app
目录进行编译,acfg_set_profile
和acfg_tool
会分别依赖动态库libacfg.so
完成编译并生成。
LDFLAGS += -L$(PWD)/../src -lacfg
qca-acfg/acfg_profile_app/目录下:
all:
@echo -e "\nBuilding ACFG PROFILE APP..."
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) $(COPTS) -o $(ACFG_PROFILE_BINARY)
qca-acfg/acfg_test_app/目录下:
all:
@echo -e "\nBuilding ACFG TESTAPP..."
$(CC) $(SRCS) $(CFLAGS) $(LDFLAGS) $(COPTS) -o $(ACFG_BINARY) $(Bx86_LIB)
回到src
目录下的Makefile
文件,完成src
路径下的all
目标后,会执行make installromfs
,将生成的动态库和bin文件复制到对应目录中,完成安装。
installromfs:
...
-cp -v $(ACFG_TEST_DIR)/$(ACFG_BINARY) $(TARGET_ROMFS_PATH)/sbin
-cp -v $(ACFG_TEST_DIR)/$(ACFG_CONF_FILE) $(TARGET_ROMFS_PATH)/etc/wlan
-cp -v acfg_common.conf $(TARGET_ROMFS_PATH)/etc/wlan
-cp -v $(ACFG_PROFILE_TEST_DIR)/$(ACFG_PROFILE_BINARY) $(TARGET_ROMFS_PATH)/sbin
-cp -v libacfg.so $(TARGET_ROMFS_PATH)/lib
综上,qca-acfg
完成编译,共生成一个动态库libacfg.so
和两个bin文件acfg_set_profile、acfg_tool
。
总结
以上就是这次WiFi工具移植过程中对makefile学习的总结,欢迎指点交流学习。
学习链接:
https://zhuanlan.zhihu.com/p/648106991
https://zhuanlan.zhihu.com/p/654905927
https://blog.csdn.net/2301_77312300/article/details/131502680
https://blog.csdn.net/weixin_44617175/article/details/130873856
https://blog.csdn.net/pingxiaozhao/article/details/122378076
https://blog.csdn.net/tjcwt2011/article/details/127227905
bug记录:
https://blog.csdn.net/Liuwe1Ye/article/details/139814384