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

1.2 Makefile基本知识

嵌入式Linux开发实用教程
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中常用函数较多,笔者就不一一例举了,读者可参考相关书籍进行深入了解。

时间: 2025-01-21 19:07:40

《嵌入式Linux开发实用教程》——1.2 Makefile基本知识的相关文章

《嵌入式Linux开发实用教程》——1.1 Linux基本命令

1.1 Linux基本命令 嵌入式Linux开发实用教程 在学习嵌入式Linux开发的过程中,将经常使用到Linux的操作命令.实际上,Linux系统中的命令也是为实现特定的功能而编写的,而且绝大数的命令是用C语言编写的.有些实用性强的程序被广泛使用和传播,逐渐地演变成Linux的标准命令.但是Linux的操作命令繁多,本节将在U-Boot.Linux移植过程中常用到的Linux操作命令罗列出来进行讲解,为后续的学习做良好的铺垫.读者不要认为这是Linux简单命令则不屑一顾,嵌入式Linux学习

《嵌入式Linux开发实用教程》——4.3 块设备驱动

4.3 块设备驱动 嵌入式Linux开发实用教程 块设备和字符设备从字面上理解最主要的区别在于读写的基本单元不同,块设备的读写基本单元为数据块,数据的输入输出都是通过一个缓冲区来完成的.而字符设备不带有缓冲,直接与实际的设备相连而进行操作,读写的基本单元为字符.从实现的角度来看,块设备和字符设备是两种不同的机制,字符设备的read.write的API直接到字符设备层,但是块设备相对复杂,是先到文件系统层,然后再由文件系统层发起读写请求. 数据块指的是固定大小的数据,这个值的大小由内核来决定.一般

《嵌入式Linux开发实用教程》——导读

前言 嵌入式Linux开发实用教程 2012年11月,当我看到论坛中的同龄大学生在学习嵌入式Linux寸步难行,我就计划将我学习嵌入式Linux的点点滴滴记录下来,从一个学生的角度去写,或许更能让初学者接受.2013年1月,当写完初稿再重新审视的时候,总感觉不尽如意.2013年3月,我联系了我的师弟李强,两人打算以一个全新的思维重新完成这本书. 2013年6月,书稿终于定型. 本书一共有6章,从Linux指令基础到Linux常用软件:从U-Boot移植到Linux移植:从Linux驱动程序设计到

《嵌入式Linux开发实用教程》——4.1 设备驱动概述

4.1 设备驱动概述 嵌入式Linux开发实用教程Linux系统将设备分成3种基本类型:字符设备.块设备.网络接口. (1)字符设备 字符设备是一个能够像字节流一样被访问的设备,字符终端(/dev/console)和串口(/dev/ttys0)就是两个字符设备.字符设备可以通过文件系统节点来访问,比如/dev/tty1和/dev/lp0等.这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道. (2)块设备 块设备和字符设备相

《嵌入式Linux开发实用教程》——第1章 嵌入式Linux基础 1.1 Linux基本命令

第1章 嵌入式Linux基础 1.1 Linux基本命令 在学习嵌入式Linux开发的过程中,将经常使用到Linux的操作命令.实际上,Linux系统中的命令也是为实现特定的功能而编写的,而且绝大数的命令是用C语言编写的.有些实用性强的程序被广泛使用和传播,逐渐地演变成Linux的标准命令.但是Linux的操作命令繁多,本节将在U-Boot.Linux移植过程中常用到的Linux操作命令罗列出来进行讲解,为后续的学习做良好的铺垫.读者不要认为这是Linux简单命令则不屑一顾,嵌入式Linux学习

《嵌入式Linux开发实用教程》——1.3 arm-linux交叉编译链

1.3 arm-linux交叉编译链 平常我们做的编译叫本地编译,也就是在当前平台编译,编译得到的程序也是在本地执行.相对而言的交叉编译指的是在一个平台上生成另一个平台的可执行代码. 常见的交叉编译有以下3种. 在Windows PC上,利用ADS(ARM 开发环境),使用armcc编译器,编译出针对ARM CPU的可执行代码. 在Linux PC上,利用arm-linux-gcc编译器,编译出针对Linux ARM平台的可执行代码. 在Windows PC上,利用cygwin环境,运行arm-

《嵌入式Linux开发实用教程》——1.5 嵌入式Linux移植常用软件

1.5 嵌入式Linux移植常用软件 在进行嵌入式Linux学习与开发的过程中,需要使用到一些常用的开发工具,熟练使用这些软件,能让学习与开发达到事半功倍的效果. 1.5.1 SecureCRT SecureCRT是可以在Window环境下登录UNIX和Linux服务器主机的软件,它不仅支持SSH1.SSH2,而且支持TeInet和rlogin协议. 在Ubuntu宿主机上安装SSH. zhuzhaoqi@zhuzhaoqi-desktop:~/sudo apt-get install open

《嵌入式Linux开发实用教程》——4.2 字符设备驱动

4.2 字符设备驱动 Linux操作系统将所有的设备都会看成是文件,因此当我们需要访问设备时,都是通过操作文件的方式进行访问.对字符设备的读写是以字节为单位进行的. 对字符设备驱动程序的学习过程,主要以两个具有代表性且在OK6410开发平台可实践性的字符驱动展开分析,分别为LED驱动程序.ADC驱动程序. 4.2.1 LED驱动程序设计 为了展现LED的裸板程序和基于Linux系统的LED驱动程序的区别与减少难度梯度,在写LED驱动程序之前很有必要先看一下LED的裸板程序是怎样设计的. 1.LE

《嵌入式Linux开发实用教程》——1.4 映像文件的生成和运行

1.4 映像文件的生成和运行 德国罕见的科学大师莱布尼茨,在他的手迹里留下这么一句话:"1与0,一切数字的神奇渊源.这是造物的秘密美妙的典范,因为,一切无非都来自上帝."二进制0和1两个简单的数字,构造了神奇的计算机世界,对人类的生产活动和社会活动产生了极其重要的影响,并以强大的生命力飞速发展.在嵌入式系统移植过程中,不管文件数量多么庞大,经过编译工具的层层处理后,最终生成一个可以加载到存储器内执行的二进制映像文件(.bin).本节内容将会探讨映像文件的生成过程,以及它在存储设备的不同