根文件系统

根文件系统

根文件系统常见目录

1、/bin 目录

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

2、/dev 目录

dev 是 device 的缩写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在 Linux 下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如/dev/ttyS0就表示PC的串口 0,我们要想通过串口 0发送或者接收数据就要操作文件/dev/ttyS0,通过对文件/dev/ttyS0 的读写操作来实现串口0 的数据收发。

3、/etc 目录

此目录下存放着各种配置文件。

4、/lib 目录

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

5、/mnt 目录

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

6、/proc 目录

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

7、/usr 目录

要注意,usr 不是 user 的缩写,而是 Unix Software Resource 的缩写,也就是 Unix 操作系统软件资源目录。这里有个小知识点,那就是 Linux 一般被称为类 Unix 操作系统,苹果的 MacOS也是类 Unix 操作系统。关于 Linux 和 Unix 操作系统的渊源大家可以直接在网上找 Linux 的发展历史来看。既然是软件资源目录,因此/usr 目录下也存放着很多软件,一般系统安装完成以后此目录占用的空间最多。

8、/var 目录

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

9、/sbin 目录

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

10、/sys 目录

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

11、/opt 目录

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

BusyBox 构建根文件系统

BusyBox 简介

BusyBox!其名字分为“Busy”和“Box”,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、mv、ifconfig 等命令 BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。

Busybox下载链接:https://busybox.net/downloads/

编译 BusyBox 构建根文件系统
1
2
3
4
# 首先创建rootfs目录
mkdir rootfs
# 解压 busybox 源码
tar -vxjf busybox-1.29.0.tar.bz2

解压后如图所示:

busybox 中文支持

如果默认直接编译 busybox 的话,在使用 SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。不知道从哪个版本开始 busybox中的 shell 命令对中文输入即显示做了限制,即使内核支持中文但在 shell 下也依然无法正确显示。

所以我们需要修改 busybox 源码,取消 busybox 对中文显示的限制,打开文件 busybox-1.29.0/libbb/printable_string.c,找到函数 printable_string,缩减后的函数内容如下:

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
41
42
43
44
45
46
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') {
/* 99+% of inputs do not need conversion */
if (stats) {
stats->byte_count = (s - str);
stats->unicode_count = (s - str);
stats->unicode_width = (s - str);
}
return str;
}
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)
*d = '?';
d++;
}
if (stats) {
stats->byte_count = (d - dst);
stats->unicode_count = (d - dst);
stats->unicode_width = (d - dst);
}
}
#endif
return auto_string(dst);
}

if (c >= 0x7f) 当字符大于0x7f就跳出去了

if (c < ' ' || c >= 0x7f)如果支持unicode码的时候,字符大约0x7f就输出?

所以需要进行修改,修改后如下所示:

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
示例代码 38.2.2.3 libbb/printable_string.c 代码段
12 const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
13 {
14 char *dst;
15 const char *s;
16
17 s = str;
18 while (1) {
......
30 if (c < ' ')
31 break;
32 /* 注释掉下面这个两行代码 */
33 /* if (c >= 0x7f)
34 break; */
35 s++;
36 }
37
38 #if ENABLE_UNICODE_SUPPORT
39 dst = unicode_conv_to_printable(stats, str);
40 #else
41 {
42 char *d = dst = xstrdup(str);
43 while (1) {
44 unsigned char c = *d;
45 if (c == '\0')
46 break;
47 /* 修改下面代码 */
48 /* if (c < ' ' || c >= 0x7f) */
49 if( c < ' ')
50 *d = '?';
51 d++;
52 }
......
59 #endif
60 return auto_string(dst);
61 }

主要就是禁止字符大于 0X7F 以后 break 和输出‘?’

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
//示例代码 38.2.2.4 libbb/unicode.c 代码段
1003 static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
1004 {
1005 char *dst;
1006 unsigned dst_len;
1007 unsigned uni_count;
1008 unsigned uni_width;
1009
1010 if (unicode_status != UNICODE_ON) {
1011 char *d;
1012 if (flags & UNI_FLAG_PAD) {
1013 d = dst = xmalloc(width + 1);
......
1022 *d++ = (c >= ' ' && c < 0x7f) ? c : '?';
1023 src++;
1024 }
1025 *d = '\0';
1026 } else {
1027 d = dst = xstrndup(src, width);
1028 while (*d) {
1029 unsigned char c = *d;
1030 if (c < ' ' || c >= 0x7f)
1031 *d = '?';
1032 d++;
1033 }
1034 }
......
1040 return dst;
1041 }
......
1130
1131 return dst;
1132 }

第 1022 行,当字符大于 0X7F 以后,*d++就为‘?’。

