本帖最后由 正点原子运营 于 2021-6-19 11:56 编辑
1)实验平台:正点原子STM32MP157开发板
2) 章节摘自【正点原子】《STM32MP157嵌入式Linux驱动开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=629270721801
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyzmp157.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32MP157技术交流群:691905614
第十八章 Busybox根文件系统构建
前面我们已经移植了TF-A、Uboot和Linux kernel,就剩最后一个rootfs(根文件系统)了,本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是Linux系统移植的最后一步,根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系统。以后我们就在这个最小系统上编写、测试Linux驱动,移植一些第三方组件,逐步的完善这个最小系统。最终得到一个功能完善、驱动齐全、相对完善的操作系统。
18.1 根文件系统简介
根文件系统一般也叫做rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是FATFS、FAT、EXT4、YAFFS和NTFS等这样的文件系统。在这里,根文件系统并不是FATFS、EXT4这样的文件系统代码,我们在学单片机的时候听到的FATFS是具体的文件系统代码,这个是属于Linux内核的一部分。Linux中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。根目录和子目录中会有很多的文件,这些文件是Linux运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。对于根文件系统专业的解释,百度百科上是这么说的:
根文件系统首先是内核启动时所mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
百度百科上说内核代码镜像文件保存在根文件系统中,但是我们嵌入式Linux并没有将内核代码镜像保存在根文件系统中,而是保存到了其他地方。比如NAND Flash的指定存储地址、EMMC专用分区中。根文件系统是Linux内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如rcS,inittab等。根文件系统和Linux内核是分开的,单独的Linux内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统,Linux内核在启动的时候就会提示内核崩溃(Kernel panic)的提示,这个在17.5小节已经说过了。
根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的ls、mv、ifconfig等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要通过输入命令的形式来运行。这些小软件就保存在根文件系统中,这些小软件是怎么来的呢?这个就是我们本章教程的目的,教大家来构建自己的根文件系统,这个根文件系统是满足Linux运行的最小根文件系统,后续我们可以根据自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对完善的根文件系统。
在构建根文件系统之前,我们先来看一下根文件系统里面大概都有些什么内容,以Ubuntu为例,根文件系统的目录名字为‘/’,没看错就是一个斜杠,所以输入如下命令就可以进入根目录中:
进入根目录以后输入“ls”命令查看根目录下的内容都有哪些,结果如图18.1.1所示:
图18.1.1 Ubuntu根目录
图18.1.1中根目录下子目录和文件不少,但是这些都是Ubuntu所需要的,其中有很多子目录和文件我们嵌入式Linux是用不到的,所以这里就讲解一些常用的子目录:
1、/bin目录
看到“bin”大家应该能想到bin文件,bin文件就是可执行文件。所以此目录下存放着系统需要的可执行文件,一般都是一些命令,比如ls、mv等命令。此目录下的命令所有的客户都可以使用。
2、/dev目录
dev是device的缩写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在Linux下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如/dev/ttySTM0(STM32MP1根目录会有此文件)就表示STM32MP157的串口4,我们要想通过串口4发送或者接收数据就要操作文件/dev/ttySTM0,通过对文件/dev/ttySTM0的读写操作来实现串口4的数据收发。
3、/etc目录
此目录下存放着各种配置文件,大家可以进入Ubuntu的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的发展历史来看。既然是软件资源目录,因此/usr目录下也存放着很多软件,一般系统安装完成以后此目录占用的空间最多。
8、/var目录
此目录存放一些可以改变的数据。
9、/sbin目录
此目录页用户存放一些可执行文件,但是此目录下的文件或者说命令只有管理员才能使用,主要用户系统管理。
10、/sys目录
系统启动以后此目录作为sysfs文件系统的挂载点,sysfs是一个类似于proc文件系统的特殊文件系统,sysfs也是基于ram的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录,此目录通过一定的组织结构向用户提供详细的内核数据结构信息。
11、/opt
可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中。
关于Linux的根目录就介绍到这里,接下来的构建根文件系统就是研究如何创建上面这些子目录以及子目录中的文件。
18.2 BusyBox构建根文件系统
18.2.1 BusyBox简介
上一小节说了,根文件系统里面就是一堆的可执行文件和其他文件组成的?难道我们得一个一个的从网上去下载这些文件?显然这是不现实的!那么有没有人或者组织专门干这个事呢?他们负责“收集”这些文件,然后将其打包,像我们这样的开发者可以直接拿来用。答案是有的,它就叫做BusyBox!其名字分为“Busy”和“Box”,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。BusyBox是一个集成了大量的Linux命令和工具的软件,像ls、mv、ifconfig等命令BusyBox都会提供。BusyBox就是一个大的工具箱,这个工具箱里面集成了Linux的许多工具和命令。一般下载BusyBox的源码,然后配置BusyBox,选择自己想要的功能,最后编译即可。
BusyBox可以在其官网下载到,官网地址为:https://busybox.net/,官网比较简陋,如图18.2.1.1所示:
图18.2.1.1 BusyBox官网
在官网左侧的“Get BusyBox”栏有一行“Download Source”,点击“Download Source”即可打开BusyBox的下载页,如图18.2.1.2所示:
图18.2.1.2 BusyBox下载页
从图18.2.1.2可以看出,目前最新的BusyBox版本是1.32.0,大家下载“busybox-1.32.0.tar.bz2”这个压缩包即可,我们已经将其放到了开发板光盘中,路径为:1、例程源码->6、BusyBox源码->busybox-1.32.0.tar.bz2,BusyBox准备好以后就可以构建根文件系统了。
18.2.2 编译BusyBox构建根文件系统
一般我们在Linux驱动开发的时候都是通过nfs挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到EMMC或者NAND中。所以要在4.2.1小节中设置的nfs服务器目录中创建一个名为rootfs的子目录(名字大家可以随意起,为了方便就用了rootfs),比如我的电脑中“/home/zuozhongkai/linux/nfs”就是我设置的NFS服务器目录,使用如下命令创建名为rootfs的子目录:
- cd /home/zuozhongkai/linux/nfs
- mkdir rootfs
复制代码
创建好的rootfs子目录就用来存放我们的根文件系统了。
将busybox-1.32.0.tar.bz2发送到Ubuntu中,存放位置大家随便选择。然后使用如下命令将其解压:
- tar -vxjf busybox-1.32.0.tar.bz2
复制代码
解压完成以后进入到busybox-1.32.0目录中,此目录中的文件和文件夹如图18.2.2.1所示:
图18.2.2.1 busybox-1.32.0目录内容
1、修改Makefile,添加编译器
同Uboot和Linux移植一样,打开busybox的顶层Makefile,添加ARCH和CROSS_COMPILE的值,如下所示:
示例代码18.2.2.1 Makefile代码段
- 164 CROSS_COMPILE ?= /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-
- ......
- 190 ARCH ?= arm
复制代码
在示例代码18.2.2.1中CORSS_COMPILE使用了绝对路径!主要是为了防止编译出错。
2、busybox中文字符支持
如果默认直接编译busybox的话,在使用SecureCRT的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。不知道从哪个版本开始busybox中的shell命令对中文输入即显示做了限制,即使内核支持中文但在shell下也依然无法正确显示。
所以我们需要修改busybox源码,取消busybox对中文显示的限制,打开文件busybox-1.32.0/libbb/printable_string.c,找到函数printable_string2,缩减后的函数内容如下:
示例代码18.2.2.2 libbb/printable_string.c代码段
- 12 const char* FAST_FUNC printable_string2(uni_stat_t *stats,
- const char *str)
- 13 {
- 14 char *dst;
- 15 const char *s;
- 16
- 17 s = str;
- 18 while (1) {
- 19 unsigned char c = *s;
- 20 if (c == '\0') {
- 21 /* 99+% of inputs do not need conversion */
- 22 if (stats) {
- 23 stats->byte_count = (s - str);
- 24 stats->unicode_count = (s - str);
- 25 stats->unicode_width = (s - str);
- 26 }
- 27 return str;
- 28 }
- 29 if (c < ' ')
- 30 break;
- 31 if (c >= 0x7f)
- 32 break;
- 33 s++;
- 34 }
- 35
- 36 #if ENABLE_UNICODE_SUPPORT
- 37 dst = unicode_conv_to_printable(stats, str);
- 38 #else
- 39 {
- 40 char *d = dst = xstrdup(str);
- 41 while (1) {
- 42 unsigned char c = *d;
- 43 if (c == '\0')
- 44 break;
- 45 if (c < ' ' || c >= 0x7f)
- 46 *d = '?';
- 47 d++;
- 48 }
- 49 if (stats) {
- 50 stats->byte_count = (d - dst);
- 51 stats->unicode_count = (d - dst);
- 52 stats->unicode_width = (d - dst);
- 53 }
- 54 }
- 55 #endif
- 56 return auto_string(dst);
- 57 }
复制代码
第31和32行,当字符大于0X7F以后就跳出去了。
第45和46行,如果支持UNICODE码的话,当字符大于0X7F就直接输出‘?’。
所以我们需要对这4行代码进行修改,修改以后如下所示:
示例代码18.2.2.3 libbb/printable_string.c代码段
- 12 const char* FAST_FUNC printable_string2(uni_stat_t *stats, const char *str)
- 13 {
- 14 char *dst;
- 15 const char *s;
- 16
- 17 s = str;
- 18 while (1) {
- 19 unsigned char c = *s;
- 20 if (c == '\0') {
- 21 /* 99+% of inputs do not need conversion */
- 22 if (stats) {
- 23 stats->byte_count = (s - str);
- 24 stats->unicode_count = (s - str);
- 25 stats->unicode_width = (s - str);
- 26 }
- 27 return str;
- 28 }
- 29 if (c < ' ')
- 30 break;
- 31 /* 注释掉下面这个两行代码 */
- 32 /* if (c >= 0x7f)
- 33 break; */
- 34 s++;
- 35 }
- 36
- 37 #if ENABLE_UNICODE_SUPPORT
- 38 dst = unicode_conv_to_printable(stats, str);
- 39 #else
- 40 {
- 41 char *d = dst = xstrdup(str);
- 42 while (1) {
- 43 unsigned char c = *d;
- 44 if (c == '\0')
- 45 break;
- 46 /* 修改下面代码 */
- 47 /* if (c < ' ' || c >= 0x7f) */
- 48 if( c < ' ')
- 49 *d = '?';
- 50 d++;
- 51 }
- 52 if (stats) {
- 53 stats->byte_count = (d - dst);
- 54 stats->unicode_count = (d - dst);
- 55 stats->unicode_width = (d - dst);
- 56 }
- 57 }
- 58 #endif
- 59 return auto_string(dst);
- 60 }
复制代码
示例代码18.2.2.3中红色部分的代码就是被修改以后的,主要就是禁止字符大于0X7F以后break和输出‘?’。
接着打开文件busybox-1.32.0/libbb/unicode.c,找到如下内容:
示例代码18.2.2.4 libbb/unicode.c代码段
- 1009 static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
- 1010 {
- 1011 char *dst;
- 1012 unsigned dst_len;
- 1013 unsigned uni_count;
- 1014 unsigned uni_width;
- 1015
- 1016 if (unicode_status != UNICODE_ON) {
- 1017 char *d;
- 1018 if (flags & UNI_FLAG_PAD) {
- 1019 d = dst = xmalloc(width + 1);
- 1020 while ((int)--width >= 0) {
- 1021 unsigned char c = *src;
- 1022 if (c == '\0') {
- 1023 do
- 1024 *d++ = ' ';
- 1025 while ((int)--width >= 0);
- 1026 break;
- 1027 }
- 1028 *d++ = (c >= ' ' && c < 0x7f) ? c : '?';
- 1029 src++;
- 1030 }
- 1031 *d = '\0';
- 1032 } else {
- 1033 d = dst = xstrndup(src, width);
- 1034 while (*d) {
- 1035 unsigned char c = *d;
- 1036 if (c < ' ' || c >= 0x7f)
- 1037 *d = '?';
- 1038 d++;
- 1039 }
- 1040 }
- 1041 if (stats) {
- 1042 stats->byte_count = (d - dst);
- 1043 stats->unicode_count = (d - dst);
- 1044 stats->unicode_width = (d - dst);
- 1045 }
- 1046 return dst;
- 1047 }
- ......
- 1139 return dst;
- 1140 }
复制代码
第1028行,当字符大于0X7F以后,*d++就为‘?’。
第1036和1037行,当字符大于0X7F以后,*d也为‘?’。
修改示例代码18.2.2.4,修改后内容如下所示:
示例代码18.2.2.5 libbb/unicode.c代码段
- 1009 static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
- 1010 {
- 1011 char *dst;
- 1012 unsigned dst_len;
- 1013 unsigned uni_count;
- 1014 unsigned uni_width;
- 1015
- 1016 if (unicode_status != UNICODE_ON) {
- 1017 char *d;
- 1018 if (flags & UNI_FLAG_PAD) {
- 1019 d = dst = xmalloc(width + 1);
- 1020 while ((int)--width >= 0) {
- 1021 unsigned char c = *src;
- 1022 if (c == '\0') {
- 1023 do
- 1024 *d++ = ' ';
- 1025 while ((int)--width >= 0);
- 1026 break;
- 1027 }
- 1028 /* 修改下面一行代码 */
- 1029 /* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
- 1030 *d++ = (c >= ' ') ? c : '?';
- 1031 src++;
- 1032 }
- 1033 *d = '\0';
- 1034 } else {
- 1035 d = dst = xstrndup(src, width);
- 1036 while (*d) {
- 1037 unsigned char c = *d;
- 1038 /* 修改下面一行代码 */
- 1039 /* if (c < ' ' || c >= 0x7f) */
- 1040 if(c < ' ')
- 1041 *d = '?';
- 1042 d++;
- 1043 }
- 1044 }
- 1045 if (stats) {
- 1046 stats->byte_count = (d - dst);
- 1047 stats->unicode_count = (d - dst);
- 1048 stats->unicode_width = (d - dst);
- 1049 }
- 1050 return dst;
- 1051 }
- ......
- 1143 return dst;
- 1144 }
复制代码
示例代码18.2.2.5中红色部分的代码就是被修改以后的,同样主要是禁止字符大于0X7F的时候设置为‘?’。busybox中文字符支持跟代码修改有关的就改好了,最后还需要配置busybox来使能unicode码,这个稍后我们配置busybox的时候在设置。
3、配置busybox
和我们编译Uboot、Linux kernel一样,我们要先对busybox进行默认的配置,有以下几种配置选项:
①、defconfig,缺省配置,也就是默认配置选项。
②、allyesconfig,全选配置,也就是选中busybox的所有功能。
③、allnoconfig,最小配置。
我们一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下busybox:
busybox也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:
打开以后如图18.2.2.2所示:
图18.2.2.2 busybox图形化配置界面
配置路径如下:
- Location:
- -> Settings
- -> Build static binary (no shared libs)
复制代码
选项“Build static binary (no shared libs)”用来决定是静态编译是动态编译,静态编译的话就不需要库文件,但是编译出来的根文件系统会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的busybox会小很多。这里我们不能采用静态编译!因为采用静态编译的话DNS会出问题!无法进行域名解析,配置如图18.2.2.3所示:
图18.2.2.3 不选择“Build static binary (no shared libs)”
继续配置如下路径配置项:
- Location:
- -> Settings
- -> vi-style line editing commands
复制代码
结果如图18.2.2.4所示:
图18.2.2.4 选择“vi-style line editing commands ”
继续配置如下路径配置项:
- Location:
- -> Linux Module Utilities
- -> Simplified modutils
复制代码
默认会选中“Simplified modutils”,这里我们要取消勾选!!结果如图18.2.2.5所示:
图18.2.2.5 取消选中“Simplified modutils”
继续配置如下路径配置项:
- Location:
- -> Linux System Utilities
- -> mdev (16 kb) //确保下面的全部选中,默认都是选中的
复制代码
结果如图18.2.2.6所示:
图18.2.2.6 “mdev”配置项
最后就是使能busybox的unicode编码以支持中文,配置路径如下:
- Location:
- -> Settings
- -> Support Unicode //选中
- -> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中
复制代码
结果如图18.2.2.7所示:
图18.2.2.7 中文支持
busybox的配置就到此结束了,大家也可以根据自己的实际需求选择配置其他的选项,不过对于初学者笔者不建议再做其他的修改,可能会出现编译出错的情况发生。
我们使用图形化修改过Busybox的配置以后最好保存一下配置文件,以防“make clean”以后删除掉以前的配置,在配置主界面上,选中“Save Configuration to an Alternate File”,如图18.2.2.8所示:
图18.2.2.8 保存配置文件
选中以后会让你输入配置文件名,Busybox的默认配置文件都是保存到configs目录下,配置文件的名字后缀必须是“_defconfig”, 这里设置配置文件名字为“stm32mp1_atk_defconfig”,如图18.2.2.9所示:
图18.2.2.9 设置配置文件名字
点击图18.2.2.9中的“Ok”按钮即可完成保存,以后要想使用我们的自己的配置文件,直接输入:
- make stm32mp1_atk_defconfig
复制代码
4、编译busybox
配置好busybox以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编译结果存放到前面创建的rootfs目录中,输入如下命令:
- make
- make install CONFIG_PREFIX=/home/zuozhongkai/linux/nfs/rootfs
复制代码
COFIG_PREFIX指定编译结果的存放目录,比如我存放到“/home/zuozhongkai/linux/nfs/rootfs”目录中,等待编译完成。编译完成以后如图18.2.2.8所示:
图18.2.2.8 busybox编译完成
编译完成以后会在busybox的所有工具和文件就会被安装到rootfs目录中,rootfs目录内容如图18.2.2.9所示:
图18.2.2.9 rootfs目录
从图18.2.2.9可以看出,rootfs目录下有bin、sbin和usr这三个目录,以及linuxrc这个文件。前面说过Linux内核init进程最后会查找用户空间的init程序,找到以后就会运行这个用户空间的init程序,从而切换到用户态。如果bootargs设置init=/linuxrc,那么linuxrc就是可以作为用户空间的init程序,所以用户态空间的init程序是busybox来生成的。
busybox的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,我们继续来完善rootfs。
18.2.3 向根文件系统添加lib库
1、向rootfs的“/lib”目录添加库文件
Linux中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要先根文件系统中添加动态库。在rootfs中创建一个名为“lib”的文件夹,命令如下:
lib文件夹创建好了,库文件从哪里来呢?lib库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们作为初学者肯定不知道,既然我不知道那就简单粗暴的把所有的库文件都放到我们的根文件系统中。这样做出来的根文件系统肯定比较大,而且有很多我们可能用不到的库文件,但是我们现在是学习阶段,还做不了裁剪。
进入如下路径对应的目录:
- /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib
复制代码
此目录下有很多的*so*(*是通配符)文件,这些就是库文件,将此目录下所有的*so*文件都拷贝到rootfs/lib目录中,拷贝命令如下:
- cp *so* /home/zuozhongkai/linux/nfs/rootfs/lib/ -d
复制代码
后面的“-d”表示拷贝符号链接,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于Windows下的快捷方式。会链接到库ld-2.30.so上,输入命令“ls ld-linux-armhf.so.3 -l”查看此文件详细信息,如图18.2.3.1所示:
图18.2.3.1 文件ld-linux-armhf.so.3
从图18.2.3.1可以看出,ld-linux-armhf.so.3后面有个“->”,表示其是个软连接文件,链接到文件ld-2.30.so,因为是一个“快捷方式”,因此大小只有10B。但是,ld-linux-armhf.so.3不能作为符号链接,否则的话在根文件系统中执行程序无法执行!所以我们需要ld-linux-armhf.so.3完成逆袭,由“快捷方式”变为“本尊”,方法很简单,那就是重新复制ld-linux-armhf.so.3,只是不复制软链接即可,先将rootfs/lib中的ld-linux-armhf.so.3文件删除掉,命令如下:
rm ld-linux-armhf.so.3
然后重新进入到/usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib目录中,重新拷贝ld-linux-armhf.so.3,命令如下:
- cp ld-linux-armhf.so.3 /home/zuozhongkai/linux/nfs/rootfs/lib/
复制代码
拷贝完成以后再到rootfs/lib目录下查看ld-linux-armhf.so.3文件详细信息,如图18.2.3.2所示:
图18.2.3.2 文件ld-linux-armhf.so.3
从图18.2.3.2可以看出,此时ld-linux-armhf.so.3已经不是软连接了,而是实实在在的一个库文件,而且文件大小为1279392B。
继续进入如下目录中:
- /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/lib
复制代码
此目录下也有很多的的*so*和.a库文件,我们将其也拷贝到rootfs/lib目录中,命令如下:
- cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/lib/ -d
复制代码
rootfs/lib目录的库文件就这些了,完成以后的rootfs/lib目录如图18.2.3.3所示:
图18.2.3.3 lib目录
2、向rootfs的“usr/lib”目录添加库文件
在rootfs的usr目录下创建一个名为lib的目录,将如下目录中的库文件拷贝到rootfs/usr/lib目录下:
- /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib
复制代码
将此目录下的so和.a库文件都拷贝到rootfs/usr/lib目录中,命令如下:
- cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -d
复制代码
完成以后的rootfs/usr/lib目录如图18.2.3.4所示:
图18.2.3.4 rootfs/usr/lib目录
至此,根文件系统的库文件就全部添加好了,可以使用“du”命令来查看一下rootfs/lib和rootfs/usr/lib这两个目录的大小,命令如下:
- cd rootfs //进入根文件系统目录
- du ./lib ./usr/lib/ -sh //查看lib和usr/lib这两个目录的大小
复制代码
结果如图18.2.3.5所示:
图18.2.3.5 lib和usr/lib目录大小
可以看出lib和usr/lib这两个文件的大小分别为158MB和89MB,加起来就是158+89=247MB,还是挺大的,但是正点原子STM23MP157开发板板载8GB的EMMC,大家不用担心存储不够用的问题。。
18.2.4 创建其他文件夹
在根文件系统中创建其他文件夹,如dev、proc、mnt、sys、tmp、etc和root等,创建完成以后如图18.2.4.1所示:
图18.2.4.1 创建好其他文件夹以后的rootfs
目前来看,这个根文件系统好像已经准备好了,究竟有没有准备好,直接测一下就知道了!
18.3 根文件系统初步测试
18.3.1 修改Ubuntu的nfs版本配置
我们测试跟文件系统的时候不是直接烧写到EMMC里面,这样测试效率太低了,Ubuntu的rootfs目录已经保存了根文件系统,我们只需要在开发板上通过nfs挂载Ubuntu下的rootfs目录即可。也就是说,根文件系统一直在Ubuntu下,开发板通过网络在使用这个根文件系统,这样方便我们开发调试。
但是,Ubuntu18的nfs默认只支持3和4版本的nfs,uboot默认使用的是版本2,所以直接需要修改Ubuntu18的nfs配置,否则nfs根文件系统会报如图18.3.1.1所示错误,导致无法挂载。
图18.3.1.1 nfs挂载错误
解决方法很简单,打开Ubuntu下的/etc/default/nfs-kernel-server文件,然后在最后面添加下面这一行:
- RPCNFSDOPTS="--nfs-version 2,3,4 --debug --syslog"
复制代码
如图18.3.1.2所示:
图18.3.1.2 修改nfs配置
添加完成以后保存退出,输入如下命令重启NFS服务即可:
- sudo /etc/init.d/nfs-kernel-server restart
复制代码
18.3.2 bootargs环境变量设置
接下来我们要设置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>
<server-ip>:服务器IP地址,也就是存放根文件系统主机的IP地址,那就是Ubuntu的IP地址,比如我的Ubuntu主机IP地址为192.168.1.249。
<root-dir>:根文件系统的存放路径,比如我的就是/home/zuozhongkai/linux/nfs/rootfs。
<nfs-options>:NFS的其他可选选项,一般不设置。
<client-ip>:客户端IP地址,也就是我们开发板的IP地址,Linux内核启动以后就会使用此IP地址来配置开发板。此地址一定要和Ubuntu主机在同一个网段内,并且没有被其他的设备使用,在Ubuntu中使用ping命令ping一下就知道要设置的IP地址有没有被使用,如果不能ping通就说明没有被使用,那么就可以设置为开发板的IP地址,比如我就可以设置为192.168.1.250。
<server-ip>:服务器IP地址,前面已经说了。
<gw-ip>:网关地址,我的就是192.168.1.1。
<netmask>:子网掩码,我的就是255.255.255.0。
<hostname>:客户机的名字,一般不设置,此值可以空着。
<device>:设备名,也就是网卡名,一般是eth0,eth1….,正点原子STM32MP157开发板只有一个网口,名字为eth0。
<autoconf>:自动配置,一般不使用,所以设置为off。
<dns0-ip>:DNS0服务器IP地址,不使用。
<dns1-ip>:DNS1服务器IP地址,不使用。
根据上面的格式bootargs环境变量的root值如下:
root=/dev/nfs nfsroot=192.168.1.249:/home/zuozhongkai/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.250:192.168.1.249:192.168.1.1:255.255.255.0::eth0ff
“proto=tcp”表示使用TCP协议,“rw”表示nfs挂载的根文件系统为可读可写。启动开发板,进入uboot命令行模式,然后重新设置bootargs环境变量,命令如下:
- setenv bootargs 'console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.249:/home/zuozhongkai/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.250:192.168.1.249:192.168.1.1:255.255.255.0::eth0ff' //设置bootargs
- saveenv //保存环境变量
复制代码
设置好以后使用“boot”命令启动Linux内核,结果如图18.3.2.1所示:
图18.3.2.1 进入根文件系统
大家要注意,由于是通过网络挂载根文件系统,因此Linux系统在启动的时候需要初始化网络,因此会卡一下,大概在初始化完EMMC以后会卡几秒钟,这是正常现象。
我们可以输入“ls”命令测试一下,结果如图18.3.2.2所示:
图18.3.2.2 ls命令测试
可以看出ls命令工作正常!那么是不是说明我们的rootfs就制作成功了呢?大家注意,在进入根文件系统的时候会有下面这一行错误提示:
- can't run '/etc/init.d/rcS': No such file or directory
复制代码
提示很简单,说是无法运行“/etc/init.d/rcS”这个文件,因为这个文件不存在。如图18.3.3所示
图39.3.3 “/etc/init.d/rcS”不存在
看来我们的rootfs还是缺文件啊,没什么说的,一步一步的完善吧。
18.3.3 无法进入根文件系统
如果大家使用nfs挂载根文件系统的时候发现并没有进入根文件系统,那么可能的原因有一下几点。
1、网络硬件原因
可能是开发板网络硬件有问题,首先检查一下网线是否接好,网络硬件是否有损坏。最简单的检测方法就是烧写正点原子出厂系统,然后在出厂系统里面测试网络是否能正常工作,如果可以正常工作的话就说明不是硬件问题。
2、bootargs环境变量设置错误
bootargs环境变量设置错误是最常见的,典型的就是单词写错!少个字母或者多个字母,直接进入uboot里面,输入:
print bootargs
输入上述命令以后就会打印出bootargs环境变量的值,如图18.3.3.1所示:
图18.3.3.1 bootargs环境变量值
3、bootargs环境变量没有传递给内核
这个问题不常见,但是是最容易忽略的!bootargs环境变量的值会传递给内核作为命令行(command line)参数,Linux内核启动的时候会打印出命令行参数,如图18.3.3.2所示:
图18.3.3.2 命令行参数
从图18.3.3.2可以看出,有“Kernel command line:”这一行,这一行就是命令行参数,可以看到命令行参数和bootargs环境变量一模一样,如果不一致的话就说明bootargs没有传递给内核。
没有传递进来的原因无非两点:
①、bootargs环境变量设置错误,比如设置的时候将bootargs打错,写成了“botargs”、“bottargs”等等。导致无法正确识别为“bootargs”。
②、bootargs环境变量被覆写,也就是说bootargs环境变量设置正确,但是就是传递不进来,或者传递给命令行参数是错误的,这是因为uboot在启动linux内核的时候将bootargs覆写了。比如ST官方uboot里面的bootcmd是一个脚本文件,此脚本文件比较复杂,会经过一系列的计算得到具体的bootcmd命令以及bootargs参数,在这个过程中bootargs环境变量就会被覆写掉,解决方法就是在uboot命令行先输入“eraseenv”命令来擦除默认环境变量内容,然后再手动设置bootcmd和bootargs这两个环境变量的值,其他的网络IP地址、MAC地址能也需要重新设置。
18.4 完善根文件系统
18.4.1 创建/etc/init.d/rcS文件
rcS是个shell脚本,Linux内核启动以后需要启动一些服务,而rcS就是规定启动哪些文件的脚本文件。在rootfs中创建/etc/init.d/rcS文件,然后在rcS中输入如下所示内容:
示例代码18.4.1.1 /etc/init.d/rcS文件
- 1 #!/bin/sh
- 2
- 3 PATH=/sbin:/bin:/usr/sbin:/usr/bin<img src="static/image/smiley/default/shy.gif" border="0" smilieid="8" alt=":[ DISCUZ_CODE_27 ]quot;>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文档。
示例代码18.4.1.1中的rcS文件内容是最精简的,大家如果去看Ubuntu或者其他大型Linux操作系统中的rcS文件,就会发现其非常复杂。因为我们是初次学习,所以不用搞这么复杂的,而且这么复杂的rcS文件也是借助其他工具创建的,比如buildroot等。
创建好文件/etc/init.d/rcS以后一定要给其可执行权限!
使用如下命令给予/ec/init.d/rcS可执行权限:
设置好以后就重新启动Linux内核,启动以后如图18.4.1.1所示:
图18.4.1.1 Linux启动过程
从图18.4.1.1可以看到,提示找不到/etc/fstab文件,还有一些其他的错误,我们先把/etc/fstab这个错误解决了。说不定把这个问题解决以后其他的错误也就解决了。前面我们说了“mount -a”挂载所有根文件系统的时候需要读取/etc/fstab,因为/etc/fstab里面定义了该挂载哪些文件,好了,接下来就是创建/etc/fstab文件。
18.4.2 创建/etc/fstab文件
在rootfs中创建/etc/fstab文件,fstab在Linux开机以后自动配置哪些需要自动挂载的分区,格式如下:
<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文件中输入如下内容:
示例代码18.4.2.1 /etc/fstab文件
- 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
复制代码
fstab文件创建完成以后重新启动Linux,结果如图18.4.2.1所示:
图18.4.2.1 Linux启动过程
从图18.4.2.1可以看出,在运行/etc/init.d/rcS脚本第11行出现问题,提示不能创建“/proc/sys/kernel/hotplug”,这个是Linux内核配置问题,最后在讲解怎么处理。我们接下来还需要创建一个文件/etc/inittab。
18.4.3 创建/etc/inittab文件
inittab的详细内容可以参考busybox下的文件examples/inittab。init程序会读取/etc/inittab这个文件,inittab由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的4个段组成,格式如下:
- <id>:<runlevels>:<action>:<process>
复制代码
<id>:每个指令的标识符,不能重复。但是对于busybox的init来说,<id>有着特殊意义。对于busybox而言<id>用来指定启动进程的控制tty,一般我们将串口或者LCD屏幕设置为控制tty。
<runlevels>:对busybox来说此项完全没用,所以空着。
<action>:动作,用于指定<process>可能用到的动作。busybox支持的动作如表18.4.3.1所示:
动作 描述
sysinit 在系统初始化的时候process才会执行一次。
respawn 当process终止以后马上启动一个新的。
askfirst 和respawn类似,在运行process之前在控制台上显示“Please press Enter to activate this console.”。只要用户按下“Enter”键以后才会执行process。
wait 告诉init,要等待相应的进程执行完以后才能继续执行。
once 仅执行一次,而且不会等待process执行完成。
restart 当init重启的时候才会执行procee。
ctrlaltdel 当按下ctrl+alt+del组合键才会执行process。
shutdown 关机的时候执行process。
表18.4.3.1 动作
<process>:具体的动作,比如程序、脚本或命令等。
参考busybox的examples/inittab文件,我们也创建一个/etc/inittab,在里面输入如下内容:
示例代码18.4.3.1 /etc/inittab文件
- 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作为控制台终端,也就是ttySTM0。
第4行,重启的话运行/sbin/init。
第5行,按下ctrl+alt+del组合键的话就运行/sbin/reboot,看来ctrl+alt+del组合键用于重启系统。
第6行,关机的时候执行/bin/umount,也就是卸载各个文件系统。
第7行,关机的时候执行/sbin/swapoff,也就是关闭交换分区。
/etc/inittab文件创建好以后就可以重启开发板即可,至此!根文件系统要创建的文件就已经全部完成了。
18.4.4 使能内核uevet helper
根文件系统要创建的文件已经全部完成了,但还是会存在如图18.4.4.1所示问题:
图18.4.4.1 hotplug错误
前面已经说了,这是Linux内核配置问题,只要配置一下内核就行了,路径如下:
- Location:
- -> Device Drivers
- -> Generic Driver Options
- ->Support for uevent helper //选中
复制代码
配置如图18.4.4.2所示:
图18.4.4.2 内核配置
选中图18.4.4.2中的“Support for uevent helper”选项,选中以后就会在.config中存在:
配置好以后重新编译内核,然后使用新的内核启动,如图18.4.4.3所示:
图18.4.4.3 根文件系统运行结果
从图18.4.4.3可以看出,没有任何错误提示,说明根文件系统工作已经正常了。接下来就要对根文件系统进行其他的测试,比如我们自己编写的软件运行是否正常、是否支持软件开机自启动、中文支持是否正常以及能不能链接等。
18.5 根文件系统其他功能测试
18.5.1 软件运行测试
我们使用Linux系统的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,以后我们学习Linux驱动的时候就把所有的实验文件放到这个文件夹里面。
在Ubuntu下使用vim编辑器新建一个hello.c文件,在hello.c里面输入如下内容:
示例代码18.5.1.1 hello.c文件
- 1 #include <stdio.h>
- 2
- 3 int main(void)
- 4 {
- 5 while(1) {
- 6 printf("hello world!\r\n");
- 7 sleep(2);
- 8 }
- 9 return 0;
- 10 }
复制代码
hello.c内容很简单,就是循环输出“hello world”,sleep相当于Linux的延时函数,单位为秒,所以sleep(2)就是延时2秒。编写好以后就是编译,因为我们是要在ARM芯片上运行的,所以要用交叉编译器去编译,也就是使用arm-linux-gnueabihf-gcc编译,命令如下:
- arm-none-linux-gnueabihf-gcc hello.c -o hello
复制代码
使用arm-none-linux-gnueabihf-gcc将hello.c编译为hello可执行文件。这个hello可执行文件究竟是不是ARM使用的呢?使用“file”命令查看文件类型以及编码格式:
- file hello //查看hello的文件类型以及编码格式
复制代码
结果如图18.5.1.1所示:
图18.5.1.1 查看hello编码格式
从图18.5.1.1可以看出,输入“file hello”输入了如下所示信息:
- hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked……
复制代码
hello是个32位的LSB可执行文件,ARM架构的,并且是动态链接的。所以我们编译出来的hello文件没有问题。将其拷贝到开发板的rootfs/drivers目录下,在开发板中输入如下命令来执行这个可执行文件:
- cd /drivers //进入drivers目录
- ./hello //执行hello
复制代码
结果如图18.5.1.2所示:
图18.5.1.2 hello运行结果
可以看出,hello这个软件运行正常,说明我们的根文件系统中的共享库是没问题的,要想终止hello的运行,按下“ctrl+c”组合键即可。此时大家应该能感觉到,hello执行的时候终端是没法用的,除非使用“ctrl+c”来关闭hello,那么有没有办法既能让hello正常运行,而且终端能够正常使用?那肯定是有的,让hello进入后台运行就行了,让一个软件进入后台的方法很简单,运行软件的时候加上“&”即可,比如“./hello &”就是让hello在后台运行。在后台运行的软件可以使用“kill -9 pid(进程ID)”命令来关闭掉,首先使用“ps”命令查看要关闭的软件PID是多少,ps命令用于查看所有当前正在运行的进程,并且会给出进程的PID。输入“ps”命令,结果如图18.5.1.3所示:
图18.5.1.3 ps命令结果
从图18.5.1.3可以看出hello对应的PID为137,因此我们使用如下命令关闭在后台运行的hello软件:
因为hello在不断的输出“hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而hello是输出。在数据流上是没有打断的,只是显示在SecureCRT上就好像被打断了,所以只管输入“kill -9 137”即可。hello被kill以后会有提示,如图18.5.1.4所示:
图18.5.1.4 提示hello被kill掉。
再去用ps命令查看一下当前的进程,发现没有hello了。这个就是Linux下的软件后台运行以及如何关闭软件的方法,重点就是3个操作:软件后面加“&”、使用ps查看要关闭的软件PID、使用“kill -9 pid”来关闭指定的软件。
18.5.2 中文字符测试
在ubuntu中向在rootfs目录新建一个名为“中文测试”的文件夹,然后在MobaXterm下查看中文名能不能显示正确。输入“ls”命令,结果如图18.5.2.1所示:
图18.5.2.1 中文文件夹测试
可以看出“中文测试”这个文件夹显示正常,接着“touch”命令在“中文测试”文件夹中新建一个名为“测试文档.txt”的文件,并且使用vim编辑器在其中输入“这是一个中文测试文件”,借此来测试一下中文文件名和中文内容显示是否正常。在SecureCRT中使用“cat”命令来查看“测试文档.txt”中的内容,结果如图18.5.2.2所示:
图18.5.2.2 中文文档内容显示
从图18.5.2.2可以看出,“测试文档.txt”的中文内容显示正确,而且中文路径也完全正常,说明我们的根文件系统已经完美支持中文了!
18.5.3 开机自启动测试
在18.5.1小节测试hello软件的时候都是等Linux启动进入根文件系统以后手动输入命令“./hello”来完成的。我们一般做好产品以后都是需要开机自动启动相应的软件,本节我们就以hello这个软件为例,讲解一下如何实现开机自启动。前面我们说过了,进入根文件系统的时候会运行/etc/init.d/rcS这个shell脚本,因此我们可以在这个脚本里面添加自启动相关内容。添加完成以后的/etc/init.d/rcS文件内容如下:
示例代码18.5.3.1 rcS文件代码
- 1 #!/bin/sh
- 2 PATH=/sbin:/bin:/usr/sbin:/usr/bin
- 3 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
- 4 runlevel=S
- 5 umask 022
- 6 export PATH LD_LIBRARY_PATH runlevel
- 7
- 8 mount -a
- 9 mkdir /dev/pts
- 10 mount -t devpts devpts /dev/pts
- 11
- 12 echo /sbin/mdev > /proc/sys/kernel/hotplug
- 13 mdev -s
- 14
- 15 #开机自启动
- 16 cd /drivers
- 17 ./hello &
- 18 cd /
复制代码
第16行,进入drivers目录,因为要启动的软件存放在drivers目录下。
第17行,以后台方式执行hello这个软件。
第18行,退出drivers目录,进入到根目录下。
自启动代码添加完成以后就可以重启开发板,看看hello这个软件会不会自动运行。结果如图18.5.3.1所示:
图18.5.3.1 hello开机自启动
从图18.5.3.1可以看出,hello开机自动运行了,说明开机自启动成功。
18.5.4 外网连接测试
这里说的外网不是外国哪些404网站的连接测试,而是百度、淘宝等这些网站的测试。也就是说看看我们的开发板能不能上网,能不能和我们的局域网外的这些网站进行通信。测试方法很简单,就是通过ping命令来ping一下百度的官网:www.baidu.com。输入如下命令:
ping www.baidu.com
结果如图18.5.4.1所示:
图18.5.4.1 ping测试结果
可以看出,测试失败,提示www.baidu.com是个“bad address”,也就是地址不对,显然我们的地址是正确的。之所以出现这个错误提示是因为www.baidu.com的地址解析失败了,并没有解析出其对应的IP地址。我们需要配置域名解析服务器的IP地址,一般域名解析地址可以设置为所处网络的网关地址,比如192.168.1.1。也可以设置为114.114.1144.114,这个是运营商的域名解析服务器地址。
在rootfs中新建文件/etc/resolv.conf,然后在里面输入如下内容:
示例代码18.5.4.1 resolv.conf文件内容
- 1 nameserver 114.114.114.114
- 2 nameserver 192.168.1.1
复制代码
设置很简单,nameserver表示这是个域名服务器,设置了两个域名服务器地址:114.114.114.114和192.168.1.1,大家也可以改为其他的域名服务器试试。如果使用“udhcpc”命令自动获取IP地址,“udhcpc”命令会修改nameserver的值,一般是将其设置为对应的网关地址。修改好以后保存退出,重启开发板!重启以后重新ping一下百度官网,结果如图18.5.4.2所示:
图18.5.4.2 ping百度官网结果
可以看出ping百度官网成功了!域名也成功的解析了,至此!我们的根文件系统就彻底的制作完成,这个根文件系统最好打包保存一下,防止以后做实验不小心破坏了根文件系统而功亏一篑,又得从头制作根文件系统。uboot、Linux kernel、rootfs这三个共同构成了一个完整的Linux系统,现在的系统至少是一个可以正常运行的系统,后面我们就可以在这个系统上完成Linux驱动开发的学习。
18.6 烧写根文件系统到EMMC中
一个最小的根文件系统已经制作好了,而且通过nfs测试正常,接下来就学习一下如何将其烧写到开发板EMMC中。根文件系统烧写进去以后,一个完成的嵌入式Linux开发环境就准备好了,我们以后实际产品开发也是用的这套流程。
18.6.1 根文件系统打包
根文件系统打包和17.4.1小节中对uImage和stm32mp157d-atk.dtb打包方法一样,也是制作ext4格式的根文件系统包,这里我们需要对/home/zuozhongkai/linux/nfs/rootfs目录进行打包,这里我们再讲解一遍。
1、新建ext4格式磁盘
首先新建一个ext4格式的磁盘,然后挂载这个ext4格式的磁盘,将/home/zuozhongkai/linux/nfs/rootfs这个目录下的文件拷贝到这个ext4磁盘即可。
首选创建一个名为rootfs文件夹,注意不要和/home/zuozhongkai/linux/nfs/rootfs这个目录重复了!比如我就在/home/zuozhongkai/linux/目录下创建一个名为“rootfs”目录,然后输入如下命令创建ext4磁盘:
示例代码18.6.1.1 ext4磁盘创建命令
- 1 cd rootfs
- 2 dd if=/dev/zero of=rootfs.ext4 bs=1M count=1024
- 3 mkfs.ext4 -L rootfs rootfs.ext4
复制代码
第1行,进入rootfs目录。
第2行,使用dd命令创建一个名为rootfs.ext4的磁盘,由于根文件系统比较大,因此count设置为1024,也就是根文件系统总空间为1GB,这个是可以调整的,大家要根据自己的实际根文件系统大小调整count的值,只要空间不超过开发板所使用的8GB EMMC即可。最好不要设置太大,否则烧写会很慢!
第3行,使用mkfs.ext4将rootfs.ext4磁盘格式化为ext4格式。
完成以后就会生成名为“rootfs.ext4”的磁盘,如图18.6.1.2所示:
图18.6.1.2 rootfs.ext4磁盘
2、将系统镜像拷贝到ext4磁盘中
首先创建一个目录用来挂载前面制作制作出来的rootfs.ext4,比如我这里创建目录/mnt/rootfs,命令如下:
接下来使用mount命令将rootfs.ext4挂载到/mnt/rootfs目录下,命令如下:
- cd /home/zuozhongkai/linux/atk-mp1/linux/rootfs
- sudo mount rootfs.ext4 /mnt/rootfs/
复制代码
挂载成功以后就将/home/zuozhongkai/linux/nfs/rootfs目录下的所有根文件系统文件拷贝到/mnt/bootfs目录下,命令如下:
- cd /home/zuozhongkai/linux/nfs/rootfs/
- sudo cp * /mnt/rootfs/ -drf
复制代码
拷贝完成以后使用umount卸载/mnt/rootfs即可,命令如下:
至此,根文件系统就已经打包到了图18.6.1.2中的rootfs.ext4中,稍后使用STM32CubeProgrammer软件将其烧写到EMMC里面。烧写之前最好在Windows下打开rootfs.ext4看一下,看看是否已经将根文件系统打包进去,如图18.6.1.3所示:
图18.6.1.3 bootfs.ext4
18.6.2 烧写到EMMC
接下来就是将上一小节打包好的ext4格式的根文件系统rootfs.ext4烧写到开发板的EMMC里面,使用STM32CubeProgrammer软件完成此操作。将rootfs.ext4拷贝到以前创建的images目录下,如图18.6.2.1所示:
图18.6.2.1 images目录
修改flashlayout文件tf-a.tsv,在后面加入rootfs.ext4的烧写脚本,如图18.6.2.2所示:
图18.6.2.2 修改后的flashlayout
图18.6.2.2中第8行就是根文件系统rootfs.ext4的烧写脚本,设置好以后就可以使用STM32CubeProgrammer烧写系统了,烧写完成以后设置拨码开关从EMMC启动。启动以后进入uboot的命令行,设置bootcmd和bootargs这两个环境变量,命令如下:
- setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
- setenv bootargs 'console=ttySTM0,115200 root=/dev/mmcblk1p3 rootwait rw'
- saveenv
- boot
复制代码
注意bootargs环境变量里面root的值为/dev/mmcblk1p3,说明根文件系统存放在mmcblk1的第3个分区。细心的朋友可能会有疑问,正点原子出厂系统的根文件系统是存放在mmcblk2的第3个分区,而这里设置的是mmcblk1,为什么会不同呢?这是因为正点原子出厂系统使能了SDIO WIFI,而SDIO WIFI也是SDIO接口,如果使能了SDIO WIFI那么SDIO WIFI所使用的那个SDIO接口就变成了mmcblk1,而EMMC所使用的SDIO接口就会变为mmcblk2。
设置以后启动系统,此时就会从EMMC里面加载Linux系统镜像,并且正确读取到根系统,如图18.6.2.3所示:
图18.6.2.3 EMMC启动系统
从图18.6.2.3可以看出,正常进入根文件系统。至此,Linux系统移植就全部完成,包括tf-a、uboot、Linux kernel和rootfs。 |