Mr. Kin's Blog

计算机知识分享/软件应用讲解

本文绝大部分命令都是在sudo -i切换到root权限后操作的。

1   测试项目

2   VideoPose3d

2.1   训练推理自定义视频

设置过程参见:Inference in the wild

2.1.1   安装ffmpeg

1
sudo apt install ffmpeg

2.1.2   安装detectron2

官方提供的whl安装包并没有arm架构,因此需手动从源码构建安装。

也不要用命令安装最新版的detectron2:python -m pip install 'git+https://github.com/facebookresearch/detectron2.git',因为新版detectron2要求python3.8版本。问题参见:Cannot import name 'cached_property'

配置流程参照:Installation

1
2
3
4
5
6
7
8
9
# 方案1:直接pip安装
# 指定特定commit,安装适合python3.7的版本
pip3.7 install 'git+https://github.com/facebookresearch/detectron2.git@d779ea63faa54fe42b9b4c280365eaafccb280d6'

# 方案2:手动从源码构建安装
# 如果网络不佳,直接访问网页下载压缩包:https://github.com/facebookresearch/detectron2/tree/d779ea63faa54fe42b9b4c280365eaafccb280d6
# zip包链接:https://github.com/facebookresearch/detectron2/archive/d779ea63faa54fe42b9b4c280365eaafccb280d6.zip
git clone -b d779ea63faa54fe42b9b4c280365eaafccb280d6 https://github.com/facebookresearch/detectron2.git
python3.7 -m pip install -e detectron2

2.1.3   下载预训练模型

1
2
3
# 如果VideoPose3D目录下没有checkpoint文件夹的话,先mkdir checkpoint
cd checkpoint
wget https://dl.fbaipublicfiles.com/video-pose-3d/pretrained_h36m_detectron_coco.bin

2.1.4   推理2D关键点

  1. 在 inference 文件夹中新建两个文件夹 input_directory 和 output_directory,input_directory用来存放需要处理的自定义视频,output_directory用来存放程序生成的每个视频的2D关键点数据文件,后缀名是.npz。
  2. 将需要处理自定义视频放入VideoPose3D/inference/input_directory/中,切记需要先放入视频后再执行后面的命令。若不提前放入视频,最后执行run.py是会出现报错:keyError:output.mp4。其中「推理2D关键点」步骤中不会有ffmpeg相关视频处理的日志输出,「创建自定义数据集」步骤中不会显示出处理了xxx.npz文件和处理了多少帧frame,只输出saving和done这两句简单的日志,并且这种情况在data目录中生成data_2d_custom_myvideos.npz的数据集文件只有798字节,不到1KB的大小,属于无效文件。
  3. 执行命令推理2D关键点
1
2
3
4
5
6
cd inference
python3.7 infer_video_d2.py \
--cfg COCO-Keypoints/keypoint_rcnn_R_101_FPN_3x.yaml \
--output-dir output_directory \
--image-ext mp4 \
input_directory
  1. 创建自定义数据集:程序根据VideoPose3D/inference/output_directory/的每个视频的2D关键点数据文件.npz,在data目录下生成自定义的data_2d_custom_myvideos.npz文件。
1
2
3
cd data
python3.7 prepare_data_2d_custom.py -i /home/nvidia/VideoPose3D/inference/output_directory/ -o myvideos
cd ..
  1. 渲染自定义视频并导出坐标:output.mp4是在VideoPose3D根目录下,不是inference/output_directory/。
1
2
3
python3.7 run.py -d custom -k myvideos -arc 3,3,3,3,3 -c checkpoint --evaluate pretrained_h36m_detectron_coco.bin --render --viz-subject test_video.mp4 --viz-action custom --viz-camera 0 --viz-video /home/nvidia/VideoPose3D/inference/input_directory/test_video.mp4 --viz-output output.mp4 --viz-size 6
# 下面输入视频路径是绝对路径
# python3.7 run.py -d custom -k myvideos -arc 3,3,3,3,3 -c checkpoint --evaluate pretrained_h36m_detectron_coco.bin --render --viz-subject test_video.mp4 --viz-action custom --viz-camera 0 --viz-video /home/nvidia/VideoPose3D/inference/input_directory/test_video.mp4 --viz-output output.mp4 --viz-size 6

2.2   训练推理h36m视频

数据集设置步骤参考:Dataset setup

  1. 准备数据文件:在data目录下新建h36m文件夹,传入3d数据集的压缩包,目录结构图如下
1
2
3
4
5
6
7
8
9
data/
└── h36m/
├── Poses_D3_Positions_S1.tgz
├── Poses_D3_Positions_S5.tgz
├── Poses_D3_Positions_S6.tgz
├── Poses_D3_Positions_S7.tgz
├── Poses_D3_Positions_S8.tgz
├── Poses_D3_Positions_S9.tgz
└── Poses_D3_Positions_S11.tgz
  1. 数据处理
1
2
3
4
5
6
7
8
# 解压数据集:
cd /home/nvidia/data/h36m/
for file in *.tgz; do tar -xvzf $file; done

cd /home/nvidia/data
pip3.7 install cdflib
python3.7 prepare_data_h36m.py --from-source-cdf /home/nvidia/data/h36m/
# 运行成功后,data目录下生成两个文件:data_3d_h36m.npz和data_2d_h36m_gt.npz。
  1. 下载预训练模型
1
2
3
4
5
mkdir checkpoint
cd checkpoint
wget https://dl.fbaipublicfiles.com/video-pose-3d/pretrained_h36m_cpn.bin
wget https://dl.fbaipublicfiles.com/video-pose-3d/pretrained_humaneva15_detectron.bin
cd ..
  1. 测试Human3.6M模型数据
1
python run.py -k cpn_ft_h36m_dbb -arc 3,3,3,3,3 -c checkpoint --evaluate pretrained_h36m_cpn.bin

3   HRFAE

官方要求的依赖库(注意torch需要是cuda版本的,因为源码是使用cuda设备的方法)

  • Python 3.7
  • Pytorch 1.1
  • Numpy
  • Opencv
  • TensorboardX
  • Tensorboard_logger

别人测试成功的依赖库版本:参见成功运行HRFAE面部年龄编辑

  • Python 3.7.13
  • Pytorch 1.10.2
  • Numpy 1.21.5
  • Opencv 4.6.0
  • Tensorboard 1.14.0
  • TensorboardX
  • Tensorboard-logger

3.1   预训练模型的配置步骤

  1. 安装依赖库
1
pip3.7 install TensorboardX Tensorboard_logger
  1. 修改test.py代码
1
2
3
4
cd HRFAE
nano test.py
# 找到下面该行代码,将load函数,改成safe_load或者full_load
# config = yaml.load(open('./configs/' + opts.config + '.yaml', 'r'))
  1. 下载预训练模型
1
2
3
4
5
# 科学网络条件好的,可以直接参考官方命令下载checkpoint
cd ./logs/001
./download.sh
# 如果网络条件不行的话,直接使用链接下载后在传入logs目录,注意上传后记得修改属性权限755或者777
# https://partage.imt.fr/index.php/s/7DzqFHQggfJDw79/download
  1. 运行命令生成结果
1
2
3
cd HRFAE
# 年龄区间:20-70
python3.7 test.py --config 001 --target_age 40

3.2   进程被Killed的故障排查

现象:终端输出一句「Killed」后,进程就闪退退出了。

原因:这是由于pytroch程序占用过多内存,引起系统OOM(Out of memory)机制。

解决方式:无。在我的尝试方法中,尝试了增加虚拟内存的大小至13GB,仍然是无法解决,依然是触发OOM了(即使我对pytorch进程禁用系统OOM killer,并观察内存占用情况,物理4GB全部吃满,虚拟内存zram占用到3.6GB后就整个系统卡死了)。推测可能还因为显存不足。

内存管理的相关命令:

1
2
3
4
5
6
7
8
9
# 查看虚拟内存使用分配机制,vm.swappiness = 0:最大限度使用物理内存,vm.swappiness = 100:积极的使用交换空间
sysctl -a| grep vm.swappiness
# vm.swappiness = 60
# 临时设置swappiness
echo 10 > /proc/sys/vm/swappiness
# 查看内存使用率高的进程id
top
# 对某个进程禁用系统OOM killer,$PID替换为进程ID数字即可
echo -17 > /proc/$PID/oom_adj

查看系统是否主动杀掉进程的日志排查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看系统内核日志中最后7行
sudo dmesg | tail -7

# 通过以下三种命令查看系统是否主动杀死程序进程
# 将dmesg命令的输出通过管道传递给egrep命令进行过滤。egrep命令使用正则表达式'killed process'来匹配包含"killed process"的行,并且使用-i选项忽略大小写。-B100选项会在匹配到的行之前显示100行的上下文。
dmesg | egrep -i -B100 'killed process'
# 在/var/log/messages文件中搜索包含"killed process"的行,并且使用-i选项忽略大小写
egrep -i 'killed process' /var/log/messages
# 在/var/log目录及其子目录下递归地搜索包含"killed process"的行,并且使用-i选项忽略大小写
egrep -i -r 'killed process' /var/log
# 将journalctl -xb命令的输出通过管道传递给egrep命令进行过滤。journalctl -xb命令用于查看系统启动时的日志,并且使用-i选项忽略大小写
journalctl -xb | egrep -i 'killed process'

# 如果未发现/var/log/messages文件,编辑50-default.conf文件,取消注释一行内容
nano /etc/rsyslog.d/50-default.conf
# mail,news.none -/var/log/messages

3.3   自训练模型的配置步骤

3.4   Windows x86版环境搭建

  1. 安装miniconda3:跳转下载
  2. 创建并切换新的虚拟环境
1
2
conda create -n hrfae python=3.7
conda activate hrfae
  1. 先下载并安装torch和torchvision的cuda版whl(建议先关闭科学上网,避免浪费科学上网的流量),使用pip install即可:torch-1.13.1+cu117-cp37-cp37m-win_amd64.whltorchvision-0.14.1+cu117-cp37-cp37m-win_amd64.whl。别使用conda install torch安装torch,否则易出现一些版本依赖的问题。例如:解决python urllib3 v2.0 only supports OpenSSL 1.1.1+, currently。这是因为openSSL版本太低,可以安装低版本解决:pip install urllib3==1.23 -i https://pypi.tuna.tsinghua.edu.cn/simple
  2. 之后一一安装其他库,如果conda install找不到库,就使用pip install,例如Tensorboard-logger

4   硬件型号

NVIDIA Jetson TX2 NX,16GB eMMC,额外挂载一个120G的固态盘。

5   烧录系统

5.1   烧录前准备

  • 一台Ubuntu 18.00 LTS系统的PC(其他的jetson设备不能作为刷机主机,必须用ubuntu电脑。也不能用vbox或者vmware之类的虚拟机创建ubuntu刷机主机,因为虚拟机系统无法处理usb等底层的驱动,这会导致刷机时一直提示未正确进入刷机模式,但实际机器已经真正进入到刷机模式的)
  • 一条micro-usb数据线
  • 根据硬件型号选择对应版本驱动包(BSP包)示例根文件系统(目前适配TX2 NX的最新版为R32.7.4)。

5.2   烧录步骤

  1. 在刷机主机ubuntu系统中启用ssh。
  2. 通过WinSCP将驱动包(BSP包)和示例根文件系统复制至刷机主机系统中的家目录/home
  3. 刷机命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 安装必要库
sudo apt-get install qemu-user-static
sudo apt-get install python
# j为使用bzip2算法,x为解压,v为显示操作的详细信息,f为指定操作的文件
sudo tar -jxvf Jetson_Linux_R32.7.4_aarch64.tbz2
cd Linux_for_Tegra/rootfs
# p为于保留文件的权限和属性,即保持原有文件的权限模式和时间戳信息
sudo tar -jxpf ../../Tegra_Linux_Sample-Root-Filesystem_R32.7.4_aarch64.tbz2
cd ..
# 拷贝NVIDIA库文件到文件系统
sudo ./apply_binaries.sh
cd Linux_for_Tegra/tools
# u为用户名,p为密码
sudo ./l4t_create_default_user.sh -u nvidia -p nvidia
# NVIDIA Jetson是通过Micro USB接口烧录系统。Recovery模式下可以进行文件系统更新包含:内核Kernel,启动Bootloader,文件系统Rootfs等。
# 进入Recovery模式的步骤:
# 1)使用Micro USB 的数据线连接Jetson和Ubuntu Host主机(一端插在开发板的OTG口,一端插在Ubuntu Host主机的usb插口);
# 2)连接开发板的电源;
# 3) 按住RECOVERY按键,同时按下RESET复位按键1-2秒后释放,等待3秒释放RECOVERY按键,此时开发板进入Recovery刷机模式 (可通过在Ubuntu Host主机上运行命令:lsusb查看是否有Nvidia Corporation 设备(不同Jetson模块USB VID/PID 不同)来确认是否进入正常)
# 执行刷机命令
cd Linux_for_Tegra
sudo ./flash.sh jetson-xavier-nx-devkit-tx2-nx mmcblk0p1

5.3   烧录后无法进入到桌面环境

Jetson报错无法进到桌面环境: Failed to start nvpmode1 server. 和Failed to start load kernel modules

无桌面环境的条件下进入到终端操作的两种方式:

  • ssh连接虚拟ip:jetson的usb有虚拟ip,可以通过ssh远程连接,用上位机通过USB数据线(有的USB线不能传数据只能充电,因为只有电源线和地线)连接jetson。打开上位机远程登陆开发板,登陆ip为:192.168.55.1:ssh nvidia@192.168.55.1
  • 按ctrl+alt+F1-F6等组合键切换命令窗口1-6

Failed to start nvpmode1 server一般是桌面环境出问题引起的,重装桌面环境即可。

1
2
sudo apt-get install nvidia-l4t-x11
sudo reboot

Failed to start load kernel modules一般配置方面出问题,重新运行更新配置。

1
2
3
4
5
6
7
# 切换到root模式,并保留当前终端环境
sudo -i
apt-get update
dpkg --configure -a
apt-get dist-upgrade
apt-get -f install
reboot

5.4   为TX2 NX这种老硬件安装Ubuntu 20.04

为Jetson TX2 NX或者Jetson Nano这种老硬件安装Ubuntu 20.04(官方最新只支持Ubuntu 18.04)的两种方式:

6   为开发板设置SSD固态为系统盘

NVMe SSD固态硬盘仅作为系统盘(rootfs和用户区),系统的启动引导依然是通过SD卡或者内置EMMC的存储,比如升级设备树dtb仍然是在SD卡或者EMMC中。

  1. 格式化硬盘:打开软件列表搜索disk,打开ubuntu自带的Disks工具,选择识别到的SSD,按Ctrl+F对硬盘进行快速格式化,点击Format(不覆盖已存在的数据),点击Format(只是弹窗查看确认该操作影响到的设备),输入密码提权操作。默认最大分区,直接下一步。分区名字填ssd,其他选项默认(Type:ext4),点击create创建。点击分区左下角的三角符号(▶)进行挂载(状态变化:Not Mounted -> Mounted at /media/nvidia/ssd)。
  2. 下载系统盘转换程序的源码:git clone https://github.com/jetsonhacks/rootOnNVMe.git。如果克隆仓库失败,访问网页下载压缩包,再解压通过WinSCP上传到开发板家目录(上传后记得将文件的权限设置为0755,否则运行程序时会提示无权限)。
1
2
3
4
5
6
cd rootOnNVMe
# 这个过程耗时较久,请耐心等待执行完毕再执行下一步
# 建议是刚烧录好系统之后就执行这一步转换,否则EMMC数据一旦很多的话,这步操作的转换过程十分漫长
./copy-rootfs-ssd.sh
./setup-service.sh
sudo reboot
  1. 运行命令df -h:可以看到挂载根目录的分区大小已经是SSD固态硬盘的120G的容量,不是以前EMMC的16G。

7   Jetson TX2更换软件源

备份并编辑软件源

