今天给大家介绍一下rootfs根文件系统制作和挂载方式,希望这篇文章对大家有所帮助。
本章主要是对rootfs根文件系统制作和挂载方式进行详细讲解。
Linux“三巨头”已经完成了2个了,就剩最后一个 rootfs(根文件系统)了,本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是 Linux 移植的最后一步,根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系统。
以后我们就在这个最小系统上编写、测试 Linux 驱动,移植一些第三方组件,逐步的完善这个最小系统。最终得到一个功能完善、驱动齐全、相对完善的操作系统。
1. 根文件系统简介
根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是 FATFS、 FAT、 EXT4、 YAFFS 和 NTFS 等这样的文件系统。在这里,根文件系统并不是 FATFS 这样的文件系统代码, EXT4 这样的文件系统代码属于 Linux 内核的一部分。Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。
根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。对于根文件系统专业的解释,百度百科上是这么说的。
根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
百度百科上说内核代码镜像文件保存在根文件系统中,但是我们嵌入式 Linux 并没有将内核代码镜像保存在根文件系统中,而是保存到了其他地方。比如 NAND Flash 的指定存储地址、EMMC 专用分区中。根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如 rcS, inittab 等。根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统, Linux 内核在启动的时候就会提示内核崩溃(Kernel panic)的提示。
根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的 ls、 mv、 ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中,这些小软件是怎么来的呢?这个就是我们本章教程的目的,教大家来构建自己的根文件系统,这个根文件系统是满足 Linux 运行的最小根文件系统,后续我们可以根据自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对完善的根文件系统。
根文件系统的选择和配置对于操作系统的性能、功能和可靠性都具有重要影响。不同的应用场景和需求可能需要定制化的根文件系统,以满足特定的要求。因此,根文件系统的设计和管理是操作系统开发和系统管理的重要方面之一。
2. 根文件系统构建软件
构建根文件系统常用的软件有三种,分别是busybox,buildroot,yocto。busybox 仅仅只是帮我们构建好了一些常用的命令和文件,像 lib 库、/etc 目录下的一些文件都需要我们自己手动创建,而且 busybox 构建的根文件系统默认没有用户名和密码设置。在后续的实验中,我们还要自己去移植一些第三方软件和库,比如 alsa、iperf、mplayer 等等。那么有没有一种傻瓜式的方法或软件,它不仅包含了 busybox 的功能,而且里面还集成了各种软件,需要什么软件就选择什么软件,不需要我们去移植。
答案肯定是有的,buildroot 就是这样一种工具,buildroot比 busybox 更上一层楼,buildroot 不仅集成了 busybox,而且还集成了各种常见的第三方库和软件,需要什么就选择什么,就跟我们去吃自助餐一样,想吃什么就拿什么。buildroot 极大的方便了我们嵌入式 Linux 开发人员构建实用的根文件系统。
从 busybox 开始一步一步的构建根文件系统适合学习、了解根文件系统的组成,但是不适合做产品(主要是自己构建的话会有很多不完善、没有注意到的细节)。buildroot 会帮我们处理好各种细节,根文件系统也会更加的合理、有效。因此在做产品的时候推荐大家使用 buildroot 来构建自己的根文件系统,当然了,类似 buildroot 的软件还有很多,比如后面要讲的 yocto。
buildroot 和 uboot、Linux Kernel 很类似,我们需要到其官网上下载源码,然后对其进行配置,比如设置交叉编译器、设置目标 CPU 参数等,最主要的就是选择所需要的第三方库或软件。一切配置好以后就可以进行编译,编译完成了以后就会在一个文件夹里面存放好编译结果,也就是根文件系统。
3. busybox构建根文件系统
3.1 BusyBox 简介
其名字分为“Busy”和“Box”,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、 mv、 ifconfig 等命令 BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。
BusyBox 官网下载地址为:
https://busybox.net/
登录官网我们可以看到,界面非常的简洁
在官网左侧的“Get BusyBox”栏有一行“Download Source”,点击“Download Source”即可打开 BusyBox 的下载页,如下所示:
从上面可以看出,目前最新的 BusyBox 版本是 1.36.1,不过本次不考虑使用最新的版本进行构建,选择一个比较新的就行,本次选择的是 busybox-1.32.0.tar.bz2。BusyBox 下载好以后我们就开始构建根文件系统了。
3.2 编译 BusyBox 构建根文件系统
busybox的编译构建过程:
- 使用make menuconfig构建出图形配置界面。
- 通过配置图形配置界面的选项配置busybox的安装路径、编译工具、命令功能使能等。生成.config配置文件
- 使用make编译busybox
- 使用make install命令安装由busybox生成的根文件系统
- 完善根文件系统
- 使用和测试根文件系统
1.创建文件夹保存根文件系统
mkdir rootfs
2.解压BusyBox压缩包到rootfs目录
tar -jxvf busybox-1.32.0.tar.bz2
3.修改 Makefile,添加编译器
# Cross compiling and selecting different set of gcc/bin-utils # --------------------------------------------------------------------------- # # When performing cross compilation for other architectures ARCH shall be set # to the target architecture. (See arch/* for the possibilities). # ARCH can be set during invocation of make: # make ARCH=ia64 # Another way is to have ARCH set in the environment. # The default ARCH is the host where make is executed. # CROSS_COMPILE specify the prefix used for all executables used # during compilation. Only gcc and related bin-utils executables # are prefixed with $(CROSS_COMPILE). # CROSS_COMPILE can be set on the command line # make CROSS_COMPILE=ia64-linux- # Alternatively CROSS_COMPILE can be set in the environment. # Default value for CROSS_COMPILE is not to prefix executables # Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile CROSS_COMPILE ?= arm-linux-gnueabihf- # bbox: we may have CONFIG_CROSS_COMPILER_PREFIX in .config, # and it has not been included yet... thus using an awkward syntax. ifeq ($(CROSS_COMPILE),) CROSS_COMPILE := $(shell grep ^CONFIG_CROSS_COMPILER_PREFIX .config 2>/dev/null) CROSS_COMPILE := $(subst CONFIG_CROSS_COMPILER_PREFIX=,,$(CROSS_COMPILE)) CROSS_COMPILE := $(subst ",,$(CROSS_COMPILE)) #") endif # SUBARCH tells the usermode build what the underlying arch is. That is set # first, and if a usermode build is happening, the "ARCH=um" on the command # line overrides the setting of ARCH below. If a native build is happening, # then ARCH is assigned, getting whatever value it gets normally, and # SUBARCH is subsequently ignored. ifneq ($(CROSS_COMPILE),) SUBARCH := $(shell echo $(CROSS_COMPILE) | cut -d- -f1 | sed 's:^.*/::g') else SUBARCH := $(shell uname -m) endif SUBARCH := $(shell echo $(SUBARCH) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ -e s/sa110/arm/ \ -e s/s390x/s390/ -e s/parisc64/parisc/ \ -e s/ppc.*/powerpc/ -e s/mips.*/mips/ ) ARCH ?= arm
修改“CROSS_COMPILE ?=”和“ARCH ?= arm”这两行内容,修改如上代码。
3.3 busybox中文字符支持
使用 busybox 默认配置直接编译的话,使用 SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。这是因为 busybox 中的 shell 命令对中文输入即显示做了限制,所以我们需要对 busybox 源码进行修改,取消 busybox 对中文显示的限制,下面我们来一步步修改busybox使其支持中文。
3.3.1 修改printable_string2函数
打开文件 busybox-1.32.0/libbb/printable_string.c,找到函数 printable_string2,函数具体内容如下:
const char* FAST_FUNC printable_string2(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); }
从上面代码20和21行以及34和35行可以看出:大于0x7F的字符直接被break掉,或者直接被“?”代替了。所以就算是linux内核设置了支持中文,也是无法显示出来的,被“?”代替了。修改后的代码如下:
const char* FAST_FUNC printable_string2(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) */ if( c < ' ') *d = '?'; d++; } if (stats) { stats->byte_count = (d - dst); stats->unicode_count = (d - dst); stats->unicode_width = (d - dst); } } #endif return auto_string(dst); }
3.3.2 修改unicode_conv_to_printable2函数
打开文件 busybox-1.29.0/libbb/unicode.c,找到函数 nicode_conv_to_printable2,函数具体内容如下:
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); while ((int)--width >= 0) { unsigned char c = *src; if (c == '\0') { do *d++ = ' '; while ((int)--width >= 0); break; } *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; src++; } *d = '\0'; } else { d = dst = xstrndup(src, width); while (*d) { unsigned char c = *d; if (c < ' ' || c >= 0x7f) *d = '?'; d++; } } if (stats) { stats->byte_count = (d - dst); stats->unicode_count = (d - dst); stats->unicode_width = (d - dst); } return dst; } ...
从代码中可以看出,第 20行,当字符小于空格或者字符大于 0X7F, *d++就为‘?’。第 28 和 29 行,也是一样,当字符小于空格或者字符大于 0X7F, *d 也为‘?’。修改后的代码如下:
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); while ((int)--width >= 0) { unsigned char c = *src; if (c == '\0') { do *d++ = ' '; while ((int)--width >= 0); break; } /* 修改下面一行代码 */ /* *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++; } } if (stats) { stats->byte_count = (d - dst); stats->unicode_count = (d - dst); stats->unicode_width = (d - dst); } return dst; } ...
busybox 中文字符支持跟代码修改有关的就改好了,但是最后还需要配置 busybox来使能 unicode 码,这样才能显示中文字符。
3.4 配置 busybox
编译 busybox的步骤跟编译 uboot和Linux kernel是一样的,首先都是进行默认配置,然后再进行编译。选择默认配置来配置 busybox 就行了:
make defconfig
busybox 也支持图形化配置,进入图形配置界面进行如下配置:
配置路径如下:
Location: -> Settings -> Build static binary (no shared libs)
选项“Build static binary (no shared libs)”用来决定是静态编译 busybox 还是动态编译,静态编译的话就不需要库文件,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的 busybox 会小很多。这里我们不能采用静态编译!因为采用静态编译的话 DNS 会出问题!无法进行域名解析,配置如下所示:
继续配置如下路径配置项:
Location: -> Settings -> vi-style line editing commands
如下图所示:
继续配置如下路径配置项:
Location: -> Linux Module Utilities -> Simplified modutils
默认会选中“Simplified modutils”,这里我们要取消勾选!!如下图所示:
继续配置如下路径配置项:
Location: -> Linux System Utilities -> mdev (17 kb) //确保下面的全部选中,默认都是选中的
如下图所示:
最后就是使能 busybox 的 unicode 编码以支持中文,配置路径如下:
Location: -> Settings -> Support Unicode //选中 -> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中
如下图所示:
至此,busybox 的默认配置就完成了,大家也可以根据自己的实际需求选择配置其他的选项,不过对于初学者笔者不建议再做其他的修改,可能会出现编译出错的情况发生。接下来就是对busybox进行编译。
3.5 编译 busybox
首先是指定编译结果的存放目录,当前是将编译结果存放到前面创建的 rootfs 目录中,输入如下命令:
make make install CONFIG_PREFIX=/home/toto/workspace/rootfs/rootfs
COFIG_PREFIX 指 定 编 译 结 果 的 存 放 目 录 , 比 如 我 存 放 到“/home/toto/workspace/rootfs/rootfs”目录中,等待编译完成。编译完成以后下图所示:
/home/toto/workspace/rootfs/rootfs//usr/sbin/setlogcons -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/svlogd -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/telnetd -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/tftpd -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubiattach -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubidetach -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubimkvol -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubirename -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubirmvol -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubirsvol -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/ubiupdatevol -> ../../bin/busybox /home/toto/workspace/rootfs/rootfs//usr/sbin/udhcpd -> ../../bin/busybox -------------------------------------------------- You will probably need to make your busybox binary setuid root to ensure all configured applets will work properly. --------------------------------------------------
编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中, rootfs 目录内容如下所示:
toto@toto:~/workspace/rootfs/rootfs$ ls bin linuxrc sbin usr
从上面可以看出, rootfs 目录下有 bin、 sbin 和 usr 这三个目录,以及 linuxrc 这个文件。Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。
busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,我们继续来完善 rootfs。
4. 根文件系统添加lib库
4.1 rootfs文件系统的“/lib”目录添加库文件
Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要先根文件系统中添加动态库。
在 rootfs 中创建一个名为“lib”的文件夹,命令如下:
mkdir lib
lib 文件夹创建好了,库文件从哪里来呢?lib 库文件从交叉编译器中获取, 前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。
交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们肯定并不清楚也先不管,把所有的库文件都放到我们的根文件系统中。这样做出来的根文件系统肯定很大,但处于学习阶段,就只管怎么讲rootfs移植成功就行。
进入如下路径对应的目录:
/home/toto/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/ toto@toto:~/workspace/rootfs/rootfs$ ls /home/toto/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/ debug libcidn.so.1 libgomp.so libnss_dns.so.2 libssp_nonshared.a ld-2.25.so libcilkrts.a libgomp.so.1 libnss_files-2.25.so libssp.so ld-linux-armhf.so.3 libcilkrts.so libgomp.so.1.0.0 libnss_files.so.2 libssp.so.0 ldscripts libcilkrts.so.5 libgomp.spec libnss_hesiod-2.25.so libssp.so.0.0.0 libanl-2.25.so libcilkrts.so.5.0.0 libitm.a libnss_hesiod.so.2 libstdc++.a libanl.so.1 libcilkrts.spec libitm.so libnss_nis-2.25.so libstdc++fs.a libasan.a libcrypt-2.25.so libitm.so.1 libnss_nisplus-2.25.so libstdc++.so libasan_preinit.o libcrypt.so.1 libitm.so.1.0.0 libnss_nisplus.so.2 libstdc++.so.6 libasan.so libc.so.6 libitm.spec libnss_nis.so.2 libstdc++.so.6.0.24 libasan.so.4 libdl-2.25.so libm-2.25.so libpcprofile.so libstdc++.so.6.0.24-gdb.py libasan.so.4.0.0 libdl.so.2 libmemusage.so libpthread-2.25.so libsupc++.a libatomic.a libgcc_s.so libm.so.6 libpthread.so.0 libthread_db-1.0.so libatomic.so libgcc_s.so.1 libnsl-2.25.so libresolv-2.25.so libthread_db.so.1 libatomic.so.1 libgfortran.a libnsl.so.1 libresolv.so.2 libubsan.a libatomic.so.1.2.0 libgfortran.so libnss_compat-2.25.so librt-2.25.so libubsan.so libBrokenLocale-2.25.so libgfortran.so.4 libnss_compat.so.2 librt.so.1 libubsan.so.0 libBrokenLocale.so.1 libgfortran.so.4.0.0 libnss_db-2.25.so libsanitizer.spec libubsan.so.0.0.0 libc-2.25.so libgfortran.spec libnss_db.so.2 libSegFault.so libutil-2.25.so libcidn-2.25.so libgomp.a libnss_dns-2.25.so libssp.a libutil.so.1
从上面可以看到,有很多的.so和.a的文件,这些就是库文件,将此目录下所有的.so和.a文件都拷贝到 rootfs/lib 目录中, 拷贝命令如下:
cp *so* *.a /home/toto/workspace/rootfs/rootfs/lib/ -d
-d:表示拷贝符号链接,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于 Windows 下的快捷方式。会链接到库 ld-2.25.so 上,输入命令“ls ld-linux-armhf.so.3 -l”查看此文件详细信息,如下所示:
toto@toto:~/workspace/rootfs/rootfs/lib$ ls ld-linux-armhf.so.3 -l lrwxrwxrwx 1 toto toto 10 6月 12 16:02 ld-linux-armhf.so.3 -> ld-2.25.so
可以看出, ld-linux-armhf.so.3 后面有个“->”,表示其是个软连接文件,链接到文件 ld-2.25.so,因为其是一个“快捷方式”,因此大小只有 10B。但是, ld-linuxarmhf.so.3 不能作为符号链接,否则的话在根文件系统中执行程序无法执行!所以我们需要 ldlinux-armhf.so.3 完成逆袭,由“快捷方式”变为“本尊”,方法很简单,那就是重新复制 ld-linuxarmhf.so.3,只是不复制软链接即可,先将 rootfs/lib 中的 ld-linux-armhf.so.3 文件删除掉,命令如下:
rm ld-linux-armhf.so.3
然 后 重 新 进 入 到 /home/toto/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/ 目录中,重新拷贝 ld-linux-armhf.so.3,命令如下:
cp ld-linux-armhf.so.3 /home/toto/workspace/rootfs/rootfs/lib/
拷贝完成以后再到 rootfs/lib 目录下查看 ld-linux-armhf.so.3 文件详细信息,如下所示:
toto@toto:~/workspace/rootfs/rootfs/lib$ ls ld-linux-armhf.so.3 -l -rwxr-xr-x 1 toto toto 1110968 6月 12 16:17 ld-linux-armhf.so.3
可以看出,此时 ld-linux-armhf.so.3 已经不是软连接了,而是实实在在的一个库文件,而且文件大小为 1110968B。继续进入如下目录中:
/home/toto/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/lib toto@toto:~/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/lib$ ls debug libatomic.so.1 libgfortran.a libgomp.spec libssp.so libsupc++.a ldscripts libatomic.so.1.2.0 libgfortran.so libitm.a libssp.so.0 libubsan.a libasan.a libcilkrts.a libgfortran.so.4 libitm.so libssp.so.0.0.0 libubsan.so libasan_preinit.o libcilkrts.so libgfortran.so.4.0.0 libitm.so.1 libstdc++.a libubsan.so.0 libasan.so libcilkrts.so.5 libgfortran.spec libitm.so.1.0.0 libstdc++fs.a libubsan.so.0.0.0 libasan.so.4 libcilkrts.so.5.0.0 libgomp.a libitm.spec libstdc++.so libasan.so.4.0.0 libcilkrts.spec libgomp.so libsanitizer.spec libstdc++.so.6 libatomic.a libgcc_s.so libgomp.so.1 libssp.a libstdc++.so.6.0.24 libatomic.so libgcc_s.so.1 libgomp.so.1.0.0 libssp_nonshared.a libstdc++.so.6.0.24-gdb.py
此目录下也有很多的的.so和.a 库文件,我们将其也拷贝到 rootfs/lib 目录中,命令如下:
cp *so* *.a /home/toto/workspace/rootfs/rootfs/lib/ -d
rootfs/lib 目录的库文件就这些了,完成以后的 rootfs/lib 目录如下所示:
toto@toto:~/workspace/rootfs/rootfs/lib$ ls ld-2.25.so libcidn-2.25.so libgfortran.so.4 libnss_compat-2.25.so libpthread-2.25.so libstdc++.so.6 ld-linux-armhf.so.3 libcidn.so.1 libgfortran.so.4.0.0 libnss_compat.so.2 libpthread.so.0 libstdc++.so.6.0.24 libanl-2.25.so libcilkrts.a libgomp.a libnss_db-2.25.so libresolv-2.25.so libstdc++.so.6.0.24-gdb.py libanl.so.1 libcilkrts.so libgomp.so libnss_db.so.2 libresolv.so.2 libsupc++.a libasan.a libcilkrts.so.5 libgomp.so.1 libnss_dns-2.25.so librt-2.25.so libthread_db-1.0.so libasan.so libcilkrts.so.5.0.0 libgomp.so.1.0.0 libnss_dns.so.2 librt.so.1 libthread_db.so.1 libasan.so.4 libcrypt-2.25.so libitm.a libnss_files-2.25.so libSegFault.so libubsan.a libasan.so.4.0.0 libcrypt.so.1 libitm.so libnss_files.so.2 libssp.a libubsan.so libatomic.a libc.so.6 libitm.so.1 libnss_hesiod-2.25.so libssp_nonshared.a libubsan.so.0 libatomic.so libdl-2.25.so libitm.so.1.0.0 libnss_hesiod.so.2 libssp.so libubsan.so.0.0.0 libatomic.so.1 libdl.so.2 libm-2.25.so libnss_nis-2.25.so libssp.so.0 libutil-2.25.so libatomic.so.1.2.0 libgcc_s.so libmemusage.so libnss_nisplus-2.25.so libssp.so.0.0.0 libutil.so.1 libBrokenLocale-2.25.so libgcc_s.so.1 libm.so.6 libnss_nisplus.so.2 libstdc++.a libBrokenLocale.so.1 libgfortran.a libnsl-2.25.so libnss_nis.so.2 libstdc++fs.a libc-2.25.so libgfortran.so libnsl.so.1 libpcprofile.so libstdc++.so
4.2 rootfs文件系统的“usr/lib”目录添加库文件
在 rootfs 的 usr 目录下创建一个名为 lib 的目录,将如下目录中的库文件拷贝到 rootfs/usr/lib目录下:
/home/toto/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib toto@toto:~/workspace/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib$ ls audit libanl.a libc_nonshared.a libg.a libnsl.so libnss_nisplus.so libresolv.so libutil.so crt1.o libanl.so libcrypt.a libieee.a libnss_compat.so libnss_nis.so librpcsvc.a Mcrt1.o crti.o libBrokenLocale.a libcrypt.so libm.a libnss_db.so libpthread.a librt.a Scrt1.o crtn.o libBrokenLocale.so libc.so libmcheck.a libnss_dns.so libpthread_nonshared.a librt.so gconv libc.a libdl.a libm.so libnss_files.so libpthread.so libthread_db.so gcrt1.o libcidn.so libdl.so libnsl.a libnss_hesiod.so libresolv.a libutil.a
将此目录下的 .so 和.a 库文件都拷贝到 rootfs/usr/lib 目录中,命令如下:
cp *so* *.a /home/toto/workspace/rootfs/rootfs/usr/lib/ -d
完成以后的 rootfs/usr/lib 目录如下所示:
toto@toto:~/workspace/rootfs/rootfs/usr/lib$ ls libanl.a libcidn.so libdl.a libmcheck.a libnss_db.so libnss_nis.so libresolv.so libutil.a libanl.so libc_nonshared.a libdl.so libm.so libnss_dns.so libpthread.a librpcsvc.a libutil.so libBrokenLocale.a libcrypt.a libg.a libnsl.a libnss_files.so libpthread_nonshared.a librt.a libBrokenLocale.so libcrypt.so libieee.a libnsl.so libnss_hesiod.so libpthread.so librt.so libc.a libc.so libm.a libnss_compat.so libnss_nisplus.so libresolv.a libthread_db.so
至此,根文件系统的库文件就全部添加好了,可以使用“du”命令来查看一下 rootfs/lib 和rootfs/usr/lib 这两个目录的大小,命令如下:
toto@toto:~/workspace/rootfs/rootfs$ du ./lib ./usr/lib -sh 114M ./lib 45M ./usr/lib
可以看出 lib和 usr/lib这两个文件的大小分别为 114MB和 45MB,加起来就是 114+45=159MB。非常大!没办法,因为我们是将全部的库文件,不管用得着的还是用不着的,都复制了。后面在真正工作中一般都是将自己需要的库文件才进行复制,这样制作出来的根文件就小很多了。
4.3 创建其他所需的文件夹
在根文件系统中创建其他文件夹,如 dev、 proc、 mnt、 sys、 tmp 和 root 等,创建完成以后如下所示:
toto@toto:~/workspace/rootfs/rootfs$ ls bin dev lib linuxrc mnt proc root sbin sys tmp usr
到这里,根文件系统差不多已经准备好了,但是能不能挂载成功并使用,下面就来上电测一下就知道了!
5. 根文件系统挂载方式
根文件系统挂载有三种方式:
- NFS挂载rootfs
- SD卡挂载rootfs
- eMMC挂载rootfs
由于为了模拟实际工作的情况下,在开始移植linux系统到开发板之前,就将eMMC中的分区信息和全部的系统信息通过u-boot下的mmc指令将其全部擦除了。因此本次只能将系统烧写到sd卡中,然后从sd卡启动系统,并且挂载sd卡中的根文件系统了。
下面将会对三种方式挂载根文件系统的方法进行介绍。
5.1 NFS挂载rootfs
5.1.1 安装NFS服务
使用如下命令安装 NFS 服务:
sudo apt-get install nfs-kernel-server rpcbind
5.1.2 配置网络访问文件目录
等待安装完成,创建的 nfs 文件夹供 nfs 服务器使用,以后我们可以在开发板上通过网络文件系统来访问 nfs 文件夹,要先配置 nfs,使用如下命令打开 nfs 配置文件/etc/exports:
sudo vi /etc/exports
打开/etc/exports 以后在后面添加如下所示内容:
/home/toto/workspace/nfs *(rw,sync,no_root_squash) /home/toto/workspace/rootfs/rootfs *(rw,sync,no_root_squash)
添加完成以后的/etc/exports 如下所示:
# /etc/exports: the access control list for filesystems which may be exported # to NFS clients. See exports(5). # # Example for NFSv2 and NFSv3: # /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check) # # Example for NFSv4: # /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check) # /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check) # /home/toto/workspace/nfs *(rw,sync,no_root_squash) /home/toto/workspace/rootfs/rootfs *(rw,sync,no_root_squash)
5.1.3 开启NFS服务
重启 NFS 服务,使用命令如下:
sudo /etc/init.d/nfs-kernel-server restart
5.1.4 NFS挂载rootfs
使用 NFS 挂载rootfs,就是在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>:<gwip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>
各参数含义如下:
- [server-ip]:服务器 IP 地址,也就是存放根文件系统主机的 IP 地址,那就是 Ubuntu 的 IP地址,比如我的 Ubuntu 主机 IP 地址为 192.168.1.250
- [root-dir]:根文件系统的存放路径,比如我的就是/home/toto/workspace/rootfs/rootfs/
- [nfs-options]:NFS 的其他可选选项,一般不设置
- [client-ip]:客户端 IP 地址,也就是我们开发板的 IP 地址, Linux 内核启动以后就会使用此 IP 地址来配置开发板。此地址一定要和 Ubuntu 主机在同一个网段内,并且没有被其他的设备使用,在 Ubuntu 中使用 ping 命令 ping 一下就知道要设置的 IP 地址有没有被使用,如果不能ping 通就说明没有被使用,那么就可以设置为开发板的 IP 地址,比如我就可以设置为192.168.1.251
- [server-ip]:服务器 IP 地址,前面已经说了
- [gw-ip]:网关地址,我的就是 192.168.1.1
- [netmask]:子网掩码,我的就是 255.255.255.0
- [hostname]:客户机的名字,一般不设置,此值可以空着
- [device]:设备名,也就是网卡名,一般是 eth0, eth1….,正点原子的 I.MX6U-ALPHA 开发板的 ENET2 为 eth0, ENET1 为 eth1。如果你的电脑只有一个网卡,那么基本只能是 eth0。这里我们使用 ENET2,所以网卡名就是 eth0
- [autoconf]:自动配置,一般不使用,所以设置为 off
- [dns0-ip]:DNS0 服务器 IP 地址,不使用
- [dns1-ip]:DNS1 服务器 IP 地址,不使用
根据上面的格式 bootargs 环境变量的 root 值如下:
root=/dev/nfs nfsroot=192.168.1.43:/home/toto/workspace/rootfs/rootfs,proto=tcp rwip=192.168.1.100:192.168.1.43:192.168.1.1:255.255.255.0::eth0:off
“proto=tcp”表示使用 TCP 协议,“rw”表示 nfs 挂载的根文件系统为可读可写。
启动开发板,进入 uboot 命令行模式,然后重新设置 bootargs 环境变量,命令如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.250:/home/toto/workspace/rootfs/rootfs,proto=tcp rw ip=192.168.1.100:192.168.1.43:192.168.1.1:255.255.255.0::eth0:off' //设置 bootargs saveenv //保存环境变量
设置好以后使用“boot”命令启动 Linux 内核,测试根文件系统是否挂载成功了。
5.2 SD卡挂载rootfs
这里我们为了节省时间,就直接将正点原子的固化系统到sd卡的脚本文件(imx6mksdboot.sh)拿来使用,在上面做一些修改就行了。先看一下都有哪些文件:
boot filesystem imx6mksdboot.sh
其中:
- boot文件夹用于存放系统的u-boot镜像文件、linux内核系统、以及设备树文件(u-boot-imx6ull-14x14-ddr512-emmc.imx、zImage、imx6ull-14x14-emmc-10.1-1280x800-c.dtb);
- filesystem文件夹是用于存放打包后的根文件系统压缩文件(rootfs.tar.bz2);
- imx6mksdboot.sh为将系统烧写到SD卡的脚本文件。
首先先了解一下脚本 imx6mksdboot.sh 大致做了哪些工作。脚本部分内容如下:
#! /bin/sh ... if [ -z $number ];then echo "使用默认参数:EMMC版本,DDR大小为512MB" else case $number in 1) echo '您已经选择开发板参数为:EMMC版本,DDR大小为512MB' Uboot='u-boot-imx6ull-14x14-ddr512-emmc.imx' ;; 2) echo '您已经选择开发板参数为:EMMC版本,DDR大小为256MB' Uboot='u-boot-imx6ull-14x14-ddr256-emmc.imx' ;; 3) echo '您已经选择开发板参数为:NAND FLASH版本,DDR大小为512MB' Uboot='u-boot-imx6ull-14x14-ddr512-nand-sd.imx' ;; 4) echo '您已经选择开发板参数为:NAND FLASH版本,DDR大小为256MB' Uboot='u-boot-imx6ull-14x14-ddr256-nand-sd.imx' ;; *) echo '输入的参数有误,退出制卡';exit; ;; esac fi else #命令行处理,根据选项获得参数 while [ $# -gt 0 ]; do case $1 in --help | -h) usage $0 ;; -device) shift; device=$1; shift; ;; -flash) shift; flash=$1; shift; ;; -ddrsize) shift; ddrsize=$1; shift; ;; --version) version $0;; *) copy="$copy $1"; shift; ;; esac done if [ $flash = "emmc" -a $ddrsize = "512" ];then Uboot='u-boot-imx6ull-14x14-ddr512-emmc.imx' echo '您已经选择开发板参数为:EMMC版本,DDR大小为512MB' elif [ $flash = "emmc" -a $ddrsize = "256" ];then Uboot='u-boot-imx6ull-14x14-ddr256-emmc.imx' echo '您已经选择开发板参数为:EMMC版本,DDR大小为256MB' elif [ $flash = "nand" -a $ddrsize = "512" ];then Uboot='u-boot-imx6ull-14x14-ddr512-nand-sd.imx' echo '您已经选择开发板参数为:NAND FLASH版本,DDR大小为512MB' elif [ $flash = "nand" -a $ddrsize = "256" ];then Uboot='u-boot-imx6ull-14x14-ddr256-nand-sd.imx' echo '您的开发板参数为:NAND FLASH版本,DDR大小为256MB' else echo '参数有误!' usage $0 fi fi #测试制卡包当前目录下是否缺失制卡所需要的文件 sdkdir=$PWD if [ ! -d $sdkdir ]; then echo "错误: $sdkdir目录不存在" exit 1 fi if [ ! -f $sdkdir/filesystem/ *.tar.* ]; then echo "错误: $sdkdir/filesystem/下找不到文件系统压缩包" exit 1 fi if [ ! -f $sdkdir/boot/zImage ]; then echo "错误: $sdkdir/boot/下找不到zImage" exit 1 fi #判断选择的块设备是否存在及是否是一个块设备 if [ ! -b $device ]; then echo "错误: $device 不是一个块设备文件" exit 1 fi #这里防止选错设备,否则会影响Ubuntu系统的启动 if [ $device = '/dev/sda' ];then echo "请不要选择sda设备,/dev/sda通常是您的Ubuntu硬盘! 继续操作你的系统将会受到影响!脚本已自动退出" exit 1 fi echo "即将进行制作SD系统启动卡,大约花费几分钟时间,请耐心等待!" echo "************************************************************" echo "* 注意:这将会清除$device所有的数据 *" echo "* 在脚本执行时请不要将$device拔出 *" echo "* 请按<Enter>确认继续 *" echo "************************************************************" read enter #格式化前要卸载 for i in `ls -1 $device?`; do echo "卸载 device '$i'" umount $i 2>/dev/null done #执行格式化$device execute "dd if=/dev/zero of=$device bs=1024 count=1024" #第一个分区为64M用来存放设备树与内核镜像文件,因为设备树与内核都比较小,不需要太大的空间 #第二个分区为SD卡的总大小-64M,用来存放文件系统 ... #两个分区处理 PARTITION1=${device}1 if [ ! -b ${PARTITION1} ]; then PARTITION1=${device}1 fi PARTITION2=${device}2 if [ ! -b ${PARTITION2} ]; then PARTITION2=${device}2 fi #第一个分区创建为Fat32格式 echo "格式化 ${device}1 ..." if [ -b ${PARTITION1} ]; then mkfs.vfat -F 32 -n "boot" ${PARTITION1} else echo "错误: /dev下找不到 SD卡 boot分区" fi #第二个分区创建为ext4格式 echo "格式化${device}2 ..." if [ -b ${PARITION2} ]; then mkfs.ext4 -F -L "rootfs" ${PARTITION2} else echo "错误: /dev下找不到 SD卡 rootfs分区" fi while [ ! -e $device ] do sleep 1 echo "wait for $device appear" done echo "正在烧写${Uboot}到${device}" execute "dd if=$sdkdir/boot/$Uboot of=$device bs=1024 seek=1 conv=fsync" sync echo "烧写${Uboot}到${device}完成!" echo "正在准备复制..." echo "正在复制设备树与内核到${device}1,请稍候..." execute "mkdir -p /tmp/sdk/$$" execute "mount ${device}1 /tmp/sdk/$$" execute "cp -r $sdkdir/boot/*${flash}*.dtb /tmp/sdk/$$/" execute "cp -r $sdkdir/boot/zImage /tmp/sdk/$$/" #execute "cp $sdkdir/boot/alientek.bmp /tmp/sdk/$$/" sync echo "复制设备树与内核到${device}1完成!" if [ "$copy" != "" ]; then echo "Copying additional file(s) on ${device}p1" execute "cp -r $copy /tmp/sdk/$$" fi echo "卸载${device}1" execute "umount /tmp/sdk/$$" sleep 1 #解压文件系统到文件系统分区 #挂载文件系统分区 execute "mkdir -p /tmp/sdk/$$" execute "mount ${device}2 /tmp/sdk/$$" echo "正在解压文件系统到${device}2 ,请稍候..." rootfs=`ls -1 filesystem/ *.tar.*` execute "tar jxfm $rootfs -C /tmp/sdk/$$" sync echo "解压文件系统到${device}2完成!" ...
可以看到脚本的工作大致分为三步:
- 清除SD卡中的数据
- 将SD卡格式化分为两个区,第一个分区为64M用来存放设备树与内核镜像文件,分区Fat32格式;第二个分区为SD卡的总大小-64M,用来存放文件系统,ext4格式
- 将系统文件拷贝到sd卡对应的分区中,u-boot-imx6ull-14x14-ddr512-emmc.imx、zImage、imx6ull-14x14-emmc-10.1-1280x800-c.dtb、rootfs.tar.bz2
好了,了解了该脚本的大致内容后就需要根据我们自己实际的系统文件来进行修改,系统文件如下:
u-boot-dtb.imx zImage imx6ull-toto.dtb rootfs.tar.bz2
脚本 imx6mksdboot.sh 修改后的内容如下:
#! /bin/sh #I.MX6 SD卡启动系统烧写脚本 #版本v1.0 #Author:ALIENTEK VERSION="1.0" #打印用法 usage () { echo " 用法: `basename $1` [选项] <(必选)-device> <(可选)-flash> <(可选)-ddrsize> 用法示例: sudo ./imx6mksdboot.sh -device /dev/sdd sudo ./imx6mksdboot.sh -device /dev/sdd -flash emmc -ddrsize 512 命令选项: -device SD卡块设备节点 (例如/dev/sdx) -flash 请选择开发板Flash类型(emmc | nand) -ddrsize 请选择DDR大小 (512 | 256) 可选选项: --version 打印版本信息. --help 打印帮助信息. " exit 1 } #Uboot默认值 Uboot='u-boot-dtb.imx' #execute执行语句成功与否打印 execute () { $* >/dev/null if [ $? -ne 0 ]; then echo echo "错误: 执行 $*" echo exit 1 fi } #打印版本信息 version () { echo echo "`basename $1` version $VERSION" echo "I.MX6 SD卡制卡脚本" echo exit 0 } #判断参数个数 arg=$# if [ $arg -ne 6 ];then number=1 while [ $# -gt 0 ]; do case $1 in --help | -h) usage $0 ;; -device) shift; device=$1; shift; ;; --version) version $0;; *) copy="$copy $1"; shift; ;; esac done #判断字符串是否为零 test -z $device && usage $0 echo "" echo "根据下面的提示,补全缺省的参数-flash -ddrsize" read -p "请选择开发板参数,输入数字1~4,按Enter键确认 1.-flash emmc,-ddrsize 512 2.-flash emmc,-ddrsize 256 3.-flash nand,-ddrsize 512 4.-flash nand,-ddrsize 256 输入数字1~4(default 1): " number if [ -z $number ];then echo "使用默认参数:EMMC版本,DDR大小为512MB" else case $number in 1) echo '您已经选择开发板参数为:EMMC版本,DDR大小为512MB' Uboot='u-boot-dtb.imx' ;; 2) echo '您已经选择开发板参数为:EMMC版本,DDR大小为256MB' Uboot='u-boot-imx6ull-14x14-ddr256-emmc.imx' ;; 3) echo '您已经选择开发板参数为:NAND FLASH版本,DDR大小为512MB' Uboot='u-boot-imx6ull-14x14-ddr512-nand-sd.imx' ;; 4) echo '您已经选择开发板参数为:NAND FLASH版本,DDR大小为256MB' Uboot='u-boot-imx6ull-14x14-ddr256-nand-sd.imx' ;; *) echo '输入的参数有误,退出制卡';exit; ;; esac fi else #命令行处理,根据选项获得参数 while [ $# -gt 0 ]; do case $1 in --help | -h) usage $0 ;; -device) shift; device=$1; shift; ;; -flash) shift; flash=$1; shift; ;; -ddrsize) shift; ddrsize=$1; shift; ;; --version) version $0;; *) copy="$copy $1"; shift; ;; esac done if [ $flash = "emmc" -a $ddrsize = "512" ];then Uboot='u-boot-dtb.imx' echo '您已经选择开发板参数为:EMMC版本,DDR大小为512MB' elif [ $flash = "emmc" -a $ddrsize = "256" ];then Uboot='u-boot-imx6ull-14x14-ddr256-emmc.imx' echo '您已经选择开发板参数为:EMMC版本,DDR大小为256MB' elif [ $flash = "nand" -a $ddrsize = "512" ];then Uboot='u-boot-imx6ull-14x14-ddr512-nand-sd.imx' echo '您已经选择开发板参数为:NAND FLASH版本,DDR大小为512MB' elif [ $flash = "nand" -a $ddrsize = "256" ];then Uboot='u-boot-imx6ull-14x14-ddr256-nand-sd.imx' echo '您的开发板参数为:NAND FLASH版本,DDR大小为256MB' else echo '参数有误!' usage $0 fi fi #测试制卡包当前目录下是否缺失制卡所需要的文件 sdkdir=$PWD if [ ! -d $sdkdir ]; then echo "错误: $sdkdir目录不存在" exit 1 fi if [ ! -f $sdkdir/filesystem/ *.tar.* ]; then echo "错误: $sdkdir/filesystem/下找不到文件系统压缩包" exit 1 fi if [ ! -f $sdkdir/boot/zImage ]; then echo "错误: $sdkdir/boot/下找不到zImage" exit 1 fi #判断选择的块设备是否存在及是否是一个块设备 if [ ! -b $device ]; then echo "错误: $device 不是一个块设备文件" exit 1 fi #这里防止选错设备,否则会影响Ubuntu系统的启动 if [ $device = '/dev/sda' ];then echo "请不要选择sda设备,/dev/sda通常是您的Ubuntu硬盘! 继续操作你的系统将会受到影响!脚本已自动退出" exit 1 fi echo "即将进行制作SD系统启动卡,大约花费几分钟时间,请耐心等待!" echo "************************************************************" echo "* 注意:这将会清除$device所有的数据 *" echo "* 在脚本执行时请不要将$device拔出 *" echo "* 请按<Enter>确认继续 *" echo "************************************************************" read enter #格式化前要卸载 for i in `ls -1 $device?`; do echo "卸载 device '$i'" umount $i 2>/dev/null done #执行格式化$device execute "dd if=/dev/zero of=$device bs=1024 count=1024" #第一个分区为64M用来存放设备树与内核镜像文件,因为设备树与内核都比较小,不需要太大的空间 #第二个分区为SD卡的总大小-64M,用来存放文件系统 cat << END | fdisk -H 255 -S 63 $device n p 1 +64M n p 2 t 1 c a 1 w END #两个分区处理 PARTITION1=${device}1 if [ ! -b ${PARTITION1} ]; then PARTITION1=${device}1 fi PARTITION2=${device}2 if [ ! -b ${PARTITION2} ]; then PARTITION2=${device}2 fi #第一个分区创建为Fat32格式 echo "格式化 ${device}1 ..." if [ -b ${PARTITION1} ]; then mkfs.vfat -F 32 -n "boot" ${PARTITION1} else echo "错误: /dev下找不到 SD卡 boot分区" fi #第二个分区创建为ext4格式 echo "格式化${device}2 ..." if [ -b ${PARITION2} ]; then mkfs.ext4 -F -L "rootfs" ${PARTITION2} else echo "错误: /dev下找不到 SD卡 rootfs分区" fi while [ ! -e $device ] do sleep 1 echo "wait for $device appear" done echo "正在烧写${Uboot}到${device}" execute "dd if=$sdkdir/boot/$Uboot of=$device bs=1024 seek=1 conv=fsync" sync echo "烧写${Uboot}到${device}完成!" echo "正在准备复制..." echo "正在复制设备树与内核到${device}1,请稍候..." execute "mkdir -p /tmp/sdk/$$" execute "mount ${device}1 /tmp/sdk/$$" execute "cp -r $sdkdir/boot/imx6ull-toto.dtb /tmp/sdk/$$/" execute "cp -r $sdkdir/boot/zImage /tmp/sdk/$$/" #execute "cp $sdkdir/boot/alientek.bmp /tmp/sdk/$$/" sync echo "复制设备树与内核到${device}1完成!" if [ "$copy" != "" ]; then echo "Copying additional file(s) on ${device}p1" execute "cp -r $copy /tmp/sdk/$$" fi echo "卸载${device}1" execute "umount /tmp/sdk/$$" sleep 1 #解压文件系统到文件系统分区 #挂载文件系统分区 execute "mkdir -p /tmp/sdk/$$" execute "mount ${device}2 /tmp/sdk/$$" echo "正在解压文件系统到${device}2 ,请稍候..." rootfs=`ls -1 filesystem/ *.tar.*` execute "tar jxfm $rootfs -C /tmp/sdk/$$" sync echo "解压文件系统到${device}2完成!" #判断是否存在这个目录,如果不存在就为文件系统创建一个modules目录 if [ ! -e "${device}p2/lib/modules/" ];then mkdir -p ${device}p2/lib/modules/ fi #echo "正在解压模块到${device}2/lib/modules/ ,请稍候..." #modules=`ls -1 modules/ *.tar.*` #execute "tar jxfm $modules -C /tmp/sdk/$$/lib/modules/" #sync #echo "解压模块到${device}2/lib/modules/完成!" echo "卸载${device}2" execute "umount /tmp/sdk/$$" execute "rm -rf /tmp/sdk/$$" sync echo "SD卡启动系统烧写完成!"
- 将81行和111行修改为“Uboot='u-boot-dtb.imx'”
- 将238行修改为 execute "cp -r $sdkdir/boot/imx6ull-toto.dtb /tmp/sdk/$$/"
- 将268行至272行屏蔽 接着我们需要做的就是将我们自己的系统文件放入对应的文件夹下:
u-boot-dtb.imx ---> 拷贝到boot文件夹 zImage ---> 拷贝到boot文件夹 imx6ull-toto.dtb ---> 拷贝到boot文件夹 rootfs.tar.bz2 ---> 拷贝到filesystem文件夹
好了,准备工作已经做完了,下面就是最重要的工作了,就是烧写系统到sd卡 烧写系统文件到sd卡 直接执行./imx6mksdboot.sh --help 查看脚本的使用方法:
toto@toto:~/workspace/imx6mksdboot/files$ ./imx6mksdboot.sh --help 用法: imx6mksdboot.sh [选项] <(必选)-device> <(可选)-flash> <(可选)-ddrsize> 用法示例: sudo ./imx6mksdboot.sh -device /dev/sdb sudo ./imx6mksdboot.sh -device /dev/sdb -flash emmc -ddrsize 512 命令选项: -device SD卡块设备节点 (例如/dev/sdx) -flash 请选择开发板Flash类型(emmc | nand) -ddrsize 请选择DDR大小 (512 | 256) 可选选项: --version 打印版本信息. --help 打印帮助信息.
用法说明:
用法: imx6mksdboot.sh [选项] <(必选)-device> <(可选)-flash> <(可选)-ddrsize>
- -device:指明设备节点(SD 卡挂载的节点如/dev/sdx),执行脚本时必需要加这个参数
- -flash:指明核心板上的媒体存储介质,可选为(emmc|nand)
- -ddrsize:指明核心板上的 ddr 容量大小,可选为(512|256) MB比如现在用户是核心板的 ddr 容量大小是 512MB,媒体存储介质是 eMMC。SD 卡挂载节点为/dev/sdb。
那么固化 SD 卡的指令如下,执行指令后脚本会有中文再次询问 SD 卡所挂载的节点是否对应,将会清空 SD 卡上的所有数据,请注意备份重要的数据。按 Enter 键确认后继续,固化 SD 卡需要大约需要几分钟时间,这里根据个人电脑不同和所用 SD 卡不同,可能花费的时间差异比较大。
运行脚本 imx6mksdboot.sh 烧写系统文件,具体命令如下:
sudo ./imx6mksdboot.sh -device /dev/sdb -flash emmc -ddrsize 512
注意:其中/dev/sdb请根据自己的实际进行操作,可以输入“sudo fdisk -l”,查看 SD 卡挂载节点。
toto@toto:~/workspace/imx6mksdboot/files$ sudo ./imx6mksdboot.sh -device /dev/sdb -flash emmc -ddrsize 512
[sudo] toto 的密码: 您已经选择开发板参数为:EMMC版本,DDR大小为512MB 即将进行制作SD系统启动卡,大约花费几分钟时间,请耐心等待! ************************************************************ * 注意:这将会清除/dev/sdb所有的数据 * * 在脚本执行时请不要将/dev/sdb拔出 * * 请按<Enter>确认继续 * ************************************************************
敲“Enter”键继续,固化时有中英文结合,提示固化的过程,固化完成如下图:
卸载 device '/dev/sdb1' 卸载 device '/dev/sdb2' 记录了1024+0 的读入 记录了1024+0 的写出 1048576 bytes (1.0 MB, 1.0 MiB) copied, 3.19621 s, 328 kB/s 欢迎使用 fdisk (util-linux 2.31.1)。 更改将停留在内存中,直到您决定将更改写入磁盘。 使用写入命令前请三思。 设备不包含可识别的分区表。 创建了一个磁盘标识符为 0xbaabddd3 的新 DOS 磁盘标签。 命令(输入 m 获取帮助): 分区类型 p 主分区 (0个主分区,0个扩展分区,4空闲) e 扩展分区 (逻辑分区容器) 选择 (默认 p): 分区号 (1-4, 默认 1): 第一个扇区 (2048-31116287, 默认 2048): 上个扇区,+sectors 或 +size{K,M,G,T,P} (2048-31116287, 默认 31116287): 创建了一个新分区 1,类型为“Linux”,大小为 64 MiB。 分区 #1 包含一个 vfat 签名。 命令(输入 m 获取帮助): 分区类型 p 主分区 (1个主分区,0个扩展分区,3空闲) e 扩展分区 (逻辑分区容器) 选择 (默认 p): 分区号 (2-4, 默认 2): 第一个扇区 (133120-31116287, 默认 133120): 上个扇区,+sectors 或 +size{K,M,G,T,P} (133120-31116287, 默认 31116287): 创建了一个新分区 2,类型为“Linux”,大小为 14.8 GiB。 分区 #2 包含一个 ext4 签名。 命令(输入 m 获取帮助): 分区号 (1,2, 默认 2): Hex 代码(输入 L 列出所有代码): 已将分区“Linux”的类型更改为“W95 FAT32 (LBA)”。 命令(输入 m 获取帮助): 分区号 (1,2, 默认 2): 分区 1 的 可启动 标志已启用。 命令(输入 m 获取帮助): 分区表已调整。 将调用 ioctl() 来重新读分区表。 正在同步磁盘。 格式化 /dev/sdb1 ... mkfs.fat 4.1 (2017-01-24) mkfs.fat: warning - lowercase labels might not work properly with DOS or Windows 格式化/dev/sdb2 ... mke2fs 1.44.1 (24-Mar-2018) /dev/sdb2 有一个标签为“rootfs”的 ext4 文件系统 上一次挂载于 /tmp/sdk/26687, 时间 Mon Jun 12 22:36:06 2023 创建含有 3872896 个块(每块 4k)和 969136 个inode的文件系统 文件系统UUID:4bdc82c7-5e83-4992-9966-cd99a2317944 超级块的备份存储于下列块: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208 正在分配组表: 完成 正在写入inode表: 完成 创建日志(16384 个块) 完成 写入超级块和文件系统账户统计信息: 已完成 正在烧写u-boot-dtb.imx到/dev/sdb 记录了407+0 的读入 记录了407+0 的写出 416768 bytes (417 kB, 407 KiB) copied, 1.70141 s, 245 kB/s 烧写u-boot-dtb.imx到/dev/sdb完成! 正在准备复制... 正在复制设备树与内核到/dev/sdb1,请稍候... 复制设备树与内核到/dev/sdb1完成! 卸载/dev/sdb1 正在解压文件系统到/dev/sdb2 ,请稍候... 解压文件系统到/dev/sdb2完成! 卸载/dev/sdb2 SD卡启动系统烧写完成!
将开发板上的拨码开关拨至 10000010,从 SD 卡启动系统,插入刚刚烧写好系统的SD卡,上电启动系统即可。
在u-boot下设置bootargs 和 bootcmd 这两个环境变量,命令如下:
注:若不知道“mmc dev 0”,可以输入命令“mmc list”,来查看哪个是SD卡,哪个是eMMC,如下:
=> mmc list FSL_SDHC: 0 (SD) FSL_SDHC: 1
bootcmd含义如下:
mmc dev 0 //切换到 SD卡 fatload mmc 0:1 0x80800000 zImage //从SD卡分区1中读取 zImage 到 0x80800000 处 fatload mmc 0:1 0x83000000 imx6ull-14x14-evk.dtb //从SD卡分区1中读取设备树到 0x83000000 处 bootz 0x80800000 - 0x83000000 //启动 Linux
bootargs含义如下:
- console:用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?如果是串口的话应该是串口几等等。一般设置串口作为 Linux 终端,这样我们就可以在电脑上通过 SecureCRT 来和 linux 交互了。这里设置 console 为 ttymxc0,因为 linux启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,在 Linux 下,一切皆文件。ttymxc0 后面有个“,115200”,这是设置串口的波特率, console=ttymxc0,115200 综合起来就是设置 ttymxc0(也就是串口 1)作为 Linux 的终端,并且串口波特率设置为 115200。
- rootroot:用来设置根文件系统的位置, root=/dev/mmcblk0p2 用于指明根文件系统存放在mmcblk0 设备的分区 2 中。EMMC 版本的核心板启动 linux 以后会存在/dev/mmcblk0、/dev/mmcblk1、 /dev/mmcblk0p1、 /dev/mmcblk0p2、 /dev/mmcblk1p1 和/dev/mmcblk1p2 这样的文件,其中/dev/mmcblkx(x=0~n)表示 mmc 设备,而/dev/mmcblkxpy(x=0~n,y=1~n)表示 mmc 设备x 的分区 y。在 I.MX6U-ALPHA 开发板中/dev/mmcblk0 表示 SD卡,而/dev/mmcblk1p2 表示SD卡 的分区 2。root 后面有“rootwait rw”, rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。
- rootfstype:此选项一般配置 root 一起使用, rootfstype 用于指定根文件系统类型,如果根文件系统为ext 格式的话此选项无所谓。如果根文件系统是 yaffs、 jffs 或 ubifs 的话就需要设置此选项,指定根文件系统的类型。系统挂载根文件系统成功,打印信息如下:
[ 2.502135] No soundcards found. [ 2.533819] EXT4-fs (mmcblk0p2): mounted filesystem 4bdc82c7-5e83-4992-9966-cd99a2317944 with ordered data mode. Quota mode: none. [ 2.570874] VFS: Mounted root (ext4 filesystem) on device 179:2. [ 2.581547] devtmpfs: mounted [ 2.586663] Freeing unused kernel image (initmem) memory: 1024K [ 2.593232] Run /sbin/init as init process [ 2.718227] usb 1-1: new high-speed USB device number 2 using ci_hdrc can't run '/etc/init.d/rcS': No such file or directory
可以看到最后一行“can't run '/etc/init.d/rcS': No such file or directory”,提示不存在该文件和目录,不能运行。说明根文件系统还缺少一些文件,后面继续完善。
5.3 eMMC挂载rootfs
eMMC挂载方法跟SD卡是一样,这里就不详细讲解了,步骤如下:
- 格式化分区,第一个分区boot,Fat32格式,用于存放u-boot-toto.imx、zImage、imx6ull-toto.dtb;第二个分区rootfs,ext4格式,用于存放根文件系统
- u-boot下设置bootcmd和bootargs两个环境变量
6. 完善根文件系统
6.1 创建/etc/init.d/rcS文件
rcS 是个 shell 脚本, Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。在 rootfs 中创建/etc/init.d/rcS 文件,然后在 rcS 中输入如下所示内容:
#!/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 mkdir /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug 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 以后一定要给其可执行权限!命令如下:
chmod 777 /etc/init.d/rcS
重新启动 Linux 内核,启动后打印信息如下所示:
mount: can't read '/etc/fstab': No such file or directory /etc/init.d/rcS: line 11: can't create /proc/sys/kernel/hotplug: nonexistent directory mdev: /sys/dev: No such file or directory Please press Enter to activate this console.
提示找不到/etc/fstab 文件,还有一些其他的错误,我们先把/etc/fstab这个错误解决了。前面我们说了“mount -a”挂载所有根文件系统的时候需要读取/etc/fstab,因为/etc/fstab 里面定义了该挂载哪些文件,接下来就是创建/etc/fstab 文件。
6.2 创建/etc/fstab文件
创建/etc/fstab 文件,并在 fstab 文件中输入如下内容:
/ # vi /etc/fstab #<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
fstab格式如下:
<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 文件创建完成以后,重新启动开发板,打印信息如下所示:
VFS: Mounted root (ext4 filesystem) on device 179:26. [ 2.556792] devtmpfs: mounted [ 2.561310] Freeing unused kernel image (initmem) memory: 1024K [ 2.567854] Run /sbin/init as init process [ 2.718088] usb 1-1: new high-speed USB device number 2 using ci_hdrc /etc/init.d/rcS: line 11: can't create /proc/sys/kernel/hotplug: nonexistent directory
还是提示“/etc/init.d/rcS: line 11: can't create /proc/sys/kernel/hotplug: nonexistent directory”,通过网上查询资料得知,是编译内核时没有选CONFIG_HOTPLUG=y,因此在/proc/sys/kernel下将不会创建hotplug文件.(参见kernel/sysctl.c)。修改内核的配置,选中宏CONFIG_HOTPLUG。继续添加其他文件。
6.3 创建/etc/inittab文件
创建/etc/inittab 文件,并在 inittab 文件中输入如下内容:
::sysinit:/etc/init.d/rcS console::askfirst:-/bin/sh ::restart:/sbin/init ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r ::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,也就是关闭交换分区。
inittab 的详细内容可以参考 busybox 下的文件 examples/inittab。init 程序会读取/etc/inittab这个文件, inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组成,格式如下:
<id>:<runlevels>:<action>:<process>
- [id]:每个指令的标识符,不能重复。但是对于 busybox 的 init 来说,有着特殊意义。对于 busybox 而言用来指定启动进程的控制 tty,一般我们将串口或者 LCD 屏幕设置为控制 tty
- [runlevels]:对 busybox 来说此项完全没用,所以空着
- [action]:动作,用于指定可能用到的动作
- [process]:具体的动作,比如程序、脚本或命令等。
busybox 支持的动作如下所示:
动作 | 描述 |
sysinit | 在系统初始化的时候process才会执行一次 |
respawn | 当 process 终止以后马上启动一个新的 |
askfirst | 和 respawn 类似,在运行 process 之前在控制台上显示“Please press Enter to activatethis console.”。只要用户按下“Enter”键以后才会执行 process |
wait | 告诉 init,要等待相应的进程执行完以后才能继续执行 |
once | 仅执行一次,而且不会等待 process 执行完成 |
restart | 当 init 重启的时候才会执行 proceec |
trlaltdel | 当按下 ctrl+alt+del 组合键才会执行 process |
shutdown | 关机的时候执行 process |
至此!根文件系统要创建的文件就已经全部完成了。接下来就要对根文件系统进行其他的测试,比如是我们自己编写的软件运行收费正常、是否支持软件开机自启动、中文支持是否正常以及能不能链接等。
7. 根文件系统功能测试
7.1 应用程序运行测试
我们使用 Linux 的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,以后我们学习 Linux 驱动的时候就把所有的实验文件放到这个文件夹里面。
在 ubuntu 下使用 vim 编辑器新建一个 hello.c 文件,在 hello.c 里面输入如下内容:
#include <stdio.h> int main(void) { while(1) { printf("hello world!\r\n"); sleep(2); } return 0; }
因为我们是要在开发板上运行,因此需要使用交叉编译器去编译,具体编译命令如下:
arm-linux-gnueabihf-gcc hello.c -o hello
将其拷贝到 rootfs/drivers 目录下,在开发板中输入如下命令来执行这个可执行文件:
cd /drivers //进入 drivers 目录 ./hello //执行 hello
程序正常运行,说明我们的根文件系统中的共享库是没问题的。
7.2 中文字符测试
在开发板上新建一个名为“中文测试”的文件夹,然后在 SecureCRT 下查看中文名能不能显示正确。输入“ls”命令,结果如下所示:
/ # mkdir 中文测试 / # ls bin lib mnt sbin usr dev linuxrc proc sys 中文测试 etc lost+found root tmp / #
说明根文件系统已经支持中文了。
7.3 开机自启动测试
将hello程序添加到/etc/init.d/rcS 文件中,进行开机自启动,内容如下:
#!/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 mkdir /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s ./hello &
自启动代码添加完成以后就可以重启开发板,hello 开机自动运行了,说明开机自启动成功。
今天的内容到此就完了,感谢大家的收看