Linux 根文件系统构建

根文件系统首先是内核启动时所mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

一、根文件系统简介

cpp 复制代码
/ # cd /
/ # ls
bin          etc          linuxrc      music        sbin         tmp
dev          include      minicom.log  proc         share        usr
drivers      lib          mnt          root         sys          var
/ # 

1、/bin 目录

bin 文件就是可执行文件。所以此目录下存放着系统需要的可执行文件,一般都是一些命令,比如ls、mv 等命令。此目录下的命令所有的客户都可以使用。

2、/dev 目录

dev 是device 的缩写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在Linux 下一切皆文件,即使是硬件设备,也是以文件的形式存在的

3、/etc 目录

此目录下存放着各种配置文件,大家可以进入Ubuntu 的etc 目录看一下,里面的配置文件非常多!但是在嵌入式Linux 下此目录会很简洁。

cpp 复制代码
/ # cd etc/
/etc # ls
fstab        inittab      profile      ts.conf
init.d       passwd       resolv.conf
/etc #

4、/lib 目录

lib 是library 的简称,也就是库的意思,因此此目录下存放着Linux 所必须的库文件。这些库文件是共享库,命令和用户编写的应用程序要使用这些库文件。

5、/mnt 目录

临时挂载目录,一般是空目录,可以在此目录下创建空的子目录,比如/mnt/sd、/mnt/usb,这样就可以将 SD 卡或者 U 盘挂载到 /mnt/sd 或者 /mnt/usb 目录中。

6、/proc 目录(process(进程)文件系统)

此目录一般是空的,当Linux 系统启动以后会将此目录作为proc 文件系统的挂载点,proc是个虚拟文件系统,没有实际的存储设备。proc 里面的文件都是临时存在的,一般用来存储系统运行信息文件。

7、/usr 目录

要注意,usr 不是user 的缩写,而是Unix Software Resource 的缩写,也就是Unix 操作系统软件资源目录,/usr 目录下也存放着很多软件,一般系统安装完成以后此目录占用的空间最多。

8、/var 目录

此目录存放一些可以改变的数据。

9、/sbin 目录

此目录页用户存放一些可执行文件,但是此目录下的文件或者说命令只有管理员才能使用,主要用户系统管理。

10、/sys 目录(system file system(系统文件系统))

系统启动以后此目录作为sysfs 文件系统的挂载点,sysfs 是一个类似于proc 文件系统的特殊文件系统,sysfs 也是基于ram 的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录,此目录通过一定的组织结构向用户提供详细的内核数据结构信息。

11、/opt目录

可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中。

二、BusyBox 构建根文件系统

BusyBox 是一个集成了大量的Linux 命令和工具的软件,像ls、mv、ifconfig 等命令BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。

1、下载源码,编译BusyBox 构建根文件系统

一般我们在Linux 驱动开发的时候都是通过nfs 挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到EMMC 或者NAND 中。

cpp 复制代码
/**在 nfs 文件夹下创建 rootfs*/
mkdir rootfs

源码解压:

修改Makefile 中的 ARCH,CROSS_COMPILE 的值

cpp 复制代码
/*使用绝对路径,防止编译出错*/
CROSS_COMPILE ?= /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
ARCH ?= arm

2、修改为busybox 中文字符支持

修改busybox源码,取消对中文的限制。打开文件busybox-1.29.0/libbb**/printable_string.c** ,找到函数printable_string,缩减后的函数内容如下:

cpp 复制代码
const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{
    char *dst;
    const char *s;

    s = str;
    while (1) {
        unsigned char c = *s;
        if (c == '\0') {
            break;
        }
        if (c < ' ')
            break;
        if (c >= 0x7f)        //当字符大于0X7F 以后就跳出去了
            break;
        s++;
    }

#if ENABLE_UNICODE_SUPPORT
    dst = unicode_conv_to_printable(stats, str);
#else
    {
        char *d = dst = xstrdup(str);
        while (1) {
            unsigned char c = *d;
            if (c == '\0')
                break;
            if (c < ' ' || c >= 0x7f)  //如果支持UNICODE 码的话,当字符大于0X7F 就直接输出'?'
                *d = '?';
            d++;
        }
    }
#endif
    return auto_string(dst);
}

修改后:主要就是禁止字符大于0X7F 以后break 和输出'?'。

cpp 复制代码
const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{
    char *dst;
    const char *s;

    s = str;
    while (1) {
        unsigned char c = *s;
        if (c == '\0') {
            break;
        }
        if (c < ' ')
            break;

        /* 注释掉下面这两行代码 */
        /* if (c >= 0x7f)
               break; */

        s++;
    }

#if ENABLE_UNICODE_SUPPORT
    dst = unicode_conv_to_printable(stats, str);
#else
    {
        char *d = dst = xstrdup(str);
        while (1) {
            unsigned char c = *d;
            if (c == '\0')
                break;

            /* 修改下面代码 */
            /* if (c < ' ' || c >= 0x7f) */
            if (c < ' ')
                *d = '?';

            d++;
        }
    }
#endif
    return auto_string(dst);
}

接着打开文件busybox-1.29.0/libbb/unicode.c:

