分类 硬件 下的文章

起因是收到一个任务:在PYNQ-Z2平台上搭建一个人脸识别系统。

当时第一时间就去了github,找到最热门的项目face_recognition,打算将其移植到arm平台上。然而在过程中遇到了数不胜数的坑。。其中之一就是依赖环境的配置。

dlib是一个提供机器学习和数据分析功能的C++库,也提供了python版本,使得可以通过python来调用其中的功能。然而,其提供方式简单粗暴,或许是为了能够达到每台机器的最佳性能,只提供了源码下载,自行编译安装的方法,甚至连Windows版的预编译包都没有提供(至少我没找到)。这就对于一些软件环境不全或者硬件配置不够的机器上安装该库的过程造成了一定的困难。(devs:反正我们的库是高级用途,那些辣鸡环境不配安装

好在当前的硬件环境还是有通用型的,起码在同一平台下编译的软件可以在同一指令集架构Instruction Set Architecture, ISA下兼容运行,使得这些平台还是有运行dlib的可能。

本文即将提到的Pine A64+ 1GB SoC即属于上述的第二种情况:硬件配置不够。这里的不够指的并不是无法运行dlib。在编译过程中,目测到的最高内存占用达到2.1GB(amd64-aarch64交叉编译时),而默认配置的Pine A64内存1GB,内存交换空间swap 512MB,远远无法达到编译要求。实际上在Pine A64+上编译时,也可以观察到在内存不足时,会自动终止编译过程,并尝试从头开始。因而,在编译都无法完成的情况下,根本谈不上能不能运行。在对于交叉编译技术有了初步的了解后,决定使用amd64平台,配置更高的个人计算机进行交叉编译。

经过在Python包管理中心Python Package Index, PyPI的搜索后,发现一个名为crossenv的项目,根据描述,其目的在于为Python包交叉编译提供一个简化的配置过程,而这与我的目的恰好符合。过程记录如下。

第一步 下载目标平台的gcc工具链toolchain

到arm官网上可以找到下载地址(这里)。选择目标平台为aarch64-linux-gnu的工具链进行下载,使用时解压到linux文件系统中即可。

第二步 配置环境变量

甲 将工具链添加至搜索路径PATH

export PATH=/path/to/your/toolchain/bin

记得PATH中添加的是可执行文件的路径,所以不要忘记加上bin子目录。 /path/to/your/toolchain是工具链根目录的位置,例如/home/esper/gcc-arm-8.3-2019.03-x86_64-aarch64-linux-gnu

乙 配置编译相关的环境变量

Python库编译过程中会识别一些环境变量,比如CC, CFLAGS, C_INCLUDE_PATH一类的,C++对应的应该也是可用的。

export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++

这些是按照crossenv推荐而进行的设置。CFLAGS就不用设置了,可能会覆盖原有的比如-O2一类的优化参数。

第三步 安装crossenv(要求两个python版本一致)

甲 挂载目标平台的文件系统

由于需要为目标平台编译软件,故需要使用目标平台的配置信息进行编译时的配置。 假设使用的存储卡被系统分配的标识符为/dev/sdb,挂载至用户的家目录下:

mkdir pine64
sudo mount /dev/sdb2 ~/pine64

此时可通过~/pine64访问目标文件系统。 至于使用第二个分区的原因很简单,第一个是boot分区。

乙 配置目标平台的系统信息模块_sysconfigdata_m

备份原有_sysconfigdata.py,将_sysconfigdata_m.py复制为_sysconfigdata.py。Python原本的设计方法是由_sysconfigdata.py通过当前程序的变量决定加载的系统信息文件,而crossenv会检查两者之间的信息兼容性,还会报错(不知道原理是啥,反正这里让人摸不着头脑)

cd ~/pine64/usr/include/python3.5/
sudo mv _sysconfigdata.py _sysconfigdata.py.bak
sudo mv _sysconfigdata_m.py _sysconfigdata.py

丙 配置config-3.5m

crossenv不认带有后缀的config文件夹,使用软链接解决

sudo ln -s config-3.5m-* config-3.5m

丁 安装crossenv

pip3 install crossenv
python3 -m crossenv ~/pine64/usr/bin/python3 vcenv

使用以上命令安装crossenv,并根据目标平台的python可执行文件进行交叉编译环境的配置。 crossenv可以直接通过Python可执行文件进行调用,接收两个参数,其一为目标平台的Python可执行文件,其二为创建的虚拟环境的文件夹位置。以上命令将在当前目录生成一个名为vcenv的文件夹。

第四步 配置编译杂项

甲 配置python头文件

把对应架构下的pyconfig.h复制到gcc的include文件夹中

cp ~/pine64/usr/include/aarch64-linux-gnu/python3.5m/pyconfig.h /path/to/your/toolchain/aarch-linux-gnu/include/c++/8.3.0/aarch64-linux-gnu/python3.5m/pyconfig.h

目标文件夹为工具链中aarch-linux-gnu/include/c++/8.3.0/aarch64-linux-gnu/python3.5m子目录。理论上来说,只有最后一层文件夹python3.5m是缺失的,需要手动创建。

乙 修改strip

strip v. 脱掉;脱去(衣服)

使用该命令可以脱除生成的文件中多余的符号信息,缩小目标文件的大小,为cmake自动调用。然而不知为何,cmake咬死了只认/usr/bin/strip,导致其无法正确读取编译生成的文件,进而报错,阻碍了下一步工作的进行。

莫得办法,好在由于交叉编译不需要宿主机的工具链,使用取巧的办法,暂时屏蔽原有的strip,换成交叉编译所需要的。 备份原有strip,把交叉工具链的软链接一下,因为cmake不认识环境变量定义的strip。

sudo mv /usr/bin/strip /usr/bin/strip-host
sudo ln -s /path/to/toolchain/bin/aarch64-linux-gnu-strip /usr/bin/strip

完成编译后记得撤销操作

sudo rm /usr/bin/strip
sudo mv /ust/bin/strip-host /usr/bin/strip

第六步 编译打包

甲 编译

python setup.py build

乙 打包

python setup.py bdist_egg

轮子格式的参数为bdist_wheel

第七步 安装,完事

egg格式的可以使用easy_install命令直接安装。 whl格式的使用pip install命令安装。

不知为啥,我这里安装完以后又会自动重新开始下载编译dlib,取消就行了,再进Python发现已经可以import dlib了。 但是由于使用的工具链是gcc8,使用了glibc2.28的特性。而系统编译时只使用了2.23,故需要升级glibc方可测试所编译的dlib库是否可以正常运行。该过程将另起一篇文章叙述。

PYNQ-Z2的更麻烦,是32位系统。。。需要32位的python(咕咕)

=======================================================

后续补充
麻烦个吉尔,还简单些。采用的交叉工具链使用的glibc版本为2.28,而pine64所使用的glibc版本为2.23。然而这里兼容性恰好相反,pine64的系统只支持最高版本为2.23,而实际编译好的库需要使用2.27。glibc相当于系统的地基了,又不能轻易动。。。反倒是pynq还好,是近两年出来的,用的glibc库似乎恰好是2.27,编个32位的python,直接交叉编译,完成就能用。

也就是说,要在pine64上运行dlib的话,只有两个方法了:一个是拿更新的glibc(>=2.27)重新编译系统,另一个是编译一个更老的gcc(<=2.23)用于编译dlib。

=======================================================

我又回来了
可以利用虚拟内存完成编译
临时性地扩大总可用内存容量

dd if=/dev/zero of=/home/esper/tmp.swp bs=1024K count=1024
sudo chown root && sudo chgrp root && sudo chmod 600
sudo mkswap /home/esper/tmp/tmp.swp
sudo swapon /home/esper/tmp/tmp.swp

关机/重启会卸载虚拟内存,如何持久性地增大虚拟内存百度即可。

起因是高二的时候,看到有100+ RMB的SBC,还能运行Android。
当时父亲还是比较支持我去体验一下新东西的,于是便央求着父亲给我买了一块。
彼时的Pine A64有三个版本,分别是内存512MB,1GB,2GB的。存储空间全部靠外插卡,系统也从此处启动。
当时省钱观念还是比较强的,在考虑用途的同时选择了折中的1GB版。
然后拿它打了半年的废狗,当时用的三棒手机连cpu都没写是哪家的
然而在后来才发现java是个内存怪兽,早知道就直接上2GB的了。
然而,现在2GB的变成了Long Term Supply版,结构也重新被设计过,内存从DDR3L换成了LPDDR3,即低功耗版。
也就是说,现在的2GB的Pine A64已经不能使用以前的镜像了。
不过问题不大,现在看来玩玩GPIO,Python,以及搭建个小服务器什么的,性能还是绰绰有余的。
然后当时为了省钱,做了另一件事情——没有买配套的Wifi+Bluetooth模块。
其结果就是,每当要上网的时候,就要找个有网线的地方(捂脸)。
其实当时手头还是有usb的wifi模块的——万能的RTL88188EU
当时也试过在台式机上给这块网卡编译驱动,不过当时什么都不懂,看着教程一路makemake install就完事了。
后来才发现在半闭源的Android上编译驱动是件难事,而当时也几乎不懂Linux。

1.jpg

到了大学之后,有了更多的时间来研究这个东西,也学习了更多Linux和硬件相关的知识。
在全球巡回演唱会级别的咕咕之后,终于把这个问题解决。步骤如下:

其之一 获取内核源码

首先uname -a一下,从里面找出内核版本kernel version。我的是3.10.104

2.jpg

再到对应的内核仓库repository去看一下,找到对应的发行版本release
仓库地址:https://github.com/longsleep/linux-pine64
点releases去查看所有的发行版本。找到对应的源码source code包如下:

3.jpg

其中第四组数字为内核的修补号,对内核功能没有什么影响。这里选择最新的3.10.104-2-pine64版本。
下载后,使用对应的命令解压,应有如下的目录结构:

4.jpg

其之二 编译内核

在此采用~/Desktop/linux-pine64-3.10.104-2-pine64作为临时目录。

为稳妥起见其实是看不懂参数,获取当前内核的配置文件,作为接下来的配置文件。

cd ~/Desktop/linux-pine64-3.10.104-2-pine64
make mrproper  # clean all previously generated file
cp /proc/config.gz .
gunzip config
mv config .config

就在源码的根目录下直接完成了配置。

然后就直接是make编译生成内核。由于本人在编译gcc时深受-j,即多线程multithreading参数的折磨,再加上没贴散热片怕CPU烧掉,这里采用单线程编译。(当然有兴趣的可以试试-j(自动设置为最大可用线程数)或者-jX(指定为X线程编译))

放了一晚上(也不知道多久),编译完成。

其之三 安装内核头文件kernel_headers

这个编译镜像的人是真的让人服气,tmd一块开发板,你还把头文件排除在外,搞个红红的软链接soft link放在镜像里。
结果,凡是想要编译和内核相关模块的全都搞不了,况且这东西还不知道apt上的general版能不能适配。
好在内核的开发者为我们提供了继续开发的希望,将其中一些工序写成了脚本提供出来,其中恰好有我们需要的。
https://github.com/longsleep/build-pine64-image/blob/master/kernel/install_kernel_headers.sh
没错就是上面这个。
我是直接把脚本的部分内容复制出来执行(为了学习理解),有用的部分从27行开始到最后都是。
理论上把28行删掉,就能够直接将脚本在Pine A64上运行,但是我没有测试。(本机编译不需要跨平台)
其实感觉差不多就是直接运行脚本了。。
我的配置如下:

LINUX_ARCH=arm64
CROSS_COMPILE=
DEST=
TARGET=$DEST/usr/src/linux-pine64-3.10.104-2-pine64

完成一系列命令后,kernel_headers安装完成。
文件目录应当如下:

5.jpg

再在/lib/modules/<kernel-version>下配置好相关的文件链接:

sudo rm -f build
sudo ln -s /usr/src/linux-pine64-3.10.104-2-pine64 $(pwd)/build

这样系统就能正常调用了。

由于系统自身的架构声明为aarch64,实际使用的却是arm64的配置文件,推荐进行以下操作:

sudo ln -s /usr/src/linux-pine64-3.10.104-2-pine64/arch/arm64 \
/usr/src/linux-pine64-3.10.104-2-pine64/arch/aarch64

就可以让某些应用程序编译时获得正确的配置文件。(下文即是其中之一)

其之四 编译网卡驱动

这一部分是写起来最快乐的,因为步骤和一般x86 PC上的没有任何区别。
make allsudo make install,完事!

然后sudo modprobe 8188eu即可加载驱动。

附注:

  1. 请务必确保在安装好的头文件目录下有一个Module.symvers文件,该文件用于链接符号和对应的操作码。 若是缺少的话,加载驱动会失败,且在dmesg中会看到如下信息: 6.jpg 其中8188eu模块报出的是没有Module.symvers文件时的错误,下面的R8188EU是操作正确下发出的信息。 Module.symvers由内核编译时动态生成,这也是前面为什么要编译内核的原因。

  2. 如果有已经配置好的内核源码的话,可以通过使用make modules_prepare进行编译驱动模块的辅助程序配置。 该命令可以解决的问题诸如scripts/basic/fixdep not found一类的问题。

啊,写到下午一点,差不多写完了,肚子早饿了,吃饭去。

几年前看杂志的时候,看到了SBC(Single Board Computer)专区。当时一眼见到只需要两位数RMB价格的【电脑】,现在还记忆犹新。然后,就有了我现在面前的这块Pine64开发板。它一直在我柜子里吃灰,直到最近我在linux方面有了一定的了解后,它才得以重见天日。(Pine64: QAQ)

之前我的想法是装Android来看看电视,打打游戏什么的,之后由于硬件匮乏,不了了之。

现在打算了解一下linux的结构和功能,然后到wiki上找了个ubuntu装上,结果。。编译无线网卡驱动的时候傻眼了,没有内核头文件。到apt上去下,又只有4.x的包,望着这个3.10.104的kernel,我傻眼了。仔细想想,kernel headers说不定就是kernel编译时用到的文件。然后就到longsleep大牛的github里翻到了对应的内核源码。然后对照一台正常amd64架构kernel headers的文件结构看了一下,发现两者区别不大,就依葫芦画瓢,放到/usr/src/下,再跟/lib/modules/<kernel-name>/build一类的相关文件(夹)做好软链接。

然后就遇到了arm64aarch64的问题。按照网络上的说法,两者虽然相似,但还是有区别的。但是看到内核的build脚本里用的也是arm64就直接把arm64软链接到了aarch64,然后居然还没出什么问题。

之后根据make的提示,对内核包做了一下预编译以使它可以参与编译驱动(预警:预编译配置将会面临巨大数量的选项,基本看不懂,看得我一脸懵逼)。然后就又出错了。这回的错误好像与内核的版本有关。在网络上查到这么一条解决方案,但是并没有看懂:https://blog.csdn.net/playboysssss/article/details/52493751

至于标题中的这个“伪”字,用意为提示,因为我也不知道这样到底是不是正确方法。如果我的猜测没有问题的话,kernel headers应该算是配好了。至于编译网卡驱动还有没有后续,就要看缘分了……

==========================================================

啊哈!然而当你看到这里的时候,会得到这样一个消息:实际上,这篇教程是不完整而错误的(逃

由于这也算是前期学习的过程记录,我也不打算删除,不过贴一个跳转:Pine A64 Xenial Mate [20161215] 安装 kernel_headers + USB网卡驱动

上面这个是完整版的哟

惊了,才发现把故事讲了两遍 但是显然第二遍讲的更好啊