目录
[Q1: 编译是什么?](#Q1: 编译是什么?)
[Q2: 交叉编译是什么?](#Q2: 交叉编译是什么?)
[Q3: 为什么要交叉编译](#Q3: 为什么要交叉编译)
Q3.1:树莓派相对于C51大得多,可以集成编译器比如gcc,那么树莓派就不需要交叉编译了吗?
[Q4: 什么是宿主机和目标机?](#Q4: 什么是宿主机和目标机?)
[Q5: 如何进行交叉编译?](#Q5: 如何进行交叉编译?)
[交叉编译器/工具链 的安装](#交叉编译器/工具链 的安装)
[实现效果 :](#实现效果 :)
交叉编译的初认识Q&A
Q1: 编译是什么?
A: 在一个平台上生成该平台的可执行代码
Q2: 交叉编译是什么?
A: 交叉编译是在一个平台上生成另一个平台上的可执行代码
其实在之前学习C51的时候就需要交叉编译。只不过对于C51,交叉编译的发生在keil(集成环境)上。其 实质就是在windows平台上编译C51平台的可执行代码。
Q3: 为什么要交叉编译
A: 因为平台上不允许或不能够安装所需要的编译器(比如:C51很小,如果再集成编译器会浪费大量资源甚至根本就无法安装)。
Q3.1:树莓派相对于C51大得多,可以集成编译器比如gcc,那么树莓派就不需要交叉编译了吗?
A:并不是,树莓派也需要交叉编译 。因为树莓派也是从无到有的,在树莓派的操作系统还不存在的时候,根本就无法运行什么编译器,但是操作系统的本质也是代码,所以操作系统也是需要通过编译才能在树莓派上运行的!这就不得不在另一个平台编译生成树莓派的可执行代码,而这就是交叉编译。(接下来的学习中我即将在Linux平台上编译可以在树莓派中执行的代码)
PS :平台运行至少需要两样东西:bootloader (启动引导代码) 以及 操作系统核心
Q4: 什么是宿主机和目标机?
A: 简单来说比如在UBUNTU-Linux平台交叉编译树莓派的可执行代码,那么UBUNTU就是宿主机,树莓派就是目标机。
- 宿主机(host):编辑和编译程序的平台,一般是基于X86的PC机,通常也被称为主机
- 目标机(target):用户开发的系统,通常都是非X86平台。host编译得到的可执行代码在target上运行
Q5: 如何进行交叉编译?
A: 交叉编译需要用到工具,一般称作交叉编译器或交叉编译工具链
交叉编译器/工具链 的安装
现在我的目的是在Ubuntu-Linux平台上交叉编译生成树莓派平台上的可执行代码:
- 由于树莓派是作为目标机 的,所以安装树莓派相对应的交叉编译器/工具链
- 而安装的平台则应该是宿主机,也就是Linux虚拟机
- 在这个网址下载工具链: GitHub - raspberrypi/tools
复制网址,然后打开虚拟机,使用以下命令用git clone来下载:
git clone https://github.com/raspberrypi/tools.git
我尝试过直接"Download ZIP" ,但是下载下来的内容是缺少的,且windows会报同名文件的错,我猜测是因为windows系统下对大小写的文件名不敏感导致的,总之就是很麻烦,还是使用git clone省心。
下载好之后,依次进入:tools -> arm-bcm2708 -> gcc-linaro-arm-linux-gnueabihf-raspbian-x64 -> bin:
图中,浅蓝色文件代表的是"软链接 ",我们需要用到的交叉编译器 就是浅蓝色的"arm-linux-gnueabihf-gcc",通过"ls -l":
可见,这个"arm-linux-gnueabihf-gcc"软链接是指向"arm-linux-gnueabihf-gcc-4.8.3 "的,软链接是不占内存的,作用就是指向真正的可执行程序,在使用中,就可以直接使用软链接的名字来调用真正的可执行文件。
所以,"arm-linux-gnueabihf-gcc"就是交叉编译器/工具链 ,使用和gcc无异,比如编译XXX.C,编译语句就是:
cpp
./arm-linux-gnueabihf-gcc XXX.C //只不过把"gcc"替换成了"./arm-linux-gnueabihf-gcc"
将交叉编译器/工具链添加到环境变量
现在,已经拥有了交叉编译器并且知道了大概如何编译。
但是每次使用这个编译器都需要cd到"/home/mjm/ras_CrossCompile/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin "下,或在编译的时候就把这个路径加在编译器前面,非常的繁琐复杂,我希望在使用交叉编译器的时候能和使用gcc一样不管在什么路径都可以直接打出来,这就要使用到环境变量了。
临时有效的添加环境变量
临时的添加环境变量,其实在前几节学习动态库的制作时就有提到过,那就是使用export ,临时的意思就是"仅在添加完环境变量后的当前窗口有效",也就是说如果再开一个窗口就又识别不到新添加的环境变量了。
- 使用"echo $PATH"可以查看当前的环境变量:
这一长串中:前面的"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:" 是不变的
- 然后使用"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games: 想要添加的路径"来临时添加环境变量:
cpp
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/mjm/ras_CrossCompile/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
运行后,退回工作目录,输入"arm-linux-gnueabihf-gcc -v ":("-v"查看编译器信息)
可见,此时在其他目录下,也可以暂时使用交叉编译器了!
此时,新开一个终端,再次输入"arm-linux-gnueabihf-gcc -v":
可见无法识别,所以export确实只能临时有效。
永久有效的添加环境变量
临时有效虽然一句命令就能搞定,但是有风险,因为终端随时可能关闭,然后添加的临时环境变量就失效了,所以还是学习一下如何一劳永逸的修改环境变量:
- 修改工作目录下的 .bashrc 隐藏文件(该文件用于配置命令终端的)
cpp
vi /home/mjm/.bashrc
然后在文件的最后一行加上刚刚的export指令:
其实,bashrc隐藏文件就是一个脚本,永久有效的核心思路就是通过脚本自动运行export,和动态库写脚本写export的思路是一样的。
- 然后运行"source /home/mjm/.bashrc" 加载配置文件,生效配置
- 此时,再开一个新的终端并运行"arm-linux-gnueabihf-gcc -v":
可见,这样就永久有效的将交叉编译器的目录添加到了环境变量中去了!
交叉编译实战
现在,尝试将之前博文实现的服务器客户端聊天代码中的服务器端代码交叉编译到树莓派平台:
服务器和客户端的代码分别是"/home/mjm/NET "下的"server_final.c "和"client_final.c"
使用以下指令交叉编译客户端的代码:
cpp
arm-linux-gnueabihf-gcc server_final.c -o server_from_linux
在一开始编译的时候报了巨量的错误:
- 经过查证,我认为主要是头文件的重定义?在"server_final.c"中,将"netinet/in.h"和"linux/in.h"两个头文件注释掉就好了!
然后可以执行"file server_from_linux" 来查看文件属性:
可见,相比于原来ubuntu下的可执行文件,经过交叉编译后的可执行文件的属性发生了改变
使用以下指令将交叉编译生成的可执行文件发送到开发板:
cpp
scp server_from_linux pi@192.168.2.26:/home/pi/mjm_code
//指令 文件名 开发板用户名@开发板地址:开发板的绝对路径
然后使用Moba连接到树莓派:
可见,文件成功发送到了树莓派
实现效果 :
cpp
//在树莓派上
./server_from_linux 192.168.2.26 8888
//在ubuntu-linux虚拟机上
./client 192.168.2.26 8888
可见,经过交叉编译的可执行代码成功在树莓派平台运行!
但是,我发现如果把客户端交叉编译发送到树莓派,虽然也可以运行但是无法和虚拟机的服务端连接。也就是说树莓派只能做服务端不能做客户端,因为树莓派没法连接到虚拟机的IP。 所以我尝试了ping,发现:
- 虚拟机可以ping通树莓派和主机
- 树莓派可以ping通主机
- 主机可以ping通树莓派和虚拟机
- 唯独树莓派没法ping通虚拟机
解决办法:
- 断开电脑的宽带,连接局域网(能Ping通之后可以改回来)
- 将虚拟机的网络设置从NAT 改为桥接
- 在VMWare左上的"编辑 "里选择"虚拟网络编辑器 ",然后修改"已桥接到 "后的选项框,选择我电脑的网络"Media Tek WI-FI 6 MT7921 Wireless LAN Card"
此时再次打开虚拟机,先输入ifconfig来查看IP(切换网络设置之后IP会变):
然后在树莓派尝试ping这个地址:
成功ping通!此时将虚拟机linux平台的client端代码交叉编译并上传到树莓派试运行:
- 成功运行!此时树莓派成功作为客户端连接到了虚拟机的ubuntu-linux平台!!
- 并且此时可以断开电脑的局域网换回宽带了!
带wiringPi库的交叉编译实战
在刚刚的交叉编译实例中,成功的将ubuntu-linux环境下的代码交叉编译成为了可以在树莓派上运行的arm-linux可执行文件。
但是,刚刚交叉编译的代码在编译时不需要链库,如果代码包含wiringPi或线程库里的函数,那么在gcc编译时就需要链库,对于这种需要链库的C代码进行交叉编译不能直接无脑的在"arm-linux-gnueabihf-gcc XXX.c"后加上"-lwiringPi",其原因是库文件也是当前平台下的文件,并不能被树莓派平台所识别!
先将wiringPi库下载到/home/mjm下:
然后运行"./build"安装:
然后再"/usr/local/lib" 下就可以看到这个wiringPi的动态库:
通过"file /usr/local/lib/libwiringPi.so"查看文件属性:
发现是个软链接,指向"libwiringPi.so.2.46 " ,那就对它再file一下:
可见,的确是当前平台下的格式,显然在树莓派上是无法运行的。
方法1:交叉编译wiringPi库
既然这个库文件无法被树莓派平台识别,那就先交叉编译库文件,再链库并交叉编译代码。
但是对于动态库的交叉编译和之前的方法可能不同,所以这种方法暂时不展开。
方法2:把树莓派的wiringPi库拿来
直接把能在树莓派平台上使用的wiringPi库拿来也可以解决问题。
在树莓派中的依次从**/lib-->/usr/lib-->/usr/local/lib** 找wiringPi库 ,然后在**/usr/lib** 下找到了**:**
有了刚刚的经验,这个估计也是个软链接,使用file看一下:
果然,是指向libwiringPi.so.2.52的软链接,所以应该发送到虚拟机的是这个库文件。
通过scp命令发送给虚拟机:
cpp
scp ./usr/lib/libwiringPi.so.2.52 mjm@192.168.2.27:/home/mjm/ras_CrossCompile
虚拟机要先"sudo apt-get install openssh-server"安装ssh服务才能支持scp文件传输!
创建软链接
在得到wiringPi的动态库后,我们也可以学着对其创建一个名称更加简洁的软链接:
软硬链接概念补充
参考:https://www.cnblogs.com/zhangna1998517/p/11347364.html
- 软链接 的创建通常使用"ln -s 被链接对象 软链接文件名 "指令,会在选定的位置上生成一个被链接对象的镜像,且不会占用磁盘空间,类似于Windows下的快捷方式。软链接也叫符号连接,在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息
- 软链接相对应的就是硬链接 ,通常使用"ln 被链接对象 硬链接文件名 "指令创建(没有参数-s),会在选定的位置上生成一个和被链接对象大小相同的文件
- 不管是软链接还是硬链接,创建的镜像/文件都会随着链接对象的变化而同步变化
使用以下指令创建软链接:
cpp
ln -s libwiringPi.so.2.52 libwiringPi.so
//指令 + 参数 + 要被链接的文件 + 软链接文件名字
创建好之后可以file一下:
可见,软链接创建成功!
再使用file查看一下:
确实是树莓派平台下的wiringPi动态库!
交叉编译
现在有了树莓派平台下的wiringPi库,可以开始尝试交叉编译带有wiringPi库的C代码了!
先写一段符合要求的,即带wiringPi库的代码:
beep.c:
cpp
#include <stdio.h>
#include <wiringPi.h>
#define BEEP 7
int main (void)
{
wiringPiSetup () ; //初始化wiringPi库
pinMode (BEEP, OUTPUT); //配置输入输出模式
while(1){
digitalWrite (BEEP, LOW) ; //蜂鸣器响
delay (1000) ; // mS
digitalWrite (BEEP, HIGH) ; //蜂鸣器不响
delay (1000) ;
}
return 0;
}
然后使用以下指令交叉编译:
cpp
arm-linux-gnueabihf-gcc blink.c -L ./ -lwiringPi -I /home/mjm/WiringPi/wiringPi -o blink_from_linux
- 使用"-L ":指定动态库的优先查找目录为当前目录
- 使用"-I"(大写i):指定头文件的优先查找目录为/home/mjm/WiringPi/wiringPi
编译时报错:
解决办法:
- 重新向虚拟机导入版本2.50的wiringPi动态库:
- 删除之前的软链接,然后重新设置软链接:
- 再次尝试编译:
成功编译,没有报错!
最后使用scp发送到树莓派:
cpp
scp blink_from_linux pi@192.168.2.26:/home/pi/mjm_code
然后在树莓派尝试运行:
成功运行,蜂鸣器以固定间隔开始发出声音。