cpp 复制代码
static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t*stats, 
                                                  const char *src, 
                                                  unsigned width, 
                                                  int flags)
{
    char *dst;
    unsigned dst_len;
    unsigned uni_count;
    unsigned uni_width;

    if (unicode_status != UNICODE_ON) {
        char *d;
        if (flags & UNI_FLAG_PAD) {
            d = dst = xmalloc(width + 1);
......
            *d++ = (c >= ' ' && c < 0x7f) ? c : '?';    //当字符大于0X7F 以后,*d++就为'?'
            src++;
        }
        *d = '\0';
    } else {
        d = dst = xstrndup(src, width);
        while (*d) {
            unsigned char c = *d;
            if (c < ' ' || c >= 0x7f)        //当字符大于0X7F 以后,*d 也为'?'
                *d = '?';
            d++;
        }
    }
......
    return dst;
}
......

return dst;
}

修改为:主要是禁止字符大于0X7F 的时候设置为'?'。

cpp 复制代码
static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t*stats,
                                                  const char *src,
                                                  unsigned width, 
                                                  int flags)
{
    char *dst;
    unsigned dst_len;
    unsigned uni_count;
    unsigned uni_width;

    if (unicode_status != UNICODE_ON) {
        char *d;
        if (flags & UNI_FLAG_PAD) {
            d = dst = xmalloc(width + 1);
......
            /* 修改下面一行代码 */
            /* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
            *d++ = (c >= ' ') ? c : '?';
            src++;
        }
        *d = '\0';
    } else {
        d = dst = xstrndup(src, width);
        while (*d) {
            unsigned char c = *d;
            /* 修改下面一行代码 */
            /* if (c < ' ' || c >= 0x7f) */
            if(c < ' ')
                *d = '?';
            d++;
        }
    }
......
    return dst;
}
......

return dst;
}

3、配置busybox

根我们编译Uboot、Linux kernel 一样,我们要先对busybox 进行默认的配置,有以下几种配置选项:

①、defconfig,缺省配置,也就是默认配置选项。

②、allyesconfig,全选配置,也就是选中busybox 的所有功能。

③、allnoconfig,最小配置。

先默认配置一遍:

cpp 复制代码
make defconfig

进行图形化配置:

1)配置为静态编译
cpp 复制代码
Location:
    -> Settings
        -> Build static binary (no shared libs)

用来决定是静态编译busybox 还是动态编译静态编译的话就不需要库文件 ,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的busybox 会小很多。这里不能采用静态编译!因为采用静态编译的话DNS 会出问题!无法进行域名解析

2)使能 VI 编辑

"vi-style line editing commands " 是指在命令行输入时,可以使用 vi(或 Vim)编辑器的键位 来编辑当前输入的命令行。

cpp 复制代码
Location:
    -> Settings
        -> vi-style line editing commands
3)轻量级模块加载

Simplified modutils 是什么?

BusyBox 内置一套"简化版的 Linux 模块工具(modutils)",替代传统的:

  • insmod

  • rmmod

  • modprobe

  • depmod

等 Linux 内核模块管理工具。

Simplified modutils = BusyBox 的轻量级模块加载/卸载工具。

它不包含完整的依赖处理和高级功能,只能执行基本的模块加载/卸载。


📌 开启它可以做什么?

启用 Simplified modutils 后,BusyBox 会为你提供简单的:

  • insmod(加载 .ko 模块)

  • rmmod(卸载模块)

  • modprobe(按名字加载模块,但功能简化)

  • depmod(生成依赖,但极简)

这些工具在 嵌入式 Linux(如路由器、OpenWrt、initramfs、BusyBox 系统)中非常常见。


📉 有什么限制?

因为是 simplified(简化版),所以:

  • 不支持复杂的模块依赖解析

  • 不支持 modprobe 的高级功能(如别名、blacklist)

  • 不完全兼容标准的 kmod 或 module-init-tools

适合 极小系统,不适合完整桌面或服务器。


🧭 什么时候需要开启?

你通常在:

  • initramfs

  • 嵌入式系统

  • BusyBox 制作的最小 rootfs

  • 没有 kmod 的小系统

才会启用它。

如果你的系统已经有完整的 kmod 包,就 不要开启,避免冲突。

cpp 复制代码
Location:
    -> Linux Module Utilities
        -> Simplified modutils
4)使能设备管理器

mdev 是什么?

mdev 是 BusyBox 提供的 轻量级设备管理器 ,是标准 Linux udev 的替代品。

它负责:

  • /dev/ 下创建与内核设备对应的设备节点

  • 热插拔设备时自动创建/删除节点

  • 读取规则文件(例如 /etc/mdev.conf)决定如何命名、权限、运行脚本

  • 在开机时自动扫描 /sys 并创建设备节点

适用于 嵌入式系统、小型 rootfs、initramfs


📌 mdev 的典型用途

