download: https://github.com/Jvlegod/lfs-from-riscv/releases/

1 环境搭建

全程建议给执行的用户目标权限. 这样后面就尽量别用 sudo, 容易出错.

1
sudo chown -R lfs:lfs $LFS/tools $LFS/sources $LFS/usr $LFS

如果没有创建该用户.

1
2
3
4
5
6
# 创建 lfs 组
sudo groupadd lfs
# 创建 lfs 用户并加入组,指定 bash 为 shell
sudo useradd -s /bin/bash -g lfs -m -k /dev/null lfs
# 给 lfs 用户设置密码
sudo passwd lfs

后期恢复权限.

1
2
3
sudo chown -R root:root $LFS/tools
sudo chown -R root:root $LFS/sources
sudo chown -R root:root $LFS/usr

安装 riscv64 所需要的模拟器.

1
2
sudo apt install qemu-user-static qemu-system-riscv64

安装编译工具链.

see more: https://github.com/riscv-collab/riscv-gnu-toolchain

1
2
3
4
5
6
7
8
sudo apt-get install gcc-riscv64-linux-gnu
sudo apt install autoconf automake autotools-dev curl python3 python3-pip
sudo apt install libmpc-dev libmpfr-dev libgmp-dev gawk
sudo apt install build-essential bison flex texinfo gperf libtool patchutils
sudo apt install bc zlib1g-dev libexpat-dev ninja-build git cmake libglib2.0-dev
git clone https://github.com/riscv/riscv-gnu-toolchain
./configure --prefix=/opt/riscv
make

安装需要 LFS 的最小软件包集合.

1
wget -r -l1 --no-parent --no-directories --reject "index.html*" https://ftp.osuosl.org/pub/lfs/lfs-packages/12.2/

你的目录大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ls
acl-2.3.2.tar.xz e2fsprogs-1.47.1.tar.gz gperf-3.1.tar.gz libpipeline-1.5.7.tar.gz patch-2.7.6.tar.xz tcl8.6.14-html.tar.gz
attr-2.5.2.tar.gz elfutils-0.191.tar.bz2 grep-3.11.tar.xz libtool-2.4.7.tar.xz perl-5.40.0.tar.xz tcl8.6.14-src.tar.gz
autoconf-2.72.tar.xz expat-2.6.2.tar.xz groff-1.23.0.tar.gz libxcrypt-4.4.36.tar.xz pkgconf-2.3.0.tar.xz texinfo-7.1.tar.xz
automake-1.17.tar.xz expect-5.45.4-gcc14-1.patch grub-2.12.tar.xz linux-6.10.5.tar.xz procps-ng-4.0.4.tar.xz tzdata2024a.tar.gz
bash-5.2.32.tar.gz expect5.45.4.tar.gz gzip-1.13.tar.xz lz4-1.10.0.tar.gz psmisc-23.7.tar.xz udev-lfs-20230818.tar.xz
bc-6.7.6.tar.xz extra.sh iana-etc-20240806.tar.gz m4-1.4.19.tar.xz python-3.12.5-docs-html.tar.bz2 util-linux-2.40.2.tar.xz
binutils-2.43.1.tar.xz file-5.45.tar.gz inetutils-2.5.tar.xz make-4.4.1.tar.gz Python-3.12.5.tar.xz vim-9.1.0660.tar.gz
bison-3.8.2.tar.xz findutils-4.10.0.tar.xz intltool-0.51.0.tar.gz man-db-2.12.1.tar.xz readline-8.2.13.tar.gz wget-list
bzip2-1.0.8-install_docs-1.patch flex-2.6.4.tar.gz iproute2-6.10.0.tar.xz man-pages-6.9.1.tar.xz sed-4.9.tar.xz wheel-0.44.0.tar.gz
bzip2-1.0.8.tar.gz flit_core-3.9.0.tar.gz jinja2-3.1.4.tar.gz MarkupSafe-2.1.5.tar.gz setuptools-72.2.0.tar.gz XML-Parser-2.47.tar.gz
check-0.15.2.tar.gz gawk-5.3.0.tar.xz kbd-2.6.4-backspace-1.patch md5sums shadow-4.16.0.tar.xz xz-5.6.2.tar.xz
check.sh gcc-14.2.0.tar.xz kbd-2.6.4.tar.xz meson-1.5.1.tar.gz sysklogd-2.6.1.tar.gz zlib-1.3.1.tar.gz
coreutils-9.5-i18n-2.patch gdbm-1.24.tar.gz kmod-33.tar.xz mpc-1.3.1.tar.gz systemd-256.4.tar.gz zstd-1.5.6.tar.gz
coreutils-9.5.tar.xz gettext-0.22.5.tar.xz less-661.tar.gz mpfr-4.2.1.tar.xz systemd-man-pages-256.4.tar.xz
dbus-1.14.10.tar.xz glibc-2.40-fhs-1.patch lfs-bootscripts-20240825.tar.xz ncurses-6.5.tar.gz sysvinit-3.10-consolidated-1.patch
dejagnu-1.6.3.tar.gz glibc-2.40.tar.xz libcap-2.70.tar.xz ninja-1.12.1.tar.gz sysvinit-3.10.tar.xz
diffutils-3.10.tar.xz gmp-6.3.0.tar.xz libffi-3.4.6.tar.gz openssl-3.3.1.tar.gz tar-1.35.tar.xz