1
2
3
4
5
6
# 备份/etc/lib路径下的source.list文件,
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
# i进入编辑模式。按esc键退出模式,先按gg(到达顶部),然后dG全部删除。:wq保存编辑并退出vi。
sudo vim /etc/apt/sources.list
# 编辑软件源后,更新拉取源表
sudo apt update

清华源:

1
2
3
4
5
6
7
8
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main universe restricted
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main universe restricted

假如默认官方源被删除的话,可以用下面的可用官方源恢复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted
deb http://ports.ubuntu.com/ubuntu-ports/ bionic universe
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic universe
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates universe
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates universe
deb http://ports.ubuntu.com/ubuntu-ports/ bionic multiverse
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic multiverse
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates multiverse
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates multiverse
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted universe multiverse
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted universe multiverse
# deb http://archive.canonical.com/ubuntu bionic partner
# deb-src http://archive.canonical.com/ubuntu bionic partner
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security universe
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security universe
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security multiverse
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security multiverse

8   配置python

查看torchvision的README页面,可知torch和torchvision对应版本信息,以及所要求的python版本。

torchtorchvisionPython
1.100.11>=3.6, <=3.9

综合考虑,因为需要安装v1.10.2的pytorch,因此python选用v3.7(很多深度学习项目要求最低的python版本是v3.7),避免v3.8-v3.9可能会因此太新导致编译出错。

8.1   pyton3.6

系统自带pyton3.6.9,运行pip3报错:pip3:command not found

解决方法:sudo apt install python3-pip

8.2   pyton3.7

pyton3.7版本需要手动编译。下载python3.7.16源码压缩包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 通过ssh连接到开发板系统,通过winscp上传python源码压缩包到开发板的家目录
tar -xf Python-3.7.16.tar.xz
# 安装构建pyhon所需的包,使用清华源的话,安装libncurses5库可能会出现库依赖错误,这个需要用ubuntu官方源(bionic源)才能解决,其他库可以用清华源
sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libbz2-dev liblzma-dev
# 或者build-dep参数让ubuntu自动搜索相关包。使用这个命令,记得apt list自行核对下其他额外包有无安装好,参照「系统缺乏库的一些报错」小节。
# 如果build-dep报错:Error :: You must put some 'source' URIs in your sources.list,请启用/etc/apt/sources.list文件中的deb-src源,即取消注释。修改完成后重新运行sudo apt update。
sudo apt build-dep python3
# prefix配置安装路径,方便卸载时直接删除prefix的路径即可
./configure --prefix=/usr/local/python3.7

# 假设使用conda创建虚拟环境的话,可以用以下方式配置安装路径
# 安装到miniconda环境(base)
./configure --prefix=/root/miniconda3/lib/python3.7

# npro查看处理器核心数,lscpu查看处理器详情,-j无参为使用所有核心,后面跟数字就是并行编译数量
make -j
# 安装python
sudo make install
cd /usr/bin/
# 创建python3.7和pip3.7软链接
sudo ln -s /usr/local/python3.7/bin/python3.7 python3.7
sudo ln -s /usr/local/python3.7/bin/pip3.7 pip3.7
# 配置pip源
pip3.7 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# export bin目录到path
export PATH=$PATH:/usr/local/python3.7/bin

8.2.1   系统缺乏库的一些报错:

  • 缺乏libssl-dev库:pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available。实际上
  • 缺乏libffi-dev库:ModuleNotFoundError: No module named '_ctypes'
  • 缺乏libbz2-dev库(Detectron2需要用到):ModuleNotFoundError: No module named '_bz2'
  • 缺乏liblzma-dev库(Detectron2需要用到):ModuleNotFoundError: No module named '_lzma'

8.2.2   TLS/SSL报错的另类解决方式

如果因为缺乏libssl-dev库,导致编译出来的python报错TLS/SSL的话,实际上这个问题可以通过换pip源解决,必须是http源。

创建并编辑pip配置文件:

1
2
3
4
mkdir ~/.pip
cd ~/.pip
touch pip.conf
nano pip.conf

pip源设置内容:

1
2
3
4
[global]
index-url = http://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host = pypi.tuna.tsinghua.edu.cn

8.2.3   安装系统库时的依赖版本报错

报错内容:libssl-dev : Depends: libssl1.0.0 (= 1.0.2g-1ubuntu4) but 1.0.2g-1ubuntu4.15 is to be installed

报错原因:要装的库OpenSSL,它需要依赖的包是X,但需要的X是A版本,但是系统环境中已经存在另外的程序,它也需要的依赖X,并且需要的是X另外一个版本B,两个版本之间发生冲突了。所以无法安装。

解决方法:sudo apt install libssl1.0.0=1.0.2g-1ubuntu4,即安装推荐的折中版本(报错提示有给出)。

8.3   pypi清华源

  • 临时使用:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
  • 设置默认 (版本需>=10.0.0) :pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
  • 升级pip本体
    • 官方源升级pip:python -m pip install --upgrade pip
    • 清华源升级pip:python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade pip

9   解决报错「Illegal instruction (core dumped)」

为了防止出现 “Illegal instruction (core dumped)” 报错,需要在 ~/.bashrc 最后添加如下语句,保存修改后,重启系统

1
export OPENBLAS_CORETYPE=ARMV8

若是export导出环境变量之后,仍是出现相同的报错的话,则可能是安装包版本问题或者程序包问题。

10   安装jetson-stats查看开发板情况

安装jetson-stats前记得把pip源更换为清华源:sudo -H pip3.7 install -U jetson-stats

安装好jetson-stats之后,可以安装nvidia-jetpack包,使用jetson_release查看开发板信息(注意需提前装好jetson-stats)

1
2
sudo apt install nvidia-jetpack
sudo jetson_release

10.1   The jetson_stats.service is not active

报错情况,jtop(jetson_stats.service)不能运行,systemctl查看服务状态为activating:

1
2
The jetson_stats.service is not active. Please run:
sudo systemctl restart jetson_stats.service

解决方法:把pip源更换为清华源后,重新安装jetson-stats。

11   为开发板安装CUDA、cuDNN、TensorRT库

示例根文件系统并未包含cuda等库,因此烧录系统之后,需要手动安装CUDA、cuDNN、TensorRT库。

  1. 打开sdk-manager下载网站,注册英伟达开发者帐号(注意是developer.nvidia.com域名,搜索nvidia register关键字注册的域名可能是partner.nvidia.com,即nvonline)。
  2. 下载最新的deb安装包,当前最新的是NVIDIA SDK Manager 1.9.3.deb。
  3. 安装sdk-manager:sudo apt install sdkmanager_1.9.3-10904_amd64.deb
  4. 运行sdk-manager:终端运行命令sdk-manager或者在应用程序列表中找到SDKManager图标打开。
  5. 登录帐号(developer开发者帐号会打开浏览器登录,nvonline是直接在软件内页面登录),建议勾选上「Stay logged in」,避免因为安装出错后重复打开应用又要重新登录。
  6. sdk-manager第一步:等待程序自动检测出开发板信息,若没正确识别到开发板,请手动在Target Hardware选择正确的开发板型号(比如我的是TX2 NX型号)。剩余默认即可,附加SDK(DeepStreem)不需要勾选上。
  7. sdk-manager第二步:HOST COMPONENTS设置的是烧录主机的环境(清华镜像源可装),TARGET COMPONENTS(开发板环境,Jetson OS不要勾选,因为前面的步骤已经烧录过了。其他的SDK Components全部勾上)(清华源无法安装,会报错:SDK Manager received errors while using apt commands on your system,需换回官方源,并且是需要编辑开发板上的sources.list,编辑host主机上的sources.list源是没用的)。用micro-usb线连接开发板和烧录主机,点击下一步,提示指定路径不存在,点击create完成创建即可,这里输入烧录主机的密码用来提权。之后弹出一个窗口设置开发板帐号密码信息: 确认开发板型号,IPv4用192.168.55.1(usb连接的虚拟ip),系统账户名和系统密码,其余默认,之后点击Install确认安装库。
  8. sdk-manager第三步:安装过程。
  9. sdk-manager第四步:exit退出。
  10. 设置环境变量:sdk-manager安装好库之后,会自动添加export环境变量语句到用户级的.bashrc配置文件中(注意只是添加而已,仍需手动source ~/.bashrc,确保是在普通账户的终端下执行该命令)。但是root账户目录的.bashrc配置文件需手动配置
1
2
3
4
5
6
7
8
9
10
# 刷新bash配置,普通账户nvidia的终端下执行该命令
source ~/.bashrc
# 切换root权限
sudo -i
# 切换root权限后,工作目录会自动转到root的家目录下
nano .bashrc
# 将以下两条命令添加到.bashrc的底部
export PATH=/usr/local/cuda-10.2/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATH
source .bashrc
  1. 查看cuda库的安装状态的命令:jtopnvcc -V/nvcc --version
  2. 配置 cuDNN:sdk-manager虽然安装了cuDNN,但没有将对应的头文件、库文件放到cuda目录。

不配置这个也可以,但编译opencv with cuda时需要手动指定路径到/usr/include和/usr/lib/aarch64-linux-gnu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# sdk-manager安装路径
# cuDNN 默认安装路径在 /usr/lib/aarch64-linux-gnu
# CUDA 默认安装路径在 /usr/local/cuda 下
# cuDNN的头文件在:/usr/include,库文件位于:/usr/lib/aarch64-linux-gnu

# 复制文件到cuda目录下
cd /usr/include && sudo cp cudnn* /usr/local/cuda-10.2/include
cd /usr/lib/aarch64-linux-gnu && sudo cp libcudnn* /usr/local/cuda-10.2/lib64

# 修改文件权限,修改复制完的头文件与库文件的权限,所有用户都可读,可写,可执行:
sudo chmod 777 /usr/local/cuda-10.2/include/cudnn.h
sudo chmod 777 /usr/local/cuda-10.2/lib64/libcudnn*

# 软链接移动之后就失效了,需重新软链接,这里的8.2.1和8对应安装的cudnn版本号和首数字
cd /usr/local/cuda-10.2/lib64

sudo ln -sf libcudnn.so /etc/alternatives/libcudnn_so
sudo ln -sf libcudnn.so.8.2.1 libcudnn.so.8

sudo ln -sf libcudnn_ops_train.so /etc/alternatives/libcudnn_ops_train_so
sudo ln -sf libcudnn_ops_train.so.8.2.1 libcudnn_ops_train.so.8
sudo ln -sf libcudnn_ops_infer.so /etc/alternatives/libcudnn_ops_infer_so
sudo ln -sf libcudnn_ops_infer.so.8.2.1 libcudnn_ops_infer.so.8

sudo ln -sf libcudnn_adv_train.so /etc/alternatives/libcudnn_adv_train_so
sudo ln -sf libcudnn_adv_train.so.8.2.1 libcudnn_adv_train.so.8
sudo ln -sf libcudnn_adv_infer.so /etc/alternatives/libcudnn_adv_infer_so
sudo ln -sf libcudnn_adv_infer.so.8.2.1 libcudnn_adv_infer.so.8

sudo ln -sf libcudnn_cnn_train.so /etc/alternatives/libcudnn_cnn_train_so
sudo ln -sf libcudnn_cnn_train.so.8.2.1 libcudnn_cnn_train.so.8
sudo ln -sf libcudnn_cnn_infer.so /etc/alternatives/libcudnn_cnn_infer_so
sudo ln -sf libcudnn_cnn_infer.so.8.2.1 libcudnn_cnn_infer.so.8

sudo libcudnn_static.a /etc/alternatives/libcudnn_stlib
# 更新系统动态链接库
sudo ldconfig
  1. 测试Cudnn(可选)
1
2
3
4
5
6
7
8
9
sudo cp -r /usr/src/cudnn_samples_v8/ ~/
cd ~/cudnn_samples_v8/mnistCUDNN
sudo chmod 777 ~/cudnn_samples_v8
sudo make clean && sudo make
./mnistCUDNN

# 配置成功输出:
# Result of classification: 1 3 5
# Test passed!

12   安装pytorch和pytorch vision

12.1   pyton3.6

12.1.1   为python3.6安装pytorch

pyton3.6直接访问nvidia官网PyTorch for Jetson帖子下载官方编译提供的版本:点击跳转下载whl,安装命令:pip3.6 install torch-1.10.0-cp36-cp36m-linux_aarch64.whl

12.1.2   为python3.6编译torchvision

为python3.7编译torchvision

12.2   pyton3.7

12.2.1   为python3.7编译pytorch

注意:编译过程十分耗时,请耐心等待!我测试的机器型号为TX2 NX,编译耗时23个小时(大部分时间都是停留在编译caffe2仓库)。

由于英伟达官方只提供python3.6版本的pytorch安装包,并且torch库官方下载站也没有arm芯片(aarch)的cu102版whl安装包,cpu版倒是有提供,因此pyton3.7的cuda版pytorch需要自行从源码编译。

编译步骤参考nvidia官网PyTorch for Jetson帖子的Instructions小节的Build from Source内容。

  1. 设置开发板功率模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设置最大功率模式,CPU和GPU频率最大