在精简 Linux 里,因为不能用体积庞大的 udev,mdev 就取代它来:

  • 处理 USB 插拔(自动生成 /dev/sdX, /dev/ttyUSB0 等)

  • 管理块设备、字符设备

  • 创建驱动加载后的节点(如 /dev/i2c-*, /dev/spidev*

  • 在插拔时触发脚本 (hotplug events)

对很多嵌入式系统而言是 必选组件


📁 为什么"确保下面的全部选中"?

进入 mdev 选项后,通常有子选项:

  • Support /etc/mdev.conf

  • Support mdev -s (coldplug)

  • Support hotplug events

  • Support firmware loading

  • Support sysfs trigger

这些功能用于:

✔ 创建正确的设备节点

✔ 处理开机冷插(扫描 sysfs)

✔ 处理插拔热插事件

✔ 让用户自定义规则

✔ 加载固件(给驱动用)

默认 BusyBox 都会选中这些,否则 mdev 功能不完整。


一句简单总结

mdev = BusyBox 版本的 udev,用来管理 /dev 下的设备节点。
如果你要做嵌入式或 minimal Linux,这个必须开,并确保相关子选项都打开。

cpp 复制代码
Location:
    -> Linux System Utilities
        -> mdev (16 kb) //确保下面的全部选中,默认都是选中的
5)使能busybox 的unicode 编码以支持中文
cpp 复制代码
Location:
    -> Settings
        -> Support Unicode //选中
            -> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中

根据自己的实际需求来配置。

4、编译busybox

cpp 复制代码
make
make install CONFIG_PREFIX=/home/v11/linux/nfs/rootfs

编译安装后:

其中:linuxrc 可以作为用户空间的init 程序,即用户态空间的init 程序是busybox 来生成的。

5、向根文件系统添加lib 库

1)向 rootfs 的 "/lib" 目录添加库文件

Linux 中的应用程序一般都是需要动态库的,向根文件系统中添加动态库。在rootfs 中创建一个名为"lib"的文件夹。

cpp 复制代码
mkdir lib

lib 文件夹创建好了,库文件从哪里来呢?lib 库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了"/usr/local/arm/"目录中。

cpp 复制代码
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linuxgnueabihf/libc/lib

此目录下有很多的*so*(*是通配符)和.a 文件,这些就是库文件,将此目录下所有的***so*和.a**文件都拷贝到rootfs/lib 目录中:

cpp 复制代码
cp *so* *.a /home/v11/linux/nfs/rootfs/lib/ -d

后面的**"-d" 表示拷贝符号链接** ,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于Windows 下的快捷方式。会链接到库 ld-2.19-2014.08-1-git.so 上,输入命令"ls ld-linux-armhf.so.3 -l"查看此文件详细信息:

重新复制 ld-linuxarmhf.so.3,只是不复制软链接即可,先将rootfs/lib 中的ld-linux-armhf.so.3 文件删除掉,命令如下:

cpp 复制代码
rm ld-linux-armhf.so.3

然后重新进入到/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinux-gnueabihf/libc/lib 目录中,重新拷贝ld-linux-armhf.so.3,命令如下:

cpp 复制代码
cp ld-linux-armhf.so.3 /home/v11/linux/nfs/rootfs/lib/

检查是否还是软链接:

/***********************************************************************************************************/

继续进入如下目录中:

cpp 复制代码
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib

拷贝文件:

cpp 复制代码
cp *so* *.a /home/v11/linux/nfs/rootfs/lib/ -d

完成以后:

2)向rootfs 的"usr/lib"目录添加库文件

在rootfs 的 usr 目录下创建一个名为 lib 的目录 ,将如下目录中的库文件拷贝到rootfs/usr/lib目录下:

cpp 复制代码
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
cpp 复制代码
cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -d

拷贝完成后:

至此,根文件系统的库文件就全部添加好了,可以使用"du"命令来查看一下rootfs/librootfs/usr/lib 这两个目录的大小,命令如下:

cpp 复制代码
cd rootfs             //进入根文件系统目录
du ./lib ./usr/lib/ -sh         //查看lib 和usr/lib 这两个目录的大小
3)创建其他文件夹

在根文件系统中创建其他文件夹,如dev、proc、mnt、sys、tmp 和root 等。创建完以后为:

6、根文件系统初步测试

测试方法就是使用NFS 挂载,uboot 里面的bootargs 环境变量会设置"root"的值,所以我们将root 的值改为NFS 挂载即可。

格式为:

cpp 复制代码
root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gwip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>

📌 参数说明表

1. root= 参数

参数 说明
/dev/nfs 告诉内核根文件系统通过 NFS 挂载,而不是本地磁盘。

2. nfsroot= 参数

字段 示例 说明
<server-ip> 192.168.1.10 NFS 服务器的 IP;可省略,省略时会从 DHCP 获取。
<root-dir> /export/rootfs NFS 服务器上根文件系统的路径(必填)。
<nfs-options> vers=3,proto=tcp NFS 挂载的可选参数,例如协议版本、挂载方式等。

格式:

nfsroot=<server-ip>:<root-dir>,<nfs-options>


3. ip= 参数

格式:

ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0>:<dns1>

字段 示例 说明
<client-ip> 192.168.1.100 启动设备(客户端)的 IP 地址。
<server-ip> 192.168.1.10 NFS 或 TFTP 服务器 IP。
<gw-ip> 192.168.1.1 默认网关 IP;无则填 0.0.0.0
<netmask> 255.255.255.0 子网掩码。
<hostname> myboard 客户端主机名。
<device> eth0 使用的网络设备(如 eth0、eth1)。
<autoconf> off / dhcp 网络配置方式:off 手动配置,dhcp 自动获取。
<dns0> 8.8.8.8 主 DNS 服务器(可选)。
<dns1> 8.8.4.4 次 DNS 服务器(可选)。

