Hi3798MV200 恩兔N2 NS-1 (三): 制作 Ubuntu 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 包

解压

在本地创建工作目录, 将压缩包解压到工作目录下, 注意要用 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 的软链, 使其生效

参考

相关推荐
阿赭ochre11 分钟前
Linux环境变量&&进程地址空间
linux·服务器
Iceberg_wWzZ11 分钟前
数据结构(Day14)
linux·c语言·数据结构·算法
可儿·四系桜26 分钟前
如何在多台Linux虚拟机上安装和配置Zookeeper集群
linux·服务器·zookeeper
Flying_Fish_roe30 分钟前
linux-软件包管理-包管理工具(Debian 系)
linux·运维·debian
大广-全栈开发1 小时前
centos 7 安装gitlab
linux·git·centos
666786661 小时前
Mysql高级篇(中)—— SQL优化
linux·运维·服务器·数据库·sql·mysql
宇宙第一小趴菜2 小时前
虚拟机安装xubuntu
linux·服务器·vmware
悲伤的创可贴2 小时前
Docker安装以及简单使用
linux·docker·centos
zhaowangji3 小时前
ubuntu虚拟机装载共享文件夹导致的诡异错误
linux·运维·ubuntu
小崔爱读书3 小时前
普元DWS - Linux下安装DWS标准版
linux·运维·服务器