开发平台:Ubuntu 18.04.6

目标平台:imx-6ull

busybox 版本:busybox-1.32.0.tar.bz2

编译工具链:gcc versions 10.3.1 20210621 (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29))

# 环境搭建

# 交叉编译器

关于 arm linux 交叉编译工具链的搭建,这里就再不展开说明了,毕竟在此之前如果有移植过 uboot,那么这里就可以省略了,对于没有搭过交叉编译工具链的,可以查看上一篇 imx-6ULL uboot 移植 的交叉编译搭建的环节。

# 文件系统

你可能听过这么一句 “Unix 系统中一切皆文件” 的话,它是指 Unix 系统中的所有的一切都可以通过文件的方式访问、管理,即使不是文件,也以文件的形式来管理;那么对于这些文件的管理,需要一个系统去管理集中访问,而这样一个系统称为文件系统,它可以使我们以文件 IO 的形式对文件目录进行访问,而非以 Flash 存储地址进行访问,在使用上更为方便轻松。

# 组成

对于现有的 Linux 版本,不同的 Linux 衍生版本其文件系统的根目录都略有不同,但整体上的组成没多大区别。

image-20240817230144056

image-20240817230822485

一般来说,在构成最小 rootfs 的目录结构会包含以下几个:

目录说明补充
bin可执行文件,主要的是系统命令,普通用户和 root 都可以执行必须存在
dev设备文件,设备树定义的节点就在这里必须存在
etc存放着各配置文件必须存在
home系统预设的使用者家目录非必须
lib存放的是 32 bit 系统中的动态和静态链接库文件必须存在
lib6464 bit 桌面系统存在的东西,存放着支持 64 bit 的运行库嵌入式系统不需要
mnt临时挂载目录,一般是空目录非必须
opt第三方软件安装存放目录 ,目前都安装在 /usr/local 目录里非必须
proc虚拟文件系统,是系统内存的映射,主要存放系统的内核、进程和网络状态必须存在
sbin系统管理命令存放目录,只有 root 可以使用,普通用户只可查看必须存在
sys系统使用的目录,虚拟文件系统。和 /proc/ 相似,该目录中的数据都保存在内存中必须存在
usr是 Unix Software Resource 的缩写,也就是 Unix 操作系统软件资源目录非必须
var存放一些可以改变的数据,包括系统运行中需调用或改变的数据必须存在

# 构建

根文件系统的制作方法有很多,像在已知的系统中把整个根文件系统打包出来然后再进行裁剪,或者使用三大神器 busybox、buildroot、yocto 构建等。

利用已有的系统提取裁剪,这种方法一般适用于同平台版本的情况下,对于不同的平台版本下,可能会出现运行环境错误的情况;

使用 busybox 构建文件系统,则仅仅只是帮我们构建好一些 shell 命令程序集,像 lib 库、/etc 目录下的一些文件都需要自己手动创建,如果需要用到第三方软件,还需要去移植一些第三方软件库,比如 alsa、iperf、mplayer 等等;而且 busybox 构建的根文件系统默认没有用户名和密码设置;

buildroot,它不仅包含了 busybox 的功能,而且里面还集成了各种软件包,当需要什么软件就选择什么软件,不需要额外移植;buildroot 可以构建完整的根文件系统,因此极大的方便了嵌入式 Linux 开发人员对根文件系统进行制作;

至于 yocto 构建根文件系统,是一个古老的、很庞大的系统构建工具,其实其不单止可以构建根文件系统,还可以用来构建 u-boot、kernel、交叉编译工具链等等,一般为 SOC 厂家、设备厂商、系统开发这些用于统一发布标准套件,但由于操作复杂,开发学习时间也久,一般用户开发不会选择这一方式。

# busybox

官方下载:https://busybox.net/downloads/

在这里选择下载 busybox-1.32.0.tar.bz2 版本。

对下载下来的 busybox 进行解压:

tar -jxvf busybox-1.32.0.tar.bz2
cd busybox-1.32.0

# 编译验证

