BusyBox 构建并启动 RISC-V Linux 内核

根文件系统

文件系统与根文件系统

文件系统(File System)是操作系统用于管理和存储数据的一种方式。它定义了如何在存储设备(如硬盘、SSD、USB 驱动器等)上组织文件和目录,以及如何进行数据的读写操作。

常见的文件系统类型有:

  • ext4:Linux 最常用的文件系统,支持大文件和大分区。
  • NTFS:Windows 操作系统常用的文件系统,支持文件加密和权限控制。
  • FAT32:一种兼容性广泛的文件系统,常用于 USB 驱动器和内存卡。
  • XFS:适用于高性能和高容量存储需求的文件系统。
  • btrfs:一种现代 Linux 文件系统,支持快照、压缩和多设备管理。

根文件系统(Root File System,通常简称为 rootfs)是文件系统层次结构中的顶级文件系统。它包含了操作系统启动和运行所需的所有基本文件和目录。根文件系统是整个文件系统层次的起点,在 Linux 中由单个斜杠(/)表示。根文件系统首先是内核启动时所 mount 的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

根文件系统是建立在文件系统之上的。根文件系统使用某种具体的文件系统类型(如 ext4)来管理和存储其内容。

基于内存的文件系统

ramdisk 是一种基于内存的文件系统,它将内存的一部分用作硬盘驱动器,这样就可以在内存中创建一个文件系统。ramdisk 是一个虚拟磁盘,它的大小和硬盘驱动器的大小一样。ramdisk 的优点是速度快,缺点是断电后数据丢失。

根文件系统中各种配置文件的作用以及配置文件的格式介绍

/etc/inittab

/etc/inittab 是 Linux 系统中的一个配置文件,它是 init 程序的配置文件,用于配置系统的运行级别和 init 程序的行为。在 Linux 系统中,init 程序是系统的第一个进程,它负责启动系统中的所有其他进程。/etc/inittab 文件中的每一行都是一个配置项,每个配置项由四个字段组成,字段之间用空格或制表符分隔。/etc/inittab 文件的格式如下:

id:runlevels:action:process

每个字段用冒号分隔,可以缺省。各字段的含义如下:

  • id:配置项的标识符,用于标识配置项。
  • runlevels:配置项所对应的运行级别,可以是一个或多个运行级别的组合。
  • action:配置项的动作,可以是以下几种动作之一:
    • sysinit:系统初始化时运行。
    • respawn:如果进程终止,立即重新启动。
    • askfirst:在运行 process 之前询问用户。并在控制台上显示 Please press Enter to active this console。
    • wait:等待进程终止,然后继续执行下一个配置项。
    • once:只运行一次,进程终止后不会重新启动。
    • boot:在系统引导时运行。
    • bootwait:在系统引导时运行,等待进程终止后继续引导。
    • initdefault:设置默认运行级别。
    • shutdown:在系统关机时运行。
  • process:要执行的进程或脚本的路径。

示例:

# /etc/inittab
#系统开机或重新启动,执行rcS文件
::sysinit:/etc/init.d/rcS
#系统启动后,运行登录程序  
::askfirst:-/bin/login
#按下组合键“ctrl+alt+del”,重启Linux系统
::ctrlaltdel:-/sbin/reboot   
#系统关机时,卸载所有文件系统
::shutdown:/bin/umount -a -r
#重启init进程
::restart:/sbin/init

/etc/fstab

/etc/fstab 是 Linux 系统中的一个配置文件,用于配置文件系统的挂载信息。在 Linux 系统中,文件系统是通过挂载的方式来访问的,/etc/fstab 文件中记录了系统中所有文件系统的挂载信息。/etc/fstab 文件的格式如下:

device mount_point fs_type options dump pass

各字段的含义如下:

  • device:设备文件或 UUID。
  • mount_point:挂载点。
  • fs_type:文件系统类型。
  • options:挂载选项。
  • dump:备份标志,用于备份工具。
  • pass:文件系统检查顺序。

示例:

# /etc/fstab
# <文件系统>  <挂载点>  <类型>  <选项>        <dump>  <fsck>

# 根文件系统
/dev/sda1       /           ext4    defaults        1       1  # 根分区,使用 ext4 文件系统,默认挂载选项

# /boot 分区,用于存放引导加载程序和内核镜像
/dev/sda2       /boot       ext4    defaults        1       2  # /boot 分区,使用 ext4 文件系统,默认挂载选项

# 交换分区,用作虚拟内存
/dev/sda3       none        swap    sw              0       0  # 交换分区,不需要挂载点,使用 swap 类型

# /home 分区,用于存放用户的个人数据
/dev/sda4       /home       ext4    defaults        1       2  # /home 分区,使用 ext4 文件系统,默认挂载选项

# /tmp 分区,用于存放临时文件
/dev/sda5       /tmp        ext4    defaults        1       2  # /tmp 分区,使用 ext4 文件系统,默认挂载选项

