【回眸】香橙派zero2交叉编译、Uboot移植、内核移植

110 阅读21分钟

 前言

在实际的项目开发工作过程中,更多的还是使用交叉编译环境进行代码的编译。再编译完成之后再把代码放到香橙派等ARM开发板上运行。因此这篇博客专门讲解下交叉编译环境的搭建、Uboot移植和内核移植内容。

OrangePi Zero2 SDK

SDK 全称 Software Development Kit,即软件开发工具包。一般包括了一些工具(如交叉编译工具链)、库、文档和示例代码。香橙派的Linux SDK其实指的就是 orangepi-build 这套代码集,orangepibuild在脚本和配置文件中会指定 u-boot、Linux内核和交叉编译工具链的地址,运行 orangepi-build时,当其发现本地没有这些东西,会自动去相应的地方下载的。使用 orangepi-build 可以编译出多个版本的 Linux 镜像。

环境要求

新版本的orangepi-build对编译主机(也就是搭建的vmware 虚拟机)的要求:新版本的orangepibuild是在Ubuntu22.04的x64电脑或者虚拟机上运行的。

可从ubuntu官网重新下载ubuntu 22.04 x64镜像,重新更新下虚拟机,ubuntu 22.04
X64镜像下载地址:

http://releases.ubuntu.com/22.04/ubuntu-22.04.3-desktop-amd64.iso

建议在创建虚拟机时,至少分配50G的存储给虚拟机使用

获取Linux SDK

sudo apt update
sudo apt install git
git clone https://github.com/orangepi-xunlong/orangepi-build.git -b next

orangepi-build 下载完后会包含下面的文件和文件夹
a. build.sh: 编译启动脚本, 我们可以通过build.sh编译uboot、内核、根文件系统甚至完整的img
b. external: 包含编译镜像需要用的配置文件、特定的脚本以及部分程序的源码等
c. LICENSE: GPL 2 许可证文件
d. README.md: orangepi-build 说明文件
e. scripts: 编译 linux 镜像的通用脚本

解压完后,需要去修改orangepizero2的配置脚本

vi ./external/config/sources/families/sun50iw9.conf

修改nex)分支里的内核配置版本(默认是6.1.y这里改成5.16.y版本),不然6.1.y默认编译出来的内核默认没有无线网卡、I2C也不支持

从百度网盘下载(推荐使用该方法, 就不用考虑github下载过程中出现下载失败导致最后编译出
来的系统可能是异常的)
从下面的百度网盘链接下载提前编译好的oragepi-build SDK包:

下载下来后是几个拆分好的压缩包, 如下所示:

test@test:~/orangepi-build$ ls
orangepi-build-ok.tar.gz_00 orangepi-build-ok.tar.gz_01 orangepi-buildok.
tar.gz_02

合并解压命令

cat orangepi-build-ok.tar.gz_0* > orangepi-build-ok.tar.gz
tar -xvf orangepi-build-ok.tar.gz

首次编译完整SDK

下载完源码后, 即可用build.sh进行首次编译

  1. 运行 build.sh 脚本
sudo ./build.sh

2.选择Full OS image for flashing 进行完整镜像的编译 

3.选择不修改配置:

4.根据实际的香橙派派开发版的型号, 选orangepizero2

  1. 选择根文件系统类型, 这边选择ubuntu 22.04 的根文件系统, 也就是jammy:

6.选择带桌面环境,即接入HDMI显示器后,是有桌面显示的:

7.最后选择桌面环境,这边选择xfce:

  1. 这里的软件主要是一些额外第三方软件包的安装, 都不选择,直接ok开始编译

  2. 如果是第一次运行 orangepi-build 中的 build.sh 脚本时会自动下载交叉编译工具链、 u-boot 和
    linux 内核源码,成功编译完一次 linux 镜像后在 orangepi-build 中可以看到 的文件和文件夹有:

a. build.sh: 编译启动脚本

b. external: 包含编译镜像需要用的配置文件、特定功能的脚本以及部分程序 的源码,编译镜像过
程中缓存的 rootfs 压缩包也存放在 external 中
c. kernel: 存放 linux 内核的源码,内核源码的文件夹的名字请不要手动修改,如 果修改了,编译系
统运行时会重新下载内核源码
d. LICENSE: GPL 2 许可证文件
e. README.md: orangepi-build 说明文件
f. output: 存放编译生成的 u-boot、linux 等 deb 包、编译日志以及编译生成的 镜像等文件
g. scripts: 编译 linux 镜像的通用脚本
h. toolchains: 存放交叉编译工具链
i. u-boot: 存放 u-boot 的源码,u-boot 源码的文件夹的名字请不要手动修改,如果修改了,编译系
统运行时会重新下载 u-boot 源码
j. userpatches: 存放编译脚本需要用到的配置文件。

  1. 经过漫长等待编译完成后, 会在orangepibuild/
    output/images/Orangepizero2_3.1.0_ubuntu_jammy_desktop_xfce_linux5.16.17/
    下Orangepizero2_3.1.0_ubuntu_jammy_desktop_xfce_linux5.16.17.img镜像。 可以直接拿这个
    img 烧入到SD卡中运行

