《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​

时间:2023-01-16 18:59:12

根文件系统构建​

Linux“三巨头”已经完成了2个了,就剩最后一个rootfs(根文件系统)了,本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是Linux移植的最后一步,根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系统。以后我们就在这个最小系统上编写、测试Linux驱动,移植一些第三方组件,逐步的完善这个最小系统。最终得到一个功能完善、驱动齐全、相对完善的操作系统。



根文件系统简介

根文件系统一般也叫做rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是FATFSFAT、EXT4、YAFFS和NTFS等这样的文件系统。在这里,根文件系统并不是FATFSEXT4这样的文件系统,这些文件系统属于Linux内核的一部分。Linux中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是Linux运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。对于根文件系统专业的解释,百度百科上是这么说的(原谅我把百度百科引用为专业解释,因为我实在找不到根文件系统的最初定义,也不要建议我到哪些404网站去查找,毕竟我胖,我怕翻到一般*不稳把我摔丑了)

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

百度百科上说内核代码镜像文件保存在根文件系统中,但是我们嵌入式Linux并没有将内核代码镜像保存在根文件系统中,而是保存到了其他地方。比如NAND Flash的指定存储地址、eMMC专用分区中。根文件系统是Linux内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如rcS,inittab等。根文件系统和Linux内核是分开的,单独的Linux内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统,Linux内核在启动的时候就会提示内核崩溃(Kernel panic)的提示,这个在13.7小节已经说过了。

根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的ls、mv、ifconfig等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中,这些小软件是怎么来的呢?这个就是我们本章教程的目的,教大家来构建自己的根文件系统,这个根文件系统是满足Linux运行的最小根文件系统,后续我们可以根据自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对完善的根文件系统。

在构建根文件系统之前,我们先来看一下根文件系统里面大概都有些什么内容,以Ubuntu为例,根文件系统的目录名字为‘/’,没看错,就是一个斜杠,所以输入如下命令就可以进入根目录中:

cd /   //进入根目录

进入根目录以后输入“ls”命令查看根目录下的内容都有哪些,结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.1.1 Ubuntu根目录

上图中根目录下子目录和文件不少,但是这些都是Ubuntu所需要的,其中有很多子目录和文件我们嵌入式Linux是用不到的,所以这里就讲解一些常用的子目录:

1、/bin目录

看到“bin”大家应该能想到bin文件,bin文件是可执行文件,所以此目录下存放着系统需要的可执行文件,一般都是一些命令,比如ls、mv等命令。此目录下的命令所有的用户都可以使用。

2、/dev目录

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

3、/etc目录

此目录下存放着各种服务程序的配置文件,里面的配置文件非常多。但是在嵌入式Linux下此目录会很简洁。

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的发展历史。

8、/var目录

此目录用于存放一些动态的程序数据,如系统日志等。

9/sbin目录

此目录用于存放系统管理员常用的管理程序。具有管理员权限才可以使用,主要用户系统管理。

10、/sys目录

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

11、/opt

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

12、/home

普通用户的工作目录。

13、/root

超级用户的工作目录

关于Linux的根目录就介绍到这里,接下来的构建根文件系统就是研究如何创建上面这些子目录以及子目录中的文件。

Petalinux构建根文件系统

构建根文件系统有两种方式,一种是手工创建根目录并编译配置相关文件,另一种是通过相应的工具来构建。手工构建方式耗时耗力并无多大意义,这里我们采用相应的工具来构建。构建根文件系统的工具有很多,如BusyBox、buildrootYocto等。由于Petalinux工具已经包含了Yocto,所以我们可以直接使用Petalinux工具来构建根文件系统。

实际上我们在6.3.6配置Linux根文件系统就是在构建根文件系统,当时我们使用的是默认配置,在后面的使用中可以看到默认配置是可以满足一般的需求的。现在我们进入到第六章创建的Petalinux工程目录下,具体看下Petalinux的根文件系统构建。

进入Petalinux工程目录后,在终端输入下面的命令配置根文件系统:

petalinux-config -c rootfs

下图就是根文件系统的配置界面:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.2.1 根文件系统的配置界面

可以看到有6个子菜单,其中“Filesystem Packages”菜单主要是配置根文件常用的工具软件,包括内核调试软件、流媒体软件、Python软件以及图形界面软件等,具体的我们就不细看了。

Petalinux Package Groups”菜单可以使能Petalinux提供的软件包组,所谓的软件包组即将与该软件相关的组件和模块放入一组,譬如“packagegroup-petalinux-opencv”包含opencv需要的包:opencv、libopencv-core、libopencv-highgui、libopencv-imgproc、libopencv-objdetect、libopencv-ml、libopencv-calib3d、libopencv-ccalib,当使能“packagegroup-petalinux-opencv”时,这些包也会自动使能,这样做的好处是解决了软件依赖的问题。