2 一些重要的环境变量

一些目录.

1
2
mkdir -p /mnt/lfs
mkdir $LFS/sources

重要的环境变量.

1
2
3
4
export LFS=/mnt/lfs
export SOURCES=$LFS/sources
export TOOLS=$LFS/tools
export PATH=$TOOLS/bin:$PATH

3 正式开始安装

3.1 binutils

安装 binutils. 这一套工具是帮助我们交叉编译的.

1
2
3
4
5
6
7
8
9
cp -ar binutils-2.43.1.tar.xz $SOURCES
cd $SOURCES
tar -xf binutils-2.43.1.tar.xz -C $SOURCES
cd binutils-2.43.1/
mkdir build
cd build
../configure --prefix=$LFS/tools --target=riscv64-unknown-linux-gnu --disable-nls --enable-gold --enable-ld=default
make
make install

3.2 linux-header

首先 pull 下 linux kernel.

1
2
make mrproper
make ARCH=riscv INSTALL_HDR_PATH=$LFS/usr headers_install

3.3 gcc stage1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
tar -xf gcc-14.2.0.tar.xz
cd gcc-14.2.0
# 如果 gmp, mpfr, mpc 没有拉到 gcc, 则将他们拉过去.
cp gmp-6.3.0.tar.xz mpfr-4.2.1.tar.xz mpc-1.3.1.tar.gz $SOURCES/gcc-14.2.0/
tar -xf gmp-6.3.0.tar.xz
tar -xf mpfr-4.2.1.tar.xz
tar -xf mpc-1.3.1.tar.gz
mv -v gmp-6.3.0 gmp
mv -v mpfr-4.2.1 mpfr
mv -v mpc-1.3.1 mpc
mkdir -p build
cd build
../configure \
--target=riscv64-unknown-linux-gnu \
--prefix=$LFS/tools \
--with-glibc-version=2.40 \
--with-sysroot=$LFS \
--with-newlib \
--without-headers \
--enable-default-pie \
--enable-default-ssp \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
make -j$(nproc)
make install

3.4 glibc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cp -v glibc-2.40.tar.xz glibc-2.40-fhs-1.patch $SOURCES/
tar -xf glibc-2.40.tar.xz
cd glibc-2.40
patch -Np1 -i ../glibc-2.40-fhs-1.patch
mkdir build && cd build
# 这里 prefix=/usr 是正确的, prefix 在这里指定的是在目标机器运行时的预期位置
# 4.15 表示兼容的最低版本.
../configure \
--prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--build=$(../scripts/config.guess) \
--enable-kernel=4.15 \
--with-headers=$LFS/usr/include \
--disable-soft-fp \
libc_cv_slibdir=/usr/lib \
libc_cv_rtlddir=/usr/lib
make -j$(nproc)
make DESTDIR=$LFS install

此时会看见多出许多文件.

1
2
$ ls $LFS
etc linux-6.18.5 sbin sources tools usr var

验证是否安装正确.

1
2
3
4
5
6
7
8
9
10
# 动态链接器
ls -l $LFS/usr/lib/ld-linux-riscv64*.so.1
# 标准 C
ls -l $LFS/usr/lib/libc.so.6
file $LFS/usr/lib/libc.so.6
# 头文件
ls -l $LFS/usr/include/stdio.h
# 验证动态链接器路径
grep 'RTLDLIST=' $LFS/usr/bin/ldd
# RTLDLIST="/usr/lib/ld-linux-riscv64-lp64.so.1 /usr/lib/ld-linux-riscv64-lp64d.so.1 /usr/lib/ld-linux-riscv32-ilp32.so.1 /usr/lib/ld-linux-riscv32-ilp32d.so.1"

现在来验证一下程序.

1
2
echo '#include <stdio.h>
int main() { printf("Hello, RISC-V!\n"); return 0; }' > hello.c

编译.

1
riscv64-unknown-linux-gnu-gcc hello.c -o hello

查看文件格式.

1
2
file hello
# hello: ELF 64-bit LSB pie executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, with debug_info, not stripped

3.5 gcc stage2

目前的 GCC 是不够完整的: 它没有 C++ 标准库(libstdc++), 不支持线程, 也不支持共享库.

我们删除之前编译的版本.

1
2
3
4
cd $SOURCES/gcc-14.2.0
rm -rf build
mkdir build
cd build

这次配置增加了共享库, 线程, c++.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
../configure                                       \
--target=riscv64-unknown-linux-gnu \
--prefix=$LFS/tools \
--with-glibc-version=2.40 \
--with-sysroot=$LFS \
--enable-default-pie \
--enable-default-ssp \
--disable-nls \
--disable-multilib \
--disable-libvtv \
--enable-languages=c,c++ \
--enable-shared \
--enable-threads=posix \
--enable-libstdcxx \
--enable-libatomic \
--enable-libgomp \
--enable-libquadmath \
--enable-libssp \
--enable-libitm
make -j$(nproc)
make install

验证.

1
2
3
4
5
6
7
8
9
10
11
12
cat <<'EOF' > hello.cpp
#include <iostream>
#include <vector>