交叉编译工具链配置

关于编译

编译是指将源代码文件(如C/C++文件)经过预处理、编译、汇编和链接等步骤,转换为可执行文件的过程。将源代码转换成机器代码的过程称为编译(Compile),编译的工作需要编译器(Complier)来完成。

本地编译是指在当前的编译平台上,生成能在当前平台上运行的可执行文件。例如,在x86平台上,使用x86平台上的工具,开发针对x86平台本身的可执行程序,这个编译过程称为本地编译。

本地编译就是以前的博客里使用的,直接在开发板上进行编译运行。

交叉编译是指在当前的编译平台上,生成能在体系结构不同的另一种目标平台上运行的可执行文件。例
如,在x86平台上,使用针对ARM平台的工具,开发针对ARM平台的可执行程序,这个编译过程称为交
叉编译。
以一个简单的例子来说明本地编译,假设有一个hello.c文件,它包含以下内容:

#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}

想要在x86平台上进行交叉编译,并在ARM平台上运行这个程序。首先需要在家目录下的.bashrc最后配置添加交叉编译工具链: 

test@test:~$ vi .bashrc

最后面添加

export PATH=$PATH:/home/$(whoami)/orangepi-build/toolchains/gcc-arm-9.2-2019.12-
x86_64-aarch64-none-linux-gnu/bin

然后断开重连桌重新登陆后, 执行export指令,即可看到最新导入的PATH环境变量

test@test:~$ export
declare -x DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
declare -x DISPLAY="localhost:10.0"
declare -x HOME="/home/pg"
declare -x LANG="en_US.UTF-8"
declare -x LC_ADDRESS="zh_CN.UTF-8"
declare -x LC_IDENTIFICATION="zh_CN.UTF-8"
declare -x LC_MEASUREMENT="zh_CN.UTF-8"
declare -x LC_MONETARY="zh_CN.UTF-8"
declare -x LC_NAME="zh_CN.UTF-8"
declare -x LC_NUMERIC="zh_CN.UTF-8"
declare -x LC_PAPER="zh_CN.UTF-8"
declare -x LC_TELEPHONE="zh_CN.UTF-8"
declare -x LC_TIME="zh_CN.UTF-8"
declare -x LESSCLOSE="/usr/bin/lesspipe %s %s"
declare -x LESSOPEN="| /usr/bin/lesspipe %s"
declare -x LOGNAME="pg"
declare -x MOTD_SHOWN="pam"
declare -x OLDPWD="/home/pg/orangepi-build/toolchains/gcc-arm-9.2-2019.12-x86_64-
aarch64-none-linux-gnu/bin"
declare -x
PATH="/home/pg/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin
:/bin:/usr/games:/usr/local/games:/snap/bin:/home/pg/orangepibuild/
toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linuxgnu/
bin:/home/pg/orangepi-build/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-
none-linux-gnu/bin"

同时执行aarch64-none-linux-gnu-gcc --version 可以看到对应的版本号:

test@test:~$ aarch64-none-linux-gnu-gcc --version
aarch64-none-linux-gnu-gcc (GNU Toolchain for the A-profile Architecture 9.2-
2019.12 (arm-9.10)) 9.2.1 20191025
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

在x86平台上进行交叉编译,可以使用以下命令:

aarch64-none-linux-gnu-gcc -o hello hello.c

利用file命令可以看到编译出来的程序是ARM aarch64的二进制程序

test@test:~/test$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically
linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, with
debug_info, not stripped

这时候需要将该文件拷贝到比如香橙派等ARM开发板上运行, 在X86宿主机上是无法正常运行的

scp hello orangepi@192.168.1.28:/home/orangepi

Makefile入门

编译工具及构建工具介绍

直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程
时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性。make工具就根据makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。

​编辑

cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文件,例如Makefile、Visual Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还提供了一些高级功能,如测试、打包、安装等。
qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程文件。
ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写配置文件,而是由其他构建系统(如cmake)来生成
auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,auto conf是一个用于生成configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的Makefile文件的脚本Makefile.am是一种用于auto make的配置文件格式,它包含了一些指令和变量,用于定义程序或库的源文件、目标文件、依赖关系和编译选项等。
make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。 

编译的四个阶段
回顾下编译的四个过程:预处理(Pre-Processing)、编译(Compiling)、汇编 (Assembliang)、链接(Linking)

​编辑

Makefile基本规则 

基本规则

target ... : prerequisites ...
<tab缩进>command
<tab缩进>...
<tab缩进>...

target 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是一个标签( Label),对于标签这种特性,在后续的讲“伪目标”中会有叙述。prerequisites 就是,要生成那个 target 所需要的文件或是目标。command 也就是 make 需要执行的任意shell命令。
Makefile一个示例:

