如果把整個Linux操作系統看作層級關係, 根文件系統是位於內核之上的模塊,對於同樣的硬體和架構, Linux各個發行版的區別主要在於根文件系統, 而底層的內核部分幾乎是一樣的. 通過製作根文件系統, 可以更換成其它發行版, 定製自己的最小化安裝. ...
目錄
- 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/[email protected]
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 的軟鏈, 使其生效