前言:
在阅读本文之前,你哦需要了解makefile文件的编写规则,这里我们推荐两篇入门:
Makefile 规则-CSDN博客
Makefile 快速入门-CSDN博客
编译定义
编译是指将源代码文件(如C/C++文件)经过预处理、编译、汇编和链接等步骤,转换为可执行文件的过程。将源代码转换成机器代码的过程称为编译(Compile),编译的工作需要编译器(Complier)来完成。
本地编译
定义:
本地编译是指在当前的编译平台上,生成能在当前平台上运行的可执行文件。
例如,在x86平台上,使用x86平台上的工具,开发针对x86平台本身的可执行程序,这个编译过程称为本地编译。
以一个简单的例子来说明本地编译,假设有一个hello.c文件,它包含以下内容
验证程序
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
我们想要在x86平台上进行本地编译,并在x86平台上运行这个程序。
编译:
gcc -o hello hello.c
运行:
./hello
演示
交叉编译
定义
交叉编译是指在当前的编译平台上,生成能在体系结构不同的另一种目标平台上运行的可执行文件。例如,在x86平台上,使用针对ARM平台的工具,开发针对ARM平台的可执行程序,这个编译过程称为交叉编译。一句话 --> 这本平台上编译生成其他平台能运行的可执行文件。
准备
想要在x86平台上进行交叉编译,并在ARM平台上运行这个程序。首先需要在家目录下的.bashrc最后配置添加交叉编译工具链:
进入配置文件
vi .bashrc
添加:
export PATH=$PATH:/home/$(whoami)/orangepi-build/toolchains/gcc-arm-9.2-2019.12-
x86_64-aarch64-none-linux-gnu/bin
验证 -- 查看版本号:
执行aarch64-none-linux-gnu-gcc --version 可以看到对应的版本号:
-------------------------
file命令补充
定义
file
命令是一个在 Unix 和类 Unix 系统(如 Linux)中常用的命令,用于确定文件的类型。通过检查文件的内容、魔数(magic number,位于文件开头的几个字节,用于标识文件类型)或其他特定的签名,file
命令可以准确地告诉你一个文件是什么类型的。
作用
file
命令主要用于确定文件的MIME类型或更一般的文件类型。- 它对于检查未知文件特别有用,因为它可以帮助你了解文件的内容而不需要打开或执行它。
- 它也可以用来检查文件的编码(如文本文件的字符集)。
格式
file
命令的基本格式如下:
bash复制代码
file [选项] 文件名... |
选项
file
命令有一些选项,用于改变其输出或行为。以下是一些常用的选项:
-b
:只显示文件类型,不显示文件名。-i
:显示MIME类型的输出,而不是更详细的描述。-z
:尝试查看压缩文件的内容。-L
:对于符号链接,显示链接指向的文件的内容类型,而不是链接文件本身。--mime-type
:与-i
选项相似,但仅显示MIME类型,不包括字符集信息。
示例
-
显示文件的类型:
bash复制代码
file example.txt | |
# 输出可能是:example.txt: ASCII text |
-
显示多个文件的类型:
bash复制代码
file file1.jpg file2.pdf | |
# 输出可能是: | |
# file1.jpg: JPEG image data, JFIF standard 1.01 | |
# file2.pdf: PDF document, version 1.4 |
-
显示MIME类型:
bash复制代码
file -i example.txt | |
# 输出可能是:example.txt: text/plain; charset=us-ascii |
-
只显示类型,不显示文件名:
bash复制代码
file -b example.txt | |
# 输出可能是:ASCII text |
通过 file
命令,你可以轻松地识别文件类型,这对于处理未知文件或确定文件是否已被篡改特别有用。
case1: 简单的交叉编译演示
在x86平台上进行交叉编译
编译
执行如下命令 (前提已经安装,配置了编译工具)
aarch64-none-linux-gnu-gcc -o hello hello.c
file命令查看类型
利用file命令可以看到编译出来的程序是ARM aarch64的二进制程序
运行
这时候需要将该文件拷贝到比如香橙派等ARM开发板上运行, 在X86宿主机上是无法正常运行的
拷贝到目标平台 -- arm 平台(香橙派)上
scp hello orangepi@192.168.1.28:/home/orangepi
在orangepi上能运行,见下图:
执行命令运行
这样就实现了交叉编译
==================================================
case2: 交叉编译wiringOP库: -- 对库文件的交叉编译
目的
在x86平台上面编译后,拷贝到orangepi 的根目录即可调用
编译完放 file 查看类型正确后,就压缩一下传到香橙派上即可使用
压缩:
tar -zcvf _install.tar.gz _install
用配置出来的 交叉编译工具编译移植wiringOP库:
0.下载wiringPi文, 然后进到wiringOP 文件夹
git clone https : //github.com/orangepi-xunlong/wiringOP // 下载源码cd wiringOP // 进入文件夹
1. 修改build.sh脚本,在echo "WiringPi Library" 之前添加:
目的 -- 在当前目录下面创建,_install/usr/local 文件夹(路径),并且分别生成bin,include,lib文件夹
mkdir $PWD/_install/usr/local/bin -p
mkdir $PWD/_install/usr/local/include -p
mkdir $PWD/_install/usr/local/lib -p
mkdir - p 补充
定义
mkdir -p
是一个在 Unix 和类 Unix 系统(如 Linux)中常用的命令,用于创建目录(或文件夹)。-p
选项是该命令的一个非常有用的参数。
功能
mkdir -p
命令允许你创建多级目录,即使上级目录不存在。如果上级目录不存在,mkdir
会自动创建它们。如果没有 -p
选项,当上级目录不存在时,mkdir
会报错。
示例
- 创建一个单层目录:
如果你只想创建一个单层目录,你可以简单地使用 mkdir
命令,不需要 -p
选项。
bash复制代码
mkdir mydir |
- 创建多级目录:
如果你想要创建一个多级目录,如 dir1/dir2/dir3
,并且 dir1
和 dir2
还不存在,你可以使用 mkdir -p
。
bash复制代码
mkdir -p dir1/dir2/dir3 |
使用 -p
选项,mkdir
会首先创建 dir1
,然后在 dir1
内部创建 dir2
,最后在 dir2
内部创建 dir3
。如果其中任何一个目录已经存在,mkdir -p
不会报错,而是继续创建剩余的目录。
注意事项
- 如果使用
mkdir
(不带-p
)尝试创建多级目录,并且上级目录不存在,你会收到一个错误消息。 mkdir -p
不会覆盖已经存在的目录或文件。如果尝试创建的目录已经存在,mkdir -p
会静默地成功退出,不会显示任何错误或警告。mkdir -p
对于脚本编写特别有用,因为你可以确保多级目录结构在继续执行脚本之前被正确创建,而无需担心目录是否已经存在。
2. 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile
可以借助 vscode的ctrl + shift +f (能快速查找,并且一键修改)
将所有Makefile中的CC := gcc 改成 CC := aarch64-none-linux-gnu-gcc
( 将本地编译需要用到的全部 gcc,修改为交叉编译需要用的命令 aarch64-none-linux-gnu-gcc)
3. 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile 、wiringPi/Makefile
修改DESTDIR?=/usr 替换为DESTDIR?= $(shell pwd)/../_install/usr
// shell pwd 拿到当前路径
// 我们希望在他的上层目录下面直接创建_install/usr/路径
4. 修改wiringPi/Makefile 的软连接
1、将$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPi.so.$(VERSION) $(DESTDIR)/lib/libwiringPi.so
修改为:$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPi.so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/libwiringPi.so
//我们前面修改了 DESTDIR 路径,这里如果还用默认的)$(DESTDIR)就找不到目标文件了,
需要加上$(PREFIX)指定到正确的路径
修改devLib/Makefile将$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPiDev.so.$(VERSION) $(DESTDIR)/lib/libwiringPiDev.so
修改为:
$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPiDev.so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/libwiringPiDev.so//修改利用同上
5. devLib/Makefile
INCLUDE = -I. 修改为INCLUDE = -I. -I$(DESTDIR)$(PREFIX)/include
//添加 -I
参数允许你指定编译器应该在哪些额外的目录中查找这些头文件。
注意: -I
参数后面直接跟的是目录的路径,而不是头文件的路径。并且,你可以使用多个 -I
参数来指定多个搜索目录。当编译器查找一个头文件时,它会按照 -I
参数指定的目录顺序进行搜索,并在找到文件后停止搜索。如果没有在 -I
指定的目录中找到文件,编译器会回退到默认的系统头文件搜索路径中查找。
6. 然后执行:
./build
然后tree 就能看到我们生成的文件/夹
7.file 查看文件类型
验证教程编译成功
编译过后我们可以看到什么生成的gpio 形式
8.压缩整合文件
9.拷贝到orangepi上
10.验证
后面我们就可以把_INSTALL里的内容拷贝到香橙派的根目录下,然后执行
sudo ldconfig
sudo gpio readlall
交叉编译 wiringPi库成功 --> 在×86平台 上编译,然后移植到 arm 平台 orangepi上面能直接运行
sudo ldconfig补充:
sudo ldconfig
是一个在 Unix 和类 Unix 系统(如 Linux)中用于更新动态链接器(如 ld.so
或 ld-linux.so
)运行时的绑定和缓存的命令。
ldconfig
命令的简要说明和其主要功能:
- 更新共享库缓存:当系统管理员添加、删除或修改了共享库(
.so
文件)时,ldconfig
会更新动态链接器的缓存。这个缓存通常存储在/etc/ld.so.cache
文件中,它包含了系统中所有共享库的位置和版本信息。 - 搜索指定目录:默认情况下,
ldconfig
会搜索/lib
、/usr/lib
以及在/etc/ld.so.conf
文件和/etc/ld.so.conf.d/
目录下的配置文件中指定的目录。这些目录通常包含系统的共享库文件。 - 重新排序和删除:当调用
ldconfig
时,它会按照链接器的配置文件的顺序对库进行排序,并且还会从缓存中删除不再需要的库的条目(例如,已经被删除或移动的文件)。 - 需要超级用户权限:由于
ldconfig
需要修改系统级的缓存文件,因此通常需要超级用户(root)权限来执行。这就是为什么你会看到sudo ldconfig
这样的命令。 - 使用场景:当你安装了新的库或更新了现有的库时,通常建议运行
sudo ldconfig
来确保动态链接器知道这些新的或更新过的库的位置。 - 错误处理:如果
ldconfig
在尝试更新缓存时遇到问题(例如,因为某些库文件损坏或丢失),它通常会显示一个错误消息,但通常会继续更新剩余的库。
总之,sudo ldconfig
是一个用于更新动态链接器缓存的命令,以确保系统能够正确地找到和使用共享库。
======================================
交叉编译 项目(以智能垃圾分类为例):
观前提醒
对这个项目不太了解的朋友推荐先看这篇: 垃圾分类最终篇 -- 添加网络控制功能-CSDN博客
希望对这个项目有更多了解的朋友,还可以去专栏了解详细内容:智能垃圾桶项目_mx_jun的博客-CSDN博客
0. 创建文件夹:
inc -- 存放所有头(.h)文件src -- 存放所有源(.c)文件
3rd -- 存放 这个工程依赖的第三方的头文件和库
--------------
1.下载需要的库文件,编译链接文件
在orangepi 上面安装好后,我们就可以拷贝到我们的宿主家 -- Ubuntu上面
执行以下命令下载:
apt download zlib1g zlib1g-dev libpython3.10 libpython3.10-dev libexpat1 libexpat1-dev libcrypt1 libcrypt-dev
如下命令拷贝到 ×86 平台-- 宿主机
scp *deb mxjun@192.168.88.132:/home/mxjun
说明:
apt download -- 只下载,不安装
apt install -- 既下载,又安装
.deb -- .deb 文件是 Debian 软件包格式的文件
2.在×86平台上面解压文件
deb 包的解压命令是 dpkg
解压文件
dpkg -x .deb文件 存放位置
3.编写Makefile 文件(细节活)
然后去makefile里面加上所有的头文件路径
Makefile 带注释形式 :带注释形式
CC := aarch64-none-linux-gnu-gcc
# SRC -- 存放所有的 .c 文件
SRC := $(shell find src -name *.c)
# INC -- 存放所有的 头文件 (包括自己写的 和 第三方)
INC := ./inc \
./3rd/usr/local/include \
.3rd/usr/include \
./3rd/usr/include/python3.10 \
./3rd/usr/include/aarch64-linux-gnu/python3.10 \
.3rd/usr/include/aarch64-linux-gnu# 把需要包含的 .c 文件,替换为.o 文件
OBJ := $(subst src/.,obj/,$(SRC:.c=.o))
# 创建目标 , 并且指定存放位置
TARGET = obj/garbage# -I./inc -- 存放头文件路径
CFLAGS := $(foreach item,$(INC),-I$(item))# -I 指定的 第三方连接 库文件路径
LIBS_PATH := ./3rd/usr/local/lib \
./3rd/lib/aarch64-linux-gnu \
./3rd/usr/lib/aarch64-linux-gnu \
./3rd/usr/lib/python3.10 \# -L ./3rd/usr/local/LIBS
LDFLAGS := $(foreach item,$(LIBS_PATH),-L$(item))# 指定我们要链接的库
LIBS := -lwiringPi -lpython3.10 -pthread -lexpat -lz -lcrypt
# 生成obj文件夹,里面包含源文件对应的.o文件
obj/%.o:src/%.CC
mkdir -p obj
$(CC) -o $@ -c $< $(CFLAGS)# 依赖obj 下面的.o文件 编译
$(TARGET) : $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS)compile : $(TARGET)
clean: rm $(TARGET) obj &(OBJ) -rf
# 打印调试信息
debug:
echo $(CC)
echo $(SRC)
echo $(INC)
echo $(OBJ)
echo $(TARGET)
echo $(CFLAGS)
echo $(LDFLAGS)
echo $(LIBS)
.PHONY: clean compile debug
=========================
纯净版本:
CC := aarch64-linux-gnu-gcc
SRC := $(shell find src -name "*.c")
INC := ./inc \
./3rd/usr/local/include \
./3rd/usr/include \
./3rd/usr/include/python3.10 \
./3rd/usr/include/aarch64-linux-gnu/python3.10 \
./3rd/usr/include/aarch64-linux-gnu
OBJ := $(subst src/,obj/,$(SRC:.c=.o))
TARGET=obj/garbage
CFLAGS := $(foreach item, $(INC),-I$(item)) # -I./inc -I./3rd/usr/local/include
LIBS_PATH := ./3rd/usr/local/lib \
./3rd/lib/aarch64-linux-gnu \
./3rd/usr/lib/aarch64-linux-gnu \
./3rd/usr/lib/python3.10
LDFLAGS := $(foreach item, $(LIBS_PATH),-L$(item)) # -L./3rd/usr/local/libs
LIBS := -lwiringPi -lpython3.10 -pthread -lexpat -lz -lcrypt
obj/%.o:src/%.c
mkdir -p obj
$(CC) -o $@ -c $< $(CFLAGS)$(TARGET) :$(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS)
compile : $(TARGET)
clean:
rm $(TARGET) obj $(OBJ) -rf
debug:
echo $(CC)
echo $(SRC)
echo $(INC)
echo $(OBJ)
echo $(TARGET)
echo $(CFLAGS)
echo $(LDFLAGS)
echo $(LIBS)
.PHONY: clean compile debug
4. 验证:
编译通过后,到obj 发现已经生成了我们的可执行文件
5. 拷贝到我们的香橙派中:
scp garbage orangepi@192.168.1.11:/home/orangepi
6.执行:
(请确保相关服务已经打开,比如我程序里面调用的是 mjpg-stream 来实现拍照功能,那么执行之前,这个服务纪就要确保打开)
-l(l) -I(i) 参数补充
-l(l) 参数补充
定义:
在 GCC(GNU Compiler Collection)中,-l
参数用于指定在链接阶段要链接的库(library)。当你编译一个程序并且它依赖于某个库时,你需要告诉 GCC 在链接阶段将这个库包含进来。
-l
参数后面通常跟着库的名字,但需要注意的是,你不需要指定库文件的前缀(如 lib
)或后缀(如 .so
、.a
等)。GCC 会自动根据系统配置和库文件的实际位置来查找这些库。
例如,如果你的程序依赖于数学库 libm.so
(或 libm.a
,取决于你的系统和编译选项),你应该在编译命令中使用 -lm
而不是 -llibm
。
以下是一个简单的示例:
bash复制代码
gcc myprogram.c -o myprogram -lm |
在这个例子中,myprogram.c
是你的源代码文件,-o myprogram
指定输出文件的名称,而 -lm
告诉 GCC 在链接阶段链接数学库。
需要注意的是,链接库的顺序有时很重要。如果库之间有依赖关系,你需要先链接依赖的库,然后再链接依赖于它的库。但是,对于大多数常见的库(如数学库、标准 C 库等),这个顺序通常不是问题。
此外,如果你知道库文件的实际路径,你也可以使用 -L
参数来指定库的搜索路径。例如:
bash复制代码
gcc myprogram.c -o myprogram -L/path/to/libs -lmylib |
在这个例子中,-L/path/to/libs
告诉 GCC 在 /path/to/libs
目录下搜索库文件,而 -lmylib
指定要链接的库名为 mylib
(即 libmylib.so
或 libmylib.a
)。
-I (-i)参数补充
在 gcc
(GNU Compiler Collection)中,-I
参数(注意是大写的 I
)用于指定编译器搜索头文件(header files)的额外目录。
当你在 C 或 C++ 代码中包含(#include
)一个头文件时,编译器需要找到这个头文件的实际位置以便能够正确地编译代码。默认情况下,编译器会在一些标准的位置(如 /usr/include
和 /usr/local/include
)中查找头文件。但是,如果你的头文件不在这些标准位置中,你就需要使用 -I
参数来告诉编译器在哪里可以找到这些头文件。
例如,如果你的头文件位于 /home/user/myproject/include
目录中,你可以在编译时添加 -I/home/user/myproject/include
来告诉编译器在那里查找头文件:
bash复制代码
gcc -I/home/user/myproject/include myfile.c -o myprogram |
在这个例子中,编译器会在 /home/user/myproject/include
目录中查找 myfile.c
中包含的任何头文件。
-I
参数对于组织大型项目或当头文件位于非标准位置时特别有用。通过使用 -I
参数,你可以确保编译器能够找到你的项目所需的所有头文件,而不管它们实际存储在哪里。