# /var 分区,用于存放可变数据文件,如日志、邮件、缓存等
/dev/sda6       /var        ext4    defaults        1       2  # /var 分区,使用 ext4 文件系统,默认挂载选项

# /mnt/data 分区,用于存放额外的数据文件
/dev/sda7       /mnt/data   ext4    defaults        1       2  # /mnt/data 分区,使用 ext4 文件系统,默认挂载选项

# 挂载 proc 文件系统,用于访问进程和系统信息
proc            /proc       proc    defaults        0       0  # proc 虚拟文件系统,默认挂载选项

# 挂载 sysfs 文件系统,用于访问设备和内核信息
sysfs           /sys        sysfs   defaults        0       0  # sysfs 虚拟文件系统,默认挂载选项

# 挂载 tmpfs 文件系统,用于临时文件存储
tmpfs           /dev/shm    tmpfs   defaults        0       0  # tmpfs 虚拟文件系统,默认挂载选项

# 挂载 devpts 文件系统,用于伪终端设备
devpts          /dev/pts    devpts  gid=5,mode=620  0       0  # devpts 虚拟文件系统,默认挂载选项

/etc/init.d/rcS

/etc/init.d/rcS 是 Linux 系统中的一个初始化脚本,用于系统初始化时运行。在 Linux 系统中,/etc/init.d/rcS 脚本是系统初始化时运行的第一个脚本,它负责初始化系统中的各种服务和配置。/etc/init.d/rcS 脚本通常包含了一些初始化命令,如加载模块、挂载文件系统、启动服务等。/etc/init.d/rcS 脚本的格式如下:

#!/bin/sh
# 这行是 shebang,用于指定这个脚本将由 /bin/sh 解释执行

# 挂载 proc 文件系统到 /proc 目录
mount -t proc none /proc
# proc 文件系统是一个虚拟文件系统,用于提供内核和进程的信息

# 挂载 sysfs 文件系统到 /sys 目录
mount -t sysfs none /sys
# sysfs 文件系统是一个虚拟文件系统,用于提供设备和内核信息

# 挂载 devtmpfs 文件系统到 /dev 目录
mount -t devtmpfs none /dev
# devtmpfs 是一个用于设备节点的临时文件系统

# 运行 mdev 以创建设备节点
/sbin/mdev -s
# mdev 是一个轻量级的设备管理工具,用于创建和管理 /dev 目录中的设备节点

# 设置主机名
echo "myhostname" > /proc/sys/kernel/hostname
# 将主机名设置为 "myhostname",这个名字可以根据需要更改

# 启动网络接口配置脚本
ifconfig lo up
# 启动回环接口 lo,这是一个特殊的网络接口,用于网络软件本地通信

# 运行所有位于 /etc/init.d/ 目录中的初始化脚本
for script in /etc/init.d/S*; do
  if [ -x "$script" ]; then
    "$script" start
  fi
done
# 遍历 /etc/init.d/ 目录中的所有以 S 开头的脚本,并执行它们以启动相应的服务
# -x 选项用于检查文件是否可执行

# 挂载用户文件系统(可选,根据实际需要)
mount -a
# 挂载 /etc/fstab 文件中列出的所有文件系统

# 打印启动完成信息
echo "System initialization complete."
# 输出系统初始化完成信息

/etc/profile

/etc/profile 是 Linux 系统中的一个全局配置文件,用于配置系统的环境变量和用户的 shell 环境。在 Linux 系统中,/etc/profile 文件是系统启动时加载的第一个配置文件,它包含了系统的全局环境变量和用户的 shell 环境配置。/etc/profile 文件的格式如下:

# /etc/profile
#!/bin/sh
# 这行是 shebang,用于指定这个脚本将由 /bin/sh 解释执行

# 设置 PATH 环境变量,定义系统命令的搜索路径
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
export PATH
# /usr/local/sbin:/usr/local/bin 是系统管理员和用户安装的程序路径
# /sbin:/bin:/usr/sbin:/usr/bin 是系统默认的命令路径

# 设置用户 umask,定义新文件和目录的默认权限掩码
umask 022
# umask 022 表示新文件的默认权限是 755,新目录的默认权限是 644

# 设置系统语言和区域
LANG="en_US.UTF-8"
export LANG
# LANG 变量定义了系统的语言环境,这里设置为美国英语 UTF-8 编码

# 设置历史记录文件和大小
HISTSIZE=1000
HISTFILESIZE=2000
export HISTSIZE HISTFILESIZE
# HISTSIZE 定义了 shell 会话历史记录的条目数,HISTFILESIZE 定义了历史记录文件的最大条目数

# 系统启动信息
echo "Welcome to your Linux system!"
# 在用户登录时显示欢迎信息

# 读取并执行 /etc/bashrc(如果存在)
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi
# /etc/bashrc 是另一个全局配置文件,通常包含 bash shell 的配置
# 使用 . /etc/bashrc 命令将其内容导入当前 shell 环境

