• [RTOS 学习记录] 工程管理工具make及makefile


    [RTOS 学习记录] 工程管理工具make及makefile

    image-20240421150217202

    这篇文章是我阅读《嵌入式实时操作系统μCOS-II原理及应用》后的读书笔记,记录目的是为了个人后续回顾复习使用。
    前置内容:

    开发工具 Borland C/C++ 3.1 精简版

    1 make 工具

    一个开发平台提供给我们,用于管理工程或项目的实用程序,它可以按照我们用户编写的makefile脚本文件对工程项目进行管理。

    2 makefile 的内容结构

    makefile是一个脚本文件,文件内容中有许多我们在命令行中常常用到的各种命令。

    makefile程序段的格式如下:

    程序段的标号(target): 关联程序段1的标号 关联程序段2的标号 ...
    	命令集
    关联程序段1的标号:
    	命令集
    关联程序段2的标号:
    	命令集
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:命令集中的所有命令行必须缩进一个tab键。

    一个makefile文件有若干个程序段,程序段的开头必须有一个target进行标注,区分各个程序段。不同的程序段之间可以进行关联,此时在target后面以空格为界罗列相关联程序段的target。每个程序段有一组实现工程项目管理的命令集。

    3 程序段标号的作用

    标号可以看作是对应程序段的名称,我们可以在make命令的后面使用标号来指定需要执行的程序段。

    3.1 makefile 示例代码

    按照makefile的内容格式编写一个makefile脚本文件,命名为makefile(makefile的默认名称):

    mkdir1:
        md dir1
    mkdir2:
        md dir2
    rmdir:
        rd dir1
        rd dir2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.2 代码说明

    上面编写的makefile文件中一共有3个程序段:mkdir1、mkdir2 和 rmdir。作用分别是:

    1. mkdir1——在当前目录下创建一个名为dir1的文件夹
    2. mkdir2——在当前目录下创建一个名为dir2的文件夹
    3. rmdir——删除前面两个步骤创建的两个文件夹dir1和dir2

    3.3 第一次运行

    在命令行窗口中使用 cd EXP2_3 进入此次示例makefile文件所在的目录中,输入 make 命令并且回车执行。可以看到,执行完成后在当前目录新建了一个名为dir1的文件夹,如下图所示:

    image-20240421154600345

    根据执行结果,我们知道了make执行了makefile中的第一个程序段mkdir1,其余两个程序段mkdir2rmdir都没有被执行。

    3.4 第二次运行

    输入命令 make mkdir2 并且回车执行,可以看到,执行完毕后当前目录下又新建了一个名为dir2的文件夹,如下图所示:

    image-20240421155052461

    3.5 第三次运行

    输入命令 make rmdir 并且回车执行,可以看到,执行后dir1和dir2这两个文件夹都被删除了,如下图所示:

    image-20240421155302943

    3.6 结论

    当使用 make 命令时,makefile的第一个程序段会被执行,即makefile的首段程序段是make.exe的默认执行程序段,makefile的其他程序段需要执行时必须在make命令后面显式地指定标号。

    4 makefile 实现编译、链接工作

    由于makefile的程序段中的命令集中可以使用一切命令行命令,所以我们可以把源文件的编译和链接工作步骤编写到makefile中,然后通过执行makefile脚本文件“自动的”完成编译、链接工作。

    4.1 示例代码

    一个具有3个源文件应用程序的示例如下:

    头文件 printA.h

    #ifndef _PRINTA_H_
    #define _PRINTA_H_
    
    extern const char *msgA;
    
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    源文件 printA.c

    #include "printA.h"
    
    const char *msgA = "AAAAA";
    
    
    • 1
    • 2
    • 3
    • 4

    头文件 printB.h

    #ifndef _PRINTB_H_
    #define _PRINTB_H_
    
    extern const char *msgB;
    
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    源文件 printB.c

    #include "printB.h"
    
    const char *msgB = "BBBBB";
    
    
    • 1
    • 2
    • 3
    • 4

    源文件 test.c

    #include 
    #include "printA.h"
    #include "printB.h"
    
    int main(void)
    {
        unsigned char i = 0;
        
        for (i=0; i<5; i++)
        {
            printf("%s\n", msgA);
            printf("   %s\n", msgB);
        }
        
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    链接文件 TESTLINK

    C:\BC\LIB\C0L.OBJ +
    PRINTA.OBJ +
    PRINTB.OBJ +
    TEST.OBJ
    TEST
    TEST
    C:\BC\LIB\CL.LIB
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来就编写一个具有4个程序段的makefile脚本文件,实现源文件的编译、中间目标文件的链接,最终生成可执行文件TEST.EXE。

    make脚本文件 makefile

    ##############################################
    #             创建可执行文件(exe)
    TEST.EXE:
        TLINK   @TESTLINK
        
    ##############################################
    #           创建各个目标文件(obj)
    PRINTA.OBJ:
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB PRINTA.C
    PRINTB.OBJ:
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB PRINTB.C
    TEST.OBJ:
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB TEST.C
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.2 makefile 代码说明

    为了增强可读性,此次示例的makefile代码中使用了文件名称作为程序段的标号,而且该文件正是对应程序段的命令集中的命令所要实现的目标结果。

    第2~4个程序段分别完成对3个源文件printA.c、printB.c和test.c的编译,然后得到3个中间目标文件printA.obj、printB.obj和test.obj。第1个程序段即首段程序段完成各个中间目标文件的链接,最终得到可执行文件TEST.EXE。

    4.3 makefile 的执行

    分别依次使用 make PRINTA.OBJmake PRINTB.OBJmake TEST.OBJmake 完成示例应用程序的编译、链接,如下图所示:

    image-20240421165535513

    image-20240421165621705

    image-20240421165657126

    image-20240421165732331

    运行可执行程序 TEST.EXE,可以看到屏幕上重复5次打印了字符串,如下图所示:

    image-20240421165907435

    5 程序段标号的目标作用

    前面的示例makefile文件中,我们使用了文件名:PRINTA.OBJPRINTB.OBJTEST.OBJ 作为它们各自程序段的标号,而该文件名对应的文件正是它的程序段命令集所要生成的文件,因此我们把满足这种关系的程序段标号又称作程序段的目标,例如:PRINTA.OBJ 是它对应程序段的目标,PRINTB.OBJ 是它对应程序段的目标,TEST.OBJ 是它对应程序段的目标等等。

    我们前面已经提到了[makefile允许关联程序段](#makefile 的内容结构),即makefile允许我们把程序段编写成如下形式:

    目标(标号): 生成目标所需的文件名(依赖文件,简称“依赖”)
    	命令集
    
    • 1
    • 2

    为了强调程序段目标与其所需文件之间的关系,我们把生成目标所需的文件称作依赖文件,简称依赖

    因此,我们可以把一个工程的编译、链接工作所需的多个程序段关联起来,从而仅需要执行一次 make 命令即可完成所有的编译和链接工作。

    对于上述示例的makefile来说,如果要把生成TEST.EXE文件的程序段和生成其依赖文件的程序段关联起来,那么按照上述格式,makefile的第一个程序段就为:

    TEST.EXE: PRINTA.OBJ PRINTB.OBJ TEST.OBJ
    	TLINK @TESTLINK
    
    • 1
    • 2

    该程序段的含义是:本程序段的目标(标号)为 TEST.EXE,该目标(标号)需要由 PRINTA.OBJPRINTB.OBJTEST.OBJ 三个文件来生成,其命令则为 TLINK @TESTLINK

    如果目标所依赖的文件都存在,满足生成目标所需要的条件,则连接命令 TLINK 被执行,否则程序会以 PRINTA.OBJPRINTB.OBJTEST.OBJ 为转移目标转向以它们为标号的程序段。也就是说,目标:依赖文件名 的这种格式是一种多分支条件转移语句。当生成目标的条件不满足(依赖文件不存在)时,程序的执行将要发生转移,其转移目标就是以依赖文件名为标号或目标的程序段。

    实际上,make工具在执行makefile的各个程序段时,首先会检查目标(target)文件是否已经存在,如果存在,则会进一步检查该目标所依赖文件的时间戳(文件属性中的“创建时间”、“修改时间”等时间信息),只有当依赖文件比现有目标新时,其命令集才会被执行。其目的就是:尽量不做不必要的重复编译工作。

    5.1 makefile 示例代码

    为了格式上的整齐,凡是以目标为标号的程序段都要写上目标的依赖。

    make 文件 makefile

    ##############################################
    #             创建可执行文件(exe)
    TEST.EXE: PRINTA.OBJ PRINTB.OBJ TEST.OBJ
        TLINK   @TESTLINK
    ##############################################
    #           创建各个目标文件(obj)
    PRINTA.OBJ: PRINTA.C PRINTA.H
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB PRINTA.C
    PRINTB.OBJ: PRINTB.C PRINTB.H
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB PRINTB.C
    TEST.OBJ: TEST.C PRINTA.H PRINTB.H
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB TEST.C
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.2 伪目标

    由上可知,makefile的target有目标和标号两种作用:当它是文件名时,它既是标号也是目标;而当它只是一个标识时,它就是标号。听起来很混乱,所以为了明确起见,人们把makefile中的target全部叫做目标,把那种仅起标号作用的目标则叫做“伪目标”。
    在makefile中,伪目标所对应的程序段是一个不与其他程序段相关联的程序段,所以在需要执行它们时,必须在make命令中显式地使用其标号,除非它是makefile的第一个程序段(几乎没人这样做)。它们通常被用来完成一些创建目录、删除目录、复制文件、移动文件及删除文件等项目管理任务。

    例如,可以为示例makefile添加一个标号为 CLEAN 的伪目标代码段,该段的任务就是为了用户目录的整洁,在已生成了最终可执行文件后,删除那些中间目标文件 PRINTA.OBJPRINTB.OBJTEST.OBJ
    修改后的makefile如下:

    ##############################################
    #             创建可执行文件(exe)
    TEST.EXE: PRINTA.OBJ PRINTB.OBJ TEST.OBJ
        TLINK   @TESTLINK
    ##############################################
    #           创建各个目标文件(obj)
    PRINTA.OBJ: PRINTA.C PRINTA.H
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB PRINTA.C
    PRINTB.OBJ: PRINTB.C PRINTB.H
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB PRINTB.C
    TEST.OBJ: TEST.C PRINTA.H PRINTB.H
        BCC -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB TEST.C
    CLEAN:
        DEL PRINTA.OBJ
        DEL PRINTB.OBJ
        DEL TEST.OBJ
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.2.1 第一次运行

    使用 make 命令执行 makefile 的第一个程序段,生成最终可执行文件 TEST.EXE,如下图所示:

    image-20240422091935127

    image-20240422092035300

    执行 make clean 表示显示地使用 CLEAN 参数执行 makefile 文件中的 CLEAN 程序段,将中间目标文件删除,如下图所示:

    image-20240422092319854

    image-20240422092334231

    6 makefile 文件的命名

    makefile是make文件的默认名称,如果用户不喜欢该名称,则完全可以自行对其进行命名(包括扩展名),但在make命令中要使用参数f,即:

    make -f 文件名
    
    • 1

    7 makefile 中的变量

    通常,在一个makefile中会有很多经常要重复使用的元素,例如示例makefile中的编译命令 BCC、编译命令中的参数 -c -ml -IC:\BC\INCLUDE -LC:\BC\LIB 等等。显然,用一些比较简洁且语义清楚的符号变量来表示它们更好,因此makefile允许人们定义变量。

    变量格式:

    变量名 = 变量的值
    
    • 1

    引用变量格式:

    $(变量名)
    
    • 1

    使用变量改写示例makefile后,代码如下:

    其中,前面带有符号“#”的为注释行;如果依赖文件表示行过长,也可以反斜杠“\”为换行符分行书写。

    ##############################################
    #             makefile
    ##############################################
    #        用变量来表示所使用的开发工具
    BORLAND = C:\BC
    CC = $(BORLAND)\BIN\BCC
    LINK = $(BORLAND)\BIN\TLINK
    ##############################################
    #               编译选项说明
    #
    # -l    生成80286实模式代码
    # -c    编译为.obj文件
    # -I    指示包含头文件所在路径
    # -k    采用标准栈帧
    # -L    指示库文件所在路径
    # -ml   Large memory内存模式
    # -n    指示生成目标文件的位置
    ##############################################
    #             C编译选项变量
    C_FLAGS = -c -ml -l -n.\ -k -I$(BORLAND)\INCLUDE -L$(BORLAND)\LIB
    ##############################################
    #             链接选项变量
    LINK_FLAGS = 
    ##############################################
    #             创建可执行文件(exe)
    TEST.EXE:       \
        PRINTA.OBJ      \
        PRINTB.OBJ      \
        TEST.OBJ
        $(LINK) $(LINK_FLAGS) @TESTLINK
    ##############################################
    #           创建各个目标文件(obj)
    PRINTA.OBJ:         \
        PRINTA.c        \
        PRINTA.h
        $(CC) $(C_FLAGS) PRINTA.c
    PRINTB.OBJ:         \
        PRINTB.C        \
        PRINTB.H
        $(CC) $(C_FLAGS) PRINTB.C
    TEST.OBJ:       \
        TEST.C      \
        PRINTA.H        \
        PRINTB.H
        $(CC) $(C_FLAGS) TEST.C
    # 以下为伪目标代码段
    CLEAN:
        DEL PRINTA.OBJ
        DEL PRINTB.OBJ
        DEL TEST.OBJ
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    运行结果如下图:

    make

    image-20240422094447941

    image-20240422094550617

    make clean

    image-20240422094621696

    image-20240422094647703

    可以看到,使用变量后,make执行时会自动将makefile中引用的变量替换成变量的值。

    参考资料:

    《嵌入式实时操作系统μCOS-II原理及应用》

  • 相关阅读:
    【TrueType】Converting Outlines to the TrueType Format(将轮廓转换为TrueType格式)
    vscode中如何将cmd设置为默认终端
    开放式耳机百元价位怎么选、公认最好的百元开放式耳机
    kubernetes 亲和、反亲和、污点、容忍
    Kolmogorov-Smirnov正态性检验
    element UI DatePicker 日期选择器 点击时间点可选限制范围前后十五天
    11.7加减计数器,可置位~,数字钟分秒,串转并,串累加转并,24位串并128,流水乘法器,一些乘法器
    动力环境监控是什么?动力环境监控系统包括哪些设备
    R语言提交后台任务Rstudio\nohup
    刷题学习记录
  • 原文地址:https://blog.csdn.net/CardistAlive/article/details/138062336