int main() {
std::vector<int> v = {1, 2, 3};
for (auto i : v) {
std::cout << "Value: " << i << std::endl;
}
return 0;
}
EOF

编译.

1
2
riscv64-unknown-linux-gnu-g++ hello.cpp -o hello_Cpp
file hello_Cpp

3.6 bash

现在不安装 bash 就进入 chroot 环境会黑屏一片.

1
2
3
4
5
6
7
8
9
10
cp -ar bash-5.2.32.tar.gz $LFS/sources/
cd $LFS/sources
tar -xf bash-5.2.32.tar.gz
cd bash-5.2.32/
./configure --prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--without-bash-malloc \
CFLAGS="-g -O2 -Wno-implicit-function-declaration"
make -j$(nproc)
make DESTDIR=$LFS install

完成之后会生成目录.

1
ls -l $LFS/usr/bash

3.7 coreutils

一个系统需要有最基础的 ls, cp, mv, mkdir 这些命令.

1
2
3
4
5
6
7
8
9
10
11
12
cp -ar coreutils-9.5* $SOURCES
cd $LFS/sources
tar -xf coreutils-9.5.tar.xz
cd coreutils-9.5/
patch -Np1 -i ../coreutils-9.5-i18n-2.patch
./configure --prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--enable-install-program=hostname \
--enable-no-install-program=kill,uptime \
FORCE_UNSAFE_CONFIGURE=1
make -j$(nproc)
make DESTDIR=$LFS install

验证.

1
2
3
ls -l $LFS/usr/bin/ls
ls -l $LFS/usr/bin/mkdir
ls -l $LFS/usr/bin/cp

3.8 安装 fastfetch

为了让进入系统的时候能够打印出系统的信息, 我们需要这个工具.

1
2
3
cd $SOURCES
git clone https://github.com/fastfetch-cli/fastfetch.git
cd fastfetch

交叉编译.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mkdir build && cd build
cmake .. \
-DCMAKE_C_COMPILER=riscv64-unknown-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=riscv64-unknown-linux-gnu-g++ \
-DCMAKE_INSTALL_PREFIX=/usr \
-DENABLE_EGL=OFF \
-DENABLE_GLX=OFF \
-DENABLE_X11=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_XEXT=OFF \
-DENABLE_WAYLAND=OFF \
-DENABLE_VULKAN=OFF -DENABLE_OPENCL=OFF -DENABLE_DRM=OFF \
-DENABLE_GIO=OFF -DENABLE_DBUS=OFF -DENABLE_PULSE=OFF \
-DENABLE_ZLIB=OFF -DENABLE_SQLITE3=OFF -DENABLE_IMAGEMAGICK=OFF -DENABLE_XFCONF=OFF \
-DCMAKE_DISABLE_FIND_PACKAGE_X11=ON \
-DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON \
-DCMAKE_DISABLE_FIND_PACKAGE_OpenCL=ON \
-DCMAKE_DISABLE_FIND_PACKAGE_Wayland=ON
make -j$(nproc)
sudo make DESTDIR=$LFS install

到目前为止, 已经可以直接去 4 了, 但是要是想在真机或者虚拟机运行的话, 还是得继续完成 3.9.

3.9 安装 sysvinit

当内核完成硬件初始化之后, 会启动第一个 init 进程.

sysvinit 和 systemd 就是传统和现代方式的两种 init 系统.

我们选择前者, 因为它的依赖不多.

1
2
3
4
5
6
7
cp -ar sysvinit-3.10* $LFS/sources/
cd $LFS/sources
tar -xf sysvinit-3.10.tar.xz
cd sysvinit-3.10/
patch -Np1 -i ../sysvinit-3.10-consolidated-1.patch
make CC=riscv64-unknown-linux-gnu-gcc
sudo make ROOT=$LFS install

此时我们可以看到 $LFS/sbin/ 目录下已经生成了 init, halt, shutdown 等核心程序

3.10 安装 utils-linux

该软件包包含 mount, umount, getty 这些基础的指令. 如果不编译的话很难成功进入安装了 sysvinit 的系统.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cp -ar util-linux-2.40.2.tar.xz $LFS/sources/
cd $LFS/sources
tar -xf util-linux-2.40.2.tar.xz
cd util-linux-2.40.2/
./configure --host=riscv64-unknown-linux-gnu \
--build=$(build-aux/config.guess) \
--libdir=/usr/lib \
--runstatedir=/run \
--disable-chfn-chsh \
--disable-login \
--disable-nologin \
--disable-su \
--disable-setpriv \
--disable-runuser \
--disable-pylibmount \
--disable-static \
--disable-liblastlog2 \
--without-python \
--without-tinfo \
--without-ncurses \
--without-readline \
ADJTIME_PATH=/var/lib/hwclock/adjtime
make -j$(nproc)
sudo env PATH="$PATH" LFS="$LFS" make DESTDIR="$LFS" install

这里可能会报错.

我们看看自己需要的软件在不在就可以了.