# 配置别名(示例)
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'
# 定义一些常用命令的别名
# ll 代表 ls -l,显示详细列表
# la 代表 ls -A,显示所有文件包括隐藏文件(但不包括 . 和 ..)
# l 代表 ls -CF,以分类格式显示文件

# 读取用户特定的配置文件(如果存在)
if [ -f "$HOME/.profile" ]; then
    . "$HOME/.profile"
fi
# .profile 是用户的个人配置文件
# 使用 . $HOME/.profile 命令将其内容导入当前 shell 环境

# 设置编辑器
EDITOR=vim
export EDITOR
# 定义默认的文本编辑器为 vim

# 设置主机名显示,也就是终端最前面提示符的样式
PS1='\u@\h:\w\$ '
export PS1
# PS1 定义了 shell 提示符的样式
# \u 代表用户名
# \h 代表主机名
# \w 代表当前工作目录

Busybox 简介以及如何制作 Busybox 文件系统

准备 QEMU

cd && mkdir -p /home/user/program/riscv64-qemu
cd workspace
git clone https://github.com/qemu/qemu.git
cd qemu
cd qemu && ./configure --target-list=riscv32-softmmu,riscv32-linux-user,riscv64-linux-user,riscv64-softmmu \
               --enable-kvm --enable-sdl \
               --prefix=/home/user/program/riscv64-qemu
make install -j $(nproc)

准备 OpenSBI

cd workspace
git clone https://github.com/riscv-software-src/opensbi.git
cd opensbi
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- PLATFORM=generic

准备 Linux 内核

cd workspace
git clone https://github.com/torvalds/linux.git
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j $(nproc)

准备 Busybox

进入下载软件包,https://busybox.net/downloads/

wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
mv busybox-1.36.1 busybox
cd busybox

配置编译选项

make CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig

选择静态库模式,设置路径:Settings -> Build static binary (no shared libs)

编译 Busybox

make CROSS_COMPILE=riscv64-unknown-linux-gnu- -j $(nproc)

如果需要频繁编译,也可以在 Menuconfig 中设置交叉编译器路径,这样就不需要每次都指定了。设置路径为:Settings -> Build Options -> Cross Compiler prefix。将其设置为 riscv64-unknown-linux-gnu-。

安装 Busybox:

make CROSS_COMPILE=riscv64-unknown-linux-gnu- install

Busybox 默认安装在当前目录的_install 目录下,也可以在 Menuconfig 中设置安装目录,路径为:Settings -> Build Options -> Destination path for ‘make install’。

查看安装目录

ls _install 
bin  linuxrc  sbin  usr

制作根文件系统

qemu-img create -f raw rootfs.img 256M
mkfs.ext4 rootfs.img
mkdir rootfs
sudo mount -o loop rootfs.img rootfs
sudo cp -r _install/* rootfs
cd rootfs
sudo mkdir proc sys dev etc etc/init.d
cd etc
cd init.d
sudo touch rcS
sudo chmod +x rcS
sudo vim rcS

写入以下内容:

#!/bin/sh  
mount -t proc none /proc  
mount -t sysfs none /sys  
/sbin/mdev -s
sudo umount rootfs

qemu 启动内核:

#!/usr/bin/env bash
RESTORE=$(echo -en '\001\033[0m\002')
YELLOW=$(echo -en '\001\033[00;33m\002')

## Configuration
vcpu=8
memory=16
drive="/home/user/workspace/rootfs.img"
kernel=" /home/user/workspace/linux-stable/arch/riscv/boot/Image"
fw="/home/user/workspace/opensbi/build/platform/generic/firmware/fw_jump.bin"
ssh_port=12070

cmd="/home/user/program/riscv64-qemu/bin/qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -cpu rv64\
  -bios "$fw" \
  -kernel "$kernel" \
  -drive file=$drive,format=raw,id=hd0 \
  -device virtio-blk-device,drive=hd0 \
  -append "root=/dev/vda console=ttyS0" "

echo ${YELLOW}:: Starting VM...${RESTORE}
echo ${YELLOW}:: Using following configuration${RESTORE}
echo ""
echo ${YELLOW}vCPU Cores: "$vcpu"${RESTORE}
echo ${YELLOW}Memory: "$memory"G${RESTORE}
echo ${YELLOW}Disk: "$drive"${RESTORE}
echo ${YELLOW}SSH Port: "$ssh_port"${RESTORE}
echo ""
echo ${YELLOW}:: NOTE: Make sure ONLY ONE .qcow2 file is${RESTORE}
echo ${YELLOW}in the current directory${RESTORE}
echo ""
echo ${YELLOW}:: Tip: Try setting DNS manually if QEMU user network doesn\'t work well. ${RESTORE}
echo ${YELLOW}:: HOWTO -\> https://serverfault.com/a/810639 ${RESTORE}
echo ""
echo ""

sleep 2

eval $cmd