第 1030 和 1031 行,当字符大于 0X7F 以后,*d 也为‘?’。

修改示例代码 38.2.2.4,修改后内容如下所示:

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.2.2.5 libbb/unicode.c 代码段
1003 static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
1004 {
1005 char *dst;
1006 unsigned dst_len;
1007 unsigned uni_count;
1008 unsigned uni_width;
1009
1010 if (unicode_status != UNICODE_ON) {
1011 char *d;
1012 if (flags & UNI_FLAG_PAD) {
1013 d = dst = xmalloc(width + 1);
......
1022 /* 修改下面一行代码 */
1023 /* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
1024 *d++ = (c >= ' ') ? c : '?';
1025 src++;
1026 }
1027 *d = '\0';
1028 } else {
1029 d = dst = xstrndup(src, width);
1030 while (*d) {
1031 unsigned char c = *d;
1032 /* 修改下面一行代码 */
1033 /* if (c < ' ' || c >= 0x7f) */
1034 if(c < ' ')
1035 *d = '?';
1036 d++;
1037 }
1038 }
......
1044 return dst;
1045 }
......
1047
1048 return dst;
1049 }

同样主要是禁止字符大于 0X7F 的时候设置为‘?’。busybox 中文字符支持跟代码修改有关的就改好了,最后还需要配置 busybox来使能 unicode 码,这个稍后我们配置 busybox 的时候在设置。

配置 busybox

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

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

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

③、allnoconfig,最小配置。

我们一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下 busybox:

1
make defconfig

busybox 也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:

1
make menuconfig

配置路径如下:

1
2
3
Location: 
-> Settings
-> Build static binary (no shared libs)

选项“Build static binary (no shared libs)”用来决定是静态编译 busybox 还是动态编译,静态编译的话就不需要库文件,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的 busybox 会小很多。这里我们不能采用静态编译!因为采用静态编译的话 DNS 会出问题!无法进行域名解析,配置如图 38.2.2.3 所示:

继续配置如下路径配置项:

1
2
3
Location: 
-> Settings
-> vi-style line editing commands

结果如图 38.2.2.4 所示:

继续配置如下路径配置项:

1
2
3
Location: 
-> Linux Module Utilities
-> Simplified modutils

默认会选中“Simplified modutils”,这里我们要取消勾选!!结果如图 38.2.2.5 所示:

继续配置如下路径配置项:

1
2
3
Location: 
-> Linux System Utilities
-> mdev (16 kb) //确保下面的全部选中,默认都是选中的

结果如图 38.2.2.6 所示:

最后就是使能 busybox 的 unicode 编码以支持中文,配置路径如下:

1
2
3
4
Location: 
-> Settings
-> Support Unicode //选中
-> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中

结果如图 38.2.2.7 所示:

编译 busybox

配置好 busybox 以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编译结果存放到前面创建的 rootfs 目录中,输入如下命令:

1
2
make
make install CONFIG_PREFIX=/home/uos/Desktop/rootfs #COFIG_PREFIX 指定编译结果存放目录

编译完成以后如下所示:

编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中,rootfs 目录内容如下所示:

从图可以看出,rootfs 目录下有 bin、sbin 和 usr 这三个目录,以及 linuxrc 这个文件。前面说过 Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。

busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,我们继续来完善 rootfs。

向根文件系统添加lib库
向 rootfs 的 /lib 目录添加库文件

Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要向根文件系统中添加动态库。在 rootfs 中创建一个名为“lib”的文件夹,命令如下:

1
mkdir lib

我们可以把交叉编译器的库全部拿过来,进入如下目录

1
/usr/aarch64-linux-gnu/lib/

可以看到此目录下有很多的.so和.a 文件,这些就是库文件,将此目录下所有的.so和.a文件都拷贝到 rootfs/lib 目录中,拷贝命令如下:

1
cp *so* *.a ~/Desktop/rootfs/lib/ -d

加上 -d 选项后,cp 会复制符号链接本身,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接。会链接到库ld-2.28.so上

ld-linux-armhf.so.3不能作为符号连接,否则在根文件系统中执行程序无法执行,所以我们先将其rm掉,然后直接cp过来

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

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