设置为:

cpp 复制代码
root=/dev/nfs nfsroot=192.168.1.250:/home/v11/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off

逐项详细解释(表格形式)

1. root=/dev/nfs

参数 含义
root=/dev/nfs 告诉内核根文件系统是通过 NFS 挂载,而不是本地磁盘或 initramfs。

2. nfsroot=192.168.1.250:/home/v11/linux/nfs/rootfs,proto=tcp rw

字段 示例值 含义
<server-ip> 192.168.1.250 NFS 服务器 IP 地址
<root-dir> /home/v11/linux/nfs/rootfs NFS 上根文件系统所在目录
proto=tcp proto=tcp 使用 TCP 协议挂载 NFS(通常更可靠)
rw rw 以可读写方式挂载根文件系统

3. ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off

格式为:

ip=<client-ip>:<server-ip>:<gwip>:<netmask>:<hostname>:<device>:<autoconf>

字段 示例值 含义
<client-ip> 192.168.1.251 启动板卡(客户端)的 IP
<server-ip> 192.168.1.250 NFS/TFTP 服务器的 IP
<gwip> 192.168.1.1 默认网关 IP
<netmask> 255.255.255.0 子网掩码
<hostname> (空,表示不设置主机名) 在参数中两个冒号连在一起表示空字段:::
<device> eth0 使用的网络接口
<autoconf> off 关闭自动获取 IP(DHCP),采用上面手动配置的值

DNS 参数没写,默认空(可省略)。

进入uboot 命令行模式,然后重新设置bootargs 环境变量,命令如下:

cpp 复制代码
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off' //设置bootargs
saveenv //保存环境变量

设置好后重启开发板,会提示:

cpp 复制代码
can't run '/etc/init.d/rcS': No such file or directory

说明无法运行 "/etc/init.d/rcS" 这个文件,因为这个文件不存在

7、完善根文件系统

1)创建/etc/init.d/rcS 文件(Linux 系统启动时执行的初始化脚本

rcS 是个shell 脚本,Linux 内核启动以后需要启动一些服务,而rcS 就是规定启动哪些文件的脚本文件。在rootfs 中创建/etc/init.d/rcS 文件,然后在rcS 中输入如下所示内容:

bash 复制代码
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH      # 设置可执行程序搜索路径
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib # 设置动态库搜索路径
export PATH LD_LIBRARY_PATH                    # 导出环境变量

mount -a                                        # 按 /etc/fstab 挂载所有文件系统
mkdir /dev/pts                                   # 创建 pts 目录(终端伪设备)
mount -t devpts devpts /dev/pts                 # 挂载 devpts,支持终端、SSH 等

echo /sbin/mdev > /proc/sys/kernel/hotplug      # 设置内核热插拔处理程序为 mdev
mdev -s                                          # 扫描 /sys 创建所有设备节点(coldplug)

给予该文件权限,并重启开发板;遇到如下问题:

2)创建/etc/fstab 文件(Linux 系统的文件系统挂载表(file system table) 配置文件)

在rootfs 中创建/etc/fstab 文件,fstab 在Linux 开机以后自动配置哪些需要自动挂载的分区,格式如下:

bash 复制代码
<file system> <mount point> <type> <options> <dump> <pass>

📌 fstab 参数说明表

字段 示例 含义
<file system> /dev/sda1, UUID=xxxx, 192.168.1.10:/nfs/rootfs 要挂载的文件系统来源,可以是设备、UUID、LABEL 或网络路径
<mount point> /, /home, /mnt/nfs 挂载到本地系统的哪个目录
<type> ext4, vfat, nfs, tmpfs 文件系统类型
<options> defaults, rw, noatime, nosuid 挂载选项,用逗号分隔,可设置读写模式、权限策略、缓存行为等
<dump> 01 是否被 dump 工具备份,几乎所有现代系统用 0
<pass> 0, 1, 2 启动时 fsck 检查顺序:0=不检查1=优先检查(通常是根)2=其他分区按顺序检查

📘 补充说明(简洁易懂)

<file system>

  • 可以是设备路径:/dev/mmcblk0p2

  • 或 UUID:UUID=xxxx

  • 或 NFS:192.168.1.88:/nfs/root

  • 或特种 fs:tmpfs

<mount point>

文件系统最终呈现出来的位置。

<type>

常见类型:

  • 本地:ext4, ext3, xfs, btrfs, vfat

  • 网络:nfs, cifs

  • 特殊:tmpfs, proc, sysfs

<options>

常用示例:

  • defaults(rw, suid, dev, exec, auto, nouser, async)

  • rw(读写)

  • ro(只读)

  • noatime(减少磁盘写入)

  • sync(同步写入)

  • nolock(NFS 不锁)

  • uid=0,gid=0(fat 系统设权限)

<dump>

几乎永远填 0,只有老系统工具会使用。

<pass>

用于启动时 fsck 检查顺序:

  • 根分区:1

  • 其他分区:2

  • 不检查:0

在创建的文件中输入如下内容:

bash 复制代码
 #<file system> <mount point> <type> <options> <dump> <pass>
 proc             /proc        proc    defaults  0     0
 tmpfs            /tmp         tmpfs   defaults  0     0
 sysfs            /sys         sysfs   defaults  0     0

解释:

📌 逐行解释(表格)

文件系统 挂载点 类型 选项 dump pass 作用说明
proc /proc proc defaults 0 0 挂载 proc 虚拟文件系统,提供内核和进程信息,如 /proc/cpuinfo/proc/meminfo。启动 Linux 必须。
tmpfs /tmp tmpfs defaults 0 0 在内存中创建临时目录 /tmp,速度快,用于临时文件、缓存。关机后自动清空。
sysfs /sys sysfs defaults 0 0 挂载 sysfs,用于提供内核对象、驱动、设备信息(如 /sys/class/sys/devices)。现代 Linux 必须。

📘 补充说明

  1. proc 文件系统
  • 是一种虚拟文件系统,不存在于磁盘上

  • 内核把许多运行信息放在 /proc 里(比如进程状态、CPU 信息、内存信息等)

  • 系统和工具(如 ps、top、free)都依赖它

没有 /proc 系统会无法正常运行。


  1. tmpfs
  • 存在于内存(RAM)中,而不是硬盘

  • /tmp 被许多软件用作临时工作目录

  • tmpfs 在内存中,速度特别快

适用于嵌入式系统或无硬盘系统。


  1. sysfs
  • 与驱动、设备、内核对象相关

  • 很多工具(如 udev/mdev)需要 /sys 才能工作

  • 也用于查看硬件信息

例如:

/sys/class/net/eth0

用于查看网络设备属性。

3)创建/etc/inittab 文件(Linux 系统启动初始化程序(init) 的配置文件)

inittab 的详细内容可以参考busybox 下的文件examples/inittab。init 程序会读取/etc/inittab这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以":"分隔的4 个段组成,格式如下:

bash 复制代码
<id>:<runlevels>:<action>:<process>
/*
    <id>:每个指令的标识符,不能重复。
    <runlevels>:对busybox 来说此项完全没用,所以空着。
    <action>:动作,用于指定<process>可能用到的动作。
    <process>:具体的动作,比如程序、脚本或命令等。
*/

参考busybox 的examples/inittab 文件,我们也创建一个/etc/inittab,在里面输入如下内容:

bash 复制代码
#etc/inittab
::sysinit:/etc/init.d/rcS        //系统启动以后运行/etc/init.d/rcS 这个脚本文件。
console::askfirst:-/bin/sh       //将console 作为控制台终端,也就是ttymxc0。
::restart:/sbin/init             //重启的话运行/sbin/init。
::ctrlaltdel:/sbin/reboot        //按下ctrl+alt+del 组合键的话就运行/sbin/reboot
::shutdown:/bin/umount -a -r     //关机的时候执行/bin/umount,也就是卸载各个文件系统
::shutdown:/sbin/swapoff -a      //关机的时候执行/sbin/swapoff,也就是关闭交换分区。

/**************************************************************************************************************/

4)三者之间的关系

1️⃣ 各自作用回顾

文件 作用
/etc/inittab init 的配置文件,定义系统启动、关机、控制台 shell、Ctrl+Alt+Del 等行为。
/etc/fstab 文件系统挂载表,定义哪些设备或网络文件系统挂载到哪些目录,挂载类型、选项、启动检查顺序。
/etc/init.d/rcS 系统初始化脚本,由 inittab 调用,用来挂载文件系统、创建设备节点、加载模块、启动必要服务。

2️⃣ 关系与执行顺序

  1. init 进程启动

    • Linux 内核启动完成后,会执行 /sbin/init(或 BusyBox init)。

    • init 读取 /etc/inittab,决定启动流程。

  2. inittab 调用 rcS

    • ::sysinit:/etc/init.d/rcS 行告诉 init,在系统初始化阶段执行 rcS 脚本。

    • rcS 是"冷启动"脚本,做基本初始化。

  3. rcS 挂载文件系统

    • rcS 内部通常会执行:

      mount -a

      这会读取 /etc/fstab,把根文件系统及其他分区、网络文件系统挂载好。

    • 挂载完成后,系统才能访问 /proc/sys/tmp、硬盘或 NFS 根目录。

  4. rcS 继续初始化系统

    • 创建 /dev/dev/pts 等设备节点

    • 设置热插拔管理器(如 mdev)

    • 加载内核模块

    • 配置环境变量

  5. 控制台和多用户环境

    • rcS 完成后,inittab 根据配置启动 shell、终端(tty)或进入多用户模式。

3️⃣ 总结它们的关系

bash 复制代码
内核启动 → init (/sbin/init) → 读取 /etc/inittab
                  │
                  ├─ sysinit 阶段 → 调用 /etc/init.d/rcS
                                  │
                                  ├─ mount -a → 挂载 /etc/fstab 定义的文件系统
                                  ├─ 创建设备节点、加载模块
                                  └─ 系统环境就绪
  • inittab:启动控制中心,告诉系统做什么

  • rcS:执行初始化操作的脚本

  • fstab:定义挂载规则,由 rcS 使用

可以理解为:inittab 是计划,rcS 是执行,fstab 是数据

