Linux基础——Makefile编写优化(二)

简介: 本文主要是为了讲述如何能够编写更专业的Makefile,而不是仅仅通过一项简单的依赖规则生成几个固定目标。(重点参考李云老师编写的《专业嵌入式软件开发——全名走向高质量高效编程》一书,该书是我在图书馆偶然看到,发现原来长期一直使用的Makefile中居然还有这么多需要注意的细节,所以特地整理记录下来。)

addprefix函数

addprefix函数被用于给名字列表_names中的每一个名字增加前缀_prefix,并将增加了前缀的名字列表返回。其形式:


$(addprefix _prefix, _names)


如下示例使用:


.PHONY : all
without_dir = foo.c bar.c main.o
with_dir := $(addprefix objs/, $(without_dir))
all:
  @echo $(with_dir)

执行结果如下:

20200119142526364.png

addsuffix函数

addsuffix函数被用于给名字列表_names中的每个名字增加后缀_suffix,并将增加了后缀_suffix的名字列表返回。其形式如下:

$(addsuffix _suffix, _names)

使用示例:

.PHONY : all
without_suffix = foo bar main
with_suffix := $(addsuffix .c, $(without_suffix))
all:
  @echo $(with_suffix)

执行如下:

20200119142915210.png

eval函数

eval函数的存在使得Makefile具有动态语言的特征。eval函数使得make将再一次解析_text语句,eval函数返回值为空字符串,形式如下:


$(eval _text)


如下示例说明了它的用法:


.PHONY : all
sources = foo.c bar.c baz.s ugh.h
$(eval sources := $(filter %.c %.s, $(sources)))
all:
  @echo $(sources)

执行结果如下:

20200119143431172.png

filter函数

filter函数被用于从一个 名字列表 _text中根据模式_pattern得到满足需要的名字列表并返回。其形式:

$(filter _pattern, _text)

如下示例使用方式:


.PHONY : all
sources = foo.c bar.c baz.s ugh.h
sources := $(filter %.c %.s, $(sources))
all:
  @echo $(sources)

执行结果如下:

20200119143838109.png

filter-out函数

filter-out函数被用于从名字列表_text中根据模式_pattern滤除一部分名字,并将滤除后的列表返回。其形式如下:


$(filter-out _pattern, _text)


如下示例说明了它的用法:


.PHONY : all
objects = main1.o foo.o main2.o bar.o
result = $(filter-out main%.o, $(objects))
all:
  @echo $(result)


执行结果如下:

20200119144211513.png

notdir函数

notdir函数被用来从路径_names中抽取文件名,并将文件名返回。其形式如下:


$(notdir _names)

如下示例说明了它的用法:

.PHONY : all
file_name := $(notdir code/foo/src/foo.c code/bar/src/bar.c)
all:
  @echo $(file_name)

执行结果:

20200119144546832.png

patsubst函数

patsubst函数被用来将名字列表_text中符合_pattern模式的名字替换为_replacement,并将替换后的名字列表返回。其使用形式如下:


$(patsubst _pattern, _replacement, _text)


如下示例说明了它的使用方法,这里采用patsubst函数进行字符串替换,将所有.c结尾的名字替换为.o结尾的文件名。


.PHONY : all
mixed = foo.c bar.c main.o
objects := $(patsubst %.c, %.o, $(mixed)
all:
  @echo $(objects)


执行结果如下:

20200119145037315.png

#### realpath函数

realpath函数被用于获取_names所对应的真实路径名。其形式如下:


$(realpath _names)

如下示例说明了它的用法:


.PHONY : all
ROOT := $(realpath ./..)
all:
  @echo $(ROOT)


执行结果如下:

20200119145339812.png


strip函数

如果希望清除名字列表中的多余空格,strip函数就是最好的选择。strip函数将_string中的多余空格去除后返回。形式如下:


$(strip _string)

示例使用如下:

.PHONY : all
original = foo.c bar.c
stripped := $(strip $(original))
all:
  @echo "original = $(original)"
  @echo "stripped = $(stripped)"


执行后结果如下:

20200119145707234.png


wildcard函数

wildcard是通配符函数,通过它可以得到当前工作目录中满足_pattern模式的文件或目录名列表。形式如下:


$(wildcard _pattern)


如下示例说明了Makefile通过使用wildcard函数得到所以C源文件的名字列表。


.PHONY : all
SRCS = $(wildcard *.c)
all:
  @echo $(SRCS)


执行结果如下:


2020011915004416.png

提高编译环境的实用性

之前的Makefile示例也只是为了介绍Makefile中可以用到的各种规则、变量、函数的使用方法。接下来,我们来构建一个略微复杂的虚拟项目——complicated项目,该项目中用到源代码如下:


  • foo.h
#ifndef _FOO_H
#define _FOO_H
void foo();
#endif


  • foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
  printf("this is foo()!\n");
}


  • main.c
#include "foo.h"
int main()
{
  foo();
  return 0;
}


让编译环境更加有序

