目录
- 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和原权限
bash
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 到目标系统
bash
sudo cp /etc/resolv.conf workroot/etc/resolv.conf
选项二, 直接写
bash
echo "nameserver 127.0.0.53" | sudo tee workroot/etc/resolv.conf
复制 qemu-xxx-static
安装 qemu-user-static, 这个包里面有各个架构的二进制执行文件, 会安装到 /usr/bin
bash
sudo apt install qemu-user-static
对于 armhf, 复制 qemu-arm-static; 对于 arm64 复制 qemu-aarch64-static
bash
# 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
bash
# 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
修改目标系统软件源
bash
vi workroot/etc/apt/sources.list
替换为USTC源
bash
: %s/http:\/\/ports.ubuntu.com\/ubuntu-ports\//http:\/\/mirrors.ustc.edu.cn\/ubuntu-ports\//gc
挂载目标系统
选项一: 手工挂载
挂载目录
bash
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
切换根目录
bash
sudo chroot workroot/
如果前面的检查没问题, 但是这一步总是提示 '/bin/bash': Exec format error
, 检查一下 binfmts 是否开启
bash
update-binfmts --display
正常应该显示如下, 对应格式为 enabled,
qemu-aarch64 (enabled):
package = qemu-user-static
...
qemu-arm (enabled):
package = qemu-user-static
...
如果显示为 disabled, 需要检查是否有软件未安装. 安装了 Docker 的 Ubuntu 环境可能会有冲突.
bash
$ 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)
选项二: 使用脚本挂载
以上的操作, 可以通过一个脚本进行简化
bash
#!/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
需要使用目标系统环境时
bash
./mount.sh -m workroot/
定制 rootfs 内容
bash
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
安装基础软件
bash
# 77M -> 300M
apt update
# 300M -> 304M
apt install nano sudo vim-tiny
修改软件源vi /etc/apt/sources.list
, 替换为USTC源
bash
: %s/http:\/\/ports.ubuntu.com\/ubuntu-ports\//http:\/\/mirrors.ustc.edu.cn\/ubuntu-ports\//gc
再安装其它软件就快多了
bash
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 等常用工具
基础设置
设置网络
bash
mkdir /etc/network/interfaces.d
echo auto eth0 > etc/network/interfaces.d/eth0
echo iface eth0 inet dhcp >> etc/network/interfaces.d/eth0
给 root 用户设置密码 注意 这一步别忘了
bash
passwd
开启 root 用户 ssh 访问, 编辑 /etc/ssh/sshd_config, 找到
#PermitRootLogin prohibit-password
替换为
PermitRootLogin yes
配置登录的串口, 修改文件 /etc/systemd/system/getty.target.wants/getty@tty1.service
bash
vi /etc/systemd/system/getty.target.wants/getty\@tty1.service
将
ConditionPathExists=/dev/tty0
修改为实际的名称
ConditionPathExists=/dev/ttyAMA0
清理文件
安装完成后, 清理apt
bash
apt autoremove
apt-get autoclean
apt-get clean
apt clean
# 结束后 368M
取消挂载目标系统
在目标系统上, exit 退出
结束后, 要先取消挂载
选项一: 手工取消挂载
bash
sudo umount workroot/proc
sudo umount workroot/sys
sudo umount workroot/dev/pts
sudo umount workroot/dev
选项二: 通过脚本取消挂载
如果通过脚本, 则是
bash
./mount.sh -u workroot/
制作 rootfs 镜像文件
bash
# 生成一个适当大小的空镜像,这个大小参考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
设为可执行
bash
#!/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
设为可执行
bash
#!/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
bash
[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 的软链, 使其生效