至此!根文件系统要创建的文件就已经全部完成了

8、测试根文件系统

1)软件运行测试

2)中文字符测试

3)开机自启动测试(在/etc/init.d/rcS文件里添加)

bash 复制代码
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
runlevel=S
umask 022
export PATH LD_LIBRARY_PATH runlevel

mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

# 开机自启动
cd /drivers
./hello &
cd /

4)外网连接测试

在rootfs 中新建文件/etc/resolv.conf,然后在里面输入如下内容:

bash 复制代码
/*nameserver 表示这是个域名服务器, 设置了两个域名服务器地址:*/
 nameserver 114.114.114.114
 nameserver 192.168.1.1

然后 ping 外网。

至此!我们的根文件系统就彻底的制作完成!!!

三、Buildroot 根文件系统构建

busybox 仅仅只是帮我们构建好了一些常用的命令和文件,像lib 库、/etc 目录下的一些文件都需要我们自己手动创建,而且busybox 构建的根文件系统默认没有用户名和密码设置。在后续的实验中,我们还要自己去移植一些第三方软件和库,比如alsa、iperf、mplayer 等等

busybox 构建的根文件系统不齐全,很多东西需要我们自行添加,比如lib 库文件。Buildroot 是另外一种实用的根文件系统构建方法,那就是使用buildroot 来构建根文件系统。

buildroot 和uboot、Linux Kernel 很类似,我们需要到其官网上下载源码,然后对其进行配置,比如设置交叉编译器、设置目标CPU 参数等,最主要的就是选择所需要的第三方库或软件。一切配置好以后就可以进行编译,编译完成了以后就会在一个文件夹里面存放好编译结果,也就是根文件系统。

1、buildroot 构建根文件系统

1)配置buildroot(进行图形化配置,配置完成以后可编译)
(1)配置Target options

首先配置Target options 选项,需要配置的项目和其对应的内容如下:

bash 复制代码
Target options
    -> Target Architecture                 = ARM (little endian)
    -> Target Binary Format                = ELF
    -> Target Architecture Variant         = cortex-A7
    -> Target ABI = EABIhf
    -> Floating point strategy             = NEON/VFPv4
    -> ARM instruction set                 = ARM

配置好后:

(2)配置Toolchain

此配置项用于配置交叉编译工具链,也就是交叉编译器,这里设置为我们自己所使用的交叉编译器即可。

bash 复制代码
Toolchain
    -> Toolchain type = External toolchain
    -> Toolchain = Custom toolchain        # 用户自带交叉编译器
    -> Toolchain origin = Pre-installed toolchain   # 已安装好的编译器
    -> Toolchain path = /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf
    -> Toolchain prefix = $(ARCH)-linux-gnueabihf   # 前缀
    -> External toolchain gcc version = 4.9.x
    -> External toolchain kernel headers series = 4.1.x
    -> External toolchain C library = glibc/eglibc

    -> [*] Toolchain has SSP support? (NEW)        # 选中
    -> [*] Toolchain has RPC support? (NEW)        # 选中
    -> [*] Toolchain has C++ support?              # 选中
    -> [*] Enable MMU support (NEW)                # 选中
bash 复制代码
Toolchain:设置为Custom toolchain,表示使用用户自己的交叉编译器。
Toolchain origin:设置为Pre-installed toolchain,表示使用预装的交叉编译器。
Toolchain path:设置自己安装的交叉编译器绝对路径!buildroot 要用到。
Toolchain prefix:设置交叉编译器前缀,要根据自己实际所使用的交叉编译器来设置,比如我们使用的是       
                    arm-linux-gnueabihf-gcc,因此前缀就是$(ARCH)-linux-gnueabihf,其中ARCH
                    我们前面已经设置为了arm。
(3)配置System configuration

此选项用于设置一些系统配置,比如开发板名字、欢迎语、用户名、密码等。需要配置的项目和其对应的内容如下:

bash 复制代码
System configuration
    -> System hostname = alpha_imx6ull          # 平台名称,自行设置
    -> System banner = Welcome to alpha i.mx6ull  # 登录欢迎语
    -> Init system = BusyBox                    # 使用 BusyBox 作为 init 系统
    -> /dev management = Dynamic using devtmpfs + mdev   # 使用 devtmpfs + mdev 管理设备节点
    -> [*] Enable root login with password (NEW)         # 启用 root 密码登录
    -> Root password = 123456                   # root 登录密码为 123456

在System configuration 选项中可以配置平台名字,登录密码等信息。

(4)配置Filesystem images

此选项配置我们最终制作的根文件系统为什么格式的,配置如下:

bash 复制代码
Filesystem images
    -> [*] ext2/3/4 root filesystem                 # 若根文件系统放在 eMMC/SD 卡上,使用 ext3/ext4
    ->     ext2/3/4 variant = ext4                 # 选择 ext4 格式作为根文件系统

    -> [*] ubi image containing an ubifs root filesystem   # 若使用 NAND Flash,则使用 UBIFS
(5)禁止编译Linux 内核和uboot

buildroot 下载的linux 和uboot官方源码,里面会缺少很多驱动文件,而且最新的linux 内核和uboot 会对编译器版本号有要求,可能导致编译失败。