1
cp arm-linux-gnueabihf/libc/usr/lib/*so arm-linux-gnueabihf/libc/usr/lib/*.a /rootfs/usr/lib/ -d

至此,根文件系统的库文件就全部添加好了

创建其他文件夹

在根文件系统中创建其他文件夹,如 dev、proc、mnt、sys、tmp 和 root 等

完善根文件系统
创建 /etc/init.d/rcS 文件

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

1
2
3
4
5
6
7
8
9
10
11
12
1 #!/bin/sh
2
3 PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
4 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
5 export PATH LD_LIBRARY_PATH
6
7 mount -a
8 mkdir /dev/pts
9 mount -t devpts devpts /dev/pts
10
11 echo /sbin/mdev > /proc/sys/kernel/hotplug
12 mdev -s

第 1 行,表示这是一个 shell 脚本。

第 3 行,PATH 环境变量保存着可执行文件可能存在的目录,这样我们在执行一些命令或者可执行文件的时候就不会提示找不到文件这样的错误。

第 4 行,LD_LIBRARY_PATH 环境变量保存着库文件所在的目录。

第 5 行,使用 export 来导出上面这些环境变量,相当于声明一些“全局变量”。

第 7 行,使用 mount 命令来挂载所有的文件系统,这些文件系统由文件/etc/fstab 来指定,所以我们一会还要创建/etc/fstab 文件。

第 8 和 9 行,创建目录/dev/pts,然后将 devpts 挂载到/dev/pts 目录中。

第 11 和 12 行,使用 mdev 来管理热插拔设备,通过这两行,Linux 内核就可以在/dev 目录下自动创建设备节点。关于 mdev 的详细内容可以参考 busybox 中的 docs/mdev.txt 文档。

上面代码中的 rcS 文件内容是最精简的,大家如果去看 Ubuntu 或者其他大型 Linux操作系统中的 rcS 文件,就会发现其非常复杂。因为我们是初次学习,所以不用搞这么复杂的,而且这么复杂的 rcS 文件也是借助其他工具创建的,比如 buildroot 等。

创建好文件/etc/init.d/rcS 以后一定要给其可执行权限!

1
chmod 777 /etc/init.d/rcS
创建 /etc/fstab 文件

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

格式如下:

1
<file system> <mount point> <type> <options> <dump> <pass>

<file system>:要挂载的特殊的设备,也可以是块设备,比如/dev/sda 等等。

<mount point>:挂载点。

<type>:文件系统类型,比如 ext2、ext3、proc、romfs、tmpfs 等等。

<options>:挂载选项,在 Ubuntu 中输入“man mount”命令可以查看具体的选项。一般使用 defaults,也就是默认选项,defaults 包含了 rw、suid、 dev、 exec、 auto、 nouser 和 async。

<dump>:为 1 的话表示允许备份,为 0 不备份,一般不备份,因此设置为 0。

<pass>:磁盘检查设置,为 0 表示不检查。根目录‘/’设置为 1,其他的都不能设置为 1,其他的分区从 2 开始。一般不在 fstab 中挂载根目录,因此这里一般设置为 0。

按照上述格式,在 fstab 文件中输入如下内容:

1
2
3
4
5
1 #<file system>	<mount point>	<type>	<options>	<dump>	<pass>
2 proc /proc proc defaults 0 0
3 tmpfs /tmp tmpfs defaults 0 0
4 sysfs /sys sysfs defaults 0 0
5 tmpfs /dev tmpfs defaults 0 0
创建 /etc/inittab 文件

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

1
<id>:<runlevels>:<action>:<process>

<id>:每个指令的标识符,不能重复。但是对于 busybox 的 init 来说,有着特殊意义。对于 busybox 而言用来指定启动进程的控制 tty,一般我们将串口或者 LCD 屏幕设置为控制 tty。

<runlevels>:对 busybox 来说此项完全没用,所以空着。

<action>:动作,用于指定<process>可能用到的动作。busybox 支持的动作如表所示:

<process>:具体的动作,比如程序、脚本或命令等。

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

1
2
3
4
5
6
7
1 #etc/inittab
2 ::sysinit:/etc/init.d/rcS
3 console::askfirst:-/bin/sh
4 ::restart:/sbin/init
5 ::ctrlaltdel:/sbin/reboot
6 ::shutdown:/bin/umount -a -r
7 ::shutdown:/sbin/swapoff -a

第 2 行,系统启动以后运行/etc/init.d/rcS 这个脚本文件。

第 3 行,将 console 作为控制台终端,也就是 ttymxc0。

第 4 行,重启的话运行/sbin/init。

第 5 行,按下 ctrl+alt+del 组合键的话就运行/sbin/reboot,看来 ctrl+alt+del 组合键用于重启系统。

第 6 行,关机的时候执行/bin/umount,也就是卸载各个文件系统。

第 7 行,关机的时候执行/sbin/swapoff,也就是关闭交换分区。

/etc/inittab 文件创建好以后就可以重启开发板即可,至此!根文件系统要创建的文件就已经全部完成了。

参考链接:


根文件系统
https://tomwithkernel.github.io/rootfs/根文件系统/
作者
Tom
发布于
2025年6月6日
更新于
2025年6月23日
许可协议