大多的软件项目都会通过合理地设计目录结构来提高它的可维护性。在编译一个项目时会产生大量的中间文件,如果中间文件与项目的源程序文件直接混放在一起,就会显得混乱不堪而不利于维护。

这里,我们通过使用目录来使得编译环境更加有序。目录的引入并不是一步到位的,我们还将在后面的项目再进一步探讨这个问题。首先,在编写Makefile之前,需要先了解对目录结构的需求,包括:


  • 将所有的目录文件放入objs子目录中。
  • 将最终生成的可执行程序放入exes子目录中。

目录的自动创建与删除

在编译目录职期间按,需要将存放生成文件的目录准备好。目录可以在项目编译之前通过手工去创建,但是我们更喜欢在编译过程中自动生成的方式。

要实现在编译过程中自动创建目录,需要记住一点:目录也是一个目标。具有自动创建目录的Makefile和其运行结果如图所示:


.PHONY: all
MKDIR = mkdir
DIRS = objs exes
all: $(DIRS)
$(DIRS):
  $(MKDIR) $@

20200119152419365.png

接下来增加一个clean目标用于清除编译目标目录,如下:


.PHONY: all
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
DIRS = objs exes
all: $(DIRS)
$(DIRS):
  $(MKDIR) $@
clean:
  $(RM) $(RMFLAGS) $(DIRS)

20200119155203817.png

通过目录管理文件

为了将项目编译时所创建的文件分别放入objs和exes目录中,需要用到Makefile中的一个函数——addprefix。如下修改:


.PHONY: all
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
all: $(DIRS) $(EXE)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(OBJS)
  $(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c
  $(CC) -o $@ -c $^
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)

20200119155923378.png

上面的修改主要有三种变化:


  • 通过运用addprefix函数,为每一个生成的目标文件加上“objs/”前缀,以便生成的文件放入objs目录中。
  • 在构建目标文件的规则中为目标名字机上“objs/”前缀,即增加“$(*DIR_OBJS)/”前缀。
  • 在clean规则的命令中增加对$(EXE)目标的删除。

提升依赖关系管理

现在假设对项目已经进行了一次成功的编译,这一点很重要,否则看不到现有的Makefile存在的问题。接着,将foo.h文件内容进行修改,但是不修改foo.c文件。理论上,头文件和源文件函数声明定义不同应该会编译出错。

20200119160548503.png

当修改后执行make后,结果居然是告诉我们没有什么事可做,此时如果执行make clean后重新make则发现会报错:

20200119160711472.png

那么是为什么make不能发现foo.h已经更改而进行重新编译呢。从Makefile文件中的内容能看出来它并不知道有foo.h文件的存在,而之所以编译会用到foo.h是因为foo.c和main.c中包含了头文件。如下:

20200119160917229.png

为了修复这一点,最简单直接的办法就是将foo.h文件通过依赖关系树纳入make的视野中,改动后的Makefile如下所示:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
all: $(DIRS) $(EXE)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(OBJS)
  $(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c foo.h
  $(CC) -o $@ -c $<
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)


执行结果如下:

20200119161409750.png

执行后正常,但是如果将每一个头文件都写入到Makefile的相应规则中,那必然很不妥当。


相关文章
|
4天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
19 9
|
1天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
14 4
|
4天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
6天前
|
机器学习/深度学习 负载均衡 算法
深入探索Linux内核调度机制的优化策略###
本文旨在为读者揭开Linux操作系统中至关重要的一环——CPU调度机制的神秘面纱。通过深入浅出地解析其工作原理,并探讨一系列创新优化策略,本文不仅增强了技术爱好者的理论知识,更为系统管理员和软件开发者提供了实用的性能调优指南,旨在促进系统的高效运行与资源利用最大化。 ###
|
5天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####
|
6天前
|
安全 网络协议 Linux
Linux操作系统的内核升级与优化策略####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统内核升级的重要性,并详细阐述了一系列优化策略,旨在帮助系统管理员和高级用户提升系统的稳定性、安全性和性能。通过实际案例分析,我们展示了如何安全有效地进行内核升级,以及如何利用调优技术充分发挥Linux系统的潜力。 ####
24 1
|
9天前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
25天前
|
监控 Linux 云计算
Linux操作系统在云计算环境中的实践与优化###
【10月更文挑战第16天】 本文探讨了Linux操作系统在云计算环境中的应用实践,重点分析了其在稳定性、安全性和高效性方面的优势。通过具体案例,阐述了Linux如何支持虚拟化技术、实现资源高效分配以及与其他开源技术的无缝集成。文章还提供了针对Linux系统在云计算中的优化建议,包括内核参数调整、文件系统选择和性能监控工具的应用,旨在帮助读者更好地理解和应用Linux于云计算场景。 ###
31 3
|
1月前
|
Linux C++
Linux c/c++之makefile的基础使用
Linux下C/C++项目中makefile的基本使用,包括基础、进阶和高级用法,以及如何创建和使用makefile来自动化编译过程。
15 0
Linux c/c++之makefile的基础使用
|
2月前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
46 0