Image Features”菜单可以配置根文件系统的某些功能,如是否支持ssh,使用dropbear的ssh还是openssh等。如果不希望每次启动linux后都得输入密码验证,可以使能该菜单下的“debug-tweaks”选项。如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.2.2 配置自动登录

这样配置后,会自动登录,不用再手动输入用户名和密码,方便调试。

apps”菜单可用来构建根文件系统的用户应用。

user packages”菜单可用来配置用户自定义的软件包,因为我们没有自定义软件包,所以该菜单为空。

PetaLinux RootFS Settings”主要是用来设置root用户的密码,默认为“root”。

介绍完这6个菜单后,我们退出配置界面。如果没有做修改,就不需要重新编译根文件系统,如果做了修改可以使用如下命令编译根文件系统:

petalinux-build -c rootfs

Petalinux工程生成的根文件系统在images/linux/目录下,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.2.3工程生成的根文件系统

图中带有“rootfs”的文件名都是Petalinux生成的根文件系统。一般我们使用rootfs.tar.gz文件。我们可以将rootfs.tar.gz文件解压到SD卡的第二个分区——ext4文件系统分区来测试根文件系统,可参考第九章Linux图形界面的搭建。不过我们在Linux驱动开发的时候一般都是通过nfs挂载根文件系统的,当产品最终上市的时候才会将根文件系统烧写到eMMC或者SD中,这样做的好处是方便调试,免得多次插拔SD卡。

我们在4.4节设置的nfs服务器目录下创建一个名为rootfs的子目录(名字随意,此处为了方便使用了rootfs),比如笔者的电脑中“/home/shang/workspace/nfs”就是笔者设置的NFS服务器目录,使用如下命令创建名为rootfs的子目录:

mkdir rootfs

创建好rootfs子目录就可以用来存放我们的根文件系统了。我们将rootfs.tar.gz文件解压到rootfs目录。命令如下:

tar -xzvf images/linux/rootfs.tar.gz -C ~/workspace/nfs/rootfs/

解压完成的rootfs目录内容如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.2.4目录

可以看到该有的目录都有了。接下来我们测试该根文件系统。

根文件系统测试

测试根文件系统rootfs的方法就是使用NFS挂载,uboot里面的bootargs环境变量会设置“root”的值,所以我们将root的值改为NFS挂载即可。在Linux内核源码里面有相应的文档讲解如何设置,文档为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>:<ntp0-ip>

root=/dev/nfs这是启用nfs挂载所必需的。注意,它不是真正的设备,只是告诉内核使用NFS加载根文件系统。

nfsroot:

<server-ip>:nfs服务器IP地址,也就是存放根文件系统主机的IP地址,比如笔者的存放根文件系统主机Ubuntu的IP地址为192.168.2.21。

<root-dir>nfs服务器上根文件系统的存放路径,比如笔者的是/home/shang/workspace/nfs/rootfs

<nfs-options>:NFS的其他可选选项,一般不设置。

ip:

可以自动配置(开发板连接路由器,自动获取IP地址),也可以手动配置(开发板直连电脑,手动设置静态IP地址),由下面的<autoconf>选项决定。使用自动配置时,可直接变成“ip=dhcp”。

<client-ip>客户端IP地址,也就是我们开发板的IP地址,Linux内核启动以后就会使用此IP地址来配置开发板。此地址一定要和Ubuntu主机在同一个网段内,并且没有被其他的设备使用,在Ubuntu中使用ping命令ping一下就知道要设置的IP地址有没有被使用,如果不能ping通就说明没有被使用,那么就可以设置为开发板的IP地址,比如笔者设置为192.168.2.22

<server-ip>服务器IP地址,前面已经说了。

<gw-ip>网关地址,笔者的网关地址是192.168.2.1

<netmask>:子网掩码,一般是255.255.255.0

<hostname>:客户机的名字,一般不设置,此值可以空着。

<device>设备名,也就是网卡名,一般是eth0,eth1….,正点原子的MPSoC开发板的PL_ETH为eth0,PS_ETH为eth1,如果开发板上的两个网口都配置的情况下。这里我们使用PL_ETH,因为linux启动后默认使用eth0,也就是所以PL_ETH

<autoconf>自动配置,一般不使用,所以设置为off。

<dns0-ip>DNS0服务器IP地址,不使用。

<dns1-ip>DNS1服务器IP地址,不使用。

根据上面的格式设置bootargs环境变量的root值如下:

root=/dev/nfs nfsroot=192.168.2.21:/home/shang/workspace/nfs/rootfs ip=192.168.2.22:192.168. 2.21:192.168.2.1:255.255.255.0::eth1:off’

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

setenv bootargs ‘cnotallow=ttyPS0,115200 root=/dev/nfs rw nfsroot=192.168.2.21:/home/ shang/workspace/nfs/rootfs,nfsvers=3 ip=192.168.2.22:192.168.2.21:192.168.2.1:255.255.255.0::eth1:off’  //设置bootargs​
saveenv //保存环境变量

如果开发板是通过网线与路由器相连接,可以使用下面的bootargs环境变量:

setenv bootargs 'cnotallow=ttyPS0,115200 root=/dev/nfs nfsroot=192.168.2.21:/home/shang/workspace/nfs/rootfs,tcp ip=dhcp rw'​
saveenv //保存环境变量

设置好以后使用“boot”命令启动Linux内核,结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.3.1进入根文件系统

从上图可以看出,系统已经进入了根文件系统,说明我们的根文件系统工作了。如果没有启动进入根文件系统的话可以重启一次开发板试试。我们可以输入“ls /”命令测试一下,结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.3.2命令测试

可以看出ls命令工作正常,说明根文件系统基本没问题。接下来我们进行其他功能测试。

根文件系统其他功能测试

软件运行测试

我们使用Linux的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在Ubuntu虚拟机的~/workspace/nfs/rootfs目录下创建一个名为“drivers”的文件夹,以后我们学习Linux驱动的时候就把所有的实验文件放到这个文件夹里面。

在前面创建的“drivers”文件夹下使用vim编辑器新建一个hello.c文件,在hello.c里面输入如下内容:

1 #include <stdio.h>​
2 ​
3 int main(void)​
4 {​
5 int i=0;​
6 while(i < 4){​
7 printf("Hello World!\r\n");​
8 i++;​
9 sleep(2);​
10 }​
11 return 0;​
12 }

hello.c内容很简单,就是循环输出“hello world”4次。sleep相当于Linux的延时函数,单位为秒,所以sleep(2)就是延时2秒。编写完代码后执行编译,因为我们是要在ARM64位芯片上运行的,所以要用交叉编译器去编译,也就是使用$CC(带参数的aarch64-xilinx-linux-gcc)编译(需要先执行10.5节的设置SDK的工作环境),然后执行如下命令:

$CC hello.c -o hello

使用$CChello.c编译为hello可执行文件。这个hello可执行文件究竟是不是ARM使用的呢?使用“file”命令查看文件类型以及编码格式:

file hello  //查看hello的文件类型以及编码格式

结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.1查看hello编码格式

从上图可以看出,输入“file hello”后输出了如下信息:

hello: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked……

意思是hello文件是64位的LSB可执行文件,基于ARM架构,并且是动态链接的,所以我们编译出来的hello可以在MPSoC开发板上运行。

同时,我们在开发板终端中也能看到生成的的可执行文件,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.2查看可执行文件

在开发板中输入如下命令来执行这个可执行文件:

cd /drivers //进入drivers目录​
./hello //执行hello

结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.3运行结果

可以看出,程序hello运行正常,说明我们的根文件系统中的共享库是没问题的。在程序运行的时候应该能感觉到,hello程序执行的时候终端是没法用的,除非使用“ctrl+c”组合键来关闭hello程序,那么有没有办法既能让hello正常运行,而且终端能够正常使用?答案是有的,让hello程序进入后台运行就行了,让一个软件进入后台的方法很简单,运行软件的时候加上“&”即可,比如“./hello &”就是让hello程序在后台运行。在后台运行的软件可以使用“kill -9 pid(进程ID)”命令来关闭掉,首先使用“ps”命令查看要关闭的软件PID是多少,ps命令用于查看所有当前正在运行的进程,并且会给出进程的PID。输入“ps”命令,结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.4命令结果
从上图可以看出hello程序对应的PID为1184,因此我们使用如下命令关闭在后台运行的hello软件:

kill -9 1184

因为hello在不断的输出“hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而hello是输出。在数据流上是没有打断的,只是显示在SecureCRT上就好像被打断了,所以只管输入“kill -9 1184”即可。hello被kill以后会有提示,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.5提示hello被kill掉

再去用ps命令查看一下当前的进程,发现没有hello了。这个就是Linux下的软件后台运行以及如何关闭软件的方法,重点就是3个操作:程序后面加“&”、使用ps查看要关闭的软件PID、使用“kill -9 pid”来关闭指定的软件。

开机自启动测试