选择交叉编译工具对其进行默认编译:

sudo -s
export ARCH=arm
export CROSS_COMPILE=arm-none-linux-gnueabihf-
make distclean
make defconfig
make
make install

最后,生成一个名为 _install 的文件目录(当然,你也可以使用 CONFIG_PREFIX=rootfs_dir 指定文件目录名称及路径),该目录就是本次默认配置所构建的残缺文件系统,为什么说是残缺,是因为还缺少相关的文件及目录,这个下面再说。

# 自主配置

前面说了,上面的操作只是构建了一个默认的配置用以验证,实际过程中,往往根据需要选择必要的功能,而在 busybox 中支持以下几种快速配置模式:

  • defconfig:缺省配置,也就是默认配置选项。
  • allyesconfi:全选配置,也就是选中 busybox 的所有功能。
  • allnoconfig:最小配置,什么都不选中,即空配置。

在应用中,你可以先选择你最接近的快速配置进行初始化配置,然后再修改定制,这里笔者选择 make allnoconfig 进行配置初始化,配置完成后可通过查看是否有 .config 文件来验证配置成功。

同样的,跟 uboot 和 kernel 一样,在 busybox 中也存在图形界面配置操作(关于无法调出图形配置界面进行配置可看 imx-6ULL kernel 移植 的图形配置说明):

make menuconfig

关于其选项操作:

Settings ---> 			# BusyBox 的通用配置,一般采用默认值即可。
--- Applets
    Archival Utilities ---> 			# 压缩、解压缩相关工具。   
    Coreutils ---> 						# 最基本的命令,如cat、cp、ls等。   
    Console Utilities ---> 				# 控制台相关命令。   
    Debian Utilities ---> 				# Debian 操作系统相关命令。   
    klibc-utils ---> 					# 供 initramfs 所使用的最小化 libc 子集。
    Editors ---> 						# 编辑工具,如 vi、awk、sed 等。   
    Finding Utilities ---> 				# 查找工具,如find、grep、xargs。   
    Init Utilities ---> 				# BusyBox init 相关命令。   
    Login/Password Management Utilities ---> # 登陆、用户账号/密码等方面的命令。   
    Linux Ext2 FS Progs ---> 			# ext2 文件系统的一些工具。   
    Linux Module Utilities ---> 		# 加载/卸载模块等相关的命令。   
    Linux System Utilities --->  		# 一些系统命令。   
    Miscellaneous Utilities ---> 		# 一些不好分类的命令,如crond、crontab。   
    Networking Utilities --->    		# 网络相关的命令和工具。   
    Print Utilities --->    			# print spool 服务及相关工具。   
    Mail Utilities --->      			# mail 相关命令。   
    Process Utilities --->      		# 进程相关命令,如ps、kill等。   
    Runit Utilities --->    			# runit 程序。   
    Shells --->             			# shell 程序。   
    System Logging Utilities --->   	# 系统日志相关工具,如 syslogd、klogd。
---
	Load an Alternate Configuration File		# 加载备用配置文件
	Save Configuration to an Alternate File		# 将配置保存到备用文件