1
2
3
ls -l $LFS/bin/mount
ls -l $LFS/sbin/agetty
ls -l $LFS/usr/lib/libmount.so.1
1
2
3
4
5
6
7
8
9
10
11
chgrp tty /mnt/lfs/usr/bin/wall
chgrp: changing group of '/mnt/lfs/usr/bin/wall': Operation not permitted
make[4]: *** [Makefile:18850: install-exec-hook-wall] Error 1
make[4]: Leaving directory '/mnt/lfs/sources/util-linux-2.40.2'
make[3]: *** [Makefile:18004: install-exec-am] Error 2
make[3]: Leaving directory '/mnt/lfs/sources/util-linux-2.40.2'
make[2]: *** [Makefile:17269: install-am] Error 2
make[2]: Leaving directory '/mnt/lfs/sources/util-linux-2.40.2'
make[1]: *** [Makefile:16951: install-recursive] Error 1
make[1]: Leaving directory '/mnt/lfs/sources/util-linux-2.40.2'
make: *** [Makefile:17262: install] Error 2

我们不用理会, 因为必要的软件其实已经成功安装了.

4 进入 chroot 环境

创建基本的目录.

1
2
sudo mkdir -pv $LFS/{dev,proc,sys,run,etc,var,tmp}
sudo mkdir -pv $LFS/usr/{bin,sbin,lib}

挂载文件系统.

1
2
3
4
5
sudo mount -v --bind /dev $LFS/dev
sudo mount -v --bind /dev/pts $LFS/dev/pts
sudo mount -v -t proc proc $LFS/proc
sudo mount -v -t sysfs sysfs $LFS/sys
sudo mount -v -t tmpfs tmpfs $LFS/run

创建必要的链接.

1
2
3
4
5
6
# 为什么创建这些链接, 因为我 chroot 的时候发现抱错了, 一个个试的.
sudo ln -sf bash $LFS/bin/sh
sudo ln -sv /usr/lib/ld-linux-riscv64-lp64d.so.1 $LFS/lib/ld-linux-riscv64-lp64d.so.1
sudo ln -sv usr/bin bin
sudo ln -sv usr/sbin sbin
sudo ln -sv usr/lib lib

正式进入 chroot

1
2
3
4
5
6
sudo chroot "$LFS" /usr/bin/env -i   \
HOME=/root \
TERM="$TERM" \
PS1='(ruyi chroot):' \
PATH=/usr/bin:/usr/sbin \
/bin/bash --login

我们来看看 fastfetch 结果.

alt text

卸载挂载的文件系统方法如下.

1
2
3
4
5
sudo umount -v $LFS/run
sudo umount -v $LFS/sys
sudo umount -v $LFS/proc
sudo umount -v $LFS/dev/pts
sudo umount -v $LFS/dev

5 在虚拟机运行 LFS 的系统

chroot 不需要内核, 因为它借用宿主机的, 但虚拟机需要.

当然! 如果想在虚拟机跑也是 ok 滴!

5.1 编译内核

1
2
3
# 我们只需要一个内核就够了
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- -j$(nproc) Image

5.2 完善系统

首先我们在进入系统之前需要先让内核能识别到硬盘.

1
2
3
cat > $LFS/etc/fstab << "EOF"
/dev/vda2 / ext4 defaults 1 1
EOF

如果希望系统进入之后就能够看见到 fastfetch.

1
echo "/usr/bin/fastfetch" >> $LFS/etc/profile

创建 inittab 文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo bash -c "cat > $LFS/etc/inittab << 'EOF'
id:3:initdefault:
# 系统启动的第一格脚本.
si::sysinit:/etc/rc.d/init.d/rc S

# ID 不能太长, 亲测试会报错.
# 如果不需要用户则执行下面这一行替换最下面的一行
# 目前用户登陆还没有更新, 后续有时间我登陆, 原因是需要安装 login
# login 必须要在 chroot 之后安装, 还要安装一些其他包, 比较麻烦
# s0:2345:respawn:/bin/sh
s0:2345:respawn:/sbin/agetty 115200 ttyS0 vt100
EOF"

sudo ln -sf agetty $LFS/sbin/getty

然后编辑我们的启动脚本.

这里我选择加入了 logo.txt.

这里如果不想加入自己的 logo, 可以跳过这一步骤.