bash 复制代码
-> Kernel
    -> [ ] Linux Kernel //不要选择编译Linux Kernel 选项!

-> Bootloaders
    -> [ ] U-Boot //不要选择编译U-Boot 选项!
(6)配置Target packages

此选项用于配置要选择的第三方库或软件、比如alsa-utils、ffmpeg、iperf 等工具,但是现在我们先不选择第三方库,防止编译不下去!先编译一下最基本的根文件系统,如果没有问题的话再重新配置选择第三方库和软件。

2)编译buildroot

配置完成以后就可以编译buildroot 了,编译完成以后buildroot 就会生成编译出来的根文件系统压缩包,我们可以直接使用。输入如下命令开始编译:

bash 复制代码
sudo make //注意,一定要加sudo,而且不能通过-jx 来指定多核编译!!!

Ubuntu 中buildroot 源码目录下的dl 文件夹中,dl 文件夹专用用于存放下载下来的源码。

等待编译完成,编译完成以后就会在buildroot-2019.02.6/output/images 下生成根文件系统:

拷贝rootfs.tar并解压后:

3)buildroot 根文件系统测试

测试方法也是通过nfs 挂载的方式,启动uboot,修改bootargs 环境变量。

设置好后启动:(自行创建不存在的目录)

2、buildroot 第三方软件和库的配置

直接在buildroot 里面配置使能,使能后重新编译拷贝即可。

3、buildroot 下的busybox 配置

1)busybox 配置

buildroot 在构建根文件系统的时候也是要用到busybox 的,既然用到了busybox 那么就涉及到busybox 的配置。buildroot 会自动下载busybox 压缩包,buildroot 下载的源码压缩包都存放在/dl 目录下,在dl 目录下就有一个叫做"busybox"的文件夹,此目录下保存着busybox 压缩包:

解压并配置(根据自己的实际需求来配置):

bash 复制代码
sudo make busybox-menuconfig
2)修改busybox 中文字符的支持
3)编译busybox

配置好以后就可以重新编译buildroot 下的busybox,进入到buildroot 源码目录下,输入如下命令查看当前buildroot 所有配置了的目标软件包,也就是packages:

bash 复制代码
sudo make show-targets

图中列出了当前buildroot 中所有使能了的packages 包,其中就包括busybox,如果我们想单独编译并安装busybox 的话执行下面命令即可:

bash 复制代码
sudo make busybox

上述命令就会重新编译busybox。编译完成以后重新编译 buildroot ,主要是对其进行打包,

输入如下命令:

bash 复制代码
sudo make

重新编译完成以后查看一下output/images 目录下rootfs.tar 的创建时间是否为刚刚编译的,如果不是的话就删除掉rootfs.tar,然后重新执行"sudo make"重新编译一下即可。最后我们使用新的rootfs.tar 启动Linux 系统。

4、根文件系统测试

可能出现的问题:输入命令的时候命令行前面一直都是"#",如果我们进入到某个目录的话前面并不会显示当前目录路径。

解决方法:

打开/etc/profile 文件,找到如下所示内容,然后将其屏蔽掉:

bash 复制代码
if [ "$PS1" ]; then
    if [ "`id -u`" -eq 0 ]; then
        export PS1='# '
    else
        export PS1='$ '
    fi
fi

上述代码为原始的设置PS1 环境变量的配置代码,就是它将命令提示符设置为了固定的"#",我们将其屏蔽掉,然后输入如下所示内容:

bash 复制代码
1 PS1='[\u@\h]:\w$:'
2 export PS1

"PS1" 环境变量用于设置命令提示符格式,格式如下:

bash 复制代码
PS1 = '命令列表'
/***************************************************/
    \! 显示该命令的历史记录编号。
    \# 显示当前命令的命令编号。
    \$ 显示$符作为提示符,如果用户是root 的话,则显示#号。
    \\ 显示反斜杠。
    \d 显示当前日期。
    \h 显示主机名。
    \n 打印新行。
    \nnn 显示nnn 的八进制值。
    \s 显示当前运行的shell 的名字。
    \t 显示当前时间。
    \u 显示当前用户的用户名。
    \W 显示当前工作目录的名字。
    \w 显示当前工作目录的路径

/********************************************************************************************************/

总结区别:

使用buildroot构建根文件系统时配置的busybox和单独使用busybox构建根文件系统时有啥区别?

这是一个非常经典且触及嵌入式Linux构建核心的问题。

简单来说,BusyBox 是"食材",而 Buildroot 是"自动料理机"

在构建根文件系统(Rootfs)时,BusyBox 只是其中的核心组件(提供 shell 和基础命令),而 Buildroot 是一个完整的构建框架,它不仅包含 BusyBox,还帮你处理了周围所有繁琐的配套工作。

以下是两者的详细对比和核心区别:


1. 核心定位区别

特性 仅使用 BusyBox 构建 使用 Buildroot 构建
本质 一个多合一的二进制工具程序(Tool)。 一个自动化的构建系统(Build System)。
产出物 只有 bin/, sbin/, usr/ 等基础命令目录。 一个完整的、可直接烧录的根文件系统镜像(如 rootfs.ext4)。
自动化程度 低(全手动)。你需要自己动手做很多"装修"工作。 高(全自动)。一键生成完整的系统。