然后在这里记录几个关键的配置:

  • 静态或者动态编译 busybox,取消选择静态编译,使得 busybox 可执行文件更小

    -> Settings
      -> [ ] Build static binary (no shared libs)
  • 打开命令行编辑,增加 vi 编辑样式,其余默认或按需修改

    -> Settings
      -> [*] Command line editing
         [*]   vi-style line editing commands
  • 打开简洁式 help 命令提示

    -> Settings
      -> [*] Show applet usage messages
         [ ]   Show verbose applet usage messages
         [*]   Store applet usage messages in compressed form
  • 选择支持 Unicode 编码,其余默认或按需修改

    -> Settings
      -> [*] Support Unicode
         [*]   Check $LC_ALL, $LC_CTYPE and $LANG environment variables
  • 取消对模块实用程序的简化,这样在该表将会额外多出几个拓展选项

    -> Linux Module Utilities
      -> [ ] Simplified modutils
  • 打开设备自主管理功能,实现初始化常用设备和动态更新

    -> Linux System Utilities
      -> [*] mdev
         [*]   Support /etc/mdev.conf
         [*]     Support subdirs/symlinks
         [*]       Support regular expressions substitutions when renaming dev
         [*]     Support command execution at device addition/removal
         [*]   Support loading of firmware
         [*]   Support daemon mode
  • 选择内核启动后 init 进程处理

    -> Init Utilities
      -> [*] init
      -> [ ] linuxrc: support running init from initrd (not initramfs)

    在这里,取消勾选 linuxrc ;在 Linux2.4 内核及更低版本,由 initrd 引导加载文件系统,核心文件就是 linuxrc ,其通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统,如果你勾选上那么编译后你将会看到一个 /linuxrc 文件;Linux2.6 内核之后被 initramfs 取代,与 initrd 需要先解压再挂载模拟成一个块设备不同, initramfs 在编译阶段就被打包成 cpio 格式,内核可以直接将其解压建立文件系统。

    为了保持向后兼容性,实际上,在这些发行版中, initrd 通常只是一个指向 initramfs 的符号链接;也就是说,你所看到的 initrd 实际上已经是现代化的 initramfs 了,通过 ls -l linuxrc 可以看到其实是个软链接而非文件脚本了。

  • shell 终端选择

    -> Shells
      -> [*] ash
      -> [*] hush

    Almquist shell (也称为 A shell、ash 和 sh) 是一种轻量级 Unix shell(最大的特点就是轻量化);Hush 是一个创新的 shell 脚本语言,它将 Lua 的简洁性和 Unix 的实用性融为一体。

    -> Shells
         Choose which shell is aliased to 'sh' name (ash)  --->
         Choose which shell is aliased to 'bash' name (hush)  --->

    分别为 sh / bash 指定 ash 和 hush shell 名称。

# 添加中文支持

在上面启用 Unicode 编码后,可以通过修改以下几个文件来实现中文显示。

1、修改 unicode.c 文件

sudo vim libbb/unicode.c

找到 unicode_conv_to_printable2 函数,进行以下修改:

image-20240822210341756

2、修改 printable_string.c 文件

sudo vim libbb/printable_string.c

找到 printable_string2 函数,进行以下修改:

image-20240822210801046

# 编写脚本

1、默认配置生成脚本

build.sh
#!/bin/bash
# 若之前已经导入到环境变量则不需要
export PATH=$PATH:/usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin
# 若已经在顶层 Makefile 文件中指定则不需要
export ARCH=arm
# 若已经在顶层 Makefile 文件中指定则不需要
export CROSS_COMPILE=arm-none-linux-gnueabihf-
make distclean
make defconfig
make -j$(nproc)
make install -j$(nproc)

2、数据修改脚本

autobuild.sh
#!/bin/bash
# 若之前已经导入到环境变量则不需要
export PATH=$PATH:/usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin
# 若已经在顶层 Makefile 文件中指定则不需要
export ARCH=arm
# 若已经在顶层 Makefile 文件中指定则不需要
export CROSS_COMPILE=arm-none-linux-gnueabihf-
make clean
make -j$(nproc)
make install -j$(nproc)

通过执行 build.shmake menuconfig 修改及执行 autobuild.sh 得到所需的二进制执行程序,这时在生成的目录下可以看到:

|
|-- bin
|-- sbin
|-- usr
     |-- bin
     |-- sbin

# 补充 rootfs 目录

根据上面 rootfs 的构成,尽管是要做最小 rootfs,除 busybox 生成的目录文件外,还需要我们去填补运行所需的目录文件。

# 添加 lib 库

Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要向根文件系统中添加动态库,包括前面提到的 busybox 配置中的动态编译。这些动态库可以从调用的交叉编译器中获取,。

1、 /lib 库添加

