《嵌入式Linux开发实用教程》——1.2 Makefile基本知识

简介:

本节书摘来自异步社区《嵌入式Linux开发实用教程》一书中的第1章,第1.2节,作者 朱兆祺,李强,袁晋蓉,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.2 Makefile基本知识

Makefile如今能得以广泛应用,这还得归功于它被包含在UNIX系统中。在make诞生之前,UNIX系统的编译系统主要由“make”、“install”shell脚本程序和程序的源代码组成。它可以把不同目标的命令组成一个文件,而且可以抽象化依赖关系的检查和存档。这是向现代编译环境发展的重要一步。1977年,斯图亚特·费尔德曼在贝尔实验室里制作了这个软件。2003年,斯图亚特·费尔德曼因发明了这样一个重要的工具而接受了美国计算机协会(ACM)颁发的软件系统奖。

Makefile文件可以实现自动化编译,只需要一个“make”命令,整个工程就能完全自动编译,极大地提高了软件开发的效率。目前虽有众多依赖关系检查工具,但是make是应用最广泛的一个。一个程序员会不会写Makefile,从一个侧面说明了这个程序员是否具备完成大型工程的能力。

1.2.1 Makefile规则

一个简单的Makefile语句由目标、依赖条件、指令组成。

smdk6400_config  :  unconfig  
   @mkdir -p $(obj)include $(obj)board/samsung/smdk6400

smdk6400_config:目标;

unconfig:先决条件;

@mkdir -p $(obj)include $(obj)board/samsung/smdk6400:指令。这里特别注意,“@”前面是Tab键,并且必须是Tab键,而不能是空格。

目标和先决条件是依赖关系,目标是依赖于先决条件生成的。

1.2.2 Makefile变量

1.变量的引用方式

使用“$(OBJTREE)”或者“${ OBJTREE }”来引用OBJTREE这个变量的定义。这个引用方式似乎很像C语言中的指针变量,使用*p来取存放在指针p中的值。

obj := $(OBJTREE)/
OBJTREE  := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
export BUILD_DIR=/tmp/build

$(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))的含义:如果“BUILD_DIR”变量值不为空,则将变量“BUILD_DIR”指定的目录作为一个子目录;否则将目录“CURDIR”作为一个子目录。

2.递归展开式变量

这类变量的定义是通过“=”和“define”来定义的。

student = lilei
CLASS = $(student) $(teacher)
teacher = yang

all:  
   @echo $(CLASS)

其优点是:这种类型递归展开式的变量在定义时,可以引用其他之前没有定义过的变量,这个变量可能在后续部分定义,或者是通过make的命令行选项传递的变量来定义。

其缺点是:其一,使用此风格的变量定义,可能会由于出现变量的递归定义而导致make陷入到无限的变量展开过程中,最终使make执行失败。

x = $(y)
y = $(z)
z = $(x)

这样的话会使得Makefile出错,因为到最终引用了自己。

其二,这种风格的变量定义中如果使用了函数,那么包含在变量值中的函数总会在变量被引用的地方执行。

3.直接展开式变量

为了避免递归展开式变量存在的问题和不方便,GNU make支持另外一种风格的变量,称为直接展开式变量。这种风格的变量使用“:=”定义。在使用“:=”定义变量时,变量值中对其他变量或者函数的引用在定义变量时被展开,也就是对变量进行替换。

X := student
Y := $(X)
X := teacher
all:  
   @echo $(X) $(Y)

这里的输出是:teacher student。

这个直接展开式变量在定义时就完成了对所引用变量和函数的展开,因此不能实现对其后定义变量的引用。

4.条件赋值

在对变量进行赋值之前,会对其进行判断,只有在这个变量之前没有进行赋值的情况下才会对这个变量进行赋值。

X := student
X ?= teacher
all:  
   @echo $(X)

由于X在之前被赋值了,所以这里的输出是student。

5.变量的替换引用

对于一个已经定义的变量,可以使用变量的替换引用将变量中的后缀字符使用指定的字符替换。格式为“$(X:a=b)”(或者“${X:a=b}”),即将变量“X”中所有以“a”字符结尾的字替换为以“b”结尾的字。

X := fun.o main.o
Y := $(X: .o=.c)
all:  
   @echo $(X) $(Y)

特别注意的是,$(X: .o=.c)的“=”两边不能有空格。这里的输出是:fun.o main.o fun.c main.c。

6.追加变量值

追加变量值是指一个通用变量在定义之后的其他一个地方,可以对其值进行追加。也就是说可以在定义时(也可以不定义而直接追加)给它赋一个基本值,后续根据需要可随时对它的值进行追加(增加它的值)。在Makefile中使用“+=”(追加方式)来实现对一个变量值的追加操作。

X = fun.o main.o
X += sub.o
all:  
   @echo $(x)

这里的输出是:fun.o main.o sub.o。

1.2.3 Makfile常用关键字

1.ifneq关键字

这个关键字是用来判断两个参数是否不相等。格式为:

ifneq “Value1”“Value2”
ifneq (Value1,Value2)

在判断之前先要将Value1和Value2的值进行展开和替换,如在U-Boot-2013.04的顶层目录Makefile中,对U-Boot的版本参数就使用了ifneq关键字进行判断。

VERSION   = 2013
PATCHLEVEL  = 04
SUBLEVEL   =
EXTRAVERSION =

ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif

先将SUBLEVEL使用$()展开和替换,如果SUBLEVEL的值不是空,则执行:

U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)

也就是说,如果$(SUBLEVEL) = 1的话,那么U_BOOT_VERSION = 2013.04.1。