在上一小节测试hello软件的时候都是等Linux启动进入根文件系统以后手动输入命令“./hello”来完成的。我们一般做好产品以后都是需要开机自动启动相应的软件,本节我们就以hello这个软件为例,讲解一下如何实现开机自启动。开机启动后进入根文件系统的时候会运行/etc/init.d/rcS这个shell脚本,Linux内核启动以后需要启动一些服务,而rcS就是规定启动哪些文件的脚本文件。因此我们可以在这个脚本里面添加自启动相关内容。添加完成以后的/etc/init.d/rcS文件内容如下:

1 #!/bin/sh​
2 #​
3 # rcS Call all S??* scripts in /etc/rcS.d in​
4 # numerical/alphabetical order.​
5 #​
6 # Version: @(#)/etc/init.d/rcS 2.76 19-Apr-1999 miquels@cistron.nl​
7 #​
8 ​
9 PATH=/sbin:/bin:/usr/sbin:/usr/bin​
10 runlevel=S​
11 prevlevel=N​
12 umask 022​
13 export PATH runlevel prevlevel​
14 ​
15 # Make sure proc is mounted​
16 #​
17 [ -d "/proc/1" ] || mount /proc​
18 ​
19 #​
20 # Source defaults.​
21 #​
22 . /etc/default/rcS​
23 ​
24 #开机自启动​
25 cd /drivers​
26 ./hello &​
27 cd ​
28 ​
29 #​
30 # Trap CTRL-C &c only in this shell so we can interrupt subprocesses.​
31 #​
32 trap ":" INT QUIT TSTP​
33 ​
34 #​
35 # Call all parts in order.​
36 #​
37 exec /etc/init.d/rc S

25行,进入drivers目录,因为要启动的软件存放在drivers目录下。

26行,以后台方式执行hello程序。

27行,退出drivers目录,进入到当前用户目录下。

自启动代码添加完成以后就可以重启开发板,看看hello这个软件会不会自动运行。结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.6开机自启动

从上图可以看出,hello程序开机自动运行了,说明开机自启动成功。

网络连接测试

本小节的网络连接测试是指测试MPSoC开发板能否连接到互联网。需要提醒的是,测试的MPSoC开发板需要和能连接到互联网的路由器用网线连接,如果MPSoC开发板是和我们的主机电脑连接的话,本小节可跳过,因为这种情况下MPSoC开发板是不能连接到互联网的,除非电脑主机有路由器的功能。

测试方法很简单,就是通过ping命令来ping百度的官网:www.baidu.com。输入如下命令:

ping www.baidu.com

结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.7测试结果

可以看出,测试失败,提示www.baidu.com是个“bad address”,也就是地址不对,显然我们的地址是正确的。之所以出现这个错误提示是因为www.baidu.com的地址解析失败,并没有解析出其对应的IP地址。我们需要配置域名解析服务器的IP地址,一般域名解析地址可以设置为所处网络的网关地址,比如192.168.2.1,也可以设置为公共DNS服务器地址如114.114.114.114

设置很简单,可以直接在串口终端中输入“udhcpc”命令,该命令可自动获取IP地址,并且修改nameserver的值,一般是将其设置为对应的网关地址。输入“udhcpc”命令后,执行结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.8 udhcpc”命令

从上图可以看到,执行“udhcpc”命令后,我们的MPSoC开发板获得了IP地址“192.168.2.124”,添加了DNS地址为网关地址“192.168.2.1”。现在我们重新ping一下百度官网,结果如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Linux开发指南》第十五章 根文件系统构建​


15.4.9百度官网结果

参数“-c 4”代表ping 4次就停止,否则无限次的ping。从上图可以看出ping百度官网成功了。域名也成功的解析了,至此,我们的根文件系统就彻底的制作完成。需要说明的是本章制作的根文件系统是能满足基本需求的,如果想运行图形界面的其他功能,可以自行移植,推荐使用我们在第九章Linux图形界面的搭建中使用的根文件系统。

uboot、Linux kernel、rootfs这三个共同构成了一个完整的Linux系统,现在的系统至少是一个可以正常运行的系统,后面我们就可以在这个系统上完成Linux驱动开发的学习。

随着本章的结束,也宣告着本书第三篇的内容也正式结束了,第三篇是系统移植篇,重点就是uboot、Linux kernel和rootfs的移植,看似简简单单的“移植”两个字,引出的却是一篇300多页的“爱恨情仇”。授人以鱼不如授人以渔,本可以简简单单的教大家修改哪些文件、添加哪些内容,怎么去编译,然后得到哪些文件。但是这样只能看到表象,并不能深入的了解其原理,为了让大家能够详细的了解整个流程,笔者义无反顾的选择了这条最难走的路,不管是uboot还是Linux kernel,从Makefile到启动流程,都尽自己最大的努力去阐述清楚。