mkdir lib
cp -d /usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib/*so* lib/
cp -d /usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib/*.a lib/

复制 arm linux 交叉编译器下的 libc/lib 库里的 *so**.a 到 rootfs 的 lib 中, -d 连同软链接也一起拷贝。

2、 /usr/lib 库添加

mkdir -p usr/lib
cp -d /usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib/*so* usr/lib
cp -d /usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib/*.a usr/lib

复制 arm linux 交叉编译器下的 libc/usr/lib 库里的 *so**.a 到 rootfs 的 usr/lib 中, -d 连同软链接也一起拷贝。

# 完善根目录

mkdir  dev  etc  home  mnt  proc  sys  var

# 测试

在完成上面的所有操作后,可以利用 NFS 网络挂载文件系统进行测试完善;关于 NFS 服务的搭建可看 Ubuntu 搭建 tftp 及 NFS 服务

对于利用 NFS 挂载根文件系统,可以通过修改 uboot 启动的 bootargs 配置来变更文件系统挂载路径,例如:

=> setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.100:/home/user/nfs_rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'
=>
=> saveenv

关于 uboot 改成 NFS 挂载 rootfs 的格式(更多信息可看 kernel 里面的 Documentation/filesystems/nfs/nfsroot.txt 及其相关文件):

root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>
<server-ip>:服务器 IP 地址;比如这里的 PC 主机 IP 地址为 192.168.1.100。
<root-dir>:根文件系统的存放路径,要保证放在 NFS 共享目录下;比如 /home/user/nfs_rootfs。
<nfs-options>:NFS 的其他可选选项,一般不设置。
<client-ip>:客户端 IP 地址,也就是开发板的 IP 地址。
<server-ip>:服务器 IP 地址,前面已经说了。
<gw-ip>:网关地址。
<netmask>:子网掩码。
<hostname>:客户机的名字,一般不设置,此值可以空着。
<device>:设备名,也就是网卡名,一般是 eth0,eth1...。
<autoconf>:自动配置,一般不使用,所以设置为 off。
<dns0-ip>:DNS0 服务器 IP 地址,不使用。
<dns1-ip>:DNS1 服务器 IP 地址,不使用。
“proto=tcp”表示使用 TCP 协议,“rw”表示 nfs 挂载的根文件系统为可读可写。

在这里,结合 imx-6ULL uboot 移植 篇章,可以配置成:

=> setenv netargs setenv bootargs 'console=${console},${baudrate} root=/dev/nfs nfsroot=${serverip}:/home/user/nfs_rootfs,proto=tcp rw ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off'
=>
=> setenv nfsboot 'run findfdt; run findtee; mmc dev ${mmcdev}; mmc dev ${mmcdev}; run netargs; run loadimage; run loadfdt; bootz ${loadaddr} - ${fdt_addr}'
=>
=> saveenv
=>
=> run nfsboot

最后,通过运行 nfsboot 来启动。

# 添加 rcS 文件

通过 uboot 执行 run nfsboot 进入系统后,可以看到已经成功挂在 NFS 上的 rootfs 了:

img

但是在进入根文件系统的时候,打印上有一行报错,显示错误提示:

can't run '/etc/init.d/rcS': No such file or directory

提示不能运行 /etc/init.d/rcS ,没有这个文件;该文件是个 shell 脚本,Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件

那么补充一下这个文件,并写入以下内容:

rcS
#!/bin/sh
#
# rcS		Call all S??* scripts in /etc/rcS.d in
#		numerical/alphabetical order.
#
# Version:	@(#)/etc/init.d/rcS  2.76  19-Apr-1999  miquels@cistron.nl
#
PATH=/sbin:/bin:/usr/sbin:/usr/bin  # 初始化环境变量 PATH,操作系统执行程序默认到 PATH 指定的目录下寻找该程序
runlevel=S  # 设置系统运行级别为 S,即 single-user(单用户)模式
prevlevel=N # 用来记录上一个运行级别的值,以便系统能够在需要时回到前一个运行级别,为 N 则表示没有
umask 022   # 指定当前用户在创建文件时的默认权限,默认 touch 创建一个文件的权限是 644
export PATH runlevel prevlevel  # 导出环境变量
#
#	Mount all file systems defined in /etc/fstab
#
mkdir /dev/pts
mount -a    # 挂载 /etc/fstab 文件中指定的文件系统
#
#	Make sure proc is mounted
#
[ -d "/proc/1" ] || mount /proc # 确保 /proc 已挂载
#
#	Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
#
trap ":" INT QUIT TSTP  # 捕捉 INT、QUIT、TSTP 信号
#
#	Call all parts in order.
#
exec /etc/init.d/rc.local   # 转去执行 rc.local 文件中内容

# 补充 fstab 和 rc.local 文件

在添加完 /etc/init.d/rcS 后再次运行:

QQ图片20240829205505

可以看到本次打印已经没有上面的提示了,但是会多出两个文件的报错:

mount: can't read '/etc/fstab': No such file or directory
mount: can't read '/etc/fstab': No such file or directory
/etc/init.d/rcS: exec: line 34: /etc/init.d/rc.local: not found

分别是缺失 /etc/fstab/etc/init.d/rc.local ,其实从上面的 rcS 文件上的注释可以看到,该文件是会调用 fstabrc.local ,因此补充这两个文件:

  • /etc/fstab

    用于指定系统需要挂载的文件系统,其格式如下:

    <file system> <mount point> <type> <options> <dump> <pass>
    <file system>:要挂载的特殊的设备,也可以是块设备,比如 /dev/sda 等等
    <mount point>:挂载点。
    <type>:文件系统类型,比如 ext2、ext3、proc、romfs、tmpfs 等等。
    <options>:挂载选项,一般使用 defaults,defaults 包含了 rw、suid、 dev、 exec、 auto、 nouser 和 async。
    <dump>:为 1 的话表示允许备份,为 0 不备份,一般不备份,因此设置为 0。
    <pass>:磁盘检查设置,为 0 表示不检查。根目录 ‘/’ 设置为 1,其他的都不能设置为 1,因此这里一般设置为 0
    fstab
    # stock fstab - you probably want to override this with a machine specific one
    proc                 /proc                proc       defaults              0  0
    tmpfs                /dev                 tmpfs      defaults              0  0
    sysfs                /sys                 sysfs      defaults              0  0
    devpts               /dev/pts             devpts     mode=0620,gid=5       0  0
  • /etc/init.d/rc.local

    该文件一般作为 rcS 文件的拓展,可有可无,用于设置特定的与系统级无关的操作

    rc.local
    #!/bin/sh
    # 设置从 /etc/hostname 文件中获取的主机名
    /bin/hostname -F /etc/hostname
    # 使用 mdev 来管理热插拔设备,使 Linux 内核就可以在 /dev 目录下自动创建设备节点
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev -s

# 完善 /dev/pts 和 /etc/hostname

在补充完上述的两个文件后,如果再次运行,那么还是会出现报错:

嘛,这也是预料之中:

  • fstab 中需要挂载一个 /dev/pts 路径的虚拟文件系统,用于实现终端设备的动态分配和管理

  • rc.local 中需要读取 /etc/hostname 文件来获取主机名

所以在 rcS 中的 mount -a 前面添加 mkdir /dev/pts 命令;创建 /etc/hostname 文件,并在里面填写你喜欢的开发板名字。

再运行一次,这次就没有报错提示了,但是有个问题就是,终端的主机名和用户名都没有显示,与常规的终端显示不一样:

# 添加 profile 文件

之前添加了 /bin/hostname 以获取主机名,并用 hostname -F 配置主机名,但实际效果是命令行的提示符是没有显示,而调用各种执行程序是没问题的,说明这部分是 Shell 变量设置导致的。

查阅常见环境变量:

  • PATH:执行命令时要搜索的目录列表

  • HOME:当前用户的家目录

  • MAIL: 当前用户的邮件储蓄目录

  • SHELL:当前用户的 Shell 路径

  • TZ:时区

  • HISTSIZE:是指保存历史命令记录的条数

  • LOGNAME:是指当前用户的登录名

  • PWD:当前的工作目录

  • HOSTNAME:是指主机的名称,许多应用程序如果要用到主机名的话,通常是从这个环境变量中来取得的

  • LANG/LANGUGE:是和语言相关的环境变量,使用多种语言的用户可以修改此环境变量

  • PS1:是基本提示符,对于 root 用户是 #,对于普通用户是 $

  • PS2:是附属提示符,默认是 “>” ,可以通过修改此环境变量来修改当前的命令符

为了验证是否靠环境变量实现这一作用,编写一个 profile 文件:

profile
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
PATH=$PATH:/usr/local/bin
if [ ! -e /etc/localtime -a ! -e /etc/TZ ]; then
	TZ="UTC"		# Time Zone. Look at http://theory.uwinnipeg.ca/gnu/glibc/libc_303.html
				# for an explanation of how to set this to your local timezone.
	export TZ
fi
USER="`id -un`"
LOGNAME=$USER
HOSTNAME='/bin/hostname'
if [ "$HOME" = "/home/root" ]; then
   PATH=$PATH:/usr/local/sbin
fi
if [ "$PS1" ]; then
# works for bash and ash (no other shells known to be in use here)
   PS1='\u@\h:\w\$ '  # 显示主机名、当前路径等信息
fi
export USER LOGNAME PATH PS1

上面的文件除了配置主机名变量和显示终端基本提示符,还追加 $PASH 路径、指定时区及用户名等。

然后重启查看效果,是否能解决命令行提示符显示:

现在是:第一,profile 文件起了作用,hostname 显示出来了,路径指示也显示了;第二,还有个问题就是登录用户名没显示出来。

# 添加 passwad , shadow , group , gshadow 文件

前面已经在 profile 中加入了用户名变量,然而还是没从终端显示出来,那么是不是因为系统没有获取到用户配置信息呢?

与用户配置信息有关的几个文件:

  • etc/passwad

/etc/passwd 文件,是系统用户配置文件,存储了系统中所有用户的基本信息,并且所有用户都可以对此文件执行读操作。

文件中每行代表一个用户信息,以 : 作为分隔符,划分为 7 个字段,每个字段所表示的含义如下:

用户名 : 密码 : UID(用户ID) : GID(组ID) : 描述性信息 : 主目录 : Shell 路径

添加以下信息:

passwad
root:x:0:0:root:/home/root:/bin/bash

note:"x" 表示此用户设有密码,但不是真正的密码,真正的密码保存在 /etc/shadow 文件中。

  • /etc/shadow

/etc/shadow 文件,用于存储 Linux 系统中用户的密码信息。

文件中每行代表一个用户信息,以 : 作为分隔符,划分为 9 个字段,每个字段所表示的含义如下:

用户名 : 加密密码 : 最后一次修改时间 : 最小修改时间间隔 : 密码有效期 : 密码需要变更前的警告天数 : 密码过期后的宽限时间 : 账号失效时间 : 保留字段

添加以下信息:

shadow
root::19424:0:99999:7:::

note:这里的加密密码保存的是真正加密的密码,目前 Linux 的密码采用的是 SHA512 散列加密算法,原来采用的是 MD5 或 DES 加密算法;而最后一次修改时间是指在 1970 年 1 月 1 日之后的第 n 天修改,eg:19424,可通过 date -d "1970-01-01 19424 days" 得到 2023年 03月 08日 星期三 00:00:00 CST

  • /etc/group

/ect/group 文件是用户组配置文件,即用户组的所有信息都存放在此文件中。

文件中每行代表一个用户组信息,以 : 作为分隔符,划分为 4 个字段,,每个字段所表示的含义如下:

组名 : 密码 : GID(组ID) : 该用户组中的用户列表

添加以下信息:

shadow
root:x:0:

note:和 /etc/passwd 文件一样,这里的 "x" 仅仅是密码标识,真正加密后的组密码默认保存在 /etc/gshadow 文件中。

  • /etc/gshadow

/etc/gshadow 文件,用于存储 Linux 系统中组用户的密码信息。

文件中每行代表一个组用户的密码信息,以 : 作为分隔符,划分为 4 个字段,每个字段所表示的含义如下:

组名 : 加密密码 : 组管理员 : 组附加用户列表

添加以下信息:

gshadow
root:*::

最后,添加上这几个文件后再运行一次:

这下子,基本的 rootfs 雏形就出来了。

# 添加 inittab 文件

为什么需要这个文件?Linux 内核启动完成后,内核通过启动第一个用户进程(init 进程)来启动其他用户记的进程或服务,init 进程是 Linux 系统中所有进程的父进程。

init 进程将解析 inittab 文件,运行操作系统的配置脚本,对 Linux 系统进行初始化:

img

inittab 文件是一个不可执行的文本文件,它被按照固定的格式书写,以供 init 进程识别;inittab 由若干条指令组成,每条指令的结构都相同,以 : 作为分隔符,划分为 4 个字段,每个字段所表示的含义如下:

identifier : runlevels : action : process
  1. identifier

    用于唯一标识 inittab 文件中的每条指令。

  2. runlevels

    levelnote
    runlevel 0让 init 关闭所有进程并终止系统。
    runlevel 1用来将系统转到单用户模式,单用户模式只能有系统管理员进入,在该模式下处理那些在有登录用户的情况下不能进行更改的文件,改 runlevel 的编号 1 也可以用 S 代替。
    runlevel 2允许系统进入多用户的模式,但并不支持文件共享,这种模式很少应用。
    runlevel 3最常用的运行模式,主要用来提供真正的多用户模式,也是多数服务器的缺省模式。
    runlevel 4一般不被系统使用,用户可以设计自己的系统状态并将其应用到 runlevel。
    runlevel 5将系统初始化为专用的 X Window 终端。对功能强大的 Linux 系统来说,这并不是好的选择,但用户如果需要这样,也可以通过在 runlevel 启动来实现该方案。
    runlevel 6关闭所有运行的进程并重新启动系统。
  3. action

    字段描述
    sysinit在系统初始化的时候 process 才会执行一次。
    wait告诉 init,要等待相应的进程执行完以后才能继续执行。
    onceinit 只运行一次该进程。
    respawninit 会监视这个 process,即使其结束后也会立即被重新启动。
    askfirst与 respawn 类似,在运行 process 之前在控制台上显示 “Please press Enter to activate this console.”。只要用户按下 “Enter” 键以后才会执行 process。
    ctrlaltdel当 Ctrl+Alt+Del 三个键同时按下时会执行 process。
    restart当 init 重启的时候才会执行 procee。
    shutdown关机的时候执行 process。
  4. process

    表示所要执行的程序、脚本或命令等。

在了解完 inittab 文件的作用后,那么你可以编写你所需要的执行操作了;在这里你可能会问,前面没有创建 /etc/inittab 文件,那为何可以对 Linux 系统进行初始化,这个其实查看代码可以看到,当无法获取 /etc/inittab 文件时,将会使用固定流程进行初始化:

image-20240829221814093

但对于固定流程,我们更偏向于自定义初始化流程,因此可以简单编写:

inittab
# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
::sysinit:/etc/init.d/rcS
::askfirst:-/bin
#::askfirst:-/bin/login
# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
# umount all filesystem
::shutdown:/bin/umount -a -r
#::shutdown:/sbin/swapoff -a
# restart init process
::restart:/sbin/init
# Example of how to put a getty on a serial line (for a terminal)
::once:/sbin/getty -L tty0 115200 vt100 -n root -I "Auto login as root ..."

# 扩展 bosybox 执行命令

这里的拓展命令,并不是指基于原有的 bosybox 工程追加命令,而是通过新的工程编译成独立的执行文件,用以拓展命令。

对于新增编译的 bosybox,一般要满足两个要求:

  1. glibc 版本的兼容
  2. 尽可能使 bosybox 版本一致

# GLIBC 版本获取

方法一:

get_glibc_version.c
#include<stdio.h>                                                                
#include<gnu/libc-version.h>
 
int main(void)
{
   char * str_gnu_ver = gnu_get_libc_version();
 
   printf("gnuc lib version : %s \n",str_gnu_ver);
   printf("__GLIBC__ =  %d \n",__GLIBC__);
   printf("__GLIBC_MINOR__  = %d \n",__GLIBC_MINOR__);
 
   return 0 ;
}

编译运行:

gcc get_glibc_version.c -o get_glibc_version
./get_glibc_version
gnuc lib version : 2.23 
__GLIBC__ =  2 
__GLIBC_MINOR__  = 23

这里利用哪个交叉编译器,输出的版本就是该编译器的 GLIBC 版本。

note:GNU C 从 2.0 开始,为 GLIBC 提供两个常量 __GLIBC____GLIBC_MINOR__ 用来指示版本。

方法二:

对于开发平台,找到你的交叉编译器所在的安装文件夹,从里面找到 libc.so.6 文件,在该目录下或指向该目录(eg: /usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib/ ),运行以下命令:

strings /usr/local/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib/libc.so.6 | grep ^GLIBC
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_2.18
GLIBC_2.22
GLIBC_2.23
GLIBC_2.24
GLIBC_2.25
GLIBC_2.26
GLIBC_2.27
GLIBC_2.28
GLIBC_2.29
GLIBC_2.30
GLIBC_2.32
GLIBC_2.33
GLIBC_PRIVATE
GLIBC_2.29
GLIBC_2.26
GLIBC_2.25
GLIBC_2.23
GLIBC_2.8
GLIBC_2.33
GLIBC_2.30
GLIBC_2.5
GLIBC_2.9
GLIBC_2.7
GLIBC_2.6
GLIBC_2.18
GLIBC_2.14
GLIBC_2.11
GLIBC_2.16
GLIBC_2.13

通常 libc.so 会支持多个版本,即向前兼容,查看该文件中包含的字符串可以看到其支持的版本,通常是连续的。

对于目标平台,同样在相关的 lib 库目录上找到 libc.so.6 文件,然后通过执行或查看软链接:

/lib/lib.so.6
GNU C Library (GNU libc) stable release version 2.21, by Roland McGrath et al.
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 5.3.1 20160113.
Available extensions:
        crypt add-on version 2.1 by Michael Glad and others
        GNU Libidn by Simon Josefsson
        Native POSIX Threads Library by Ulrich Drepper et al
        BIND-8.2.3-T5B
libc ABIs: UNIQUE
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.
ls -al /lib/lib.so.6
lrwxrwxrwx    1 root     root            12 Mar 8  2023 /lib/libc.so.6 -> libc-2.21.so

# bosybox 版本获取

主要是获取目标平台上的版本,然后下载相应的 bosybox 版本进行拓展编译。关于获取目标平台上的版本,可以通过执行:

bosybox

然后下载相应版本进行编译,在这里需要 make menuconfig 把静态编译勾上:

-> Settings
  -> [*] Build static binary (no shared libs)

因为如果用动态编译的话,需要调用 lib 库,这样就会使得需要确保交叉编译器得尽可能一致,使用的 lib 库也要尽可能一致,而利用静态编译就不用调用 lib 库了。

# 实现合并

当你编译好一个新的 busybox 后,在 rootfs 上找个目录存放,或者取一个 busybox1 之类的名字存放至 /bin 目录上,然后把生成软链接拓展命令,替换链接到新的 busybox1 即可。

# 参考

https://fuzidage.github.io/2024/07/20/Linux 内核 - rootfs 构建移植 /#3-gou-jian-gen-wen-jian-xi-tong

http://www.lujun.org.cn/?p=3574

https://www.cnblogs.com/linfeng-learning/p/9285543.html

http://blog.chinaunix.net/uid-20564848-id-73954.html

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