• 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和原权限

    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 的软链, 使其生效

    参考

  • 相关阅读:
    dubbo(5):使用dubbo进行业务分离与dubbo-admin的使用
    【微机接口】中断系统:中断的应用
    java版工程管理系统Spring Cloud+Spring Boot+Mybatis实现工程管理系统源码
    Redis 单线程模型 精讲
    微信小程序-人脸检测
    【操作系统】进程间的通信——信号
    java基础选择题--17(包含全面)
    了解3D世界的黑魔法 - 纯Java构造一个简单的3D渲染引擎
    【安全体系架构】——防御深度架构
    C++ 简易日志类封装
  • 原文地址:https://www.cnblogs.com/milton/p/17599562.html