1 根文件系统的引入#
我们知道文件系统类型有 FATFS、FAT、EXT4、YAFFS 和 NTFS,squashfs
等。文件系统可以让我们利用文件IO的形式对文件目录进行访问,而不用去访问flash存储地址,在使用上更为方便轻松。
根文件系统rootfs, 首先是内核启动时所 mount(挂载)的第一个文件系统,系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
百度百科上说内核代码镜像文件保存在根文件系统中(对电脑端的Ubuntu来说确实是,放在/boot/vmlinuz-5.4.0-152-generic
)。但是我们嵌入式 Linux 并没有将内核代码镜像保存在根文件系统中,而是保存到了其他地方,比如 NAND Flash 的指定存储地址、EMMC 专用分区中。
嵌入式根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统,Linux 内核在启动的时候就会提示内核崩溃(Kernel panic):
2 根文件系统的组成#
2.1 /bin 目录#
存放着系统需要的可执行文件,一般都是一些命令,比如ls、cp、mv
等命令。
2.2 /dev 目录#
此目录下的文件都是设备节点文件:
1 | ls -l /dev/ |
2.3 /etc 目录#
此目录下存放着各种配置文件。
2.4 /lib 目录#
存放着 Linux 所必须的库文件。这些库文件是共享库,命令和用户编写的应用程序要使用这些库文件。
2.5 /mnt 目录#
临时挂载目录,一般是空目录,可以在此目录下创建空的子目录,比如/mnt/sd、/mnt/usb,这样就可以将 SD 卡或者 U 盘挂载到/mnt/sd 或者/mnt/usb 目录中。
2.6 /proc 目录#
proc虚拟文件系统,输出硬件驱动模块的相关信息,各种输出打印。(后续专门出一篇博文介绍)
2.7 /usr 目录#
这里的usr 不是 user 的缩写,而是 Unix Software Resource
的缩写,也就是 Unix 操作系统软件资源目录。
2.8 /var 目录#
存放一些可以改变的数据。如/var/log。
[root@robin]/var/log# ls
messages messages.0 messages.1 messages.2 messages.3
2.9 /sys 目录#
此目录作为 sysfs 文件系统的挂载点,sysfs 是一个类似于 proc 文件系统的特殊文件系统,sysfs 也是基于 ram 的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录,此目录通过一定的组织结构向用户提供详细的内核数据结构信息。
查看硬件模块IP的base addr
:
1 | [root@robin]/sys/devices# cd platform/ |
查看debug相关:
1 | [root@robin]/sys/kernel/debug# ls |
重要的有:
1 | /sys/kernel/debug/debug_enabled |
查看和设置驱动模块module param变量:
1 | cd /sys/module/soph_stitch/parameters |
3 构建根文件系统#
3.1 busybox#
BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像ls、mv、ifconfig
等命令 BusyBox 都会提供。
一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。
BusyBox 可以在其官网下载到,官网地址为:
https://busybox.net/downloads/
3.1.1 配置编译 BusyBox#
这里我下载的busybox-1.29.0.tar.bz2
, 将其进行解压:
1 | mkdir rootfs;cd rootfs; tar xfj busybox-1.29.0.tar.bz2 |
修改Makefile添加toolchain
:
1 | 164 CROSS_COMPILE ?= /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- |
3.1.1.1 busybox支持中文识别#
我们知道中文的编码如果采用utf8
,那么ascii
值肯定是大于0x7f
的。因此需要修改busybox源码,让其能识别超过ascii
的编码范围0x7f
。
utf8 中文编码:
UTF-8是一种广泛使用的Unicode
编码方式,它可以用来表示各种语言和符号。对于中文字来说,UTF-8采用了一种特殊的编码方式,即中文字符主要使用三个字节(21比特)来表示。具体来说,当一个汉字的Unicode码位于0x80到0x7FF之间的时,UTF-8会用三个字节来编码这个汉字。例如,汉字“爸”在UTF-8中的编码过程是这样的:
首先,将汉字“爸”
的Unicode
码(0x5BAD)
转换为二进制数(0111 0010 0011 1000)
。
然后,按照UTF-8的三字节模式进行编码,即每个字节的最高位都设置为1,剩下的7位用于表示该字节所对应的Unicode值。在这个例子中,第一个字节的高位为1,表示这是一个16位的字节;接下来的三位为1011,对应于Unicode码中的第11位,即十进制的5;最后一位为0,代表这是一个字节的最低位。
最后,将这三个字节的二进制数转换为十六进制数(0xE788B8)
,这就是汉字“爸”
在UTF-8中的编码形式。
1 | vi libbb/unicode.c |
打开函数unicode_conv_to_printable2
,修改如下:
1 | vi libbb/printable_string.c |
3.1.1.2 busybox配置#
编译busybox前要先对 busybox 进行默认的配置,有以下几种配置选项:
1 | ①、defconfig,缺省配置,也就是默认配置选项。 |
1 | -> Settings |
这项请不要勾选,静态编译 busybox会DNS解析有问题。
1 | -> Settings |
建议勾选这项。
1 | -> Linux Module Utilities |
默认会选中“Simplified modutils”,这里我们要取消勾选。
1 | -> Linux System Utilities |
1 | -> Settings |
3.1.1.3 编译#
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- |
busybox 的所有工具和文件就会被安装到 rootfs 目录中,rootfs 目录内容如下:
命令和工具都再对应/bin /usr/bin对应的目录:
Linux内核启动流程 | Hexo (fuzidage.github.io)
说过 Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs
设置 init=/linuxrc
,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。
我们的根文件系统此时就制作好了,但此时还不能使用,还需要一些其他的文件,我们继续来完善 rootfs。
3.1.2 根文件系统添加 lib 库#
3.1.2.1 /lib库添加#
Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要向根文件系统中添加动态库。lib 库文件从交叉编译器中获取:
1 | mkdir lib |
libc库如下:将其拷贝进rootfs。
1 | cp *so* *.a /media/cvitek/robin.lee/rootfs/lib -d #-d表示将软链接也一同拷贝 |
进入工具链的lib库:
1 | cd gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib |
最终根文件系统的/lib内容如下:
3.1.2.2 /usr/lib库添加#
1 | mkdir /usr/lib |
libc/usr/lib库如下:将其拷贝进rootfs。
1 | cp *so* *.a /media/cvitek/robin.lee/rootfs/usr/lib -d #-d表示将软链接也一同拷贝 |
至此,根文件系统的库文件就全部添加好了。来看下库占用存储空间多少:
1 | robin.lee@WORKSTATION5:/media/cvitek/robin.lee/rootfs$ du ./lib ./usr/lib/ -sh |
3.1.3 根文件系统添加其他目录#
dev、proc、mnt、sys、tmp 和 root这些目录需要在rootfs进行创建。
3.1.4 根文件系统测试#
初步测试我们的rootfs功能,我们最好是利用nfs挂载根文件系统,方便修改。之前bootargs
都是设置:
1 | setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw' |
从emmc分区2启动,分区2存放了根文件系统。现在bootargs改成nfs挂载rootfs要如何设置呢?
文档为 Documentation/filesystems/nfs/nfsroot.txt
,格式如下:
1 | 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> |
最终我们设置 bootargs 环境变量的 root 值如下:root=/dev/nfs nfsroot=192.168.1.250:/media/cvitek/robin.lee/rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off
“proto=tcp”
表示使用 TCP 协议,“rw”
表示 nfs 挂载的根文件系统为可读可写。
修改保存环境变量:
1 | setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.250:/media/cvitek/robin.lee/rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off' #设置 bootargs |
设置好以后使用“boot”
命令启动 Linux 内核,输出打印如下:
可以看到成功挂载rootfs,并且进入Linux控制台。
3.1.5 根文件系统添加 etc配置脚本#
3.1.5.1 创建/etc/init.d/rcS#
前面打印有一行报错,进入根文件系统的时候会有下面这一行错误提示:
1 | can't run '/etc/init.d/rcS': No such file or directory |
rcS 是个 shell 脚本,Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。在 rootfs 中创建/etc/init.d/rcS
文件,然后在 rcS 中输入如下所示内容:
1 |
|
PATH
环境变量保存着可执行文件可能存在的目录;LD_LIBRARY_PATH
环境变量保存着库文件所在的目录;
mount 命令来挂载所有的文件系统,这些文件系统由文件/etc/fstab
来指定;
创建目录/dev/pts
,然后将 devpts
挂载到/dev/pts
目录中。
使用 mdev 来管理热插拔设备,通过这两行,Linux 内核就可以在/dev 目录下自动创建设备节点。
我们是简单构造了一个最简单的rcS, 大家如果去看 Ubuntu 或者其他大型 Linux操作系统中的 rcS 文件,就会发现其非常复杂。而且这么复杂的 rcS 文件也是借助其他工具创建的,比如 buildroot 等。
1 | chmod 777 rcS |
重新启动 Linux 内核,启动以后如下图:
3.1.5.2 创建/etc/fstab#
fstab 在 Linux 开机以后自动配置哪些需要自动挂载的分区,格式如下:
1 | <file system> <mount point> <type> <options> <dump> <pass> |
fstab内容设置如下:
1 | #<file system> <mount point> <type> <options> <dump> <pass> |
fstab
文件创建完成以后重新启动 Linux打印如下:
3.1.5.3 创建/etc/inittab文件#
inittab 的详细内容可以参考 busybox 下的文件 examples/inittab
。init 程序会读取/etc/inittab
这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组成,格式如下:
1 | <id>:<runlevels>:<action>:<process> |
我们的/etc/inittab内容设置如下:
1 | #etc/inittab |
第 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
,也就是关闭交换分区。
3.1.5.3 创建/etc/resolv.conf#
/etc/resolv.conf
是DNS客户机配置文件,用于设置DNS服务器的IP地址及DNS域名,还包含了主机的域名搜索顺序。该文件是由域名解析器(resolver,一个根据主机名解析IP地址的库)使用的配置文件。
1 | nameserver: 配置DNS服务器地址(顺序来查询,且只有当第一个nameserver没有反应时才查询下面的nameserver) |
默认/etc/resolv.conf
配置:
1 | # Generated by NetworkManager |
一般将nameserver设置成网关地址,再加入8.8.8.8
和114.114.114.114
ping blog.csdn.com
:
1 | [root@node2 ~]# ping blog.csdn.com |
ping blog
,发现不通,需要设置domain域
:
1 | [root@node2 ~]# ping blog |
调整/etc/resolv.conf
配置文件,添加domain
1 | [root@node2 ~]# vi /etc/resolv.conf |
再次ping blog
, ping不完整域名会自动补全
1 | [root@node2 ~]# ping blog |
调整/etc/resolv.conf
配置文件,添加search
1 | [root@node2 ~]# vi /etc/resolv.conf |
3.1.5.4 /proc/cmdline
#
cat /proc/cmdline
可以看到内核启动时U-boot传入参数:
4 mkfs工具制作ext4#
1 | robin.lee@WORKSTATION5:/media/cvitek/robin.lee/zip/a2_dev/install/soc_cv186ah_wevb_emmc$ dd if=/dev/zero of=rootfs.ext4 bs=1M count=1024 |