linux嵌入式学习中涉及的一些概念
交叉编译
在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码
常见的计算机架构环境有x86架构和arm架构
- x86架构主要应用于个人计算机、服务器和高性能计算等领域,如Intel的x86和AMD的x86-64处理器系列
- ARM架构广泛应用于嵌入式系统、移动设备和物联网设备等低功耗应用。例如,智能手机、平板电脑、物联网设备等常常采用ARM处理器。
交叉编译链:
编译过程包括了预处理、编译、汇编、链接等功能。既然有不同的子功能,那每个子功能都是一个单独的工具来实现,它们合在一起形成了一个完整的工具集。
经过交叉编译,使得源文件成为可执行文件
编译过程中主要使用gcc编译器,在 Linux 内核的基础上,GNU 计划开发了很多系统部件,GCC 编译器就是其中之一
GCC 编译过程详解
1)预处理
C/C++源文件中,以“ #”开头的命令被称为预处理命令,如包含命令“ #include”、宏定义命令“ #define”、条件编译命令“ #if”、“ #ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“ .i”文件中等待进一步处理。
2)编译
编译就是把 C/C++代码(比如上述的“.i”文件)“ 翻译” 成汇编代码,所用到的工具为 cc1(它的名字就是 cc1, x86 有自己的 cc1 命令, ARM 板也有自己的cc1 命令)。
3)汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux 系统上一般表现为 ELF 目标文件(OBJ 文件),用到的工具为 as。 x86 有自己的 as 命令, ARM 版也有自己的 as 命令,也可能是 xxxx-as(比如 armlinux-as)。
“反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。
4)链接
链接就是将上步生成的 OBJ 文件和系统库的 OBJ 文件、 库文件链接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为 ld 或 collect2。
链接过程:
对于程序的编译,无论是哪种编译器,首先都需要将源代码文件编译成中间代码(Win下.obj文件,UNIX/LINU下.o文件),即Object文件,然后再将Object文件链接在一起进行执行。大多数情况下,编译生成的中间文件比较多,在链接时需要明确地指出中间目标名,这对于编译很不方便,解决方法是给中间目标文件打包生成一个库文件(Win下是.lib,UNIX/LINUX是.a)
makefile
包含多个源文件的项目在编译时有长而复杂的命令行,可以通过makefile保存这些命令行来简化该工作
具体来说
先说make,make命令是 GNU 的工程化编译工具,它用于编译大量互相关联的源代码,使用它可以实现工程化管理,提高开发效率。
makefile是在执行make命令的时候指定编译和链接的规则,包括源代码文件之间的链接关系、依赖关系等。
每当我们写大型项目时,一般需要很多源文件,源文件会在不同的目录中的文件夹里面包含着,这样我们所有的源文件不会在一个文件中包含,用gcc -o main 所有的.c文件来编译,就很麻烦了,你需要记住所有的.c文件,那么为了方便编译链接,makefile就此诞生。
makefile文件编写
makefile三要素:targets(目标),prequisities(依赖),command(命令)。
每个依赖关系由一个目标文件和一组该目标所需的源文件(依赖文件)构成。
命令则描述了如何通过这些依赖关系文件创建目标。
文件格式如下:
目标文件:依赖文件
[tab键] gcc -o 目标文件 依赖文件
一个例子
mytool:main.o tool1.o tool2.o #目标文件mytool:依赖文件main.o tool1.o tool2.o
gcc main.o tool1.o tool2.o -o mytool #生成mytool的规则
# 写gcc命令时候,前面要tab按键一下
# 不写-o参数,生成默认的可执行文件名为a.out,这里我们修改为mytool
main.o:main.c #目标文件main.o,这时一个中间文件:依赖文件main.c,这是一个源文件
gcc main.c -c -Wall -g -o main.o
tool1.o:tool1.c
gcc tool1.c -c -Wall -g -o tool1.o
tool2.o:tool2.c
gcc tool2.c -c -Wall -g -o tool2.o
#-Wall 可以看到所有的警告
#-g 可以调试
#-c 只允许执行到汇编步骤,不允许链接。
clean: # 定义了一个clean的规则,需要时手动调用
rm -f main.o tool1.o tool2.o
$@ —目标文件
$^ —所有依赖文件
$< —第一个依赖文件
.PHONY —伪目标文件
# 通过自定义一些变量来简化 Makefile 的可读性和维护性
OBJS = main.o tool1.o tool2.o
TARGET = mytool
all: $(TARGET)
$(TARGET): $(OBJS) #定义了目标文件 $(TARGET) 依赖于目标文件 $(OBJS)
gcc $(OBJS) -o $@
%.o: %.c #这是一个模式规则,用于将 .c 文件编译成 .o 文件
gcc -c $< -o $@ #编译 .c 文件的命令,使用 gcc 编译器将源文件编译成目标文件
clean:
rm -f $(OBJS) $(TARGET)
执行make命令发生了什么
- make会在当前目录下找名字为“Makefile"或”makefile"的文件。
- 如果找到,第一个目标文件,上面例子中的mytool作为最终的目标可执行文件。
- 如果mytool不存在,或者mytool所依赖的后面.o文件的文件修改时间比mytool文件新,那么它就会执行后面所定义的命令来生成mytool这个文件。(这makefile自动检测更新的原因)
- 如果mytool所依赖的.o文件存在,那么make会在当前文件中找到目标文件的依赖文件.o,如果找不到则根据规则生成.o文件。
- 这样就生成了可执行文件main,就可以运行了。
makefile和CMake的区别
CMake 和 Makefile 是两种不同的构建系统工具,它们在软件开发中用于自动化构建过程,但它们的工作方式和用途有所不同。以下是它们之间的主要区别:
开发者编写 CMakeLists.txt 文件,并使用 cmake 命令生成 Makefile 或其他构建文件。
然后使用 make 命令来构建项目。
-
定义和生成方式:
- Makefile:Makefile 是一个文本文件,其中包含了构建软件所需的指令和规则。它直接定义了如何从源代码生成可执行文件或库。
- CMake:CMake 使用一个名为
CMakeLists.txt
的文件来配置构建系统。CMake 根据这个文件的内容生成 Makefile 或其他构建系统的配置文件。
-
跨平台性:
- Makefile:Makefile 通常是为特定平台编写的,可能需要针对不同平台进行修改。
- CMake:CMake 是跨平台的,它能够生成适用于不同操作系统的 Makefile 或其他构建系统的文件。
-
用户界面:
- Makefile:Make 是一个纯命令行工具,没有图形用户界面。
- CMake:CMake 提供了一个 GUI 界面,可以用于配置项目,这对于不熟悉命令行的用户来说是一个优势。
-
配置与自动化:
- Makefile:Makefile 通常需要手动编写或修改,以处理复杂的依赖关系和构建过程。
- CMake:CMake 可以自动检测依赖关系,并支持多配置切换。它提供了更高级的语法和功能,使得处理复杂项目更加容易。
-
灵活性:
- Makefile:Makefile 相对较为简单,对于简单的项目来说很有效,但对于复杂项目可能不够灵活。
- CMake:CMake 提供了更多的灵活性,可以轻松地处理复杂的依赖关系和构建要求。
-
集成与扩展性:
- Makefile:Makefile 主要生成 Makefile,通常与 Make 工具一起使用。
- CMake:CMake 支持多种构建系统和生成器,如 Makefile、Ninja、MSVC 等,使其可以很容易地集成到各种开发环境中。
-
学习曲线:
- Makefile:Makefile 对于初学者来说可能更容易上手,尤其是对于简单的项目。
- CMake:CMake 由于其高级功能和灵活性,可能有一个较陡的学习曲线。
总结来说,Makefile 是一种较为直接的构建工具,而 CMake 提供了更高的抽象层次和跨平台支持,使得它更适合大型和复杂的项目。
makefile和make
Makefile:
定义:Makefile 是一个包含了一系列规则(rules)的文本文件,用于指导 make 命令如何编译和链接源代码文件生成目标文件或可执行文件。
作用:它描述了项目中各个文件之间的依赖关系和编译命令,允许开发者通过 make 工具自动化构建和更新项目。
make 命令:
作用:make 是一个用于管理代码编译的命令行工具,通过读取 Makefile 文件中的规则,自动化地检查文件的修改时间和依赖关系,从而决定哪些文件需要重新编译,以及如何编译。
语法:在命令行中使用 make 命令,它会查找当前目录下的 Makefile 并执行其中定义的规则。
GDB server远程调试
GDB,全称GNU Debugger,是一个在GNU操作系统下的强大的程序调试工具,广泛应用于Unix及类Unix系统中用以调试多种编程语言编写的程序
GDB Server是GDB的一种运行模式,它允许开发者进行远程调试,这个功能特别适合于分布式系统、嵌入式系统或者任何难以直接在本机调试的环境。
远程调试功能的工作原理是这样的:GDB Server在目标机(也就是运行待调试程序的机器)上启动,并等待GDB的连接。开发者在自己的工作机上运行GDB客户端,并与GDB Server建立连接,之后就可以像平时使用GDB调试本地程序一样进行操作了。这其中,GDB客户端会通过网络发送调试命令到GDB Server,后者执行这些命令,并将结果返回给客户端。这个过程对开发者来说是透明的,感觉就像直接在本机调试一样。
VScode + GDB Server远程调试
在嵌入式开发领域,VSCode配合GDB Server进行远程调试非常普遍。开发者通常会在自己的电脑上使用VSCode编写代码,然后通过GDB Server与嵌入式设备上的程序进行交互。这种方式使得开发者可以在拥有丰富功能和友好用户界面的编辑器中进行开发,同时也能够直接调试运行在目标硬件上的代码。
在配置好VSCode的调试环境后,开发者可以通过启动一个调试会话来连接远程的GDB Server。这通常涉及到设置一些调试配置文件,如launch.json,在这个文件中指定GDB的路径、GDB Server的远程地址和端口号等信息。配置好之后,开发者可以在VSCode中启动调试,就像在本地机器上一样设置断点、查看变量、单步执行代码等。
SCode提供了一项名为“Remote Development”的功能,可以通过SSH(Secure Shell)直接连接到远程服务器,进行代码编辑和调试等操作。这样的话,即使开发者的本地机器与服务器的操作系统不同,他们也可以无缝地在VSCode中进行开发。此外,VSCode的这一功能也支持容器(如Docker)和Windows子系统,为开发者提供了多样的远程开发选项。
VScode支持gdb-server的launch功能和attach功能
当使用launch方式时,GDB本身会启动你的程序,并且立即获得对它的控制权。你可以在程序开始执行之前设置断点,然后让GDB运行程序直到达到这些断点。这种方式是在开发阶段常用的,因为它允许你从程序的起点开始调试,并且能够逐步跟踪程序的执行过程。在VSCode的launch.json配置文件中使用launch请求时,就是在告诉VSCode启动一个调试会话,它会通过GDB启动并控制程序。
与launch不同,attach方式是在程序已经在运行之后再由GDB连接到该程序。这种方式用于已经启动的进程,或是在没有调试信息的情况下运行的进程。当你使用attach时,你需要指定一个正在运行程序的进程ID(PID),GDB会附加到这个进程上,并且可以开始调试。这种方式在解决生产环境中的问题或调试守护进程(例如服务器进程)时非常有用。
//Launch.json配置
{
"name": "my-remote-debug",
"type": "cppdbg",
"request": "launch", //launch功能和attach功能
"program": "./main-test",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"miDebuggerServerAddress": "127.0.0.1:6000",
"linux": {
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb"
},
"serverStarted": "Listening on port 6000",
"serverLaunchTimeout": 20000,
"filterStderr": false,
"filterStdout": false,
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": false
}
]
}
"name": "my-remote-debug":这是调试配置的名称,可以根据需要进行更改。
"type": "cppdbg":这指定了调试器的类型,这里是C++调试器。
"request": "launch":这表示我们要启动一个新的调试会话。
"program": "./main-test":这是要调试的程序的路径。在这个例子中,它指向名为main-test的可执行文件。
"args": []:这是传递给程序的命令行参数。在这个例子中,没有传递任何参数。
"stopAtEntry": true:这表示调试器在程序入口处停止执行。
"cwd": "${workspaceFolder}":这是调试器的当前工作目录,${workspaceFolder}表示工作区的根目录。
"environment": []:这是要设置的环境变量列表。在这个例子中,没有设置任何环境变量。
"externalConsole": false:这表示调试器不会在外部打开一个新的控制台窗口。
"MIMode": "gdb":这是调试器的调试接口模式,这里是GDB。
"miDebuggerPath": "/usr/bin/gdb":这是GDB调试器的路径。
"miDebuggerServerAddress": "127.0.0.1:6000":这是远程GDB服务器的地址和端口号。
"linux": { ... }:这是一个特定于Linux操作系统的配置块,用于指定Linux上的调试器设置。
"serverStarted": "Listening on port 6000":这是一个调试器启动时显示的消息,表示GDB服务器已经在6000端口上监听连接。
"serverLaunchTimeout": 20000:这是等待GDB服务器启动的超时时间,以毫秒为单位。
"filterStderr": false:这表示不过滤标准错误输出。
"filterStdout": false:这表示不过滤标准输出。
"setupCommands": [ ... ]:这是一组GDB命令,用于在调试会话开始时自动执行。在这个例子中,只有一个命令用于启用GDB的漂