2. 构建流程的具体差异

A. 仅使用 BusyBox (手工流)

当你只下载 BusyBox 源码并编译时,你得到的只是一个"裸"的命令集。为了让它成为一个能启动的根文件系统,你必须手动完成以下工作:

  1. 创建目录骨架 :手动创建 /dev, /etc, /proc, /sys, /tmp, /var 等标准目录。

  2. 移植动态库 :必须手动去交叉编译工具链中找到对应的 C 库(glibc/uclibc/musl)和加载器(ld-linux.so),并复制到 /lib 目录。

  3. 编写启动脚本 :手动创建 /etc/inittab/etc/init.d/rcS,配置系统启动后的行为(如挂载文件系统、配置 IP)。

  4. 配置设备节点 :手动创建 /dev/console, /dev/null,或者配置 mdev/eudev

  5. 打包镜像 :手动使用 dd, mkfs.ext4 等工具将文件夹制作成镜像文件。

总结 :灵活但极其繁琐,容易因为漏掉某个 .so 库文件导致系统起不来(Kernel Panic)。

B. 使用 Buildroot (自动化流)

Buildroot 将 BusyBox 视为一个"软件包"。当你使用 Buildroot 时:

  1. 自动配置:Buildroot 会根据你选择的工具链和架构,自动生成最合适的 BusyBox 配置文件(你也可以覆盖它)。

  2. 自动补全骨架:它会自动生成符合 FHS 标准的目录结构。

  3. 自动处理依赖 :它会自动编译工具链,并将程序运行所需的动态链接库自动拷贝到根文件系统,不需要你操心库文件版本匹配问题

  4. 自动生成脚本:它会自动生成标准的启动脚本(init scripts)。

  5. 一键打包 :直接生成 rootfs.tar, rootfs.ext4, rootfs.cpio 等格式。


3. 配置方式的区别

  • BusyBox: 使用 make menuconfig。你只能配置有哪些命令(ls, cp, vi, awk)被编译进去。

  • Buildroot:

    • 层级 1 (Buildroot Config): 使用 make menuconfig。配置目标硬件架构、工具链、内核版本、以及需要安装哪些第三方软件(如 Qt, Python, SSH, SQLite 等)。

    • 层级 2 (BusyBox Config): 使用 make busybox-menuconfig。Buildroot 允许你钻进去单独微调 BusyBox 的内部选项。


4. 扩展性(安装第三方软件)

这是两者最大的代沟:

  • BusyBox 模式 :如果你想在系统中加一个 OpenSSH 或者 Mosquitto (MQTT),你需要:

    1. 去官网下载源码。

    2. 手动编写 Makefile 进行交叉编译。

    3. 手动解决依赖库(比如 SSH 依赖 OpenSSL/Zlib)。

    4. 手动安装到文件系统目录。

  • Buildroot 模式

    1. 运行 make menuconfig

    2. 进入 Target packages -> Networking applications

    3. 勾选 opensshmosquitto

    4. 运行 make。Buildroot 自动下载、编译、解决依赖、安装。

5. 什么时候选哪个?

选择"纯 BusyBox 手工构建"的情况:
  • 学习目的:你想通过从零构建,彻底搞懂 Linux 文件系统的组成结构、启动流程和动态库加载机制。

  • 极简系统:你需要一个极度精简的 Initramfs,大小控制在几百 KB 以内,只需要几个 Shell 命令,不需要任何第三方库。

选择"Buildroot"的情况(推荐):
  • 工程开发:你需要快速产出一个稳定、可复现的固件。

  • 复杂应用:你的系统需要 Qt、Python、WIFI 驱动、音视频库等复杂的第三方软件。

  • 团队协作:Buildroot 的配置文件(defconfig)可以放入 Git 管理,保证团队所有人构建出的镜像是一模一样的。

总结

  • BusyBox 提供了系统的基本能力(命令)。

  • Buildroot 提供了系统的完整骨架和生态(包括 BusyBox 本身、库、启动脚本、打包工具)。

相关推荐
灰灰勇闯IT1 小时前
虚拟机性能优化实战:从基础调优到深度压榨性能
开发语言·学习·性能优化·虚拟机
边疆.1 小时前
【Linux】文件系统
linux·运维·服务器·磁盘·文件系统·软硬链接
_dindong1 小时前
Linux网络编程:Reactor反应堆模式
linux·服务器·网络·设计模式·php
vi121231 小时前
农业图像预处理技术学习综述:原理、实现与应用
人工智能·学习
世界宇宙超级无敌究极特级顶级第一非常谱尼1 小时前
RF Power Amplifiers for Wireless Communications 第二章学习笔记
笔记·学习·pa·功率放大器·mmic
im_AMBER1 小时前
Leetcode 71 买卖股票的最佳时机 | 增量元素之间的最大差值
笔记·学习·算法·leetcode
DevangLic1 小时前
【win的实用官方工具集合】解决:该设备正在使用中,请关闭所有。。。
运维·学习·工具
大神的风范1 小时前
LINUX 驱动之HSR04超声波模块,设备树配置
linux·驱动开发
永远都不秃头的程序员(互关)1 小时前
鸿蒙Electron平台:Flutter技术深度解读及学习笔记
笔记·学习·flutter