如果SUBLEVEL的值是空,则执行:

U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)

那么此时U_BOOT_VERSION = 2013.04。

2.ifeq关键字

ifeq关键字和ifneq关键字是相对而言的,用来判断两个参数是否相等。格式为:

ifeq “Value1”“Value2”
ifeq (Value1,Value2)

和ifneq一样,先要将Value1和Value2展开替换之后,再进行比较。

ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-
endif

如果HOSTARCH展开替换之后和ARCH展开替换之后相等,则:

CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-

否则CROSS_COMPILE不等于/usr/local/arm/4.4.1/bin/arm-linux-。

3.ifndef关键字

ifndef关键字用来判断一个变量是否没有进行定义。格式:

ifndef Value

由于在Makefile中,没有定义的变量的值为空。

ifndef CONFIG_SANDBOX
SUBDIRS += $(SUBDIR_EXAMPLES)
endif

如果CONFIG_SANDBOX值为空,条件成立,执行如下语句:

SUBDIRS += $(SUBDIR_EXAMPLES)

否则不执行。

4.ifdef关键字

ifdef关键字用来判断一个变量是否已经进行定义过。格式:

ifdef Value

如:

ifdef CONFIG_SYS_LDSCRIPT  
     # need to strip off double quotes  
     LDSCRIPT := $(subst ",,$(CONFIG_SYS_LDSCRIPT))
endif

如果CONFIG_SYS_LDSCRIPT定义过,则执行:

LDSCRIPT := $(subst ",,$(CONFIG_SYS_LDSCRIPT))

否则不执行。

1.2.4 Makefile常用函数

1.Makefile函数语法

在Makefile中,函数的调用和变量的调用类似,都是使用“$”进行标识。语法如下:

$(函数名 函数的参数)
${函数名 函数的参数}

函数名与函数的参数之间使用空格隔开,而函数的参数间使用逗号进行分隔。以上两种写法都是可以的,但是为了风格统一,请不要两者进行混合使用。

2.shell函数

make可以使用shell函数和外部通信。shell函数本身的返回值是其参数的执行结果,没有进行任何处理,对结果的处理是由make进行的。当对函数的引用出现在规则的命令行中,命令行在执行时函数才被展开。展开时函数参数(shell命令)的执行是在另外一个shell进程中完成的,因此需要对出现在规则命令行的多级“shell”函数引用需要谨慎处理,否则会影响效率(每一级的“shell”函数的参数都会有各自的shell进程)。

建立一个测试程序,Makefile的内容:

zhu := $(shell cat func)

all:
    @ echo $(zhu)

Func文件中的内容:

juxst zhuzhaoqi

执行完成Makefile之后:

zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/Makefile/shellfunction$ make
juxst zhuzhaoqi

在U-Boot和Linux内核源码中将会大量使用到shell函数。

3.subst函数

subst函数是字符串替换函数,语法为:

$(subst 被替换字串 替换字串 替换操作字符串)

执行subst函数之后,返回的是执行替换操作之后的字符串。如下:

name := zhu zhaoqi
Alphabet_befor := z
Alphabet_after := Z
Name := $(subst $(Alphabet_befor), $(Alphabet_after), $(name))

all:
    echo $(Name)

执行上面的Makefile,输出结果为:

echo  Zhu Zhaoqi
Zhu Zhaoqi

即将“z”替换成了“Z”。

4.dir函数

dir函数作用为取出该文件的目录,其语法为:

$(dir 文件名称)

执行该函数之后返回文件目录部分。

Makefile中常用函数较多,笔者就不一一例举了,读者可参考相关书籍进行深入了解。

相关文章
|
5天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
45 13
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
111 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
Linux C++
Linux c/c++之makefile的基础使用
Linux下C/C++项目中makefile的基本使用,包括基础、进阶和高级用法,以及如何创建和使用makefile来自动化编译过程。
21 0
Linux c/c++之makefile的基础使用
|
3月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
4月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
55 6
|
4月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
61 5
|
4月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
132 3
|
4月前
|
编解码 安全 Linux
基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究
这段内容讲述了国产操作系统背景下,大牛直播SDK针对国产操作系统与Linux平台发布的RTMP/RTSP直播播放SDK。此SDK支持arm64架构,基于X协议输出视频,采用PulseAudio和Alsa Lib处理音频,具备实时静音、快照、缓冲时间设定等功能,并支持H.265编码格式。此外,提供了示例代码展示如何实现多实例播放器的创建与管理,包括窗口布局调整、事件监听、视频分辨率变化和实时快照回调等关键功能。这一技术实现有助于提高直播服务的稳定性和响应速度,适应国产操作系统在各行业中的应用需求。
141 3
|
4月前
|
传感器 人工智能 网络协议
:嵌入式 Linux 及其用途
【8月更文挑战第24天】
219 0
|
5月前
|
Ubuntu 算法 Linux
嵌入式Linux的学习误区
**嵌入式Linux学习误区摘要** 1. **过度聚焦桌面Linux** - 许多学习者误将大量时间用于精通桌面Linux系统(如RedHat、Fedora、Ubuntu),认为这是嵌入式Linux开发的基石。 - 实际上,桌面Linux仅作为开发工具和环境,目标不应是成为Linux服务器专家,而应专注于嵌入式开发工具和流程。 2. **盲目阅读Linux内核源码** - 初学者在不了解Linux基本知识时试图直接研读内核源码,这往往导致困惑和挫败感。 - 在具备一定嵌入式Linux开发经验后再有针对性地阅读源码,才能有效提升技能。