debug:
@echo "hello world"

如果,我们要编译下面这个最简单的例子:

#include <stdio.h>
int main(int argc, char *argv[])
{
printf("hello world!\n");
return 0
}

Makefile修改如下:

debug:
@echo "hello world"
test:
gcc -o test test.c

 执行命令make test 可以生成 test文件, 执行make debug可以输出“hello world”:

test@test:~/makefiletest$ rm test
test@test:~/makefiletest$ ls
Makefile test.c
test@test:~/makefiletest$ make debug
hello world
test@test:~/makefiletest$ ls
Makefile test.c
test@test:~/makefiletest$ make test
gcc -o test test.c
test@test:~/makefiletest$ ls
Makefile test test.c

伪目标

如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执行其命令。通过将目标声明为伪目标,可以避免这种情况,强制执行其命令。 

debug:
@echo "hello world"
test:
gcc -o test test.c
.PHONY: debug

变量赋值和预定义变量

Makefile中的变量赋值运算符有四种,分别是=、:=、?=和+=, 符号表示取变量的值,当变量名多于一个字符时,使用"()"=表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。例如,VARA=AVARB=符号表示取变量的值,当变量名多于一个字符时,使用"( )":= 表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。例如,VAR_A = A,VAR_B = (VAR_A) B,VAR_A = AA,那么最后VAR_B的值是AA B,而不是A B。:= 表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。例如,VAR_A := A,VAR_B := $(VAR_A) B,VAR_A := AA,那么最后VAR_B的值是A B,而不是AA B。?=表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。例如,VAR ?=new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,否则保持原来的值不变。

+= 表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。例如,VAR +=
new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,如果VAR在之前被赋值为old_value,那么VAR的值就为old_value new_value

符的其他用法:符的其他用法: ^ 表示所有的依赖文件
@表示生成的目标文件@ 表示生成的目标文件 < 代表第一个依赖文件

注释和换行符

采用#进行一行注释
采用\作为续行符

变量的替换引用

语法格式是:

$(var:a=b)或${var:a=b}

把变量var的值中的a后缀替换成b后缀。例如:

src := a.c b.c c.c
obj := $(src:c=o)

把变量src的值中的.c后缀替换成.o后缀,赋值给变量obj,结果是:

obj := a.o b.o c.o

举例 

# 这是一个Makefile的注释
TARGET = hello #TARGET延迟赋值hello
CC := gcc #CC立即赋值gcc
CC += -g #CC追加赋值-g, gcc -g表示添加调试信息,可用于gdb的调试
SRC = hello.c
OBJ = $(SRC:.c=.o) #变量的替换引用,把hello.c的.c替换成.o
debug :
@echo "hello world"
echo $(SRC)
echo $(OBJ)
$(TARGET): $(SRC)
$(CC) -o $@ $<
# $(CC) -o ${TARGET} hello.c
compile: $(TARGET)
clean:
@rm hello hello.o -r
.PHONY: clean compile

 常见函数

 Makefile函数的基本格式是:()或者是( )或者是{ },其中,是函数名,是函数的参数,参数之间要用逗号分隔开,参数和函数名之间使用空格分开。调用函数的时候要使用字符“$”,后面可以跟小括号或者大括号。
1) wildcard 通配符:
Makefile中的wildcard 是一个函数,用于扩展通配符,返回与通配符匹配的文件列表。通配符是一种特殊的字符,可以表示多个文件名或目录名,常见的通配符有 * 和 ?,分别表示任意长度的任意字符和单个任意字符。格式如下:

$(wildcard argments)

 比如*.c 表示所有以 .c 结尾的文件名,a?.txt 表示所有以 a 开头,中间有一个任意字符,以 .txt 结尾的文件名。例如:

SRC = $(wildcard src/*.c)

 表示查找并返回src目录下所有的.c文件, *表示通配符, 匹配一个或者多个任意字符

2)shell:

$(shell <cmd> <args>)

cmd: 执行命令名称
args:参数列表
返回值: 返回命令执行结果
例如:

SRC = $(shell find . -name *.c)

表示查找当前目录及子目录下的所有.c文件结尾的代码源文件
3) patsubst替换函数:

$(patsubst pattern,replacement,text)

pattern: 是一个包含通配符 % 的模式,表示匹配任意长度的任意字符
replacement: 是一个替换字符串,也可以包含 %,表示用 pattern 中匹配的字符替换。
text: 是一个要处理的文本,可以包含多个以空格分隔的单词。

返回值:patsubst 函数会在 text 中找到所有符合 pattern 的单词,并用 replacement 替换它们,然后返回替换后的文本。
例如,如果有一个变量 src,它的值是:

src = a.c b.c c.c

想把它的值中的所有 .c 后缀替换成 .o 后缀,可以这样写:

obj = $(patsubst %.c,%.o,$(src))

这样,obj 的值就是:

obj = a.o b.o c.o

4) subst替换函数

$(subst from,to,text)

from: 是要被替换的字符或单词
to: 是替换后的字符或单词
text: 是要处理的字符串。
返回值:subst 函数会在 text 中找到所有的 from,并用 to 替换它们,然后返回替换后的字符串。

例如 :

$(subst ee,EE,feet on the street)

返回:

fEEt on the strEEt

 测试工程代码目录

test@test:~/makefiletest$ tree
.
├── Makefile
└── src
└── test.c

 Makefile内容:

CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))
debug:
@echo "hello world"
echo $(SRC)
echo $(TARGET)
$(TARGET): $(SRC)
mkdir -p obj
$(CC) -o $@ $<
compile: $(TARGET)
clean:
@rm obj -r
.PHONY: clean compile

test.c内容:

#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}

执行:

make compile

生成obj/test:

pg@pg-Default-string:~/makefiletest$ tree -a
.
├── Makefile
├── obj
│ └── test
└── src
└── test.c

5 dir函数:

$(dir NAMES...)

 dir 函数是一个用于从文件名序列中提取目录部分的函数
优化Makefile内容:

CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))
debug:
@echo "hello world"
echo $(SRC)
echo $(TARGET)
$(TARGET): $(SRC)
mkdir -p $(dir $(TARGET))
$(CC) -o $@ $<
compile: $(TARGET)
clean:
@rm $(dir $(TARGET)) -r
.PHONY: clean compile

 6 suffix函数

$(suffix <names...>)

功能:从文件名序列中取出各个文件名的后缀。
返回值:返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。
例如:

$(suffix src/foo.c src-1.0/bar.c hacks)

返回:

.c .c

7)basename函数

格式

$(basename <names...>)

功能:从文件名序列中取出各个文件名的前缀部分。
返回值:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。
例如: 

$(basename src/foo.c src-1.0/bar.c hacks)

返回:

src/foo src-1.0/bar hacks

8) addsuffix函数

$(addsuffix <suffix>,<names...>)

 功能:把后缀加到中的每个单词后面。
返回:返回加过后缀的文件名序列。
例如:

$(addsuffix .c,foo bar)

返回值:

foo.c bar.c”

9)addprefix函数
功能:把前缀加到中的每个单词后面。
返回值:返回加过前缀的文件名序列。
例如:

$(addprefix src/,foo bar)

返回值:

src/foo src/bar

10)foreach函数

$(foreach <var>,<list>,<text>)

把list中使用空格分割的单词依次取出并赋值给变量var, 然后执行text表达式
例如:

files := foo bar baz
files-with-c := $(foreach file,$(files),$(file).c)

11)条件判断语言
Makefile条件判断有下面几种:
ifeq/ifneq语句:
ifeq语句 : 判断参数 是否相等,相等为 true, 否则是 false.

ifeq (arg1, arg2)
#arg1 arg2 相等执行这里的语句
else
#arg1 arg2 不相等执行这里的语句
endif

ifneq语句:判断参数 是否不等,不等为 true, 否则为 false.

ifneq (arg1, arg2)
#arg1 arg2 不相等执行这里的语句
else
#arg1 arg2 相等执行这里的语句
endif

 ifdef/ifndef语句
ifdef 语句: 判断参数 是否有值,有值为 true, 否则是 false
ifndef : 判断参数 是否没有值,没有值为 true, 否则为 false.
ifdef:

ifdef var
#如果定义了var,执行这里的内容
else
#如果没定义var,执行这里的内容
endif

ifndef:

infdef var
#如果没定义var,执行这里的内容
else
#如果定义var,执行这里的内容
endif

交叉编译wiringOP库

  1. 修改build.sh脚本,在echo "WiringPi Library" 之前添加:
mkdir $PWD/_INSTALL/usr/local/bin -p
mkdir $PWD/_INSTALL/usr/local/include -p
mkdir $PWD/_INSTALL/usr/local/lib -p

  1. 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile
1、将所有Makefile中的CC := gcc 改成 CC := aarch64-none-linux-gnu-gcc

  1. 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile 、wiringPi/Makefile
修改DESTDIR?=/usr 替换为DESTDIR?= $(shell pwd)/../_INSTALL/usr

  1. 修改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

修改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

  1. devLib/Makefile
INCLUDE = -I. 修改为INCLUDE = -I. -I$(DESTDIR)$(PREFIX)/include

  1. 然后执行:
./build

然后输入26(选择板子为修改DESTDIR?=/usr 替换为orangepizero2)
这时候,就会wiringOP-master下生成_INSTALL目录,里面有完整编译出来的库文件和头文件

后面我们就可以把_INSTALL里的内容拷贝到香橙派的根目录下,然后执行

sudo ldconfig

就可以使用该库了,可以使用

sudo gpio readall

 测试库是否链接正常。

交叉编译智能分类工程代码

原有项目的目录结构为

test@test:~/garbage$ tree
.
├── garbage.c
├── garbage.h
├── garbage.py
├── main.c
├── myoled.c
├── myoled.h
├── pwm.c
├── pwm.h
├── socket.c
├── socket.h
├── uartTool.c
└── uartTool.h

 调整目录结构

test@test:~/garbage$ tree -a
.
├── inc
│ ├── garbage.h
│ ├── myoled.h
│ ├── pwm.h
│ ├── socket.h
│ └── uartTool.h
├── src
│ ├── garbage.c
│ ├── garbage.py
│ ├── main.c
│ ├── myoled.c
│ ├── pwm.c
│ ├── socket.c
│ └── uartTool.c

增加3rd目录,用于存放wiringOP和python3.10等第三方依赖库和头文件。需增加如下几个依赖库.
首先,从香橙派上利用apt download下载依赖包的头文件和库文件,并拷贝到宿主机里:

apt download zlib1g zlib1g-dev libpython3.10 libpython3.10-dev libexpat1
libexpat1-dev libcrypt1 libcrypt-dev
scp *.deb test@192.168.1.10:/home/test #test为宿主机用户名, 192.168.1.10为宿主机ip

 然后,利用dpkg -x命令解压deb文件到garbage/3rd目录下:

dpkg -x libpython3.10_3.10.12-1~22.04.2_arm64.deb garbage/3rd
dpkg -x libpython3.10-dev_3.10.12-1~22.04.2_arm64.deb garbage/3rd/
dpkg -x libcrypt1_1%3a4.4.27-1_arm64.deb garbage/3rd/
dpkg -x libexpat1_2.4.7-1ubuntu0.2_arm64.deb garbage/3rd/
dpkg -x libpython3.10-dev_3.10.12-1~22.04.2_arm64.deb garbage/3rd/
dpkg -x libpython3.10_3.10.12-1~22.04.2_arm64.deb garbage/3rd
dpkg -x libexpat1_2.4.7-1ubuntu0.2_arm64.deb garbage/3rd/
dpkg -x libexpat1-dev_2.4.7-1ubuntu0.2_arm64.deb garbage/3rd/
dpkg -x libcrypt1_1%3a4.4.27-1_arm64.deb garbage/3rd/
dpkg -x zlib1g_1%3a1.2.11.dfsg-2ubuntu9.2_arm64.deb garbage/3rd/
dpkg -x libcrypt-dev_1%3a4.4.27-1_arm64.deb garbage/3rd/
dpkg -x zlib1g-dev_1%3a1.2.11.dfsg-2ubuntu9.2_arm64.deb garbage/3rd/

因为默认提供的aarch64-none-linux-gnu-gcc 9.2.0的版本编译是, 如果去加载上面的依赖库是, 会出现库版本的依赖问题, 因此。 宿主机安装aarch64-linux-gnu-gc 11.2版本并使用该交叉编译工具:

sudo apt install gcc-aarch64-linux-gnu

Makefile 文件内容如下:

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

最后在工程里执行

make

就会在obj目录下生成garbage文件, 将该文件拷贝到香橙派里即可执行。 

嵌入式Linux系统的组成

  1. BIOS和UEFI的作用:
    a. 进行硬件自检,检测内存,CPU,显卡,硬盘等设备的状态和配置。
    b. 设置启动顺序,选择从哪个设备加载引导程序,如硬盘,U盘等。
    c. 加载引导程序,如bootmgr,grub2等,然后由引导程序加载操作系统,如Windows,Linux等。
    d. UEFI是BIOS的一种升级替代方案。UEFI本身已经相当于一个微型操作系统。
  2. grub2和bootmgr:
    a. grub2是GNU项目开发的一种通用的引导加载器,它可以引导多种不同的操作系统,包括Linux,
    Windows,FreeBSD等。
    b. bootmgr是Windows版本的引导加载器,它只能引导Windows系统或者其他使用MBR分区表的
    系统。
    b. grub2和bootmgr都可以通过chainloader命令来加载对方的引导文件,从而实现多重引导。
    c. 加载引导程序,如bootmgr,grub2等,然后由引导程序加载操作系统,如Windows,Linux等。
  3. U-boot:
    a.uboot是一种用于嵌入式系统的引导加载器,它可以支持多种硬件平台和架构,如ARM,MIPS,
    PowerPC等.
    b. uboot可以提供BIOS和grub2的功能,它可以初始化硬件设备,设置启动顺序,加载引导文件,
    启动操作系统,或者进入命令行模式

编译u-boot

 u-boot简介

uboot是一种通用的引导加载程序,它可以用于多种嵌入式系统,支持多种操作系统,如Linux, Android,
NetBSD等。uboot的主要作用是将操作系统内核从存储设备(如Flash, SD卡等)加载到内存中,并执行
内核代码。

 XIP设备

XIP设备是指一种可以直接在存储器中执行程序代码的设备,而不需要将代码复制到内存中。XIP的全称是eXecute In Place,即芯片内执行。像片内的SRAM, NOR Flash, BROM等。

为什么需要u-boot

因为嵌入式系统的硬件资源有限,CPU上电后只能执行一小段内置的代码(BROM System),这段代码不足以完成内存初始化,文件系统访问,网络通信等复杂的任务。因此,需要一个中间层的程序,来完成这些工作,并引导操作系统启动。

u-boot启动流程

开机Uboot的运行流程一般是这样的:

​编辑

官网有对U-Boot SPL大小限制的说明:

​编辑

执行顺序:

  1. 当H616芯片上电或复位后,brom会自动执行,它会根据芯片的引脚电平或寄存器设置,确定启动模式,如从nand flash,spi flash,sd卡,usb等设备中启动。
  2. brom会根据启动模式,选择相应的设备驱动,初始化SD卡设备控制器,设置设备参数,如时钟频率,总线宽度,电压等级等
  3. brom会从启动设备的特定扇区中,读取第一级引导程序,如spl将其加载到芯片的内部sram中,并跳转到其入口点执行
  4. spl会继续初始化一些硬件设备,如ddr,pll,gpio等,然后从启动设备的特定分区中,读取第二级引导程序,如uboot proper,将其加载到ddr中,并跳转到其入口点执行,
  5. uboot会继续初始化一些硬件设备,如网卡,lcd,从SD卡中读取内核文件,启动操作系统。 

u-boot编译流程

  1. 运行 build.sh 脚本, 记得加 sudo 权限
test@test:~/orangepi-build$ sudo ./build.sh

  1. 选择 U-boot package, 然后回车

  2. 接着选择开发板的型号

  3. 重复编译 u-boot 时, 使用下面的命令无需通过图形界面选择, 可以直接开始编译 u-boot

sudo ./build.sh BOARD=orangepizero2 BRANCH=next BUILD_OPT=u-boot

  1. 查看编译生成的 u-boot deb 包
test@test:~/orangepi-build$ ls output/debs/u-boot/linux-u-boot-currentorangepizero2_
3.1.0_arm64.deb

  1. 然后登录到H616开发板, 卸载已安装的 u-boot 的 deb 包
dpkg -l | grep linux-u-boot #查看u-boot包名称
sudo apt purge -y linux-u-boot-orangepizero2-current #卸载上面查找到的u-boot包名称,有可能是linux-u-boot-orangepizero2-next

  1. 再安装刚才上传的新的 u-boot 的 deb 包
sudo dpkg -i linux-u-boot-next-orangepizero2_3.1.0_arm64.deb
sudo nand-sata-install #一路回车
sudo reoobt -f

上述官方编译u-boot的方法可以用以下步骤代替:
1、清理u-boot:

sudo chown test:test v2021.10-sunxi -R #修改所属用户为当前用户(根据实际用户名修改),保证
当前用户下权限没有问题
cd v2021.10-sunxi
make distclean

2、配置u-boot,生成.config配置文件:

make -j6 orangepi_zero2_defconfig CROSS_COMPILE="aarch64-none-linux-gnu-"

3、编译uboot, 生成u-boot-sunxi-with-spl.bin

make -j6 CROSS_COMPILE="aarch64-none-linux-gnu-"

4、将生成的u-boot-sunxi-with-spl.bin 拷贝到开发板上,参考官网对BROM的启动及SD卡数据组成的说明

​编辑

然后执行如下命令:

dd if=/dev/zero of=/dev/mmcblk1 bs=1k count=1023 seek=1 status=noxfer #格式化1k到1M为止的数据
dd if=u-boot-sunxi-with-spl.bin of=/dev/mmcblk1 bs=1k seek=8 conv=fsync

 编译内核

Linux内核的主要功能

Linux操作系统框架

​编辑

Linux内核的主要功能:进程管理、内存管理、驱动、系统调用

Linux的目录结构如下:

test@test:~/orangepi-build/kernel/orange-pi-5.16-sunxi64$ ls
arch        include       mm         scripts
block init modules.builtin security
certs ipc modules.builtin.modinfo sound
COPYING Kbuild modules-only.symvers System.map
CREDITS Kconfig modules.order tools
crypto kernel Module.symvers usr
Documentation lib net virt
drivers LICENSES README vmlinux
export.txt MAINTAINERS README.md vmlinux.o
fs Makefile samples vmlinux.symvers.

 a. arch/:这个目录包含了不同架构处理器的代码,如x86,arm,mips等。每个架构都有自己的子目
录,如arch/x86/,arch/arm/等。在每个架构的子目录中,又有一些子目录和文件,如boot/,lib/,
mm/,include/等,分别包含了特定平台的启动代码,库函数,内存管理,头文件
b. block/:这个目录包含了块设备的代码,如硬盘,光驱等。block/目录中主要包含了块设备的基本框
架和I/O调度算法,以及一些通用的块设备驱动
c. crypto/:这个目录包含了加密算法的代码,如AES,SHA1,MD5等
d. drivers/:这个目录包含了设备驱动程序的代码,如键盘,鼠标,网卡,声卡,摄像头等。drivers/目
录中的代码按照设备的类别进行分类,如char/,block/,input/,i2c/,spi/,pci/,usb/等
test@test:~/orangepi-build/kernel/orange-pi-5.16-sunxi64$ ls
arch include mm scripts
block init modules.builtin security
certs ipc modules.builtin.modinfo sound
COPYING Kbuild modules-only.symvers System.map
CREDITS Kconfig modules.order tools
crypto kernel Module.symvers usr
Documentation lib net virt
drivers LICENSES README vmlinux
export.txt MAINTAINERS README.md vmlinux.o
fs Makefile samples vmlinux.symvers.
e. fs/:这个目录包含了文件系统的代码,如ext4,fat,ntfs,nfs,cifs等。fs/目录中的代码按照文件系统的类型进行分类,如ext4/,fat/,ntfs/等。在fs/目录中,还有一些通用的文件和子目录,如
mount.h,dentry.c,proc/,sysfs/等,它们用于实现文件系统的基本功能和接口
f. include/:这个目录包含了内核所需的头文件,如linux/,asm/,uapi/等。头文件是用于声明变量,函数,结构,宏,常量等的文件,它们可以被其他的源文件引用,以便共享和重用代码。include/目录中的头文件按照不同的层次和用途进行分类,如linux/目录中的头文件是与平台无关的,asm/目录中的头文件是与平台相关的,uapi/目录中的头文件是用于内核和用户空间的API的
g. init/:这个目录包含了内核初始化的代码,如main.c,version.c,do_mounts.c等。init/目录中的代码是内核的入口和核心,它们负责调用其他子系统和模块的初始化函数
• ipc/:这个目录包含了进程间通信的代码,如sem.c,msg.c,shm.c等。进程间通信是指在不同的进程之间传递数据和信号的方法,它们可以让进程之间实现协作和同步。ipc/目录中的代码实现了一些常用的进程间通信机制,如信号量,消息队列,共享内存等
• kernel/:这个目录包含了内核的核心代码,如sched/,irq/,time/,fork.c,exit.c,signal.c等。
kernel/目录中的代码实现了一些内核的基本功能和服务,如进程调度,中断处理,时间管理,进程创建,进程终止,信号处理等
• lib/:这个目录包含了内核需要引用的一些库函数的代码,如string.c,vsprintf.c,crc32.c等
• mm/:这个目录包含了内存管理的代码
• net/:这个目录包含了网络协议的代码,和网卡驱动不相关代码、
• scripts/:这个目录包含了内核编译所需的一些脚本,如Makefile,Kconfig,checkpatch.pl等
• tools/:这个目录包含了一些和内核交互的工具,如perf/,ftrace/,cpupower/,objtool/等。工具是用于分析和调试内核的程序,它们可以让内核的性能和稳定性更加优化和提高

Linux内核编译流程 

  1. 运行 build.sh 脚本, 记得加 sudo 权限
test@test:~/orangepi-build$ sudo ./build.sh

2.选择 Kernel package, 然后回车

  1. 然后会提示是否需要显示内核配置界面, 如果不需要修改内核配置, 则选择第一个即可, 如果需要修改内核配置, 则选择第二个

  2. 接着选择开发板的型号

  3. 查看编译生成的内核相关的 deb 包
    a. linux-dtb-next-sun50iw9_3.1.0_arm64.deb 包含有内核使用的 dtb 文件
    b. linux-headers-next-sun50iw9_3.1.0_arm64.deb 包含内核头文件
    c. linux-image-next-sun50iw9_3.0.1_arm64.deb 包含内核镜像和内核模块

test@test:~/orangepi-build$ ls output/debs/linux-*
output/debs/linux-dtb-next-sun50iw9_3.1.0_arm64.deb
output/debs/linux-image-next-sun50iw9_3.1.0_arm64.deb
output/debs/linux-headers-next-sun50iw9_3.1.0_arm64.deb

  1. 重复编译 kernel 时, 使用下面的命令无需通过图形界面选择, 可以直接开始编译kernel
sudo ./build.sh BOARD=orangepizero2 BRANCH=next BUILD_OPT=kernel
KERNEL_CONFIGURE=no

  1. 如果对内核做了修改, 可以使用下面的方法来更新开发板 linux 系统的内核和内核模块
scp linux-image-next-sun50iw9_3.1.0_arm64.deb test@192.168.1.28:/home/test/
sudo apt purge -y linux-image-next-sun50iw9 # 也有可能是linux-image-next-sun50iw9
sudo dpkg -i linux-image-next-sun50iw9_3.1.0_arm64.deb
sudo reboot -f

上述官方编译kernel的方法可以用以下步骤代替:

  1. 清理旧配置文件及生成的文件
sudo apt-get install ccache #安装ccahe,用于编译加速
export PATH=$PATH:/home/$(whoami)/orangepi-build/toolchains/gcc-arm-9.2-2019.12-
x86_64-aarch64-none-linux-gnu/bin #导入教程编译环境
make ARCH=arm64 distclean

  1. 配置内核, 更新内核依赖属性和新属性, 生成.config:
cp ../../external/config/kernel/linux-5.16-sun50iw9-current.config .config #拷贝
默认配置
make ARCH=arm64 CROSS_COMPILE="aarch64-none-linux-gnu-" olddefconfig #这里执行
make menuconfig 然后exit也是可以的

a. make olddefconfig 的作用是根据已有的 .config 文件生成一个新的 .config 文件,同时更新内核的依赖属性和新属性。它会使用旧的 .config 文件中的参数作为默认参数,不会询问用户的选择。它会将新添加的内核选项设置为默认值,也不会提醒用户。它会将旧的 .config 文件重命名为 .config.old 文件,以备后用.

b. make olddefconfig 的作用和 make oldconfig 类似,但是 make oldconfig 会以交互方式询问用户对新配置的选择,而 make olddefconfig 不会.

c. make menuconfig 是基于 Ncurses 图形界面去配置 .config 文件,它可以让用户在一个菜单式的界面中选择和修改内核的各种选项,也可以查看选项的帮助信息,它是一种比较方便和直观的配置方式

  1. 编译内核:
make -j6 ARCH=arm64 'CROSS_COMPILE=ccache aarch64-none-linux-gnu-'
LOCALVERSION=-sun50iw9 Image modules dtbs

  1. 接下来运行
make modules_install INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH=$PWD/_install
ARCH=arm64 'CROSS_COMPILE=ccache aarch64-none-linux-gnu-' #安装驱动
make install INSTALL_PATH=$PWD/_install/boot #安装内核

或者直接打包成deb包:

cp external/patch/misc/headers-debian-byteshift.patch /tmp
make -j6 bindeb-pkg KDEB_PKGVERSION=3.1.0 KDEB_COMPRESS=xz BRANCH=next
LOCALVERSION=-sun50iw9 KBUILD_DEBARCH=arm64 ARCH=arm64 DEBFULLNAME="Orange Pi"
CROSS_COMPILE="ccache aarch64-none-linux-gnu-”

a.bindeb-pkg 是指定 make 命令使用 Debian 的格式来打包内核,生成一个 .deb 文件,这样可以方便地在 Debian 系的系统中安装内核 。
b. KDEB_PKGVERSION=3.1.0 是指定打包的内核的版本号为 3.1.0,这个版本号可以自定义,也可以使用内核的默认版本号 。
c. KDEB_COMPRESS=xz 是指定打包的内核使用 xz 的压缩格式,这样可以减少打包文件的大小,也可以使用其他的压缩格式,如 gzip, bzip2 等 。
d. BRANCH=next 是指定编译的内核的分支为 next
c. LOCALVERSION=-sun50iw9 是指定编译的内核的本地版本为 -sun50iw9
d. KBUILD_DEBARCH=arm64 是指定打包的内核的架构为 arm64,这个架构是用来表示 64 位的 ARM处理器。
e. ARCH=arm64 是指定编译的内核的架构为 arm64 。
f. DEBFULLNAME=Orange Pi 是指定打包的姓名为 Orange Pi。

Linux根文件系统

根文件系统也叫roofs,Linux 根文件系统是指整个文件系统的最顶层,以 "/" 来表示。它是内核启动时所挂载的第一个文件系统,包含了系统运行所必需的目录和文件:

  1. /bin目录下存放着系统需要的可执行文件比如ls、mv、cp等命令,现在新根文件系统的像ubuntu等debian系的rootfs, 基本都是软链接到/usr/bin目录下
  2. /dev目录下面存放着的文件都与设备有关,此目录下的文件都是设备文件。
  3. /etc目录存放Linux下所必须的库文件
  4. /mnt目录, 临时挂载目录,可以在从目录下创建空的子目录
  5. /proc、sys 目录, Linux虚拟文件系统,由内核生成各类节点
  6. /sbin 一般软链到/usr/sbin下,一般存放一些root权限才能执行的命令
  7. /lib 软链接到/usr/lib目录,用于存放库文件
  8. /usr/ 存放lib bin sbin目录, 另外的share目录里面存放的是共享、只读的程序和数据
  9. /tmp/存放临时文件或目录
  10. /root目录 系统管理员(root)的主文件夹,即是根用户的目录,与此对应,普通用户的目录
    是/home下的某个子目录。
  11. /var目录、与/usr目录相反,/var目录中存放可变的数据,比如log文件\临时文件等。
  12. /home目录,系统默认的用户文件夹,它是可选的,对于每个普通用户,在/home目录下都有一个以用户名命名的子目录,里面存放用户相关的配置文件。

后记

Linux内核及移植涉及的东西十分复杂,本博客只能窥得其中一隅,如果需要再深入探究,则需更多资料的学习。看到这里的读者朋友们可以点击专栏查看同系列的其他文章,希望能帮到屏幕前的每一位技术人,该博文最初发表在CSDN上。