目录
- Hi3798MV200 恩兔N2 NS-1 (一): 设备介绍和刷机说明
- Hi3798MV200 恩兔N2 NS-1 (二): HiNAS海纳思使用和修改
- Hi3798MV200 恩兔N2 NS-1 (三): 制作 Ubuntu rootfs
- Hi3798MV200 恩兔N2 NS-1 (四): 制作 Debian rootfs
关于根文件系统 rootfs
在 Linux 中, 所有的文件和目录被组织成一个树状的结构, 而根文件系统, rootfs, the root filesystem, 位于文件树的顶层(路径'/'). Linux 内核通过 root =
设置的参数挂载 rootfs. 在根文件系统中也包含了其它文件树的挂载点(mount points), 用于将其它文件(设备)挂载到当前环境中, 形成完整的系统.
在根文件系统中包含了用于系统启动和操作的关键文件. 系统引导启动程序会在根文件系统挂载之后执行初始化脚本(如rcS, init.d, profile).
如果把整个Linux操作系统看作层级关系, 根文件系统是位于内核之上的模块,对于同样的硬件和架构, Linux各个发行版的区别主要在于根文件系统, 而底层的内核部分几乎是一样的. 通过制作根文件系统, 可以更换成其它发行版, 定制自己的最小化安装.
文件准备
底包
本例使用的是稍息版 Debian 10, 替换成 Ubuntu20.04.
从 stretch.tar.bz2 中提取驱动部分, 位于 /lib/modules/4.4.35-hi3798mv2x/
下载 ubuntu-base
从国内镜像站点, 下载 ubuntu-base 包
- https://mirrors.ustc.edu.cn/ubuntu-cdimage/ubuntu-base/releases/
- https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cdimage/ubuntu-base/releases/
解压
在本地创建工作目录, 将压缩包解压到工作目录下, 注意要用 sudo
+ -p
(-p, --preserve-permissions)参数, 保留原owner和原权限
mkdir workroot sudo tar -xpf ubuntu-base-20.04.5-base-arm64.tar.gz -C workroot/
初始的目录大小为77MB左右. 可以检查一下 workroot 下的文件目录, owner是否为 root.
关于为什么要用 sudo
Even if you use tar's --same-owner flag, you will still need to extract the files as root to preserve ownership.
--same-owner flag is on by default for root.
--no-same-owner, extract files as yourself, which is default for ordinary users
准备 resolv.conf
base系统中 resolv.conf 为空, 需要设置 nameserver 否则 chroot 后目标系统 apt install 时无法解析域名
选项一, 复制
复制 resolv.conf 到目标系统
sudo cp /etc/resolv.conf workroot/etc/resolv.conf
选项二, 直接写
echo "nameserver 127.0.0.53" | sudo tee workroot/etc/resolv.conf
复制 qemu-xxx-static
安装 qemu-user-static, 这个包里面有各个架构的二进制执行文件, 会安装到 /usr/bin
sudo apt install qemu-user-static
对于 armhf, 复制 qemu-arm-static; 对于 arm64 复制 qemu-aarch64-static
# armhf sudo cp /usr/bin/qemu-arm-static workroot/usr/bin/ # arm64 sudo cp /usr/bin/qemu-aarch64-static workroot/usr/bin/
在进行下一步之前检查文件格式是否正确, 32位的 armhf 用 qemu-arm-static, 64位的 arm64 用 qemu-aarch64-static
# armhf sudo chroot workroot/ /usr/bin/qemu-arm-static /bin/ls # arm64 sudo chroot workroot/ /usr/bin/qemu-aarch64-static /bin/ls
如果文件架构不匹配, 会提示- /bin/ls: Invalid ELF image for this architecture
修改目标系统软件源
vi workroot/etc/apt/sources.list
替换为USTC源
: %s/http:\/\/ports.ubuntu.com\/ubuntu-ports\//http:\/\/mirrors.ustc.edu.cn\/ubuntu-ports\//gc
挂载目标系统
选项一: 手工挂载
挂载目录
sudo mount -t proc /proc workroot/proc sudo mount -t sysfs /sys workroot/sys sudo mount -o bind /dev workroot/dev sudo mount -o bind /dev/pts workroot/dev/pts
切换根目录
sudo chroot workroot/
如果前面的检查没问题, 但是这一步总是提示 '/bin/bash': Exec format error
, 检查一下 binfmts 是否开启
update-binfmts --display
正常应该显示如下, 对应格式为 enabled,
qemu-aarch64 (enabled): package = qemu-user-static ... qemu-arm (enabled): package = qemu-user-static ...
如果显示为 disabled, 需要检查是否有软件未安装. 安装了 Docker 的 Ubuntu 环境可能会有冲突.
$ mount | grep binfmt systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=18150) binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)
选项二: 使用脚本挂载
以上的操作, 可以通过一个脚本进行简化
#!/bin/bash mnt() { echo "MOUNTING" sudo mount -t proc /proc ${2}proc sudo mount -t sysfs /sys ${2}sys sudo mount -o bind /dev ${2}dev sudo mount -o bind /dev/pts ${2}dev/pts sudo chroot ${2} } umnt() { echo "UNMOUNTING" sudo umount ${2}proc sudo umount ${2}sys sudo umount ${2}dev/pts sudo umount ${2}dev } if [ "$1" == "-m" ] && [ -n "$2" ] ; then mnt $1 $2 elif [ "$1" == "-u" ] && [ -n "$2" ]; then umnt $1 $2 else echo "" echo "Either 1'st, 2'nd or both parameters were missing" echo "" echo "1'st parameter can be one of these: -m(mount) OR -u(umount)" echo "2'nd parameter is the full path of rootfs directory(with trailing '/')" echo "" echo "For example: ch-mount -m /media/sdcard/" echo "" echo 1st parameter : ${1} echo 2nd parameter : ${2} fi
需要使用目标系统环境时
./mount.sh -m workroot/
定制 rootfs 内容
root@Box:/# uname -a Linux Box 5.15.0-52-generic #58~20.04.1-Ubuntu SMP Thu Oct 13 13:09:46 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux # 检查 mount root@Box:/# mount /proc on /proc type proc (rw,relatime) /sys on /sys type sysfs (rw,relatime) udev on /dev type devtmpfs (rw,nosuid,noexec,relatime,size=6965676k,nr_inodes=1741419,mode=755,inode64) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
添加驱动文件
仅使用kernel自带的驱动可以启动rootfs, 但是一些板载的外设, 例如SATA硬盘和USB, 会因为没有驱动而无法识别. 需要手动将这些驱动放到rootfs中.
通过uname -r
可以看到目标系统的架构为4.4.35-hi3798mv2x
, 由此可以确定驱动的路径为
/lib/modules/4.4.35-hi3798mv2x/
从前面准备的底包中, 将驱动部分文件提取后放到这个目录下, 结构类似于
modules └── 4.4.35-hi3798mv2x ├── kernel │ ├── crypto │ ├── drivers │ ├── fs │ ├── lib │ └── net ├── modules.alias ├── modules.alias.bin ├── modules.builtin ├── modules.builtin.alias.bin ├── modules.builtin.bin ├── modules.dep ├── modules.dep.bin ├── modules.devname ├── modules.order ├── modules.softdep ├── modules.symbols └── modules.symbols.bin
安装基础软件
# 77M -> 300M apt update # 300M -> 304M apt install nano sudo vim-tiny
修改软件源vi /etc/apt/sources.list
, 替换为USTC源
: %s/http:\/\/ports.ubuntu.com\/ubuntu-ports\//http:\/\/mirrors.ustc.edu.cn\/ubuntu-ports\//gc
再安装其它软件就快多了
apt upgrade # 304M -> 440M apt install openssh-server # 440M -> 445M apt install u-boot-tools net-tools sysstat smartmontools network-manager
安装的软件包中
- openssh-server 提供 ssh 服务
- u-boot-tools 提供 fw_printenv 和 fw_setenv 方法, 用于修改 UBOOT 启动参数
- net-tools 提供 ifconfig, netstat 等常用工具
- sysstat 提供 iostat 等常用工具
基础设置
设置网络
mkdir /etc/network/interfaces.d echo auto eth0 > etc/network/interfaces.d/eth0 echo iface eth0 inet dhcp >> etc/network/interfaces.d/eth0
给 root 用户设置密码 注意 这一步别忘了
passwd
开启 root 用户 ssh 访问, 编辑 /etc/ssh/sshd_config, 找到
#PermitRootLogin prohibit-password
替换为
PermitRootLogin yes
配置登录的串口, 修改文件 /etc/systemd/system/getty.target.wants/getty@tty1.service
vi /etc/systemd/system/getty.target.wants/getty\@tty1.service
将
ConditionPathExists=/dev/tty0
修改为实际的名称
ConditionPathExists=/dev/ttyAMA0
清理文件
安装完成后, 清理apt
apt autoremove apt-get autoclean apt-get clean apt clean # 结束后 368M
取消挂载目标系统
在目标系统上, exit 退出
结束后, 要先取消挂载
选项一: 手工取消挂载
sudo umount workroot/proc sudo umount workroot/sys sudo umount workroot/dev/pts sudo umount workroot/dev
选项二: 通过脚本取消挂载
如果通过脚本, 则是
./mount.sh -u workroot/
制作 rootfs 镜像文件
# 生成一个适当大小的空镜像,这个大小参考du -h workroot dd if=/dev/zero of=rootfs.img bs=1M count=1024 # 格式化 mkfs.ext4 rootfs.img # or mkfs -t ext4 rootfs.img # 挂载空镜像 mkdir rootfs sudo mount rootfs.img rootfs/ # 写入文件, 保留权限 sudo cp -rfp workroot/* rootfs/ # 取消挂载 sudo umount rootfs/ # 检查文件系统并自动修复 e2fsck -p -f rootfs.img # 使镜像紧凑 resize2fs -M rootfs.img
问题和解决
Root 能串口登录, 无法 ssh 登录
这是因为 ssh 默认禁止 root 登录, 编辑 /etc/ssh/sshd_config, 找到
#PermitRootLogin prohibit-password
替换为
PermitRootLogin yes
然后systemctl restart sshd
重启 sshd 服务
分区可用空间为0
这是因为镜像压缩后写入, 分区大小就是镜像大小, 需要通过 resize2fs /dev/[partition] 扩充分区
方案一: 使用脚本, 手工执行
创建 /usr/bin/local_resize.sh, 内容如下, chmod +x
设为可执行
#!/bin/bash rootfs_partition=/dev/$(lsblk -l|grep /|awk '{print $1}') logger -t "resize-disk[$$]" "resizing $rootfs_partition" if [ "$(echo $rootfs_partition | grep "mmc")" = "" ];then rootfs_disk=$(echo "$rootfs_partition" |sed -E -e 's/^(.*)[0-9]+/\1/g') else rootfs_disk=$(echo "$rootfs_partition" |sed -E -e 's/^(.*)p[0-9]+/\1/g') fi if [ "$rootfs_disk" = "/dev/mmcblk0" ]; then resize2fs $rootfs_partition 2>&1 > /dev/null else rootfs_partition_num=$(echo "$rootfs_partition" |sed -E -e 's/^.*([0-9]+)/\1/g') startfrom=$(fdisk -l ${rootfs_disk} -o device,start|grep ${rootfs_partition}|awk '{print $2}') (echo d; echo $rootfs_partition_num; echo n; echo p; echo $rootfs_partition_num; echo $startfrom; echo ; echo p; echo w;) | fdisk $rootfs_disk sync resize2fs $rootfs_partition fi logger -t "resize-disk[$$]" "resized $rootfs_partition"
方案二: 使用 systemd service 在第一次启动时执行
增加 /usr/sbin/local-resize2fs.sh , chmod +x
设为可执行
#!/bin/bash if [ ! -f /etc/first_init ]; then rootfs_partition=/dev/$(lsblk -l|grep /|awk '{print $1}') logger -t "resize-disk[$$]" "resizing $rootfs_partition" if [ "$(echo $rootfs_partition | grep "mmc")" = "" ];then rootfs_disk=$(echo "$rootfs_partition" |sed -E -e 's/^(.*)[0-9]+/\1/g') else rootfs_disk=$(echo "$rootfs_partition" |sed -E -e 's/^(.*)p[0-9]+/\1/g') fi if [ "$rootfs_disk" = "/dev/mmcblk0" ]; then resize2fs $rootfs_partition 2>&1 > /dev/null else rootfs_partition_num=$(echo "$rootfs_partition" |sed -E -e 's/^.*([0-9]+)/\1/g') startfrom=$(fdisk -l ${rootfs_disk} -o device,start|grep ${rootfs_partition}|awk '{print $2}') #lastsector=$(fdisk -l ${rootfs_disk} -o device,end|grep ${rootfs_partition}|awk '{print $2}') (echo d; echo $rootfs_partition_num; echo n; echo p; echo $rootfs_partition_num; echo $startfrom; echo ; echo p; echo w;) | fdisk $rootfs_disk sync resize2fs $rootfs_partition fi echo `date +%s%N` > /etc/first_init logger -t "resize-disk[$$]" "resized $rootfs_partition" fi exit 0
增加service: /etc/systemd/system/resize2fs.service
[Unit] Description=resize2fs local filesystem Before=local-fs-pre.target DefaultDependencies=no [Service] Type=oneshot TimeoutSec=infinity ExecStart=/usr/sbin/local-resize2fs.sh RemainAfterExit=true [Install] RequiredBy=local-fs-pre.target
在 /etc/systemd/system/local-fs-pre.target.wants/ 下面增加 resize2fs.service 的软链, 使其生效