1
2
3
4
5
6
7
8
9
sudo bash -c "printf '
\033[1;36m _ _
\033[1;36m (_)_ __ | | ___
\033[1;32m | \ \ / / | |/ _ \
\033[1;32m | |\ V / | | __/
\033[1;33m _/ | \_/ |_|\___|
\033[1;33m |__/
\033[1;34m --- RISC-V LFS --- \033[0m
' > $LFS/usr/share/jvle_logo.txt"

继续.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建目录
sudo mkdir -pv $LFS/etc/rc.d/init.d

# 写入脚本内容
sudo bash -c "cat > $LFS/etc/rc.d/init.d/rc << 'EOF'
#!/bin/sh
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export LD_LIBRARY_PATH=/lib:/usr/lib

mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /run

echo -e '\033[1;32mBooting jvle Custom System...\033[0m'

# 这里我添加了自己的 logo
/usr/bin/fastfetch --file /usr/share/logo.txt --logo-color-1 32

echo 'jvle-RISCV' > /proc/sys/kernel/hostname
PS1='[jvle@riscv \W]# '
EOF"

sudo chmod +x $LFS/etc/rc.d/init.d/rc

加入 login 功能, 这里需要安装 shadow 软件包.

首先我们需要配置一些基础的用户数据文件.

这里给出基本的模板, 有些用户我也不了解为什么会存在, 跟历史原因有关.

1
2
3
4
5
6
7
cat > $LFS/etc/passwd << "EOF"
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/dev/null:/usr/bin/false
daemon:x:6:6:Daemon User:/dev/null:/usr/bin/false
messagebus:x:18:18:D-Bus Message Daemon User:/run/dbus:/usr/bin/false
nobody:x:99:99:Unprivileged User:/dev/null:/usr/bin/false
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cat > $LFS/etc/group << "EOF"
root:x:0:
bin:x:1:daemon
sys:x:2:
kmem:x:3:
tape:x:4:
tty:x:5:
daemon:x:6:
floppy:x:7:
disk:x:8:
lp:x:9:
dialout:x:10:
audio:x:11:
video:x:12:
utmp:x:13:
usb:x:14:
cdrom:x:15:
adm:x:16:
messagebus:x:18:
input:x:24:
mail:x:34:
kvm:x:61:
wheel:x:97:
nogroup:x:99:
users:x:999:
EOF

安装 libxcrypt, 下面的 shadow 需要.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
tar -xf libxcrypt-4.4.36.tar.xz
cd libxcrypt-4.4.36/

./configure --prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--enable-hashes=strong,glibc \
--enable-obsolete-api=no \
--disable-static \
--disable-failure-tokens

make -j$(nproc)
make DESTDIR=$LFS install
# 如果权限不够
# sudo make DESTDIR=/mnt/lfs install

安装 shadow 软件包进行多用户管理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tar -xf shadow-4.16.0.tar.xz
cd shadow-4.16.0/

sed -i 's/groups$(EXEEXT) //' src/Makefile.in
find man -name Makefile.in -exec sed -i 's/groups\.1 / /' {} \;
find man -name Makefile.in -exec sed -i 's/getspnam\.3 / /' {} \;
find man -name Makefile.in -exec sed -i 's/passwd\.5 / /' {} \;

./configure --prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--sysconfdir=/etc \
--without-libbsd \
--without-selinux

make -j$(nproc)
make DESTDIR=$LFS install
# 权限不够就
# sudo PATH=$PATH make DESTDIR=/mnt/lfs install

创建 shadow 文件.

1
2
3
4
5
6
7
8
9
10
11
cat > $LFS/etc/shadow << "EOF"
root::19000:0:99999:7:::
bin:*:19000:0:99999:7:::
daemon:*:19000:0:99999:7:::
messagebus:*:19000:0:99999:7:::
nobody:*:19000:0:99999:7:::
EOF

# shadow 文件必须是 root 只读
sudo chmod 600 $LFS/etc/shadow
sudo chmod 644 $LFS/etc/passwd $LFS/etc/group

创建 /etc/nsswitch.conf 文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > $LFS/etc/nsswitch.conf << "EOF"

passwd: files
group: files
shadow: files

hosts: files dns
networks: files

protocols: files
services: files
ethers: files
rpc: files

EOF

之后进入系统.

1
echo "<your password>" | passwd --stdin root

此操作会修改 /etc/shadow

修改初始化相关配置.

1
2
3
4
5
6
7
8
9
10
11
cat > /root/.bashrc << "EOF"
export PS1='\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]\$ '
alias ls='ls --color=auto'
EOF

cat > /etc/profile << "EOF"
export PS1='[\u@\h \W]\$ '
for i in $(ls /etc/profile.d/*.sh 2> /dev/null); do
. $i
done
EOF

替换 /etc/initab 的最后一行.

这样 getty 会主动调用 login 登陆.

1
2
3
...
s0:2345:respawn:/sbin/getty -L 115200 ttyS0 vt100
...

重启系统登陆.

当我们输入 root 并尝试登录时,系统的动作如下:
查询方向: 系统查看 /etc/nsswitch.conf,确定查用户信息要找 files.
查找用户: 去 /etc/passwd 确认是否有 root 用户,找到其 UID 和家目录.
验证密码: 去 /etc/shadow 比对输入的密码经过计算后是否与存储的加密字符串一致.
确定权限: 去 /etc/group 看看 root 还属于哪些额外的组.
开启环境: 一切通过后,加载 /etc/passwd 中指定的 /bin/bash.

在有了系统镜像之后, 并且我们设置了多用户之后, 会展现出一个系统应有的模样.

login.png

5.3 创建系统镜像

然后创建系统镜像.

1
2
3
4
5
6
7
8
mkdir -pb $LFS/boot
cd $LFS/boot
dd if=/dev/zero of=riscv_lfs.img bs=1M count=20480 # 20GB
mkfs.ext4 riscv_lfs.img
mkdir -p /mnt/lfs_disk
sudo mount riscv_lfs.img /mnt/lfs_disk
sudo cp -a $LFS/* /mnt/lfs_disk/
sudo umount /mnt/lfs_disk

5.4 启动系统

1
2
3
4
5
qemu-system-riscv64 -nographic -machine virt \
-kernel $LFS/linux-6.18.5/arch/riscv/boot/Image \
-append "root=/dev/vda rw console=ttyS0" \
-drive file=$LFS/boot/riscv_lfs.img,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0

alt text

6 快速启动成品

成品链接: https://github.com/Jvlegod/lfs-from-riscv/releases/tag/v0.1.0
成品校验: 点击下载压缩包

下载我给出的 rootfs.

根据 5.3 的方法操作, 然后根据 5.4 直接启动.

7 可选功能包

7.1 kmod

这会加入 insmod, rmmod 这些模块的功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
tar -xf kmod-33.tar.xz
cd kmod-33
./configure --host=riscv64-unknown-linux-gnu \
--prefix=/usr \
--bindir=/bin \
--sysconfdir=/etc \
--with-zlib=no \
--with-xz=no \
--with-zstd=no \
--with-openssl=no \
--disable-manpages
make -j$(nproc)
sudo make DESTDIR=${LFS} install

7.2 自编译系统

最初我们只可以在宿主机系统编译程序, 从这里开始我们将尝试从自己构建的系统上开始编译程序.

7.2.1 binutils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 还是在 sources 目录下
# 重新解压 binutils
cd binutils-2.43.1/
mkdir build && cd build

# 与以往不同的是:
# --host=riscv64...: 这个程序将来要跑在 RISC-V 上
# --target=riscv64...: 这个程序将来要处理 RISC-V 的代码
# --prefix=/usr: 安装到系统的标准位置
../configure --prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--target=riscv64-unknown-linux-gnu \
--disable-nls \
--enable-ld=default \
--enable-gold

make -j$(nproc)
make DESTDIR=$LFS install

7.2.2 gcc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 解压
cd gcc-14.2.0
cp gmp-6.3.0.tar.xz mpfr-4.2.1.tar.xz mpc-1.3.1.tar.gz $SOURCES/gcc-14.2.0/
tar -xf gmp-6.3.0.tar.xz
tar -xf mpfr-4.2.1.tar.xz
tar -xf mpc-1.3.1.tar.gz
mv -v gmp-6.3.0 gmp
mv -v mpfr-4.2.1 mpfr
mv -v mpc-1.3.1 mpc

cp -ar zlib-1.3.1.tar.gz $SOURCES/gcc-14.2.0
tar -xf zlib-1.3.1.tar.gz
mv zlib-1.3.1 zlib

mkdir build && cd build

../configure \
--build=$(../config.guess) \
--host=riscv64-unknown-linux-gnu \
--target=riscv64-unknown-linux-gnu \
--prefix=/usr \
--enable-shared \
--enable-languages=c,c++ \
--enable-threads=posix \
--enable-clocale=gnu \
--enable-default-pie \
--enable-default-ssp \
--disable-multilib \
--disable-bootstrap \
--disable-libvtv \
--disable-libsanitizer \
--enable-libstdcxx-time=yes \
--enable-libgomp \
--enable-libatomic \
--enable-linker-build-id \
--disable-nls \
--disable-werror

make -j$(nproc)
make DESTDIR=$LFS install

7.3 make

我们通常需要 make 来协助我们编译需要的软件.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 解压
tar -xf make-4.4.1.tar.gz
cd make-4.4.1

# --without-guile: 禁用 Guile 扩展
./configure \
--prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--build=$(./build-aux/config.guess) \
--without-guile

make -j(nproc)
make DESTDIR=$LFS install

7.4 tar, xz, gzip

1
2
3
4
5
6
7
8
9
10
11
12
13
tar -xf tar-1.35.tar.xz
cd tar-1.35

# FORCE_UNSAFE_CONFIGURE=1 是必须的, tar 会检查权限, 交叉编译时这会报错
FORCE_UNSAFE_CONFIGURE=1 ./configure \
--prefix=/usr \
--bindir=/bin \
--host=riscv64-unknown-linux-gnu

make -j$(nproc)
make DESTDIR=$LFS install
# 如果发现没权限
# sudo make DESTDIR=/mnt/lfs install

继续.

1
2
3
4
5
6
7
8
9
10
11
tar -xf xz-5.6.2.tar.xz
cd xz-5.6.2

./configure \
--prefix=/usr \
--host=riscv64-unknown-linux-gnu \
--disable-static \
--docdir=/usr/share/doc/xz-5.6.2

make -j$(nproc)
make DESTDIR=$LFS install

继续.

1
2
3
4
5
6
7
tar -xf gzip-1.13.tar.xz
cd gzip-1.13

./configure --prefix=/usr --host=riscv64-unknown-linux-gnu

make -j$(nproc)
make DESTDIR=$LFS install

7.5 sed, grep, gawk

几乎所有的 ./configure 都是 sed, grep, gawk 来解析的, 因此我们需要安装它.

sed.

1
2
3
4
5
6
7
tar -xf sed-4.9.tar.xz
cd sed-4.9

./configure --prefix=/usr --host=riscv64-unknown-linux-gnu

make -j$(nproc)
make DESTDIR=$LFS install

grep.

1
2
3
4
5
6
7
tar -xf grep-3.11.tar.xz
cd grep-3.11

./configure --prefix=/usr --host=riscv64-unknown-linux-gnu

make -j$(nproc)
make DESTDIR=$LFS install

gawk.

1
2
3
4
5
6
7
8
9
tar -xf gawk-5.3.0.tar.xz
cd gawk-5.3.0

./configure --prefix=/usr --host=riscv64-unknown-linux-gnu

make -j$(nproc)
make DESTDIR=$LFS install
# 如果没权限
# sudo make DESTDIR=/mnt/lfs install

至此, 我们的所有编译工作已经可以放在自己编译的系统上了. 之前我们为了宿主机编译的 $LFS/tools 已经可以删除了.

8 拓展

8.1 EFI 启动

首先我们要让 kernel 支持 UEFI 启动, 需要配置 config.

1
2
3
4
CONFIG_EFI=y
CONFIG_EFI_STUB=y
# 建议开启, 否则控制台可能没显示
CONFIG_EFI_EARLYCON=y

然后制作启动盘.

1
qemu-img create -f raw qemu-virt_riscv64.raw 4G

挂载文件为回环设备.

1
sudo losetup -fP qemu-virt_riscv64.rawa

查看当前挂载的设备.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ losetup 
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop1 0 0 1 1 /var/lib/snapd/snaps/bare_5.snap 0 512
/dev/loop17 0 0 1 1 /var/lib/snapd/snaps/core22_2292.snap 0 512
/dev/loop8 0 0 1 1 /var/lib/snapd/snaps/firmware-updater_210.snap 0 512
/dev/loop15 0 0 1 1 /var/lib/snapd/snaps/snapd-desktop-integration_315.snap 0 512
/dev/loop6 0 0 1 1 /var/lib/snapd/snaps/firefox_7672.snap 0 512
/dev/loop13 0 0 1 1 /var/lib/snapd/snaps/snapd_24792.snap 0 512
/dev/loop4 0 0 1 1 /var/lib/snapd/snaps/docker_3377.snap 0 512
/dev/loop11 0 0 1 1 /var/lib/snapd/snaps/snap-store_1270.snap 0 512
/dev/loop2 0 0 1 1 /var/lib/snapd/snaps/core22_2216.snap 0 512
/dev/loop0 0 0 0 0 <path这里我隐藏了绝对路径>/lfs-from-riscv/qemu-virt_riscv64.raw 0 512
/dev/loop9 0 0 1 1 /var/lib/snapd/snaps/gnome-42-2204_202.snap 0 512
/dev/loop16 0 0 1 1 /var/lib/snapd/snaps/snapd-desktop-integration_343.snap 0 512
/dev/loop7 0 0 1 1 /var/lib/snapd/snaps/firmware-updater_167.snap 0 512
/dev/loop14 0 0 1 1 /var/lib/snapd/snaps/snapd_25935.snap 0 512
/dev/loop5 0 0 1 1 /var/lib/snapd/snaps/firefox_7633.snap 0 512
/dev/loop12 0 0 1 1 /var/lib/snapd/snaps/gtk-common-themes_1535.snap 0 512
/dev/loop3 0 0 1 1 /var/lib/snapd/snaps/core24_1267.snap 0 512
/dev/loop10 0 0 1 1 /var/lib/snapd/snaps/gnome-42-2204_247.snap 0 512

之后我们来进行分区.

步骤:

  1. 创建 GPT 分区表

  2. 创建 ESP 分区

  3. 创建 rootfs 分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ sudo fdisk /dev/loop0

Welcome to fdisk (util-linux 2.39.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS (MBR) disklabel with disk identifier 0x5d26fc48.

Command (m for help): g
Created a new GPT disklabel (GUID: 84F79F19-7BFC-45F5-9A17-6EA1EC18E1D8).

Command (m for help): n
Partition number (1-128, default 1): 1
First sector (2048-8388574, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-8388574, default 8386559): +100M

Created a new partition 1 of type 'Linux filesystem' and of size 100 MiB.

Command (m for help): t
Selected partition 1
Partition type or alias (type L to list all): 1
Changed type of partition 'Linux filesystem' to 'EFI System'.

Command (m for help): n
Partition number (2-128, default 2): 2
First sector (206848-8388574, default 206848):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (206848-8388574, default 8386559):

Created a new partition 2 of type 'Linux filesystem' and of size 3.9 GiB.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

查看 loop0 的分区情况.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ sudo fdisk -l /dev/loop0
Disk /dev/loop0: 4 GiB, 4294967296 bytes, 8388608 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 84F79F19-7BFC-45F5-9A17-6EA1EC18E1D8

Device Start End Sectors Size Type
/dev/loop0p1 2048 206847 204800 100M EFI System
/dev/loop0p2 206848 8386559 8179712 3.9G Linux filesystem

$ lsblk /dev/loop0
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 4G 0 loop
├─loop0p1 259:5 0 100M 0 part
└─loop0p2 259:6 0 3.9G 0 part

分区.

1
2
3
4
5
6
# 分区
$ sudo partprobe /dev/loop0
# 格式化 ESP 为 FAT32
sudo mkfs.vfat -F32 /dev/loop0p1
# 格式化 rootfs 为 EXT4
sudo mkfs.ext4 /dev/loop0p2

挂载分区.

1
2
3
mkdir -p mnt_esp mnt_root
sudo mount /dev/loop0p1 mnt_esp
sudo mount /dev/loop0p2 mnt_root

此时查看挂载情况.

1
2
3
4
5
$ lsblk /dev/loop0
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 4G 0 loop
├─loop0p1 259:7 0 100M 0 part /home/jvle/Desktop/works/lfs-from-riscv/mnt_esp
└─loop0p2 259:8 0 3.9G 0 part /home/jvle/Desktop/works/lfs-from-riscv/mnt_root

扩容(这里根据情况来看, 因为作者发现这里空间不够, 遂扩容)

1
2
3
4
5
6
7
8
9
10
11
sudo umount /home/jvle/Desktop/works/lfs-from-riscv/mnt_esp
sudo umount /home/jvle/Desktop/works/lfs-from-riscv/mnt_root
qemu-img resize qemu-virt_riscv64.raw +2G
sudo losetup -c /dev/loop0
# 修正表头
sudo parted /dev/loop0 unit s print
# Fix
# 调整分区2大小
sudo parted /dev/loop0 resizepart 2 100%
# 文件系统还不知道已经扩容了, 调整一下
sudo resize2fs /dev/loop0p2

继续

将 rootfs 的内容拷贝到分区.

1
sudo cp -ar $LFS/* mnt_root/

制作 efi 分区的内容.

1
2
3
sudo mkdir -p mnt_esp/EFI/BOOT
sudo mkdir -p mnt_esp/EFI/systemd
# 将准备好的 systemd-boot 文件拷贝进去

目录结构如下.

1
2
3
4
5
6
7
$ tree mnt_esp/
mnt_esp/
└── EFI
├── BOOT
│   └── BOOTRISCV64.EFI
└── systemd
└── systemd-bootriscv64.efi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo mkdir -p mnt_esp/loader
sudo mkdir -p mnt_esp/loader/entries

sudo tee mnt_esp/loader/loader.conf << EOF
default lfs.conf
timeout 3
console-mode keep
EOF

sudo tee mnt_esp/loader/entries/lfs.conf << EOF
title Linux From Scratch (RISC-V)
version 6.18.5
linux /vmlinuz-linux
options root=/dev/vda2 rw console=ttyS0 earlycon=sbi
EOF

我们还需要将编译好的 kernel 也传入进去.

1
2
3
4
5
6
7
8
9
10
11
12
$ tree mnt_esp/
mnt_esp/
├── EFI
│   ├── BOOT
│   │   └── BOOTRISCV64.EFI
│   └── systemd
│   └── systemd-bootriscv64.efi
├── loader
│   ├── entries
│   │   └── lfs.conf
│   └── loader.conf
└── vmlinuz-linux

卸载挂载的文件.

1
2
3
# 卸载
sudo umount mnt_esp mnt_root
sudo losetup -d /dev/loop0

启动.

1
./start_vm.sh

会看到我们准备好的启动项.

alt text

9 在自己编译的系统上安装更多软件

这里除了软件无法搬移到自己编译的系统以外, 可以完全用自己编译的系统操作了.

9.2 编辑器

这里我们安装 nano, vim.

实际上安装一个就够了.

但是首先要安装 ncurses, 这是所有编辑器的基础.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
tar -xf ncurses-6.5.tar.gz
cd ncurses-6.5

# --enable-widec: 开启宽字符支持(支持中文), 生成 libncursesw.so
# --with-shared: 生成动态库
./configure --prefix=/usr \
--mandir=/usr/share/man \
--with-shared \
--without-debug \
--without-normal \
--with-cxx-binding \
--enable-pc-files \
--enable-widec

make
make install

# 如果准备安装 vim 我们可以配置一下
# vim 默认可能会找 libncurses.so, 我们需要做一个软链接
ln -sfv libncursesw.so /usr/lib/libncurses.so
ln -sfv libncursesw.a /usr/lib/libncurses.a
# 处理 pkg-config
ln -sfv ncursesw.pc /usr/lib/pkgconfig/ncurses.pc

nano.

1
2
3
4
5
6
7
8
9
10
11
12
13
# 主机上还没有 wget, 我们先在宿主机拉下来, 然后放进主机系统
# 经过前面的洗礼, 这里放入主机不是什么难事
wget https://www.nano-editor.org/dist/latest/nano-8.1.tar.gz
# 解压
cd /sources/nano-8.1

./configure --prefix=/usr \
--sysconfdir=/etc \
--enable-utf8 \
--docdir=/usr/share/doc/nano-8.1

make
make install

vim.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tar -xf vim-9.1.0000.tar.gz
cd vim-9.1.0000

# 默认 vim 会去 /usr/share/vim/vimrc 找配置, 这不符合 Linux 惯例 (/etc/vimrc)
# 我们直接修改源码头文件, 把默认路径硬编码改为 /etc/vimrc
echo '#define SYS_VIMRC_FILE "/etc/vimrc"' >> src/feature.h

# --with-features=huge:开启所有功能
# --enable-multibyte: 支持多字节字符
./configure --prefix=/usr \
--with-features=huge \
--enable-multibyte

# 编译 (vim 编译比较慢, 更何况是 riscv)
make -j$(nproc)
make install
ln -sv vim /usr/bin/vi