• Linux 程序开发流程 / 基本开发工具 / Vim / GCC工具链 / Make 工具 / Makefile 模板


    编辑整理 by Staok

    本文部分内容摘自 “100ask imx6ull” 开发板的配套资料(如 百问网的《嵌入式Linux应用开发完全手册》,在 百问网 imx6ull pro 开发板 页面 中的《2.1 100ASK_IMX6ULL_PRO:开发板资料》或《2.2 全系列Linux教程:在线视频与配套资料》里面可以下载到),还有参考 菜鸟教程、C语言中文网、红联的等等等等,比较广泛,侵删。进行了精髓提取,方便日后查阅。过于基础的内容不会在此提及。如有错误恭谢指出!

    注:在 Github 上的原版文章日后可能会更新,在其它位置发的不会跟进。文章的 Gitee 仓库地址,Gitee 访问更流畅

    Linux 一般开发步骤

    p.s 本应放在最后,刻意写在前头。

    Bootloader、Linux 内核、根文件系统、APP 等等软件,需要在 Ubuntu 中编译;但是阅读、修改这些源码时,在 Windows 下会比较方便。

    所以工作日常开发流程如下:

    PC 端,使用 Source insight 编、改源码 —>传—> Ubuntu 端(通过 SSH 打开),对修改好的源码进行编译、制作 —>下载—> 嵌入式板端,在 Linux 板子上运行、测试。

    分步来说就是:

    1. 在 Windows 上(Source insight)阅读、研究、修改,修改后,上传(推荐 FileZilla)到 Ubuntu;

    2. 在 Ubuntu 上编译、制作(推荐使用 MobaXterm 通过 SSH 远程登陆 Ubuntu);

    3. 把制作好的可执行程序下载到 嵌入式开发板 上运行、测试。

    u-boot、Linux内核,在 Windows 和 Ubuntu 各存一份。根文件系统使用 buildroot (或 Busybox 或 Yocto)制作,它无需放在 Windows 上。

    Linux 下的开发

    • 代码编辑:Vim、gedit(ubuntu 下)。

    • 编译工具:gcc,make,cmake(生成 makefile,网搜教程,本篇不涉及)。

    • 项目管理:git(网搜教程,本篇不涉及)。

    详细资料-重要

    关于 Linux Shell 命令、GCC、Makefile/CMake、GDB/GDBServer、Vim 等工具的详细使用教程和使用经验,

    可见 本系类文章对应 仓库 Github 仓库Gitee 仓库 中:

    • 【1 GCC & GDB & GDBServer】

    • 【1 Linux 命令速查 & Shell & Vim】

    • 【1 Makefile & CMake 教程及模板】

    这几个文件夹内!

    Vim 编辑器

    功能:打开、新建和保存文件;文本编辑;多行、列间复制、粘贴和删除;查找和替换。

    意义:开发中,尤其对于大型项目并不常用,但是在需要临时修改、现场调试和没有 GUI 形式的编辑器等等的时候,可以快速进行一些简单文本编辑。

    Linux vi/vim | 菜鸟教程 (runoob.com)

    一图以蔽之

    • 一般模式/普通模式:用于按各种快捷键进行光标移动、复制、粘贴和删除等。

    • 编辑模式/插入模式:用于敲字符输入/编辑。

    • 命令行模式:用于输入保存、退出、查找和替换 等 控制命令,在 一般模式 打一个冒号再输入命令。

    注:当不知道处于何种模式时,按 ESC 键返回到 一般模式。可以在 Ubuntu 中安装中文输入法。

    更多可参考 vim命令大全 - 知乎 (zhihu.com)

    在 一般模式 的快捷键
    • i/a,在光标处的前/后进入 编辑模式。dw,删除一个单词;dd,删除光标所在行(d:delete) 。

    • 单击 o (字母 o)键,在当前光标所在行的下方新建一行,并进入编辑模式。

    • 单击 0(数字零) 光标移至当前行行首;$,光标移至当前行行末;%,在括号()、[]、{}间移动。

    • gg,跳到第一行,(xgg 就是跳到第x行的行首);G,跳到文件结尾。

    • ctrl + u/d 进行 半屏的前后滚动;ctrl + f/b 进行 全屏的 上下翻页。

    • 使用 v 进入可视模式,移动光标来 选定文本块内容;用 y 复制选定块到缓冲区,用 d 剪切选定块到缓冲区,用 p 粘贴缓冲区中的内容。

    • u,撤销上一步操作;ctrl + r,恢复,回退到前一个命令。

    • 针对 Ubuntu 界面来说,ctrl + "-" ,减小字号;ctrl + shift + "+",增大字号。

    在 命令行模式 的命令
    • 保存,:w;退出,:q;强制执行,在命令后加!;前面的命令可以组合。文件另存为,:w 。重命名当前文件,:f

    • 查找,:/pattern 从光标开始处向文件尾搜索字符串 "pattern",后按 n (在同一个方向重复上一次搜索命令)或 N (在反方向重复上一次搜索命令);从当前光标位置开始搜索,若光标在文件开头,则为全文搜索。

    • 替换,:%s/p1/p2/g 将文件中所有的 p1 均用 p2 替换;:%s/p1/p2/gc 替换时需要确认。释义,“ s“ 全称: substitute 替换;“ g“ 全称: global 全局;“ c“ 全称: confirm, 确认。

    • 纵向分屏 来 新打开一个文件 :sp 或 横向分屏 :vsp ,此时会同屏新增一个窗口;切换这多个窗口的方法(循环移动):ctrl + w,w(先按 ctrl + w,再按键 w),在多文件编程时,切换不同的窗口很实用。让鼠标可以在多个屏幕间切换::set mouse=a;在某个窗口输入:q,为退出此窗口。

    • 跳转到第 n 行,:n。打印当前文件名和行数,:f

    • 从 a 到 b 行的内容写入 filename 文件,:a,bw

    • 在 Vim 命令行执行 Shell 命令,:!+ shell 命令

    一日,一人,代码前坐禅,贤者模式,顿悟,曰:整个键盘,都是 Vim 的快捷键。

    恢复文件

    vi 在编辑某一个文件时,会生成一个临时文件,这个文件以 . 开头并以 .swp 结尾。正常退出该文件自动删除,如果意外退出例如忽然断电,该文件不会删除,我们在下次编辑时可以选择一下命令处理:

    • O 只读打开,不改变文件内容。

    • E 继续编辑文件,不恢复 .swp 文件保存的内容。

    • R 将恢复上次编辑以后未保存文件内容。

    • Q 退出 vi。

    • D 删除 .swp 文件。

    • 使用 vi -r 来恢复 filename 这个文件上次关闭前未保存的内容。

    vim 命令行中的 选项
    • -d,Diff 模式 (同 "vimdiff", 可迅速比较两文件不同处)。

    • -R,只读模式 (同 "view")。

    • -b,二进制模式。

    • -r ,恢复上次崩溃的文件 filename (Recover crashed session)。

    vim 编辑器的配置

    命令行键入vim -version可以看到几个 vim 的配置文件在哪,包括 系统级配置文件(对所有用户有效)system vimrc file 一般在 /etc/vim/vimrc,用户级配置文件(对当前用户有效)user vimrc file 一般在 ~/.vimrc

    部分摘自 100ask,部分摘自 《嵌入式C语言的自我修养》
    vim ~/.vimrc
    在其中(选择性)加入如下内容:
    ​
    " color scheme
    colorscheme molokai
    ​
    " disply incomplete commands
    set showcmd
    ​
    " set fileencodings 
    set fileencodings=ucs-bom,utf-8,cp936,gb2312,gb18030,big5
    set background=dark
    set encoding=utf-8
    set fenc=utf-8
    set smartindent
    set autoindent
    set cul
    set linespace=2
    set showmatch
    set lines=47 columns=90
    ​
    " font and size
    " set guifont=Andale Mono:h14
    " set guifont=Monaco:hll
    set guifont=Menlo:h14
    ​
    " Softtabs, 4 spaces
    " 编辑时 backspace 键设置为2个空格
    set backspace=2
    " 编辑时 tab 键设置为4个空格
    set tabstop=4
    " 设置自动对齐为4个空格
    set shiftwidth=4
    set shiftround
    set softtabstop=4
    set expandtab
    set smarttab
    ​
    " Highlight current line
    au Winleave * set nocursorline
    au WinEnter * set cursorline
    set cursorline
    ​
    " 底部显示光标所在行和列
    set ruler
    ​
    " 显示行号
    set number
    " 关闭行号为 set nonumber,即 "option" 前面加 "un" 前缀表失能此功能
    ​
    " 搜索时不区分大小写
    set ignorecase
    ​
    " 搜索时高亮显示
    set hlsearch
    ​
    " 关闭兼容功能
    set nocompatible

    vim配置通过按键映射实现括号补全:

    在 Vim 官网还有 很多 vim 扩展功能 .vim 文件 提供下载,放到~/.vim/plugin目录,再在~/.vimrc中对其进行配置即可使用。

    更多 Vim 配置和插件可参考 Vim编辑器_~青萍之末~的博客-CSDN博客

    GCC 命令详情

    编译流程

    gcc 编译器可以通过 apt-get 安装 或者 在 GNU 官网下载。

    要使用 gcc 等编译器或交叉编译器,应先把编译器的可执行文件放在某个目录,然后给系统环境变量加此路径(Win平台为添加环境变量,Linux 为使用 export 命令,或者在 bashrc 文件中添加,具体步骤在 “换源 和 添加系统变量” 一节 里面的 “添加系统变量” 处),对于 Make 工具同理。可以在 命令行运行 gcc -v 即可使用。

    C/C++ 程序文件的编译过程图示:

    gcc 基本命令
    • 预处理(-E):gcc -E main.c -o main.i,仅预处理。(通过重定向)输出到指定文件:gcc -E main.c > main.i

    • 编译(-S):gcc -S main.c -o main.s,编译到汇编文件。

    • 汇编(-c):gcc -c main.c -o main.o,只生成目标文件(机器码),不进行链接。

    完成整个编译(-o filename)

    • 单文件:gcc main.c -o main,编译输出最终可执行文件,-o 后面要紧跟 输出文件名。

    • 多文件:gcc main.c sub.c add.c -o ouput,其中 main.c 里面 #include 了 sub.h 和 add.h。

    • 输出所有警告:加上 -W-Wall 选项。

    文件编码指定

    • 程序文件在保存的时候就选好编码(有 ANSI、GB2312、UNICODE,和很常用的并推荐的 UTF-8),使用 记事本 或者 notepad 可以选择和转换。

    • 使用 gcc 编译器,器对于 程序文件 和 编译出的 二进制可执行程序 都默认为 UTF-8 编码。

    • 若 程序文件 的编码 不为 UTF-8 编码,则应该指定:-finput-charset=GB2312 等。

    • 对于编译出来的可执行程序,可以指定它里面的字符是以什么方式编码:-fexec-charset=GB2312 等。注意,是指定/告知 而 不是转换的意思, gcc 不能转换编码。

    • 例子:gcc -finput-charset=GB2312 -fexec-charset=UTF-8 -o test_charset_ansi test_charset_ansi.c。告知 编译器 .c 文件为 GB2312 编码,编译出的程序应为 UTF-8 编码。

    • 在代码中使用汉字这类非 ASCII 码 字符 时,要特别留意编码格式。

    头文件选项

    • 对于 #include <...> 的头文件(一般都是 标准库的头文件,比如 stdio.h、stdlib.h、string.h 等) 编译器在编译时会去 gcc 默认的路径中(编译器目录里面的 include 文件夹里)寻找头文件。可以通过 echo 'main(){}'| arm-linux-gnueabihf-gcc -E -v - 命令来 列出头文件目录、库目录(LIBRARY_PATH)。

    • 对于 #include "..." 的头文件去 -I 这个选项所指定的目录(dirname 目录)中去找(如果不加 -I 选项则默认搜 当前目录),-I 即是将 dirname 目录加入到头文件搜索目录列表中,用户引用除了 上面 的标准库头文件 而是 自己的头文件,就用这种方式。例如 main.c 目录中有 inc 文件夹,里面有 test.h 文件,并且 main.c 中通过 #include "test.h" 调用了 test.h 文件,则命令为:gcc main.c -I inc -o main

    • 编译时寻找库文件:

      • 默认的系统目录:就是交叉编译工具链里的某个 lib 目录。

      • 自己指定添加库文件搜索目录:链接时(-L dirname)。

      • 自己指定添加某一个具体的库文件:加载库名选项(-l name),比如想链接 libabc.so,那链接时加上 -labc

      • 静态库选项(-static)等选型 略。

    • 运行时寻找库文件:(程序运行时不需要再加载头文件,因为编译时已经编译进去了)

      • 系统目录:就是板子上的 /lib、/usr/lib 目录。

      • 自己指定:用环境变量 LD_LIBRARY_PATH 指定,比如 export LD_LIBRARY_PATH=/xxx_dir

    代码优化选项

    • -O 或 -O1:基本优化,使代码执行的更快。

    • -O2 或 -O3:产生尽可能小和快的代码。如无特殊要求,不建议使用 O2 以上的优化。

    • -Os:生成最小的可执行文件,适合用于嵌入式软件

    调试模式选项

    • 输出带调试信息,可以用于 GDB 单步调试来 debug:加上-g选项。

    • 产生能被 GDB 调试器使用的调试信息:gcc main.c -g -o main

    • GDB 的命令行调试指令详情略(包括运行、单步执行、加删查断点、打印变量等命令)。

    下面引用 embedded-notes/linux.md at master · xiaowenxia/embedded-notes (github.com)

    gcc工具链

    命令描述
    Binutils由汇编器(as)产生的目标代码(*.o)是不能直接在computer上运行的,它必须经过链接器(ld)的处理才能生成可执行代码。
    add2line将地址转换成文件名或行号对,以便调试程序
    ar从文件中创建、修改、扩展文件
    gasp汇编宏处理器
    nm从目标文件列举所有变量
    objcopy使用GNU BSD库把目标文件的内容从一种文件格式复制到另一种格式的目标文件中。
    objdump显示目标文件信息可发编译二进制文件,也可以对对象文件进行反汇编,并查看机器代码。
    readelf显示elf文件信息
    ranlib生成索引以加快对归档文件的访问,并将其保存到这个归档文件中。
    size列出目标模块或文件的代码尺寸。
    strings打印可打印的目标代码符号(至少4个字符)
    strip放弃所有符号连接,一般应用程序最终都要strip处理
    C++filt链接器ld通过该命令可过滤C++符号和JAVA符号,防止重载函数冲突。
    gprof显示程序调用段的各种数据

    ld 交叉链接器

    将多个编译后产生的过程文件连接为一个最终的可执行文件。

    ld [options] 链接器脚本 -o 文件名.elf

    readelf 交叉ELF文件查看器

    用来查看一个可执行文件的相关信息

    可以查看elf文件的运行架构,大小端等信息:

    readelf -a 文件名.elf

    显示程序需要的动态链接库:

    readelf -d 文件名.elf

    objdump 交叉反汇编器

    将一个可执行文件转换为汇编下的程序

    -objdump -D -S elf文件名 >目标文件

    objcopy 交叉转换器

    将elf格式文件转换成其他的格式

    objcopy -O 目标文件格式 原ELF文件 目标文件

    例子:

    objcopy -O binary a.elf a.bin

    Makefile 简述和模板

    解决问题:针对包含超多文件的工程;自动搜索被添加目录中的所有被调用的文件;在第二次全编译时,没有修改的文件只链接而不重复编译节省时间;等等。

    基本格式
    目标(target):依赖(prerequisites)
    [Tab]命令(command)
    • target:需要生成的目标文件。

    • prerequisites:生成该 target 所依赖的一些文件。

    • command:生成该目标需要执行的 命令行的命令。

    在命令前加 "@" 符号,在 make 时不显示此条命令的执行过程,只显示结果。

    举一个例子:

    # 在命令行执行 make 或者 make all 即执行这里,这里是执行 hello,其描述在下面
    all:hello
    ​
    # 这是一个规则,包含 目标、依赖 和 命令
    # 表示 执行 hello 命令/目标 ,所用到的 文件/原材料 有 main.o sum.o,要执行的命令为 gcc -o hello main.o sum.o
    # 从 多个 源文件 来组合 生成 执行文件
    hello:main.o sum.o
        gcc -o hello main.o sum.o
    ​
    # 上面所依赖的 目标 main.o 要进行的命令
    main.o:main.c
        gcc -c main.c
    ​
    sum.o:sum.c
        gcc -c sum.c
    ​
    # 清理,命令行键入 make clean 即可执行此
    clean:
        rm -f main.o sun.o hello
    变量赋值( =、:=、?=、+= )
    # Makefile 中的变量是字符串。
    PARA = 100ask
    ​
    # 注,注释必须单独一行,不能写在语句后面
    ​
    # =
    # 相当于 PARA 的指针给 CURPARA,之后 PARA 的值变,CURPARA 的值会跟着变。
    CURPARA = $(PARA)
    ​
    # :=
    # 仅赋值,之后 PARA 的值变,CURPARA 的值不会跟着变。
    CURPARA := $(PARA)
    ​
    # ?=
    # 使用“?=”给变量设置值时,如果这个变量之前没有被设置过,那么“?=”才会起效果;如果曾经设置过这个变量,那么“?=”不会起效果。相当于函数定义时的 weak 修饰符。
    ​
    # +=
    # 这样的结果是OBJ的值为:”main.o,add.o,sub.o“。说明“+=”用作与变量的追加。
    OBJ = main.o add.o
    OBJ += sub.o
    自动变量( $@、$^、$< )
    CC = gcc
    OBJ = main.o add.o sub.o
    output: $(OBJ)
        $(CC) -o $@ $^
        # $@:表示规则中的目标(target),即 output
        # $^:表示规则中的所有依赖文件(All prerequisites),即 main.o add.o sub.o
    ​
    main.o: main.c
        $(CC) -c $<
        # $<:表示规则中的第一个依赖文件(The first prerequisites),即 main.o 右边的第一个依赖,即 main.c
    add.o: add.c
        $(CC) -c $<
    sub.o: sub.c
        $(CC) -c $<
    ​
    clean:
        rm $(OBJ) output
    模式规则( %.x )
    CC = gcc
    OBJ = main.o add.o sub.o
    output: $(OBJ)
        $(CC) -o $@ $^
    %.o: %.c
        $(CC) -c $<
    # %.o: %.c 表示如下:
    # main.o 由 main.c 生成,add.o 由 add.c 生成,sub.o 由 sub.c 生成
    ​
    clean:
        rm $(OBJ) output
    伪目标
    # 在目标和文件名重名时,加上这句指令,便可以正常执行。用于避免执行命令的目标和工作目录下的实际文件出现名字冲突。
    .PHONY:clean
    clean:
        rm $(OBJ) output
    Makefile 函数

    经常使用的 Makefile 函数,主要有两个(wildcard,patsubst)。函数的调用格式 $(function(空格或tab)arguments),这里 function 是函数名, arguments 是该函数的参数。

    # $(wildcard 指定文件类型) 用于查找指定目录下指定类型的文件
    # 这条规则表示,找到目录./src下所有后缀为.c的文件,并赋值给变量SRC。命令执行完,SRC变量的值:./src/ask.c ./src/100.c
    SRC = $(wildcard ./src/*.c)
    ​
    # $(patsubst 原模式, 目标模式, 文件列表)  用于匹配替换
    # 这条规则表示,把变量 SRC 中所有后缀为.c的文件替换为.o。 命令执行完,OBJ变量的值:./src/ask.o ./src/100.o
    OBJ = $(patsubst %.c, %.o, $(SRC))
    ​
    # $(subst from,to,text)  字符串替换,在文本`text’中使用`to’替换每一处`from’。
    # 结果为‘fEEt on the strEEt’
    $(subst ee,EE,feet on the street)
    ​
    # $(strip string)  去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
    # $(findstring find,in)  在字符串`in’中搜寻`find’,如果找到,则返回值是`find’,否则返回值为空。
    # $(filter pattern...,text)   返回在`text’中由空格隔开且匹配格式`pattern...’的字,去除不符合格式`pattern...’的字。
    # $(filter-out pattern...,text)  返回在`text’中由空格隔开且不匹配格式`pattern...’的字,去除符合格式`pattern...’的字。它是函数filter的反函数。
    # $(sort list)   将‘list’中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。
    ​
    # 文件名函数 略
    一个简易的 Makefile 模板
    该 Makefile 模板 针对的 文件夹 的 树形目录结构
    ├── inc
    │  ├── add.h
    │  └── sub.h
    ├── Makefile
    └── src
      ├── add.c
      ├── main.c
      └── sub.c
    # 编译输出文件加尾缀 "_VERSION"
    VERSION = 0.0.1
    # 源文件 .c 文件的所在的名为 src 的目录
    SOURCE = $(wildcard ./src/*.c)
    # 增加一个名为 src1 的源文件所在目录
    #SOURCE += $(wildcard ./src1/*.c)
    ​
    # 引用文件 .h 文件的所在的名为 inc 的目录
    INCLUEDS = -I ./inc
    # 增加一个名为 inc1 的引用文件所在目录
    #INCLUEDS += -I ./inc1
    ​
    # 编译输出目标文件的文件名
    TARGET    = output
    # 编译输出目标文件所在的文件夹名
    OUTPUTDIR = obj
    # 编译器全名
    CC        = gcc
    ​
    # 以下都是固定的不用动
    ​
    CFLAGS  = -Wall -g
    OBJECT  = $(patsubst %.c, %.o, $(SOURCE))
    ​
    $(TARGET): $(OBJECT)
        @mkdir -p $(OUTPUTDIR)/
        $(CC) $^ $(CFLAGES) -o $(OUTPUTDIR)/$(TARGET)_$(VERSION)
    ​
    %.o: %.c
        $(CC) $(INCLUEDS) $(CFLAGES) -c $< -o $@
    ​
    .PHONY:clean
    clean:
        @rm -rf $(OBJECT) $(OUTPUTDIR)/
    ​
    更加通用的 Makefile 模板

    在对应仓库 Github 仓库Gitee 仓库 中:

    在文件 【1 Makefile CMake 教程 及其模板】\general_Makefile.zip 里。

  • 相关阅读:
    【NSFileManager常用方法之获取信息 Objective-C语言】
    RabbitMQ如何保证消息不丢失呢?
    Web自动化成长之路:selenium基础操作
    【无标题】
    视频批量混剪剪辑软件类似剪映设计一个模板后, 视频,图片,文字,转场,音频,特效都可以系统随机
    探秘C语言数组:解锁高效数据管理与多维空间编程技巧"
    清洁机器人之BMS
    ZYNQ7010的PS编程笔记
    SpringBoot整合Redis
    07_项目开发_用户信息列表
  • 原文地址:https://blog.csdn.net/Staokgo/article/details/134362152