# on Xavier NX, use -m 2 instead (15W 6-core mode)
sudo nvpmodel -m 0
# 开启最大性能(重启开发板就会自动失效),运行命令不加任何参数就是将CPU、GPU、EMC的频率设置到最大
sudo jetson_clocks
# 查询当前工作模式
sudo nvpmodel -q verbose
# 查看当前硬件频率情况
sudo jetson_clocks --show
# 如果是想要临时开启最大性能模式jetson_clocks
# 请在开启之前,运行sudo jetson_clocks --store记录普通状态的设置
# 之后运行sudo jetson_clocks开启最大性能
# 完成任务后运行sudo jetson_clocks --restore恢复普通状态的设置,就是关闭最大性能模式
# 如果开启最大性能模式之前没有sudo jetson_clocks --store记录状态,那么只能是重启机器,再运行设置
  1. 下载PyTorch源码(v1.10.2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
cd /home/nvidia
# 其实不推荐用--recursive递归克隆git子项目,容易出现克隆失败
git clone --recursive --branch v1.10.2 http://github.com/pytorch/pytorch
# 假如已经执行了上述命令,如果只有子项目报错的话,可以试试直接执行git submodule update --recursive
# 下面是pytorch官方README给出的方法
# if you are updating an existing checkout
git submodule sync
git submodule update --init --recursive --jobs 0

# 推荐方式,先克隆主项目,再克隆子项目
git clone --branch v1.10.2 http://github.com/pytorch/pytorch
cd pytorch
git submodule update --init --recursive
# 如果出现失败,重复执行这条命令,直到克隆成功
git submodule update --recursive
# 我的情况在克隆子项目时,无论是init还是update阶段,linux-syscall-support这个仓库总是克隆不下来,因为它是chromium.googlesource.com域名,没法裸连成功。在github上搜索到镜像仓库https://github.com/cpp-pm/linux-syscall-support(如果是其他的github上的镜像仓库,要注意查看该仓库是否及时从上游获取更新,确保包含有e1e7b0ad8ee99a875b272c8e33e308472e897660这个commit,否则递归处理会提示找不到这个commit)
# 在使用https://github.com/cpp-pm/linux-syscall-support镜像仓库时,我仍然出现了连接失败。奇怪的是,其他github链接可以正常连接,唯独这个库连接不上。没法子,通过ping github.com得到一个返回ip:20.205.243.166。手动指向ip的话,需要关闭ssl证书验证,不然很容易报错(尤其是http的ssl证书验证)
# 如果无法直接ping得到ip,访问网页:https://www.ipaddress.com/,输入对应域名,比如raw.githubusercontent.com,查看返回的真实ip。
# 设置git重定向仓库链接,直接指向ip地址
git config --global http.sslVerify false
git config --global https.sslVerify false
git config --global url."https://20.205.243.166/cpp-pm/linux-syscall-support".insteadOf "https://chromium.googlesource.com/linux-syscall-support"
# 或者编辑git配置文件,在.gitconfig文件中添加如下参数
# 如果仍然不起作用的话,可以在http类目下添加允许重定向:followRedirects = true。命令操作:git config --global http.followRedirects true
nano ~/.gitconfig
[http]
sslverify = false
[https]
sslverify = false
[url "https://20.205.243.166/cpp-pm/linux-syscall-support"]
insteadOf = https://chromium.googlesource.com/linux-syscall-support

# chromium.googlesource.com还可以尝试Agora.io提供的镜像网站,具体参考内容https://webrtc.agora.io/mirror/
# 但我的情况设置了,依旧也是连接超时
# git config --global url.http://120.92.49.206:3232/chromiumsrc/linux-syscall-support.git.insteadOf https://chromium.googlesource.com/linux-syscall-support.git

# 假设在克隆或者构建子项目出错时,想全部清除所有子项目的话,通过下面的命令完成(下面的目录是我对.gitmodules文件通过正则表达式处理得到的)
# cd pytorch
# rm third_party/pybind11 third_party/cub third_party/eigen third_party/googletest third_party/benchmark third_party/protobuf third_party/ios-cmake third_party/NNPACK third_party/gloo third_party/pthreadpool third_party/FXdiv third_party/FP16 third_party/psimd third_party/zstd third_party/cpuinfo third_party/python-enum third_party/python-peachpy third_party/python-six third_party/onnx third_party/onnx-tensorrt third_party/sleef third_party/ideep third_party/nccl/nccl third_party/gemmlowp/gemmlowp third_party/QNNPACK third_party/neon2sse third_party/fbgemm third_party/foxi third_party/tbb android/libs/fbjni third_party/XNNPACK third_party/fmt third_party/tensorpipe third_party/cudnn_frontend third_party/kineto third_party/pocketfft third_party/breakpad
  1. 根据patch内容手动为patch新修复的代码:就是根据patch文件内容查找对应文件的对应行数,同步改动的代码。如果懒得改动的话,直接下载我修改好的文件,上传替换就行。patch代码主要解决编译报错的问题(详见编译步骤参考帖子)
1
2
3
4
5
6
7
8
cd pytorch
# 因为WinSCP使用普通账户连接的rscp,权限不够,因此建议先直接用vscode编辑文件,之后再上传到家目录,用root权限替换掉对应文件。
mv aten/src/ATen/cpu/vec/vec256/vec256_float_neon.h aten/src/ATen/cpu/vec/vec256/vec256_float_neon.h.bak
mv ../vec256_float_neon.h aten/src/ATen/cpu/vec/vec256/vec256_float_neon.h
mv aten/src/ATen/cuda/CUDAContext.cpp aten/src/ATen/cuda/CUDAContext.cpp.bak
mv ../CUDAContext.cpp aten/src/ATen/cuda/CUDAContext.cpp
mv aten/src/ATen/cuda/detail/KernelUtils.h aten/src/ATen/cuda/detail/KernelUtils.h.bak
mv ../KernelUtils.h aten/src/ATen/cuda/detail/KernelUtils.h
  1. 设置构建参数的环境变量(如果有改变终端,记得重新export这些环境变量)
1
2
3
4
5
6
7
8
9
10
export USE_NCCL=0
# skip setting this if you want to enable OpenMPI backend
export USE_DISTRIBUTED=0
export USE_QNNPACK=0
export USE_PYTORCH_QNNPACK=0
# or "7.2;8.7" for JetPack 5 wheels for Xavier/Orin
export TORCH_CUDA_ARCH_LIST="5.3;6.2;7.2"
# without the leading 'v', e.g. 1.3.0 for PyTorch v1.3.0
export PYTORCH_BUILD_VERSION=1.10.2
export PYTORCH_BUILD_NUMBER=1
  1. 安装构建必要的系统库:sudo apt install cmake libopenblas-dev libopenmpi-dev,这里我使用的ubuntu官方源。
  2. 编译源码构建程序(在构建之前建议先备份pytorch文件夹,毕竟克隆不容易:cp -r pytorch pytorch.bak):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cd pytorch
# 安装构建必要的pip3.7库
pip3.7 install -r requirements.txt
pip3.7 install scikit-build
pip3.7 install ninja
# 开始编译源码构建程序,生成的安装包输出在dist文件夹:torch-1.10.2-cp37-cp37m-linux_aarch64.whl
python3.7 setup.py bdist_wheel
cd dist
pip3.7 install torch-1.10.2-cp37-cp37m-linux_aarch64.whl

# 在编译过程中,反复出现一个报错:c++: internal compiler error: 已杀死 (program cc1plus)
# 原因:swap交换空间不足,使用jtop命令查看当前开发板只有系统分配的1.9G的zram交换空间,需手动增加swap空间
# 弃用以下传统的swapfile方法,修改zram的大小就好
# 查看所有zram相关文件
find / -name "*zram*" 2>/dev/null
# 编辑zram配置文件
nano /etc/systemd/nvzramconfig.sh
# 修改前
mem=$(((totalmem / 2 / ${NRDEVICES}) * 1024))
#
# 修改后zram总大小为物理内存大小的三倍
mem=$(((totalmem * 3 / ${NRDEVICES}) * 1024))
# 查看zram情况
zramctl

# 新增swapfile文件大小自定义
sudo fallocate -l 8G /var/swapfile
# 配置该文件的权限
sudo chmod 600 /var/swapfile
# 建立交换分区
sudo mkswap /var/swapfile
# 启用交换分区,再次运行jtop,可看到swap空间已经变成了10G
sudo swapon /var/swapfile
# 设置为自动启用swapfile
sudo bash -c 'echo "/var/swapfile swap swap defaults 0 0" >> /etc/fstab'
# 关闭交换分区
sudo swapoff /var/swapfile
# 在现有的/var//swapfile后面追加30GB(增加容量后,请手动mkswap及swapon)
sudo dd if=/dev/zero of=/var/swapfile bs=1M count=30720 oflag=append conv=notrunc
# 删除交换分区文件
sudo rm /var/swapfile
# 删除fstab相关设置
nano /etc/fstab
  1. 检测pytorch安装是否成功
1
2
3
4
5
6
7
8
9
python3.7
import torch
# 输出True安装成功
torch.cuda.is_available()
torch.backends.cudnn.is_available()
# 查看cuda版本
print(torch.version.cuda)
# 查看cudnn版本
print(torch.backends.cudnn.version())

12.2.2   为python3.7编译torchvision

查看torchvision的README页面,可知torch和torchvision对应版本信息,以及所要求的python版本。

torchtorchvisionPython
1.100.11>=3.6, <=3.9

编译步骤参阅:Development installation

下载及编译torchvision源码:

1
2
3
4
5
6
7
8
9
10
11
# 我的网络环境没法直连github域名,手动指向cdn的ip(ping github.com得到ip),确保git的全局设置http的ssl证书验证关闭
# git clone -b v0.11.3 https://github.com/pytorch/vision.git torchvision
git clone -b v0.11.3 https://20.205.243.166/pytorch/vision.git torchvision
cd torchvision
sudo apt install libpng-dev libjpeg-turbo8-dev
# 设置编译的版本,避免生成的whl文件名不显示正确的版本名称(若不设置这个环境变量,生成的whl只有主版本号正确即v0.11,子版本号显示乱码,而不是显示3)
export BUILD_VERSION=0.11.3
# 开始编译源码构建程序,生成的安装包输出在dist文件夹:torchvision-0.11.3-cp37-cp37m-linux_aarch64.whl
python setup.py bdist_wheel
cd dist
pip3.7 install torchvision-0.11.3-cp37-cp37m-linux_aarch64.whl

测试安装是否成功:

1
2
import torchvision
print(torchvision.__version__)

13   配置opencv with cuda

13.1   python3.6环境

使用python3.6环境,可以直接使用这个帖子给出的预编译的opencv包——官方隐藏资源:Jetson伪超频与CUDA版OpenCV

预构建好的deb文件:OpenCV-4.5.0-aarch64.tar.gz

可以先卸载系统自带的CPU版的opencv4.1.1,再执行安装deb包。

如果想自行从源码构建opencv with cuda,编译步骤可参考Install OpenCV on Jetson Nano

13.2   python3.7环境

  1. 卸载默认不带cuda的opencv
1
2
3
4
5
6
sudo apt purge libopencv*
sudo apt autoremove
sudo apt update

# 确保/usr/lib/中没有其他版本的openCV影响
find / -name "*opencv*" 2>/dev/null
  1. 下载opencv和opencv_contrib源码
1
2
3
4
5
6
7
8
9
10
11
12
# 访问网页离线下载
# https://github.com/opencv/opencv/releases/tag/4.1.1
# https://github.com/opencv/opencv_contrib/releases/tag/4.1.1
curl -s -L -O https://github.com/opencv/opencv/archive/refs/tags/4.1.1.zip
curl -s -L -O https://github.com/opencv/opencv_contrib/archive/refs/tags/4.1.1.zip
sudo apt install unzip
upzip opencv-4.1.1.zip
unzip opencv_contrib-4.1.1.zip
# 两个压缩包都放在同一个目录下
cd opencv-4.1.1
mkdir build
cd build
  1. 安装依赖库(参见Install OpenCV on Jetson Nano
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# reveal the CUDA location
sudo sh -c "echo '/usr/local/cuda-10.2/lib64' >> /etc/ld.so.conf.d/nvidia-tegra.conf"
# 更新动态链接库
sudo ldconfig
# third-party libraries
sudo apt install build-essential cmake git unzip pkg-config zlib1g-dev
sudo apt install libjpeg-dev libjpeg8-dev libjpeg-turbo8-dev
sudo apt install libpng-dev libtiff-dev libglew-dev
sudo apt install libavcodec-dev libavformat-dev libswscale-dev
sudo apt install libgtk2.0-dev libgtk-3-dev libcanberra-gtk*
sudo apt install python-dev python-numpy python-pip
sudo apt install python3-dev python3-numpy python3-pip
sudo apt install libxvidcore-dev libx264-dev libgtk-3-dev
sudo apt install libtbb2 libtbb-dev libdc1394-22-dev libxine2-dev
sudo apt install gstreamer1.0-tools libgstreamer-plugins-base1.0-dev
sudo apt install libgstreamer-plugins-good1.0-dev
sudo apt install libv4l-dev v4l-utils v4l2ucp qv4l2
sudo apt install libtesseract-dev libxine2-dev libpostproc-dev
sudo apt install libavresample-dev libvorbis-dev
sudo apt install libfaac-dev libmp3lame-dev libtheora-dev
sudo apt install libopencore-amrnb-dev libopencore-amrwb-dev
sudo apt install libopenblas-dev libatlas-base-dev libblas-dev
sudo apt install liblapack-dev liblapacke-dev libeigen3-dev gfortran
sudo apt install libhdf5-dev libprotobuf-dev protobuf-compiler
sudo apt install libgoogle-glog-dev libgflags-dev
  1. 配置cmake

配置cmake一定要加上这项参数-D CUDNN_VERSION="8.0",否则会报错:Could NOT find CUDNN: Found unsuitable version "..", but required is at least "6" (found /usr/lib/aarch64-linux-gnu/libcudnn.so.8.2.1),即使是手动指定了库文件的绝对路径,仍然是会报这个错误。cudnn版本参见「为开发板安装CUDA、cuDNN、TensorRT库」小节

下载的源码压缩包不包含所有modules组件的源码,并且raw.githubusercontent.com是无法直连的,直接ping也无法得到ip,通过网页https://www.ipaddress.com/,得到ip:185.199.110.133。如果不修改hosts进行域名解析映射IP,大概率会报错:

1
2
3
4
=======================================================================
Couldn't download files from the Internet.
Please check the Internet access on this host.
=======================================================================
1
2
3
sudo nano /etc/hosts
185.199.110.133 raw.githubusercontent.com
20.205.243.166 github.com

倘若修改了hosts文件进行域名解析映射IP之后,仍然是报错提示无法连接网络下载文件的话,先移除opencv_contrib-4.1.1目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
cmake \
-D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D CMAKE_C_COMPILER=/usr/bin/gcc-7 \
-D INSTALL_PYTHON_EXAMPLES=OFF \
-D INSTALL_C_EXAMPLES=OFF \
-D OPENCV_ENABLE_NONFREE=ON \
-D BUILD_opencv_python3=ON \
-D WITH_CUDA=ON \
-D WITH_CUDNN=ON \
-D CUDNN_VERSION="8.0" \
-D WITH_TBB=ON \
-D OPENCV_DNN_CUDA=ON \
-D ENABLE_FAST_MATH=on \
-D CUDA_FAST_MATH=on \
-D CUDA_ARCH_BIN=6.2 \
-D CUDA_ARCH_PTX="" \
-D WITH_CUBLAS=on \
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.1.1/modules \
-D PYTHON3_EXECUTABLE=/usr/local/python3.7/bin/python3.7m \
-D PYTHON3_INCLUDE_DIR=/usr/local/python3.7/include/python3.7m \
-D PYTHON3_LIBRARY=/usr/local/python3.7/lib/libpython3.7m.so \
-D PYTHON3_NUMPY_INCLUDE_DIRS=/usr/local/python3.7/lib/python3.7/site-packages/numpy/core/include \
-D PYTHON3_PACKAGES_PATH=/usr/local/python3.7/lib/python3.7/site-packages \
-D PYTHON_DEFAULT_EXECUTABLE=/usr/local/python3.7/bin/python3.7m \
-D CUDNN_LIBRARY=/usr/local/cuda-10.2/lib64/libcudnn.so.8.2.1 \
-D CUDNN_INCLUDE_DIR=/usr/local/cuda-10.2/include \
-D CUDA_CUDA_LIBRARY=/usr/local/cuda-10.2/targets/aarch64-linux/lib/stubs/libcuda.so \
-D CUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-10.2/ \
-D OPENCV_PYTHON3_INSTALL_PATH=/usr/local/python3.7/lib/python3.7/site-packages \
-D WITH_WEBP=OFF \
-D WITH_OPENCL=OFF \
-D ETHASHLCL=OFF \
-D ENABLE_CXX11=ON \
-D BUILD_EXAMPLES=OFF \
-D WITH_OPENGL=ON \
-D WITH_GSTREAMER=ON \
-D WITH_V4L=ON \
-D WITH_LIBV4L=ON \
-D WITH_QT=OFF \
-D BUILD_opencv_python3=ON \
-D BUILD_opencv_python2=OFF \
-D WITH_FFMPEG=on \
-D HAVE_opencv_python3=ON \
-D EIGEN_INCLUDE_PATH=/usr/include/eigen3 \
-D WITH_EIGEN=ON \
-D ENABLE_NEON=ON \
-D WITH_OPENMP=ON \
-D BUILD_TIFF=ON \
-D WITH_TBB=ON \
-D BUILD_TBB=ON \
-D BUILD_TESTS=OFF \
-D WITH_PROTOBUF=ON \
..

如果cmake提示找不到OpenBLAS头文件和库文件的话,可以尝试方法修复:

1
2
3
4
5
6
7
8
9
10
# -- Could not find OpenBLAS include. Turning OpenBLAS_FOUND off
# -- Could not find OpenBLAS lib. Turning OpenBLAS_FOUND off
# -- Could NOT find Atlas (missing: Atlas_CBLAS_LIBRARY Atlas_BLAS_LIBRARY)
sudo apt install libopenblas-dev libopenblas-base
sudo apt install liblapacke-dev
sudo ln -s /usr/include/lapacke.h /usr/include/x86_64-linux-gnu # corrected path for the library

# Open_BLAS_INCLUDE_SEARCH_PATHS path: /usr/include/x86_64-linux-gnu
# Open_BLAS_LIB_SEARCH_PATHS path: /usr/lib/x86_64-linux-gnu
# 将以上路径分别添加到opencv/cmake/OpenCVFindOpenBLAS.cmake文件中的SET(Open_BLAS_INCLUDE_SEARCH_PATHS和SET(Open_BLAS_LIB_SEARCH_PATHS中。
  1. 编译及安装
1
2
3
4
5
6
# 使用-j参数多线程编译时会报错(makefile文件引起的),可能是由于我修改了opencv/cmake/OpenCVFindOpenBLAS.cmake文件引起的
make -j1
sudo make install

# 更新动态链接库
sudo ldconfig
  1. 测试opencv安装情况
1
2
3
4
5
6
7
# 假设编译安装好系统opencv with cuda后,python没有找到cv2,则安装opencv-python包启用。
# pip install opencv-python

python3.7
import cv2
# 返回cuda设备,即cuda显卡的数量
print(cv2.cuda.getCudaEnabledDeviceCount())

14   配置conda(可选,但不推荐)

不推荐配置,jetson tx2 nx型号能够安装成功的版本较老,并且创建虚拟环境后,默认的base环境一旦conda install任意包之后,再次运行conda的任何命令都会报错:illegal instruction (core dump),pip可以正常工作。如果是新建其他名称的虚拟环境,则是pip会报错:illegal instruction (core dump),而conda install可以正常工作。

anaconda下载:
miniconda下载:点击跳转
Archiconda下载:点击跳转

tx2 nx硬件只有这个版本能成功安装:Miniconda3-py37_4.9.2-Linux-aarch64.sh,而之后的新版本都会报错:illegal instruction (core dump)

14.1   conda基本操作

1
2
3
4
5
6
7
8
9
10
# 创建了名为xxx的虚拟环境,指定虚拟环境的 Python 版本为3.7
conda create -n xxx python=3.7
# 激活名为xxx的虚拟环境
conda activate xxx
# 退出当前虚拟环境
conda deactivate
# 删除名为xxx的虚拟环境
conda remove -n xxx --all
# 查看本地已有哪些虚拟环境
conda info --env

14.2   修改conda源

参见Anaconda 镜像使用帮助

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
nano ~/.condarc
# windows用户可以用下面的命令创建这个文件
# conda config --set show_channel_urls yes

# 为.condarc文件添加如下内容
channels:
- defaults
show_channel_urls: true
default_channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
deepmodeling: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/

# 清除索引缓存
conda clean -i

14.3   使用yml配置文件创建虚拟环境报错(这个我实测没生效)

现象:使用yml文件创建conda环境时出现Solving environment: failed 和 ResolvePackageNotFound 的错误

原因:因为部分包的版本详细是根据机器配置而定,导出来的版本不一定适用当前的机器

解决方法:剔除环境配置文件environment.yml中的版本信息,只需删除第二个等号之后的内容即可

1
2
3
pytorch=1.1.0=py3.7_cuda10.0.130_cudnn7.5.1_0
# 例如上面这行的修改如下
pytorch=1.1.0

15   参考文献

[1] Nvidia xavier NX 通过 flash.sh 烧录 linux 系统[EB/OL]. https://blog.csdn.net/Yan_uuu/article/details/126947983.
[2] Jetson Xavier NX 镜像制作、烧录及克隆[EB/OL]. https://blog.csdn.net/qhdd123/article/details/123815911.
[3] 解决 Jetson:Failed to start nvpmode1 server. 和 Failed to start load kernel modules[EB/OL]. https://blog.csdn.net/alianfibakic/article/details/123807606.
[4] Jetson TX2 更换软件源[EB/OL]. https://blog.csdn.net/qlulibin/article/details/80271096.
[5] Ubuntu18.04 默认源恢复默认源恢复备份源[EB/OL]. https://blog.csdn.net/ZeyiRTangent/article/details/114885286.
[6] PyPI 镜像使用帮助[EB/OL]. https://mirrors.tuna.tsinghua.edu.cn/help/pypi/.
[7] Ubuntu 编译安装 python3.7.10, 解决‘_ctypes‘和 ssl 问题, 建立软链接 python3.7 和 pip3.7[EB/OL]. https://blog.csdn.net/tmaccs/article/details/117029067.
[8] ubuntu18.04 下源码编译安装最新版本 Python3[EB/OL]. https://zhuanlan.zhihu.com/p/62930419.
[9] ubuntu pip is configured with locations that require TLS/SSL[EB/OL]. https://blog.csdn.net/a1007720052/article/details/107342695.
[10] libssl‑dev : Depends: libssl1.0.0 (= 1.0.2g‑1ubuntu4) but 1.0.2g‑1ubuntu4.15 is to be installed[EB/OL]. https://blog.csdn.net/weixin_38890593/article/details/102783551.
[11] TX2开启最大功耗模式[EB/OL]. https://www.cnblogs.com/gezhuangzhuang/p/11674062.html.
[12] 使用git --recursive进行循环克隆,由于网络原因,出现克隆失败的情况。[EB/OL]. https://blog.csdn.net/qq_43212651/article/details/116376103.
[13] 解决c++: internal compiler error: 已杀死 (program cc1plus)[EB/OL]. https://blog.csdn.net/chenmeng0508/article/details/122283442.
[14] Jetson nano增加Swap分区大小操作指南[EB/OL]. https://blog.csdn.net/qq_33475105/article/details/108372878.
[15] 玩转NVIDIA Jetson (25)--- jetson 安装pytorch和torchvision[EB/OL]. https://blog.csdn.net/huiyuanliyan/article/details/126686036.
[16] Ubuntu 安装PHP找不着BZip2[EB/OL]. https://blog.csdn.net/handsome_926/article/details/77933926.
[17] ModuleNotFoundError: No module named '_lzma'[EB/OL]. https://zhuanlan.zhihu.com/p/404162713.
[18] Error :: You must put some 'source' URIs in your sources.list[EB/OL]. https://askubuntu.com/questions/496549/error-you-must-put-some-source-uris-in-your-sources-list.
[19] 3d-pose-baseline[EB/OL]. https://github.com/una-dinosauria/3d-pose-baseline.
[20] How to stop jetson_clocks?[EB/OL]. https://forums.developer.nvidia.com/t/how-to-stop-jetson-clocks/144713.
[21] VideoPose3d:环境搭建+制作自己的视频[EB/OL]. https://blog.csdn.net/willbetter01/article/details/120906567.
[22] 【VideoPose3D】可视化自定义视频[EB/OL]. https://blog.csdn.net/qq_44942539/article/details/121983187.
[23] Windows下VideoPose3D成功运行记录2:运行自定义视频[EB/OL]. https://www.bilibili.com/read/cv19291005/.
[24] How to Use OpenCV with CUDA Support in Python[EB/OL]. https://saturncloud.io/blog/how-to-use-opencv-with-cuda-support-in-python/.
[25] Jetson Xavier NX OpenCV 安装[EB/OL]. https://zhuanlan.zhihu.com/p/411901208.
[26] Nvidia Jetson TX2 配置Cuda 加速的Opencv[EB/OL]. https://blog.csdn.net/weixin_62651190/article/details/129348245.
[27] Guide to build OpenCV from source with GPU support (CUDA and cuDNN)[EB/OL]. https://gist.github.com/minhhieutruong0705/8f0ec70c400420e0007c15c98510f133.
[28] How to install OpenCV 4.2.0 with CUDA 10.0 in Ubuntu distro 18.04[EB/OL]. https://gist.github.com/changx03/b4aa9bb2827217c3a6a7e08365441417.
[29] Jetson带CUDA编译的opencv4.5安装教程与踩坑指南,cmake配置很重要![EB/OL]. https://blog.csdn.net/weixin_39298885/article/details/110851373.
[30] ubuntu 编译安装支持CUDA的OpenCV[EB/OL]. https://blog.csdn.net/qq_44523137/article/details/124098406.
[31] Install OpenCV on Jetson Nano[EB/OL]. https://qengineering.eu/install-opencv-on-jetson-nano.html.
[32] 「解析」Jetson Orin NX 安装 CUDA/cuDNN[EB/OL]. https://blog.csdn.net/ViatorSun/article/details/129909317.
[33] OpenCV 4.2.0 and CuDNN for Jetson Nano?[EB/OL]. https://forums.developer.nvidia.com/t/opencv-4-2-0-and-cudnn-for-jetson-nano/112281?page=2.
[34] Correction in OpenCV's default CMAKE search Path for OpenBLAS Library on Ubuntu-64bit Machines [Solution] [EB/OL]. https://github.com/opencv/opencv/issues/12957.
[35] jetson tx2 安装miniconda失败原因(illegal instruction (core dump)原因解析与简易安装miniconda[EB/OL]. https://blog.csdn.net/buxiangyaomingzi/article/details/123297295.
[36] TypeError: load() missing 1 required positional argument: 'Loader' in Google Colab[EB/OL]. https://stackoverflow.com/questions/69564817/typeerror-load-missing-1-required-positional-argument-loader-in-google-col.
[37] 解决创建conda环境时Solving environment: failed 和 ResolvePackageNotFound 的错误[EB/OL]. https://blog.csdn.net/hshudoudou/article/details/126407029.
[38] 关于Ubuntu下ZRAM的配置和使用[EB/OL]. https://blog.xzr.moe/archives/88/.
[39] 解决python urllib3 v2.0 only supports OpenSSL 1.1.1+, currently[EB/OL]. https://blog.csdn.net/weixin_43205308/article/details/130830307.
[40] 从TensorFlow被kill到增加Swap分区[EB/OL]. https://www.zhihu.com/column/p/30562899.
[41] Linux运行程序时,程序进程莫名退出(被杀死)[EB/OL]. https://blog.csdn.net/ispringmw/article/details/112719262.
[42] linux 环境下进程被 killed掉原因分析和解决方法[EB/OL]. https://blog.csdn.net/ktigerhero3/article/details/80004315.
[43] Linux OOM Killer机制 以及防止被OOM Killer杀死的方法[EB/OL]. https://blog.csdn.net/top_explore/article/details/107733974.

1   安装arch系统镜像

  1. 下载镜像:点击跳转,使用PE U盘或者虚拟机加载CD方式加载系统镜像。
  2. 选择Arch Linux install medium (x86_64, UEFI)
  3. 验证启动模式:ls /sys/firmware/efi/efivars。若正确输出目录且无错误,则证明是UEFI模式。
  4. 连接网络(由路由器DHCP自动下发分配IP),ping测试联网状态(ip addr查看IP),可用ssh连接完成后面的工作,方便复制粘贴命令(systemctl status sshd查看ssh保护进程运行状态),passwd修改当前环境root账户的密码。
  5. 重新设置镜像源mirrorlist,使用reflector获取速度最快的6个镜像,并保存至指定路径/etc/pacman.d/mirrorlist
1
2
reflector -c China -a 6 --sort rate --save /etc/pacman.d/mirrorlist
reflector -c China -a 6 --sort rate --save /mnt/etc/pacman.d/mirrorlist

手动设置镜像源:

1
2
3
4
nano /etc/pacman.d/mirrorlist
# 顶部添加如下镜像源
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
Server = https://repo.archlinuxcn.org/$arch
  1. 打开自动同步时间:timedatectl set-ntp true。检测命令生效情况:timedatectl status
  2. lsblk或者fdisk -l查看硬盘情况(rom、loop、airoot的设备可忽略掉)。使用fdisk分区:fdisk /dev/sda,按g并回车将磁盘分区表设置为GPT格式,按n并回车新增一个分区,分区号默参直接回车,起始扇区默参直接回车,结束位置输入+300M并回车表示分区大小为300M。重复n命令三次,完成剩余的三个分区的工作。按t并回车更改分区类型,输入分区编号1并回车,输入分区类型1并回车(1代表EFI system partition)。重复t命令,将2、3、4号分区分别改为类型19(SWAP),类型23(Linux root (x86-64)),类型42(Linux home)。最后按w并回车保存分区表。df -h查看分区情况。
  3. 磁盘格式化
1
2
3
4
mkfs.fat -F 32 /dev/sda1
mkswap /dev/sda2
mkfs.ext4 /dev/sda3
mkfs.ext4 /dev/sda4
  1. 挂载文件系统
    挂载boot
1
2
3
4
5
mount /dev/sda3 /mnt
mkdir -p /mnt/home #创建/mnt/home目录供挂载
mount /dev/sda4 /mnt/home
mkdir -p /mnt/boot #创建/mnt/boot目录供挂载
mount /dev/sda1 /mnt/boot

挂载swap:swapon /dev/sda2
9. 安装系统:pacstrap /mnt base linux linux-firmware nano
10. 生成fstab文件:genfstab -U /mnt >> /mnt/etc/fstab。检查fstab文件内容:cat /mnt/etc/fstab
11. 更换当前的根目录到安装到硬盘上的系统:arch-chroot /mnt
12. 将时区设置为亚洲/上海:ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
13. 同步硬件时钟:hwclock --systohc
14. 配置本地化设置:nano /etc/locale.genctrl+w搜索,分别找到#en_US,#zh_CN带有UTF-8的两行,去掉前面的#号。ctrl+o并回车保存文件,ctrl+x并回车退出nano。生成配置:locale-gen
15. 创建并修改/etc/locale.conf文件,填入LANG=en_US.UTF-8并保存。

1
2
touch /etc/locale.conf
nano /etc/locale.conf
  1. 配置网络主机名:echo "archlinux" >> /etc/hostname
  2. 设置root密码:passwd
  3. 设置引导程序
1
2
3
pacman -S dosfstools grub efibootmgr  # 安装引导程序
grub-install --target=x86_64-efi --efi-directory=/boot --recheck # 将grub安装至EFI分区
grub-mkconfig -o /boot/grub/grub.cfg # 生成grub配置
  1. 安装新系统必要的包:pacman -S networkmanager network-manager-applet dhcpcd dialog os-prober mtools ntfs-3g base-devel linux-headers reflector git。更新软件包缓存:sudo pacman -Syy。更新系统:sudo pacman -Syu
  2. 开启dhcpcd
1
2
systemctl start dhcpcd  # 启动dhcpcd
systemctl enable dhcpcd # 使dhcpcd自启
  1. 启用sshd服务并远程控制
1
2
3
4
5
6
systemctl start sshd
systemctl enable sshd
nano /etc/ssh/sshd_config
# 将其中PermitRootLogin前面#号去掉并且将值设置为yes
# 将其中PasswordAuthentication前面#号去掉并且将值设置为yes
systemctl restart sshd
  1. 添加普通用户
1
2
3
4
5
6
7
useradd -m kin # 添加用户名为kin的用户
passwd kin # 为用户kin设置密码
# 添加用户的更多选项
# useradd -m -G additional_groups -s login_shell username
pacman -S sudo
nao /etc/sudoers
# 打开/etc/sudoers文件后,在root ALL=(ALL:ALL) ALL一行下,添加kin ALL=(ALL:ALL) ALL。
  1. 退出chroot环境,卸载掉挂载的硬盘并重启
1
2
3
4
5
exit  # 返回至arch-chroot之前的环境
umount /mnt/home
umount /mnt/boot
umount /mnt
reboot # 重启

分区方案(列表顺序就是实际的分区顺序)

  • /boot/efi 300M
  • swap(内存大小×1.5)
  • / 40G
  • /home 剩余全部空间

Swap分区的大小分配推荐方案

Amount of RAM in the systemRecommended swap spaceRecommended swap space if allowing for hibernation
⩽ 2GB2 times the amount of RAM3 times the amount of RAM
> 2GB – 8GBEqual to the amount of RAM2 times the amount of RAM
> 8GB – 64GBAt least 4 GB1.5 times the amount of RAM
> 64GBAt least 4 GBHibernation not recommended

2   安装KDE桌面环境

  1. 安装显示Xorg服务器:sudo pacman -S xorg
  2. 安装KDE Plasma桌面环境和其他KDE软件包:sudo pacman -S plasma kde-applications
  3. 安装显示管理器(开机图形界面登录)
1
2
sudo pacman -S sddm
systemctl enable sddm.service # 开机自启动sddm服务
  1. 进入桌面环境
1
2
3
4
5
6
7
8
9
10
# 手动进入桌面环境
startx
# 修改当前帐户bash配置,让账户登录后自动进入桌面环境
nano ~/.bash_profile
# 或者修改Shell的全局启动配置文件
nano /etc/profile
# 文件末尾处添加如下内容
if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
exec startx
fi

3   参考文献

[1] 在ProxmoxVE(PVE)7.0中安装ArchLinux[EB/OL]. https://www.raobee.com/archives/343/.
[2] Arch Linux + KDE 的安装和配置[EB/OL]. https://peoxin.github.io/blog/2022/05/01/Arch%20Linux%20+%20KDE%20%E7%9A%84%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AE/.
[3] Installation guide[EB/OL]. https://wiki.archlinux.org/title/Installation_guide.
[4] How to Install Deepin Desktop in Arch Linux [Complete Guide][EB/OL]. https://www.debugpoint.com/2021/01/deepin-arch-linux-install-20/.
[5] Easy Step - Arch Linux ( EFI ) With Clover ( EFI )[EB/OL]. https://www.insanelymac.com/forum/topic/294443-easy-step-arch-linux-efi-with-clover-efi/.
[6] 10 Commands to Check Disk Partitions and Disk Space on Linux[EB/OL]. https://www.binarytides.com/linux-command-check-disk-partitions/.
[7] Can't establish an internet connection in Arch Linux[EB/OL]. https://superuser.com/questions/751961/cant-establish-an-internet-connection-in-arch-linux.
[8] Deepin Desktop Environment (简体中文)[EB/OL]. https://wiki.archlinux.org/title/Deepin_Desktop_Environment_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87).
[9] Localization (简体中文)/Simplified Chinese (简体中文)[EB/OL]. https://wiki.archlinux.org/title/Localization_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)/Simplified_Chinese_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87).
[10] VirtualBox/Install Arch Linux as a guest[EB/OL]. https://wiki.archlinux.org/title/VirtualBox/Install_Arch_Linux_as_a_guest.
[11] How to enable a Virtualbox shared folder for Linux guest systems[EB/OL]. https://averagelinuxuser.com/virtualbox-shared-folder/.
[12] Recommended Partitioning Scheme[EB/OL]. https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/installation_guide/s2-diskpartrecommend-x86.

1   背景

因联通赠送的光猫性能不支持双lan口长时间同时上网,故从海鲜市场(小黄鱼)上淘了一块n3530小工控机(准系统,无内存条),计划用来安装pve虚拟机用以作软路由处理宽带拨号和网关ip分配,光猫仅作为一个光电信号转换器。

2   N3530工控机配置

  • CPU:Intel(R) Pentium(R) CPU N3530
  • 内存:海力士DDR3L-8GB单根(从老笔记本电脑上拆一根DDR3条子给它用)
  • 硬盘:海力士SH920 msata 256GB
  • 网卡:千兆网卡Intel I211 *2+千兆网卡Realtek mini-pcie RTL8211E/F(螃蟹卡,后期自己购买加装至mini pcie无线网卡口位置)

3   All in One (All in Boom) 机器硬件选型总结

3.1   CPU选型对比

All in One用途大多是使用虚拟机方案实现各子系统,其中虚拟化直通硬件方案需要CPU中一个很重要的功能才能实现。对于Intel CPU来说,就是VT-d(该功能可以在Intel官方每个CPU SKU规格页面查看是否支持);对于AMD CPU来说,就是IOMMU,也称AMD-Vi(暂未找到AMD官方资料特性说明,根据维基百科资料所言,所有 Ryzen 处理器均支持该功能)。

CPU型号内核/线程功耗/W频率/GHz核显最大分辨率最大内存规格/GBVT-xVT-d
赛扬®J19004/4102~2.42未知2×8 DDR3L 1333MT/s支持不支持
奔腾®N35304/44.5~7.52.16~2.58未知2×8 DDR3L 1333MT/s支持不支持
奔腾®J42054/4101.5~2.6未知2×8 LPDDR4 2400 MT/s支持支持
赛扬®J41254/4102~2.74096x2160@30Hz2×8 DDR4/LPDDR4 2400MT/s支持支持
赛扬®J64124/4102~2.64096x2160@ 60Hz4x32 LPDDR4/x 3733MT/s支持支持
N1004/463.44096 x 2160@60Hz16 DDR5/LPDDR5 4800 MT/s支持支持
酷睿™ i7-1255U10/1212~55E核3.5/P核4.77680 x 4320 @ 60Hz64 LPDDR5 5200 MT/s支持支持

BUG处理器的折腾性思考:小黄鱼上面流通不少Intel 12代ES测试版处理器,不同步进版本存在不同问题,例如CPU没有内建直连独立显卡的PCI-E通道,会导致所有主板上第一根直连独立显卡的PCI-E X16插槽无效。这类BUG CPU虽然价格看着很诱惑,但可能实际运行不稳定,因此不纳入All in One硬件选型考虑范围。

3.2   主板选型对比

TODO

4   安装PVE

4.1   N3530工控机BIOS的优化设置

该N3530工控机BIOS是采用经典的American Megatrends方案。

  • 设置来电自启:Chipset->North Bridge->Restore AC Power Loss->Power on
  • 启用CPU内置温度传感器:Advanced->CPU Configuration->CPU Thermal Configuration->DTS->Enabled
  • 启用VT-x:Advanced->CPU Configuration->Intel Virtualization Technology->Enabled
  • 设置自定义功率模式:Advanced->CPU Configuration->Power Technology->Custom。(默认是Efficient节能模式)

4.2   PVE安装步骤

  1. 下载PVE系统镜像:点击跳转(我的情况就是需要下载pve 7.4的iso镜像文件,因为安装pve 8.x会引起bootloader setup errors报错而导致安装PVE系统失败)
  2. 制作U盘启动项:ventoy。拷贝镜像iso文件到U盘,启动选择pve镜像进入,选择图形安装界面。
  3. 同意协议。
  4. 点击option按钮,选择硬盘和ext4格式(我的情况只有一个硬盘,不用设置option,直接下一步)
  5. 国家选择china,时区会自动修改为上海,键盘布局不改。
  6. 设置密码,邮箱随意。
  7. 管理网口,默认第一个做管理(enp1s0,pve系统里面识别的第一个网口,N3530工控机中靠近hdmi接口的rj45),第二个做wan口(方便后续维护)。域名hostname设置为pve.lan(这项随便填写都行,有能用的域名可填写上去,可解析到机器用域名管理pve虚拟机);ip:192.168.1.2;掩码:255.255.255.0;网关:192.168.1.1(留给ikuai的地址),DNS:223.5.5.5。
  8. 查看确认已设置的内容(弹窗),确认安装。
  9. 访问WEB管理地址并登录:https://192.168.1.2:8006。账户:root。密码:[前面步骤设置的密码]。
  10. 设置网卡并确保pve能联网。

4.3   PVE升级步骤(7.4->8.x)

  1. 替换PVE 7.4的Debian系统软件源和PVE软件源(国内镜像)

/etc/apt/sources.list

1
2
3
4
5
6
7
8
9
10
#deb http://ftp.debian.org/debian bullseye main contrib
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free

#deb http://ftp.debian.org/debian bullseye-updates main contrib
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
# security updates
#deb http://security.debian.org bullseye-security main contrib
deb https://security.debian.org/debian-security bullseye-security main contrib non-free

删除pve-enterprise.list,新建pve-no-subscription.list。

/etc/apt/sources.list.d/pve-no-subscription.list

1
2
3
cd /etc/apt/sources.list.d
mv pve-enterprise.list pve-enterprise.list.bak
echo deb https://mirrors.tuna.tsinghua.edu.cn/proxmox/debian/pve bullseye pve-no-subscription > pve-no-subscription.list
  1. 升级到7.4最新的版本
1
2
apt update -y && apt dist-upgrade -y
pveversion
  1. 检查兼容性:pve7to8
  2. 替换PVE 8.x的Debian系统软件源和PVE软件源(国内镜像)

/etc/apt/sources.list

1
2
3
4
5
6
7
8
9
10
# deb http://ftp.debian.org/debian bookworm main contrib
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware

# deb http://ftp.debian.org/debian bookworm-updates main contrib
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware
# security updates
# deb http://security.debian.org bookworm-security main contrib
deb https://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware

删除pve-enterprise.list,新建pve-no-subscription.list。

/etc/apt/sources.list.d/pve-no-subscription.list

1
2
3
cd /etc/apt/sources.list.d
mv pve-enterprise.list pve-enterprise.list.bak
echo deb https://mirrors.tuna.tsinghua.edu.cn/proxmox/debian/pve bookworm pve-no-subscription > pve-no-subscription.list
  1. 开始正式升级
1
2
apt update
apt dist-upgrade

升级中会出现一些交互界面,下面是官网的建议(不清楚的选项,可以选择推荐参数)

1
2
3
4
5
6
7
8
9
10
11
12
/etc/issue -> Proxmox VE will auto-generate this file on boot, and it has only cosmetic effects on the login console.
Using the default "No" (keep your currently-installed version) is safe here.

/etc/lvm/lvm.conf -> Changes relevant for Proxmox VE will be updated, and a newer config version might be useful.
If you did not make extra changes yourself and are unsure it's suggested to choose "Yes" (install the package maintainer's version) here.

/etc/ssh/sshd_config -> If you have not changed this file manually, the only differences should be a replacement of ChallengeResponseAuthentication no with KbdInteractiveAuthentication no and some irrelevant changes in comments (lines starting with #).
If this is the case, both options are safe, though we would recommend installing the package maintainer's version in order to move away from the deprecated ChallengeResponseAuthentication option. If there are other changes, we suggest to inspect them closely and decide accordingly.

/etc/default/grub -> Here you may want to take special care, as this is normally only asked for if you changed it manually, e.g., for adding some kernel command line option.
It's recommended to check the difference for any relevant change, note that changes in comments (lines starting with #) are not relevant.
If unsure, we suggested to selected "No" (keep your currently-installed version)
  1. 去除未订阅提示
1
2
3
4
sed -i_orig "s/data.status === 'Active'/true/g" /usr/share/pve-manager/js/pvemanagerlib.js
sed -i_orig "s/if (res === null || res === undefined || \!res || res/if(/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
sed -i_orig "s/.data.status.toLowerCase() !== 'active'/false/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
systemctl restart pveproxy
  1. 后续PVE系统日常更新:apt update -y && apt dist-upgrade -y

升级若遇到提示Upgrade wants to remove package 'proxmox-ve'的话,在升级前尝试执行apt remove linux-image-amd64

4.4   PVE安装遇到的坑

4.4.1   PVE安装到99%时出现unable to install the EFI boot loader on '/dev/sda'

报错信息:

1
2
bootloader setup errors:
- failed to prepare EFI boot using Grub on '/dev/sda2': unable to install the EFI boot loader on '/dev/sda'

原因:PVE 8.0版本镜像īso不兼容此台机器,估计是bug。参考其他帖子说的只保留usb的uefi设备,依旧也会报错。并且因为机器的bios设置也比较繁琐,我反复尝试将其设置为csm模式,机器始终无法以csm模式启动,估计该主板的bios也是魔改过有bug的。因此导致也无法以csm模式安装pve。

解决方法:安装pve 7.4版本,再升级到pve 8.x。

4.4.2   USB键盘一直插着时会无法响应的,无法进入并操控BIOS,只有CTRL+ALT+DELETE可以响应

原因:推测为BIOS的驱动BUG。

解决方法:先不插键盘启动机器,看屏幕右下角等待主板自检代码走到99阶段,快速插上键盘,摁DEL键。如果一次不成功,就使用CTRL+ALT+DELETE强制重启,继续尝试该操作。

4.4.3   hdmi屏幕不显bios

主板BIOS内置驱动对普通HDMI小屏幕兼容性不好,只有成功启动PVE系统后才会正常显示画面,而在自检阶段和BIOS界面只会显示白屏或者花屏,接大屏驱动板兼容性好点。

4.4.4   后面加装的RTL8211网卡不识别

原因:因为安装pve系统之前未加装好螃蟹卡RTL8211,因此后续加装之后,pve系统并未识别到。

解决方法:重装pve系统。(驱动源码编译方案,也有编译报错,不好解决,推荐是重装pve,让pve自动打驱动)

4.4.5   工控机重启bug,看门狗导致重启很长时间

原因:未查明,只要一重启,就会长时间卡在看门狗的错误提示。

错误提示:

1
2
3
watchgod: watchdog0: watchdog did't did not stop!

systemd-shutdown: Failed to finalzie DM services, ignorening.

解决方法:无法解决,只能长时间等待完成重启或者强制关机重开。

P.S. 尝试在主板BIOS中主动启用看门狗计时器设置Advanced->SIO MISC Configuration->Watchdog Dog Timer->Enabled仍无法解决,该问题暂未查明原因。

4.4.6   Repository 'Debian bookworm' changed its 'non-free component' value from 'non-free' to 'non-free non-free-firmware'

问题现象:运行apt update出现报错:Repository 'Debian bookworm' changed its 'non-free component' value from 'non-free' to 'non-free non-free-firmware'

问题原因:Debian 12版本(bookworm)的非自有固件包(如硬件驱动程序)不再放在non-free目录中,而是non-free-firmware目录。

解决方法1(修正/etc/apt/sources.list软件源):

1
2
# 确保软件源中添加了non-free-firmware目录,而不仅仅只有non-free目录
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware

解决方法2(直接屏蔽该错误):
创建一个文件/etc/apt/apt.conf.d/no-bookworm-firmware.conf,写入内容如下:

1
APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";

5   PVE硬件直通

如果机器本身就支持VT-X和VT-d等CPU虚拟化和直通硬件功能,则PVE启用IOMMU方面不需要额外设置。只需要设置好主板BIOS对应的CPU虚拟化和直通硬件功能即可,因为安装PVE系统阶段就会自动设置好启动IOMMU。

IOMMU允许系统设备在虚拟内存中进行寻址,也就是将虚拟内存地址映射为物理内存地址,让实体设备可以在虚拟的内存环境中工作,这样可以帮助系统扩充内存容量,提升性能。

换而言之,IOMMU可以使VM虚拟机能够接入一些物理设备,比如PCIe中的网卡、声卡、显卡,VM虚拟机可以直接或间接使用这些设备。

在创建虚拟机时,芯片组一定要q35。因为Q35,才能PCIE直通,否则就是PCI直通。

5.1   检查硬件是否支持直通(BIOS内查看Advanced高级选项)

  1. 开启CPU虚拟化:Intel: VT-X(Intel Virtual Technology)。AMD: AMD-V(SVM, Secure Virtual Machine)。
  2. 开启硬件直通功能:Intel: Intel VT-d。AMD: IOMMU。
  3. 部分主板会有和Intel VT-d/IOMMU相关联的Memory Remap Feature设置。

如果主板BIOS中没有VT-d或者IOMMU功能选项,则很可能不支持直通硬件功能。一般确认CPU是否支持该特性即可,例如确认Intel CPU是否支持VT-d,请查询Intel 官方CPU SKU规格页面资料即可。

若机器CPU明确支持VT-d特性,但在主板BIOS中却找不到该选项的话,则很可能是机器主板BIOS默认设置关闭,并隐藏了该设置选项。

解决方法:使用UniversalAMDFormBrowser工具查看隐藏设定并开启。

  1. 下载UniversalAMDFormBrowser工具
    • 方案1(推荐)UniversalAMDFormBrowser-VentoyUniversalAMDFormBrowser.img文件:该方案只需要将img文件拷贝到ventoy引导盘即可。
    • 方案2:Smokeless_UMAFUniversalAMDFormBrowser.zip文件:该方案需要制作FAT32分区格式的U盘制作引导盘。
  2. 重启机器,选择U盘启动项,引导加载UniversalAMDFormBrowser镜像。
  3. UniversalAMDFormBrowser中查看隐藏设定,例如隐藏的VT-d选项或者解除功耗限制选项等。

5.2   PVE提示No IOMMU detected

当pve未启用IOMMU或者硬件不支持直通(即使手动设置PVE开启IOMMU)时,在ProxmoxVE(PVE)的VM/VPS中添加PCI设备时候提示:No IOMMU detected, please activate it.See Documentation for further information.

解决方法:请确认自己的机器是否支持VT-X和VT-d等CPU虚拟化和直通硬件功能,并是否已开启IOMMU。若硬件不支持,建议更换支持这些功能特性的硬件设备。

5.3   PVE启用IOMMU

内核版本为5.15或之前的,在ProxmoxVE(PVE)系统内核中,需要手动启用IOMMU。5.15版本以后的ProxmoxVE(PVE)系统内核自带默认开启了IOMMU支持。

但我的硬件(N3530工控机)本身不支持VT-d功能(N3530 SKU规格页面明确写到不支持VT-d),因此安装PVE时并未自动启用IOMMU。

  1. 编辑grub文件:nano /etc/default/grub
  2. 找到GRUB_CMDLINE_LINUX_DEFAULT这一行:
1
2
3
4
5
6
7
...
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s <strong>2</strong>> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX=""
...
  1. 根据CPU类型修改
1
2
3
...
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on"
...
1
2
3
...
GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on"
...
4. 更新grub:`update-grub` 5. 重启PVE
  1. 编辑cmdline:nano /etc/kernel/cmdline。(若不存在,则新建该文件)
  2. 根据CPU类型在第一行末尾添加:
1
quiet intel_iommu=on
1
quiet amd_iommu=on

5.4   验证PVE的IOMMU是否启用

  1. 查询命令:dmesg | grep -e DMAR -e IOMMU。输出显示DMAR: IOMMU enabled或者DMAR: Intel(R) Virtualization Technology for Directed I/O证明已启用。
  2. 实操验证:虚拟机分配PCI设备时可见。

5.5   PVE启用PT模式

PT模式:会在IOMMU需要使用时候启动,适配器不需要使用 DMA 转换到内存,因此可以提高其他没有分配过设备的性能。

  1. 编辑grub文件:nano /etc/default/grub
  2. 找到GRUB_CMDLINE_LINUX_DEFAULT这一行:
1
2
3
4
5
6
7
...
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s <strong>2</strong>> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX=""
...
  1. 附加参数iommu=pt,Intel和AMD芯片均可使用这个参数。
  2. 添加模块设置(5.4内核需自行添加,现行版本自带有添加):nano /etc/modules。没有如下内容需添加。
1
2
3
4
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
  1. 刷新 initramfs:update-initramfs -u -k all
  2. 更新 grub:update-grub

6   创建PVE虚拟机

若机器硬件性能不太足够的话,不建议安装win系统,win系统极度消耗硬件性能(即便是空载待机状态也消耗很大),而这影响openwrt的性能。比如我的情况:安装了tiny10系统,千兆网线,宽带500M实测不到300Mbp/s(这里是指用广东联通宽带测速平台测试的结果,实际ikuai主路由测试速率却又正常),局域网速率最高只能到700Mbp/s的速率。关闭后tiny10后,实测宽带速率恢复正常,局域网速率跑到900Mbp/s出头)

6.1   PVE基础设置

  1. 访问WEB管理地址并登录:https://192.168.1.2:8006,账户:root。密码:[前面安装步骤设置的密码]。
  2. 根据硬件网卡情况,添加并设置好虚拟网桥Linux Bridge:创建>Linux Bridge>桥接接口填写物理网口地址名称。以我的机器为例,机器三个物理网口,PVE新建5个网桥,前三个网桥和物理网口绑定,第四个网桥用作iKuai和OpenWrt之间的传输,第五个网桥用作其他虚拟机系统的传输。

6.2   安装爱快iKuai

  1. 下载iKuai镜像:点击跳转
  2. local-内容-上传:iKuai的安装镜像文件。
  3. 创建虚拟机,命名虚拟机名称ikuai,VM编号100。
  4. CD/DVD光盘镜像文件选择刚上传的ISO镜像。
  5. 主硬盘分配2G,额外添加EFI磁盘(不勾选添加密钥),CPU给4核(KVM64),内存分配4G(64位iKuai要求4G内存),网卡模型选择VirtIO(半虚拟化)。
  6. 完成新建后,在硬件标签页中继续完成添加所有网桥,网桥的防火墙全部关掉(使用iKuai的),模型都选择半虚拟化。
  7. 在选项标签页中把「开机自启动」开启,启动顺序设置为1,引导顺序只开disk‘xxxx’磁盘,把网络启动关掉,CD启动上移到第一位。
  8. 点击启动虚拟机,完成安装即可。
  9. iKuai内识别的第一个网卡设置为lan1。
  10. 输入字母o回车进入其「其他选项」,开启外网访问web。
  11. 访问WEB管理地址并登录:http://192.168.1.1:80。默认账户:admin。默认密码:admin。

6.3   安装OpenWrt

6.3.1   OpenWrt镜像的选择

  • Bleach OpenWrt:推荐(本教程使用该固件)。内置SmartDNS,软件包空间设置为1G,剩余五百多兆。
  • eSir GDQ 高大全:eSir的高大全固件。不推荐。没有内置SmartDNS拓展(我测试时安装SmartDNS并未出现有服务项,无效)。并且相较于Blench版本,较为繁琐。软件包空间只剩余几兆,需自己手动扩盘。

6.3.2   安装步骤

  1. 创建虚拟机,命名虚拟机名称openwrt,VM编号101。
  2. CD/DVD光盘镜像文件设置为无介质。
  3. 主硬盘随便分配(后续会删除),额外添加EFI磁盘(不勾选添加密钥),CPU给4核(KVM64),内存分配4G,网卡模型选择VirtIO(半虚拟化)。
  4. 网桥只选择第四个网桥(用作iKuai和OpenWrt之间的传输),网卡的防火墙全部关掉(使用iKuai的),模型选择半虚拟化。
  5. 下载OpenWrt镜像:见上一小节
  6. local-内容-上传:OpenWrt的镜像文件。
  7. 向虚拟机导入OpenWrt镜像:qm importdisk 101 /var/lib/vz/template/iso/bleach-plus-20230826-openwrt-x86-64-generic-squashfs-combined-efi.img local-lvm。(直接写盘即可,无需安装)。
  8. 在pve管理页面中,加载刚才导入镜像生成的磁盘。
  9. 在选项标签页中把「开机自启动」开启,启动顺序设置为2,引导顺序只开disk‘xxxx’磁盘,把网络启动关掉。
  10. 点击启动虚拟机。
  11. pve中用shell:vi /etc/config/network,将lan口ip改为192.168.1.3reboot重启。vim编辑:按i进入修改模式,按esc退出编辑模式,输入 :wq回车保存修改。
  12. 访问WEB管理地址并登录:http://192.168.1.3。默认账户:root。默认密码:password。

6.4   安装飞牛私有云fnOS

  1. 下载飞牛私有云fnOS镜像:点击跳转
  2. local-内容-上传:fnOS的安装镜像文件。
  3. 创建虚拟机,命名虚拟机名称fnOS,VM编号102。
  4. CD/DVD光盘镜像文件选择刚上传的ISO镜像。
  5. 主硬盘分配64G,额外添加EFI磁盘(不勾选添加密钥),CPU给4核(KVM64),内存分配6G(fnOS系统内存需求还是有点大的),网卡模型选择VirtIO(半虚拟化)。
    • 其他硬盘后续自行根据需求添加。
  6. 网桥只选择第五个网桥(用作iKuai和fnOS之间的传输),网卡的防火墙全部关掉(使用iKuai的),模型选择半虚拟化。
  7. 在选项标签页中把「开机自启动」开启,启动顺序设置为3,引导顺序只开disk‘xxxx’磁盘,把网络启动关掉,CD启动上移到第一位。
  8. 点击启动虚拟机。
  9. 选择Graphical Install进行图形化安装流程。
  10. 选择系统主硬盘进行安装。
  11. 系统分区大小:64GB;Swap大小:0GB。(有论坛帖子反馈开启Swap cache时,某些下载应用例如QBittorrent可能会引导硬盘持续读取和CPU的异常占用。永久禁用Swap还需要修改fstab文件。)
  12. 点击确认安装,选择网卡界面,直接默认选项确认即可。
  13. 安装完成后,点击确认开始重启。摁ESC键,进入虚拟机BIOS设置界面,重新选择硬盘分区作为启动项启动。(否则默认启动CD)
  14. 出现Debian GNU/Linux系统引导选项就是安装成功了
  15. 进入到传统Linux登录界面(无GUI),这里能看到WEB管理地址。若需要固定IP地址,在ikuai中找到对应MAC地址加入静态分配表即可。
  16. 访问WEB管理地址并登录:http://192.168.1.5:5666/。首次访问会提示创建NAS设备名称``超级管理员账号``密码``确认密码

6.5   创建虚拟机遇到的坑

6.5.1   添加EFI磁盘后,启动无法引导磁盘,出现shell界面。

原因:创建EFI磁盘时,勾选了添加密钥。

解决方法:创建EFI磁盘时,取消勾选添加密钥。

6.5.2   安装windows镜像,无法识别找到磁盘

原因:scsi不适合windows镜像,windows安装解决识别不到硬盘。

解决方法:创建硬盘时,总线/设备选择:SATA硬盘或者IDE。

6.5.3   安装windows镜像,网卡无驱动

现象:当网卡设置为virtio模型时,安装并进入到windows系统后,网卡无驱动。

原因:windows系统自带的驱动无此虚拟硬件的驱动。

解决方式:加载virtio镜像,并运行x64程序安装驱动即可。

VirtIO镜像下载地址:点击跳转

7   iKuai +OpenWRT 做旁路由网络拓扑

iKuai作为主路由,负责拨号及DHCP,OpenWRT做旁路由。SmartDNS+AdGuardHome设置分流与去广告。SmartDNS作为DNS管理并提供DNS缓存,实现国内国外DNS分流,彻底解决DNS污染问题、实现秒开网页。同时搭配AdGuardHome实现整个局域网去广告。

DNS转发流程:设置最核心的部分就是DNS转发端口的衔接,就是把DNSMASQ、Adguardhome、SmartDNS三个插件里的DNS服务器功能分成三个层级,实现层层转发。依次是第一级DNSMASQ,第二级Adguardhome,第三级SmartDNS,第四级OpenClash。

7.1   iKuai的设置

  • 系统设置>重启关机>添加一个重启计划:每天05:00时重启。
  • 网络设置>内外网设置
    • 外网接口(选择iKuai识别到的第二个网口):填入宽带拨号信息。
    • 内网接口(iKuai识别到的第一个网口,IP地址192.168.1.1):链路桥接(选择剩余的其他全部接口)
  • DHCP设置>DHCP服务端
    • 单iKuai版:
      • 客户端地址:192.168.1.10-192.168.1.254(1-9留给专用设备的管理地址)
      • 子网掩码:255.255.255.0
      • 网关:192.168.1.1
      • 首选DNS:223.5.5.5
      • 备选DNS:114.114.114.114
    • iKuai+OpenWrt版本:
      • 客户端地址:192.168.1.10-192.168.1.254(1-9留给专用设备的管理地址)
      • 子网掩码:255.255.255.0
      • 网关:192.168.1.3
      • 首选DNS:192.168.1.3
      • 备选DNS:192.168.1.3
  • DNS设置>DNS设置
    • 单iKuai版:
      • 首选DNS:223.5.5.5
      • 备选DNS:114.114.114.114
    • iKuai+OpenWrt版本:
      • 首选DNS:192.168.1.3
      • 备选DNS:192.168.1.3
  • UPnP设置>UPnP设置
    • UPnP即插即用服务:开启
    • 允许内网IP映射:0.0.0.0-255.255.255.255
    • 默认线路设置:任意
    • 掉线检测:开启
    • 检测周期:2
    • 定时重启:开启
    • 重启周期:全选
    • 重启时间:05:00。

7.2   OpenWrt设置

7.2.1   定时重启

系统>定时重启:启用,设置每天05:00。

7.2.2   网络

7.2.2.1   接口>LAN

  • IPv4地址:192.168.1.3。管理OpenWrt的地址。
  • 和ikuai一致保证同一个网段。
  • IPv4网关:指向iKuai的网关。保证OpenWrt的上网数据传输到iKuai再传输到外网。
  • 使用自定义的DNS服务器:223.5.5.5(初始先设置阿里云DNS保证设置过程的上网)。后面等设置好SmartDNS之后改由OpenWrt代理,即输入192.168.1.3。
  • 关闭DHCP服务,统一由iKuai分配。
  • 禁用掉IPv6服务。
  • 物理设置:桥接接口。(因为选用的也是)
  • lan高级设置:不勾选IPv6,勾选【开启开机自动运行】和【强制链路】。

7.2.2.2   Turbo ACC 加速

只开启前三个即可,DNS缓存后面由SmartDNS来管理。

7.2.2.3   防火墙设置

  • SYN-flood 防御:关闭(此项开启关闭都行)
  • 丢弃无效数据包:启用(此项开启关闭都行)
  • 启用FullCone-NAT:高性能模式
  • 入站数据、出站数据、转发都设置为接受。
  • lan口开启:IP 动态伪装(只保留lan口的规格,其他接口全部删除)
  • 防火墙自定义规则(一般默认就有前四条规则,没有需加上)
1
2
3
4
5
iptables -t nat -A PREROUTING -p udp —dport 53 -j REDIRECT —to-ports 53
iptables -t nat -A PREROUTING -p tcp —dport 53 -j REDIRECT —to-ports 53
[ -n “$(command -v ip6tables)” ] && ip6tables -t nat -A PREROUTING -p udp —dport 53 -j REDIRECT —to-ports 53
[ -n “$(command -v ip6tables)” ] && ip6tables -t nat -A PREROUTING -p tcp —dport 53 -j REDIRECT —to-ports 53
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE

7.2.3   SmartDNS

Bleach OpenWrt固件自带SmartDNS,eSir GDQ高大全固件需自行手动安装。

  • 基本设置
    • 启用
    • 本地端口6053
  • 高级设置
    • 开启TCP服务器
    • 勾选域名预加载
    • 勾选缓存过期服务
    • 缓存大小设为:1000000
    • 域名TTL最大值设为:3600
  • 第二DNS服务器
    • 启用
    • 本地端口5335
    • TCP服务器
    • 服务器组:oversea
    • 跳过测速
    • 跳过address规则
    • 跳过address SOA(#)规则
    • 跳过双栈优选
    • 跳过cache
    • 停用IPV6地址解析
  • 上游服务器(添加DNS的设置里面可以设置服务器组)
    • 服务器组名称:china(10条左右,包含iKuai路由宽带拨号返回的两个宽带供应商提供的DNS)
      1. iKuai路由宽带拨号返回的宽带供应商提供的DNS1,udp协议
      2. iKuai路由宽带拨号返回的宽带供应商提供的DNS2,udp协议
      3. 114dns,114.114.114.114,udp协议
      4. 114dns,114.114.115.115,udp协议
      5. opener dns,42.120.21.30,udp协议
      6. 阿里dns,223.5.5.5,udp协议
      7. 阿里dns,223.6.6.6,udp协议
      8. 腾讯dns,119.29.29.29,udp协议
      9. 百度dns,180.76.76.76,udp协议
      10. 华为dns,122.112.208.1,udp协议
    • 服务器组名称:oversea(10条左右)
      1. google dns,8.8.8.8,udp协议
      2. google dns,8.8.4.4,tls协议
      3. CloudFlare,1.1.1.1,udp协议
      4. IBM Quad9,9.9.9.9,udp协议
      5. Norton ConnectSafe,199.85.126.10,udp协议
      6. Norton ConnectSafe,199.85.127.10,udp协议
      7. OpenDNS,208.67.222.222,tls协议
      8. OpenDNS,208.67.220.220,udp协议
      9. V2EX DNS,199.91.73.222,tls协议
      10. V2EX DNS,178.79.131.110,tls协议
  • 域名规则
    • 服务器组:china
    • 域名分流设置:跳过测试
  • 自定义设置(只设置两条,其他全部注释掉)
1
2
bind:6053 -group china
bind:5335 -group oversea
  • 保存&应用

7.2.4   AdGuard Home

7.2.4.1   手动更新内核

  1. 下载最新的AdGuardHome内核(AdGuardHome_linux_amd64.tar.gz):点击跳转
  2. 解压获取「AdGuardHome」文件。
  3. 使用WinSCP登录openwrt虚拟机,进入到路径/usr/bin/AdGuardHome/,上传「AdGuardHome」文件。右击属性,分配权限0755(rwxr-xr-x)。

能科学上网时,点击检查更新,直接更新核心即可。

7.2.4.2   设置AdGuard Home

  • 启用
  • AdGuardHome重定向模式:作为dnsmasq的上游服务器
  • 详细日志
  • 开机后网络准备好时重启
  • 在关机时备份工作目录文件(所有的选项)
  • 打开192.168.1.3:3000地址并配置
  • 网页管理界面>监听接口>所有接口,端口号设置为默认的3000
  • DNS 服务器>监听接口>所有接口,端口号设置为5351
  • 网页管理界面登录密码
  • 设置>常规设置>使用过滤器和Hosts文件以拦截指定域名(其他选项全部关闭)
  • 设置>DNS设置
    • 上游 DNS 服务器
      1
      2
      127.0.0.1:6053
      127.0.0.1:5335
      • 并行请求
      • Bootstrap DNS:127.0.0.1:6053
      • 应用
    • DNS服务配置
      • 速度限制:0
      • DNS 缓存配置:都空着(采用上游DNS服务器,让SmartDNS来管理)
  • 设置>过滤器>DNS黑名单设置(将阻止匹配 DNS 拦截清单的域名):综合性的规则列表启用几个即可。

7.2.5   DNSMASQ设置

打开网络>DHCP/DNS。主要是解除DNSMASQ的DNS功能,只保留转发功能,让DNSMASQ作为AdGuard Home的下级服务生效。

  • DNS转发:127.0.0.1#5351。如果未自动设置成这个,手动强制改成这个。
  • HOSTS 和解析文件:忽略掉解析文件,不用DNSMASQ的解析。
  • 高级设置
    • DNS服务器端口:53
    • DNS查询缓存的大小:0
    • 最大并发查询数:1500
  • 保存&应用

7.2.6   OpenClash

实测,Meta内核因DNS转发问题,导致无法正常上网。例如GOOGLE可能会出现证书异常的问题,YOUTUBE点击视频后会存在一直转圈圈的情况,无法加载视频。现阶段稳定工作的模式为:Fake-IP的普通TUN模式,模型模式可调TUN或者混合(TUN模式需要有TUN内核支持)。代理模式使用规则上网即可。

v0.46.014-beta是最后一版支持TUN内核的版本,往后一版的v0.46.031-beta版本就开始移除Dev和TUN内核的支持,如果手动操作更新OpenClash版本,会自动移除Dev和TUN内核文件。即使手动通过WinSCP上传Dev和TUN内核文件,软件内也是无法识别到的。

7.2.6.1   更新客户端

  1. 下载客户端安装包:点击跳转
  2. 使用WinSCP登录openwrt虚拟机,进入到路径家目录home,上传安装包,运行安装opkg install ./luci-app-openclash_0.46.014-beta_all.ipk。如果报错,尝试opkg update

7.2.6.2   手动更新内核

内核下载地址:

新的内核只支持fake-ip,需要上传Meta内核。

能科学上网时,点击检查并更新,直接更新核心即可。

更新步骤:

  1. 下载内核。
  2. 使用WinSCP登录openwrt虚拟机,进入到路径/etc/openclash/core,上传内核文件。右击属性,分配权限0755(rwxr-xr-x)。

内核对应的名称(压缩包解压出来的名称不一定对应,需手动修改):

  • Dev 内核: clash
  • Tun 内核: clash_tun
  • Meta 内核: clash_meta

7.2.6.3   OpenClash设置

  • 运行状态:启动OPENCLASH
  • 插件设置
    • 模式设置>运行模式:Fake-IP(TUN-混合)模式【UDP-TUN,TCP-转发】
    • DNS设置>本地DNS劫持:停用
    • GEO数据库订阅:设置自动更新
    • 大陆白名单订阅:设置自动更新
  • 配置订阅:设置自动更新,更新间隔60分钟

7.2.7   扩容Overlay软件安装空间

  1. 关闭openwrt虚拟机。
  2. 硬件>硬盘>更多,调整磁盘大小(只能增加大小,按需增加所需硬盘的空间大小)。
  3. 打开openwrt虚拟机。
  4. 系统>磁盘管理L:将新增磁盘大小新建一个分区并格式化为ext4,保存并应用
  5. 挂载刚才新增的分区,举例sda3:mount /dev/sda3 /mnt/sda3,保存并应用。
  6. 拷贝源overlay目录的文件到新分区内:cp -r /overlay/* /mnt/sda3
  7. 系统>挂载点:添加sda3挂载点为「作为外部overlay使用(/overlay)」,保存并应用。
  8. 重启openwrt虚拟机,检查扩容情况。
  9. 根据情况可能需手动移除之前挂载的/mnt/sda3状态。

8   PVE总结

8.1   强烈推荐)PVETOOLS脚本程序推荐

  • PVETOOLS仓库地址:点击跳转
  • 安装步骤(PVE SHELL执行命令):
1
2
3
4
export LC_ALL=en_US.UTF-8
apt update && apt -y install git && git clone https://github.com/ivanhao/pvetools.git
cd pvetools
./pvetools.sh
  • 执行脚本程序后,可以一键执行「安装配置CPU省电」「配置PVE的web界面显示传感器温度」「去除订阅提示」等功能。

8.2   PVE虚拟机各硬件模型总结

TODO

9   飞牛私有云fnOS(NAS总结)

9.1   下载应用例如QBittorrent在后台上传时可能会引导硬盘持续读取和CPU的异常占用。

问题原因:系统Swap cache功能引起引起硬盘持续读取和CPU的异常占用。

解决方法:永久禁用Swap cache功能。

  1. 访问fnOS虚拟机控制台。
  2. 登录fnOS账户。
  3. sudo vim /etc/fstab
  4. I键进入vim编辑器编辑模式
  5. 找到/swapfile none swap sw 0 0一行内容并注释掉,即在行头加上#
  6. ESC键,摁:键然后输入wq!保存退出。
  7. cat /etc/fstab可以查看文件内容是否修改成功,输出内容如下:
1
2
3
UUID=ea545bcc-cd3f-479f-902a-ab2b02db25 / ext4 errors=remount-ro 0 1
# /swapfile none swap sw 0 0
UUID=676D-A1AF /boot/efi vfat umask=0077 0 1

10   宽带网络总结

10.1   会话数(连接数)

会话数(连接数)在线测试工具:https://qps.itzmx.com

连接数为应用产生的网络连接数,例如在爱快首页上看到连接数。并发连接数为 qps ,即每秒发起请求,等于在同一秒内,产生了 250 个请求。

会话数(连接数)的限制,一般普通线路带宽会有会话数限制,专线则没有该限制。触发限制的常见现象为:超过并发连接数上限后,此前已建立连接数的软件和网络请求可正常通信,并且期间ping一切正常,延迟没有任何抖动现象,但是新开软件则连不上网络。

11   后记

这光猫还是有断流情况,估计是过热导致掉线,后续考虑下加装小风扇。

断流降速故障已查明:虚拟网卡模型设置为Intel E1000导致的故障,重设为VirtIO (半虚拟化)即可解决。

在这台工控机上实际使用openwrt内网测速工具发现,虚拟机网卡模型设置为Intel E1000或者Realtek RTL8139都无法正常发挥网卡的性能,Intel E1000大约测试性能为100Mbps~300Mbps不等,Realtek RTL8139模型是最为拉跨的,直接为10Mbps。使用VirtIO (半虚拟化)模型内网测速才能恢复到900Mbps以上的正常速度。

12   参考文献

[1] Installation Failing: "Failed to prepare EFI boot using Grub"[EB/OL]. https://forum.proxmox.com/threads/installation-failing-failed-to-prepare-efi-boot-using-grub.122002/.
[2] 软路由科普系列 篇二:PVE安装iKuai OpenWrt 旁路由 基础设置 保姆级全教程[EB/OL]. https://post.smzdm.com/p/awrx4lxm/.
[3] ProxmoxVE 7.4 升级到 8.0,详细步骤[EB/OL]. https://blog.margrop.net/post/pve-7-upgrade-to-8/.
[4] PVE 联网及更换国内源[EB/OL]. https://www.cnblogs.com/pdblogs/p/16218543.html.
[5] Force update from unsigned repository[EB/OL]. https://askubuntu.com/questions/732985/force-update-from-unsigned-repository.
[6] ProxmoxVE(PVE) 启用 IOMMU[EB/OL]. https://www.insilen.com/post/501.html.
[7] Enable IOMMU or VT-d in your motherboard BIOS[EB/OL]. https://us.informatiweb.net/tutorials/it/bios/enable-iommu-or-vt-d-in-your-bios.html.
[8] ProxmoxVE(PVE) 使用 IMG 镜像文件,img 转 qcow2[EB/OL]. https://www.lxtx.tech/index.php/archives/65/.
[9]『软路由踩坑指南』篇三:ESXi 8.0 虚拟机安装 iKuai 主路由及保姆级配置[EB/OL]. https://post.smzdm.com/p/a5op28x7/.
[10]『软路由踩坑指南』篇四:ESXi 8.0 虚拟机安装 openWrt 路由系统终极指南[EB/OL]. https://post.smzdm.com/p/a7ngxeel/.
[11]『软路由踩坑指南』篇五:OpenWrt 旁路由进阶篇 SmartDNS+AdGuardHome 设置 DNS 分流、秒开网页、去广告[EB/OL]. https://post.smzdm.com/p/axz6z7w9/.
[12] [openwrt(x86)] OPenWRT 旁路由 +MosDNS+OpenClash+AdGuard Home 傻瓜配置图文教程[EB/OL]. https://www.right.com.cn/forum/thread-8284982-1-1.html.
[13] OpenWrt 扩容 Overlay 和 Docker 软件安装空间教程(内置硬盘版)附:Samba 网络共享设置[EB/OL]. https://www.right.com.cn/forum/thread-7470757-1-1.html.
[14] [OpenWrt] 使用 OpenClash 科学上网[EB/OL]. http://suyu0925.github.io/blog/2022/07/25/openwrt-openclash/.
[15] 镜像站使用帮助-Proxmox 软件仓库[EB/OL]. https://mirrors.tuna.tsinghua.edu.cn/help/proxmox/.
[16] 开启直通[EB/OL]. https://skyao.io/learning-pve/docs/pass-through/enable/.
[17] [经验分享] 在线连接数测试网页,简单一键测试宽带并发连接数限制[EB/OL]. https://bbs.ikuai8.com/thread-87196-1-1.html.
[18] 禁用Swap cache[EB/OL]. https://club.fnnas.com/forum.php?mod=viewthread&tid=2492.
[19] 镜像站使用帮助-Debian 软件源[EB/OL]. https://mirrors.tuna.tsinghua.edu.cn/help/debian/.
[20] 第 5 章 bookworm 中需要注意的问题[EB/OL]. https://www.debian.org/releases/bookworm//amd64/release-notes/ch-information.zh-cn.html.
[21] 【装机帮扶站】第881期:BUG处理器能选否?聊聊12代ES处理器[EB/OL]. https://zhuanlan.zhihu.com/p/458166029.

1   Python 语法摘记

python在相对路径import导入时,包名前面的小数点.是必要的,python3之后都是严格模式。

2   安装python的踩坑记录

2.1   vscode 无法设置python解释器

原因:安装路径在C:\Program Files,vscode权限不够时会导致无法设置python解释器。

解决方法:安装路径直接按默认路径,不装program路径。

2.2   pip安装某些包异常

情况:如果pip安装某些包一直有异常,反复安装卸载包都无法解决的话,并且确认不是包的问题的话。

原因:python环境异常。

解决方法:重装python。

  1. 直接删除python主安装路径的文件
  2. 运行安装程序repair
  3. 点击卸载,再点击安装(必须要卸载再安装,否则site-packages路径下没有任何包,repair操作不会恢复这个路径的文件)

3   卸载python

3.1   卸载清除已安装的pip包的bat批处理脚本

purge_py311_packages.bat

1
2
3
4
5
6
py -3.11 -m pip freeze > py311_requirements.txt
py -3.11 -m pip uninstall -r py311_requirements.txt -y
del py311_requirements.txt

pause
exit

purge_py310_packages.bat

1
2
3
4
5
6
py -3.10 -m pip freeze > py310_requirements.txt
py -3.10 -m pip uninstall -r py310_requirements.txt -y
del py310_requirements.txt

pause
exit

purge_py39_packages.bat

1
2
3
4
5
6
py -3.9 -m pip freeze > py39_requirements.txt
py -3.9 -m pip uninstall -r py39_requirements.txt -y
del py39_requirements.txt

pause
exit

3.2   清除pip缓存

1
2
3
@echo off
pip cache purge
exit

4   python的包管理

Python包管理是指创建、发布和安装Python包的过程。Python包是一种封装和分发Python代码的方式,以便于其他开发者使用和共享。Python包管理涉及的三个关键组件:PyPI、setuptools和wheel。

4.1   PyPI

Python软件包索引(PyPI)是一个在线存储库,用于发布和查找Python包。PyPI允许开发者上传他们的包,并提供一个中心化的搜索引擎,让其他开发者能够找到并安装这些包。PyPI通过pip(Python包安装器)来安装和管理包。

基本的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 升级pip
python -m pip install --upgrade pip
pip install pip -U
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U

# pip设定清华源(升级 pip 到最新的版本 (>=10.0.0) 后进再进行配置)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# win平台pip缓存路径:\%LocalAppData\%>pip>Cache

# 临时一次性使用镜像源安装包
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package

# 安装包
pip install <package_name>
# 升级包
pip install --upgrade <package_name>
# 卸载包
pip uninstall <package_name>
# 显示包文件信息
pip show --files <package_name>
# 列出已过时的包
pip list --outdated

# 卸载全部包
pip freeze > requirements.txt
pip uninstall -r requirements.txt -y

# 安装全部包
pip install -r requirements.txt --upgrade

4.2   setuptools和wheel

setuptools是一个Python包管理工具,用于创建、构建和发布Python包。setuptools通过提供易于使用的命令行接口和配置文件(如setup.py)来简化包管理过程。通过使用setuptools,开发者可以方便地将他们的代码打包成可分发的格式,如源代码分发(sdist)和wheel分发(bdist_wheel)。

wheel是一种Python分发格式,用于提高安装速度和兼容性。与源代码分发(sdist)相比,wheel分发是预编译的,这意味着它们不需要在安装过程中进行编译。这使得wheel分发在安装速度和跨平台兼容性方面具有优势。

基本使用:

  • 安装setuptools和wheel:pip install setuptools wheel
  • 创建setup.py文件:
1
2
3
4
5
6
7
8
9
10
from setuptools import setup, find_packages

setup(
name="my_package",
version="0.1",
packages=find_packages(),
install_requires=[
"requests",
],
)
  • 构建源代码分发和wheel分发:python setup.py sdist bdist_wheel
  • 安装twine并发布包到PyPI:
1
2
pip install twine
twine upload dist/*
  • 安装wheel分发的包:pip install dist/my_package-0.1-py3-none-any.whl

5   其他

  • 官方3.9版本最后提供二进制文件下载的是3.9.13版本,之后都是补丁形式。

6   参考文献

[1]【Python 基础】Python 包管理:PyPI、setuptools 与 wheel[EB/OL]. https://blog.csdn.net/qq_33578950/article/details/130297451.

第六章:函数

练习6.1

实参和形参的区别的什么?

实参是函数调用的实际值,是形参的初始值。

练习6.2

请指出下列函数哪个有错误,为什么?应该如何修改这些错误呢?

1
2
3
4
5
6
7
8
(a) int f() {
string s;
// ...
return s;
}
(b) f2(int i) { /* ... */ }
(c) int calc(int v1, int v1) { /* ... */ }
(d) double square (double x) return x * x;

修正如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 应定义函数的返回值为string
(a) string f() {
string s;
// ...
return s;
}
// 无返回值时,应定义函数的返回值为void
(b) void f2(int i) { /* ... */ }
// 任意两个形参都不能同名
(c) int calc(int v1, int v2) { /* ... */ }
// 函数定义构成:返回类型、函数名字、0个或多个形参组成的列表(形参以逗号隔开,位于一对圆括号之内)、函数体(由语句块构成,位于一对花括号之内)。
(d) double square (double x) { return x * x; }

练习6.3

编写你自己的fact函数,上机检查是否正确。

练习6.4

编写一个与用户交互的函数,要求用户输入一个数字,计算生成该数字的阶乘。在main函数中调用该函数。

练习6.5

编写一个函数输出其实参的绝对值。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int abs(int i)
{
return i > 0 ? i : -i;
}

int main()
{
std::cout << abs(-5) << std::endl;
return 0;
}

练习6.6

说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时达到这三种形式。

形参定义在函数形参列表里面;局部变量定义在代码块里面;局部静态变量在程序的执行中,第一次经过对象定义语句时初始化,并且直到程序终止时才被销毁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 例子
int count_add(int n) // n是形参
{
static int ctr = 0; // ctr 是局部静态变量
ctr += n;
return ctr;
}

int main()
{
for (int i = 0; i != 10; ++i) // i 是局部变量
cout << count_add(i) << endl;

return 0;
}

练习6.7

编写一个函数,当它第一次被调用时返回0,以后每次被调用返回值加1。

1
2
3
4
5
int generate()
{
static int ctr = 0;
return ctr++;
}

练习6.8

编写一个名为Chapter6.h的头文件,令其包含6.1节练习中的函数声明。

练习6.9 : fact.cc | factMain.cc

编写你自己的fact.cc 和factMain.cc ,这两个文件都应该包含上一小节的练习中编写的 Chapter6.h 头文件。通过这些文件,理解你的编译器是如何支持分离式编译的。

练习6.10

编写一个函数,使用指针形参交换两个整数的值。在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。

练习6.11

编写并验证你自己的reset函数,使其作用于引用类型的参数。

练习6.12

改写6.2.1节练习中6.10的程序,使用引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?

引用更易于使用,无论是用法理解还是语法编写层面都简洁一些。

练习6.13

假设 T 是某种类型的名字,说明以下两个函数声明的区别:一个是void f(T), 另一个是 void f(&T)。

  • void f(T) 的参数通过「值传递」,在函数中T是实参的副本,改变T值不会影响到原来的实参值。
  • void f(&T) 的参数通过「引用传递」,在函数中T是实参的引用(即T是实参的别名),T的改变也就是实参的改变。

练习6.14

举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。

例如交换两个整数的函数,形参应该是引用

1
2
3
4
5
6
void swap(int& lhs, int& rhs)
{
int temp = lhs;
lhs = rhs;
rhs = temp;
}

当实参的值是右值时(比如整型常量,用作右值时,使用的是对象的值(内容),见左值和右值),形参不能为引用类型

1
2
3
4
5
6
7
8
9
10
int add(int a, int b)
{
return a + b;
}

int main()
{
int i = add(1,2);
return 0;
}

练习6.15

说明find_char 函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?

  • 因为字符串可能很长,因此使用引用避免拷贝;而在函数中我们不希望改变 s 的内容,所以令 s 为常量。
  • occurs 是要传到函数外部的变量,所以使用引用,occurs 的值会改变,所以是普通引用。
  • 因为我们只需要 c 的值,这个实参可能是右值(右值实参无法用于引用形参),所以 c 不能用引用类型。
  • 如果 s 是普通引用,函数中也可能会意外改变原来字符串的内容。
  • occurs 如果是常量引用,那么意味着不能改变它的值,那也就失去意义了。

练习6.16

下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。

1
bool is_empty(string& s) { return s.empty(); }

局限性:常量字符串字符串字面值无法作为该函数的实参,比如下面这样调用是非法的:

1
2
3
const string str;
bool flag = is_empty(str); //非法
bool flag = is_empty("hello"); //非法

改善:将这个函数的形参定义为常量引用(对const的引用,并非指引用自身是常量,谨记引用不是对象,是对象的别名)

1
bool is_empty(const string& s) { return s.empty(); }

练习6.17

编写一个函数,判断string对象中是否含有大写字母。编写另一个函数,把string对象全部改写成小写形式。在这两个函数中你使用的形参类型相同吗?为什么?

两个函数的形参不一样。第一个函数使用常量引用,第二个函数使用普通引用。

练习6.18

为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。

  • (a) 名为 compare 的函数,返回布尔值,两个参数都是 matrix 类的引用。
  • (b) 名为 change_val 的函数,返回vector的迭代器,有两个参数:一个是int,另一个是vector的迭代器。
1
2
(a) bool compare(matrix &m1, matrix &m2);
(b) vector<int>::iterator change_val(int, vector<int>::iterator);

练习6.19

假定有如下声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因。

1
2
3
4
5
6
7
8
double calc(double);
int count(const string &, char);
int sum(vector<int>::iterator, vector<int>::iterator, int);
vector<int> vec(10);
(a) calc(23.4, 55.1);
(b) count("abcda",'a');
(c) calc(66);
(d) sum(vec.begin(), vec.end(), 3.8);
  • (a) 不合法。calc只有一个参数。
  • (b) 合法。
  • (c) 合法。
  • (d) 合法。

练习6.20

引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况?

应该尽量将引用形参设为常量引用,除非有明确的目的是为了改变这个引用变量。如果形参应该是常量引用,而我们将其设为了普通引用,那么常量实参将无法作用于普通引用形参,会出现编译报错。

练习6.21

编写一个函数,令其接受两个参数:一个是int型的数,另一个是int指针。函数比较int的值和指针所指的值,返回较大的那个。在该函数中指针的类型应该是什么?

应该是 const int * 类型。

练习6.22

编写一个函数,令其交换两个int指针。

练习6.23

参考本节介绍的几个print函数,根据理解编写你自己的版本。依次调用每个函数使其输入下面定义的i和j:

1
int i = 0, j[2] = { 0, 1 };

练习6.24

描述下面这个函数的行为。如果代码中存在问题,请指出并改正。

1
2
3
4
5
void print(const int ia[10])
{
for (size_t i = 0; i != 10; ++i)
cout << ia[i] << endl;
}

当数组作为实参的时候,会被自动转换为指向首元素的指针。因此函数形参接受的是一个指针。以上代码实际传递只是指针,会有一个隐患,因此无论是const int ia[3]或者const int ia[255]都没有区别,因为无法实际传递数组的大小。如果要让这个代码成功运行,可以将实参改为数组的引用。更多讨论可参阅Confused about array parameters

1
2
3
4
5
6
// 这里的ia必须要有括号,这样才是声明为指向含有10个整数的数组的引用,没有括号就相当于引用的数组
void print(const int (&ia)[10])
{
for (size_t i = 0; i != 10; ++i)
cout << ia[i] << endl;
}

练习6.25

编写一个main函数,令其接受两个实参。把实参的内容连接成一个string对象并输出出来。

练习6.26

编写一个程序,使其接受本节所示的选项;输出传递给main函数的实参的内容。

练习6.27

编写一个函数,它的参数是initializer_list<int>类型的对象,函数的功能是计算列表中所有元素的和。

练习6.28

在error_msg函数的第二个版本中包含ErrCode类型的参数,其中循环内的elem是什么类型?

elemconst string & 类型。

练习6.29

在范围for循环中使用initializer_list对象时,应该将循环控制变量声明成引用类型吗?为什么?

应该使用常量引用类型。initializer_list 对象中的元素永远都是常量,我们无法修改initializer_list 对象中元素的值。

练习6.30

编译第200页的str_subrange函数,看看你的编译器是如何处理函数中的错误的。

错误信息(Visual Studio 2022 Developer Command Prompt v17.9.5):

“str_subrange”: 函数必须返回值 (cpp(C2561))

clang编译器应该还能提示,参见

Control may reach end of non-void function. // error #2

练习6.31

什么情况下返回的引用无效?什么情况下返回常量的引用无效?

当返回的引用的对象是局部变量时,返回的引用无效;当我们希望返回的对象被修改时,返回常量的引用无效。

练习6.32

下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。

1
2
3
4
5
6
7
int &get(int *array, int index) { return array[index]; }
int main()
{
int ia[10];
for (int i = 0; i != 10; ++i)
get(ia, i) = i;
}

合法。get 函数根据索引取得数组中的元素的引用。

练习6.33

编写一个递归函数,输出vector对象的内容。

练习6.34

如果factorial 函数的停止条件如下所示,将发生什么情况?

1
if (val != 0)

如果val为正数,从结果上来说没有区别(多乘了个 1); 如果val为负数,那么递归永远不会结束。

通常而言,负数没有阶乘,但这里函数定义是int,可以输入负数。这里是问题所在。

练习6.35

在调用factorial 函数时,为什么我们传入的值是 val-1 而非 val--?

如果传入的值是 val--,那么将会永远传入相同的值来调用该函数,递归将永远不会结束。

练习6.36

编写一个函数声明,使其返回数组的引用并且该数组包含10个string对象。不用使用尾置返回类型、decltype或者类型别名。

1
string (&fun())[10];

练习6.37

为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype关键字。你觉得哪种形式最好?为什么?

1
2
3
4
5
6
7
typedef string str_arr[10];
str_arr& fun();

auto fun()->string(&)[10];

string s[10];
decltype(s)& fun();

我觉得尾置返回类型最好。

练习6.38

修改arrPtr函数,使其返回数组的引用。

1
2
3
4
decltype(odd)& arrPtr(int i)
{
return (i % 2) ? odd : even;
}

练习6.39

说明在下面的每组声明中第二条声明语句是何含义。如果有非法的声明,请指出来。

1
2
3
4
5
6
(a) int calc(int, int);
int calc(const int, const int);
(b) int get();
double get();
(c) int *reset(int *);
double *reset(double *);
  • (a) 非法。因为顶层const 不影响传入函数的对象,所以第二个声明无法与第一个声明区分开来。
  • (b) 非法。对于重载的函数来说,它们应该只有形参的数量和形参的类型不同。返回值与重载无关。
  • (c) 合法。

练习6.40

下面的哪个声明是错误的?为什么?

1
2
(a) int ff(int a, int b = 0, int c = 0);
(b) char *init(int ht = 24, int wd, char bckgrnd);
  • (a) 正确。
  • (b) 错误。因为一旦某个形参被赋予了默认值,那么它后面的所有形参都必须要有默认值。

练习6.41

下面的哪个调用是非法的?为什么?哪个调用虽然合法但显然与程序员的初衷不符?为什么?

1
2
3
4
char *init(int ht, int wd = 80, char bckgrnd = ' ');
(a) init();
(b) init(24,10);
(c) init(14,'*');
  • (a) 非法。第一个参数不是默认参数,最少需要一个实参。
  • (b) 合法。
  • (c) 合法,但与代码设计初衷不符。实参字符 * 被编译器隐式转换成 int 传入给第二个参数。而初衷是要传给第三个参数。

练习6.42

给make_plural函数(参见6.3.2节,第201页)的第二个形参赋予默认实参's', 利用新版本的函数输出单词success和failure的单数和复数形式。

这里原书应该是写错了(英文原版写的也是second),正确应该为「给第三个形参赋予默认实参's'」。

练习6.43

你会把下面的哪个声明和定义放在头文件中?哪个放在源文件中?为什么?

1
2
(a) inline bool eq(const BigInt&, const BigInt&) {...}
(b) void putValues(int *arr, int size);

全部都放在头文件。(a) 是内联函数,(b) 是声明。

练习6.44

将6.2.2节(第189页)的isShorter函数改写成内联函数。

1
2
3
4
inline bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}

练习6.45

回顾在前面的练习中你编写的那些函数,它们应该是内联函数吗?如果是,将它们改写成内联函数;如果不是,说明原因。

一般来说,内联机制用于优化规模小、流程直接、频繁调用的函数。

例如,练习6.38的arrPtr函数和练习6.42的make_plural函数应该定义为内联函数。其他函数若规模不小,且只有一次调用的话,即使定义为内联函数,编译器也很可能会选择忽略,并不会展开内联(内联说明只是向编译器发出的一个请求,实际是否展开内联要取决于编译器)。

练习6.46

能把isShorter函数定义成constexpr函数吗?如果能,将它改写成constexpr函数;如果不能,说明原因。

不能。constexpr函数的返回值类型及所有形参都需要为字面值类型。std::string::size()不是常量函数,s1.size() < s2.size()也不是常量表达式,它的运行结果虽然可以强制转换成常量类型,但由于常量≠常量表达式,即const≠constexpr,因为constexpr关键字不能进行强制类型转换,一个常量可能在运行时才能确定值,而常量表达式要求在编译阶段就确定值。

练习6.47

改写6.3.2节(第205页)练习中使用递归输出vector内容的程序,使其有条件地输出与执行过程有关的信息。例如,每次调用时输出vector对象的大小。分别在打开和关闭调试器的情况下编译并执行这个程序。

练习6.48

说明下面这个循环的含义,它对assert的使用合理吗?

1
2
3
string s;
while (cin >> s && s != sought) { } //空函数体
assert(cin);

不合理。从这个程序的意图来看,应该用

1
assert(s == sought);

练习6.49

什么是候选函数?什么是可行函数?

  • 候选函数:与被调用函数同名,并且其声明在调用点可见。
  • 可行函数:形参与实参的数量相等,并且每个实参类型与对应的形参类型相同或者能转换成形参的类型。

练习6.50

已知有第217页对函数 f 的声明,对于下面的每一个调用列出可行函数。其中哪个函数是最佳匹配?如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性?

1
2
3
4
(a) f(2.56, 42)
(b) f(42)
(c) f(42, 0)
(d) f(2.56, 3.14)
  • (a) void f(int, int);void f(double, double = 3.14); 是可行函数。该调用具有二义性而不合法。
  • (b) void f(int);void f(double, double = 3.14); 是可行函数。void f(int); 是最佳匹配。
  • (c) void f(int, int);void f(double, double = 3.14); 是可行函数。void f(int, int); 是最佳匹配。
  • (d) void f(int, int);void f(double, double = 3.14); 是可行函数。void f(double, double = 3.14); 是最佳匹配。

练习6.51

编写函数f的4版本,令其各输出一条可以区分的消息。验证上一个练习的答案,如果你的回答错了,反复研究本节内容直到你弄清自己错在何处。

练习6.52

已知有如下声明:

1
2
void manip(int ,int);
double dobj;

请指出下列调用中每个类型转换的等级(参见6.6.1节,第219页)。

1
2
(a) manip('a', 'z');
(b) manip(55.4, dobj);
  • (a) 第3级。类型提升实现的匹配。
  • (b) 第4级。算术类型转换实现的匹配。

练习6.53

说明下列每组声明中的第二条语句会产生什么影响,并指出哪些不合法(如果有的话)。

1
2
3
4
5
6
(a) int calc(int&, int&);
int calc(const int&, const int&);
(b) int calc(char*, char*);
int calc(const char*, const char*);
(c) int calc(char*, char*);
int calc(char* const, char* const);

(c) 不合法。顶层const不影响传入函数的对象。

练习6.54

编写函数的声明,令其接受两个int形参并返回类型也是int;然后声明一个vector对象,令其元素是指向该函数的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int func(int a, int b);

using pFunc1 = decltype(func) *;
typedef decltype(func) *pFunc2;
using pFunc3 = int (*)(int a, int b);
using pFunc4 = int(int a, int b);
typedef int(*pFunc5)(int a, int b);
using pFunc6 = decltype(func);

std::vector<pFunc1> vec1;
std::vector<pFunc2> vec2;
std::vector<pFunc3> vec3;
std::vector<pFunc4*> vec4;
std::vector<pFunc5> vec5;
std::vector<pFunc6*> vec6;

练习6.55

编写4个函数,分别对两个int值执行加、减、乘、除运算;在上一题创建的vector对象中保存指向这些函数的指针。

1
2
3
4
5
6
7
8
9
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }

vec1.push_back(add);
vec1.push_back(subtract);
vec1.push_back(multiply);
vec1.push_back(divide);

练习6.56

调用上述vector对象中的每个元素并输出其结果。

R.I.P