Linux“三巨头”已经完成了2 个了,就剩最后一个rootfs(根文件系统)了,本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是Linux 移植的最后一步,根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系统。
以后我们就在这个最小系统上编写、测试Linux 驱动,移植一些第三方组件,逐步的完善这个最小系统。最终得到一个功能完善、驱动齐全、相对完善的操作系统。
根文件系统一般也叫做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)的提示,这个在37.2.4 小节已经说过了。
根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中,这些小软件是怎么来的呢?这个就是我们本章教程的目的,教大家来构建自己的根文件系统,这个根文件系统是满足Linux 运行的最小根文件系统,后续我们可以根据自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对完善的根文件系统。
内核Zimage总共就才6M,根文件系统要10几个G,Ubuntu都要好几个G。
在构建根文件系统之前,我们先来看一下根文件系统里面大概都有些什么内容,以Ubuntu为例,根文件系统的目录名字为‘/’,没看错就是一个斜杠,所以输入如下命令就可以进入根目录中:
cd / //进入根目录
进入根目录以后输入“ls”命令查看根目录下的内容都有哪些,结果如图38.1.1 所示:
图38.1.1 中根目录下子目录和文件不少,但是这些都是Ubuntu 所需要的,其中有很多子目录和文件我们嵌入式Linux 是用不到的,所以这里就讲解一些常用的子目录:
1、/bin 目录
看到“bin”大家应该能想到bin 文件,bin 文件就是可执行文件。所以此目录下存放着系统需要的可执行文件,一般都是一些命令,比如ls、mv、ping等命令。此目录下的命令所有的客户都可以使用。
2、/dev 目录
dev 是device 的缩写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在Linux 下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如/dev/ttymxc0(I.MX6ULL 根目录会有此文件)就表示I.MX6ULL 的串口0,我们要想通过串口0发送或者接收数据就要操作文件/dev/ttymxc0,通过对文件/dev/ttymxc0 的读写操作来实现串口0 的数据收发。
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 的根目录就介绍到这里,接下来的构建根文件系统就是研究如何创建上面这些子目录以及子目录中的文件。
除了BusyBox还有buildroot和yocto这两个更加专业的根文件构建软件,不过学习的话我们还是使用BusyBox更加底层的配置,后面工作的话直接使用buildroot和yocto就可以了,非常方便强大。
上一小节说了,根文件系统里面就是一堆的可执行文件和其他文件组成的?难道我们得一个一个的从网上去下载这些文件?显然这是不现实的!那么有没有人或者组织专门干这个事呢?他们负责“收集”这些文件,然后将其打包,像我们这样的开发者可以直接拿来用。
答案是有的,它就叫做BusyBox!其名字分为“Busy”和“Box”,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。BusyBox 是一个集成了大量的Linux 命令和工具的软件,像ls、mv、ifconfig 等命令BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了Linux 的许多工具和命令。一般下载BusyBox 的源码,然后配置BusyBox,选择自己想要的功能,最后编译即可。
BusyBox 可以在其官网下载到,官网地址为:https://busybox.net/,官网比较简陋,如图38.2.1.1 所示:
在官网左侧的“Get BusyBox”栏有一行“Download Source”,点击“Download Source”即可打开BusyBox 的下载页,如图38.2.1.2 所示:
从图38.2.1.2 可以看出,目前最新的BusyBox 版本是1.31.0,不过我建议大家使用我们开发板光盘里面提供的1.29.0 版本的BusyBox。因为笔者测试1.29.0 版本目前还没有出现任何问题,路径为:1、例程源码->6、BusyBox 源码->busybox-1.29.0.tar.bz2,BusyBox 准备好以后就可以构建根文件系统了。
一般我们在Linux 驱动开发的时候都是通过nfs 挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到EMMC 或者NAND 中。所以要在4.2.1 小节中设置的nfs 服务器目录中创建一个名为rootfs 的子目录(名字大家可以随意起,为了方便就用了rootfs),比如我的电脑中“/home/zuozhongkai/linux/nfs”就是我设置的NFS 服务器目录,使用如下命令创建名为rootfs 的子目录:
mkdir rootfs
创建好的rootfs 子目录就用来存放我们的根文件系统了。
将busybox-1.29.0.tar.bz2 发送到Ubuntu 中,存放位置大家随便选择。然后使用如下命令将其解压:
tar -vxjf busybox-1.29.0.tar.bz2
解压完成以后进入到busybox-1.29.0 目录中,此目录中的文件和文件夹如图38.2.2.1 所示:
1、修改Makefile,添加编译器
同Uboot和Linux 移植一样,打开busybox 的顶层Makefile,添加ARCH 和CROSS_COMPILE的值,如下所示:
164 CROSS_COMPILE ?= /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
......
190 ARCH ?= arm
在示例代码38.2.2.1 中CORSS_COMPILE 使用了绝对路径主要是为了防止编译出错。
2、busybox 中文字符支持
如果默认直接编译busybox 的话,在使用SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。不知道从哪个版本开始busybox中的shell 命令对中文输入即显示做了限制,即使内核支持中文但在shell 下也依然无法正确显示。
所以我们需要修改busybox 源码,取消busybox 对中文显示的限制,打开文件busybox-1.29.0/libbb/printable_string.c,找到函数printable_string,缩减后的函数内容如下:
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) {
19 unsigned char c = *s;
20 if (c == '\0') {
......
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 }
......
55 #endif
56 return auto_string(dst);
57 }
第31 和32 行,当字符大于0X7F 以后就跳出去了。
第45 和46 行,如果支持UNICODE 码的话,当字符大于0X7F 就直接输出‘?’。
所以我们需要对这4 行代码进行修改,修改以后如下所示:
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 }
示例代码38.2.2.3 中红色部分的代码就是被修改以后的,主要就是禁止字符大于0X7F 以后break 和输出‘?’。
接着打开文件busybox-1.29.0/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,修改后内容如下所示:
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 }
示例代码38.2.2.5 中红色部分的代码就是被修改以后的,同样主要是禁止字符大于0X7F 的时候设置为‘?’。busybox 中文字符支持跟代码修改有关的就改好了,最后还需要配置busybox来使能unicode 码,这个稍后我们配置busybox 的时候在设置。
3、配置busybox
根我们编译Uboot、Linux kernel 一样,我们要先对busybox 进行默认的配置,有以下几种配置选项:
我们一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下busybox:
make defconfig
配置完会生成config文件,表示配置好了。busybox 也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:
make menuconfig
Location:
-> Settings
-> Build static binary (no shared libs)
选项“Build static binary (no shared libs)”用来决定是静态编译busybox 还是动态编译,静态编译的话就不需要库文件,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的busybox 会小很多。
这里我们不能采用静态编译!因为采用静态编译的话DNS会出问题!无法进行域名解析(左老师自己测试发现的),配置如图38.2.2.3 所示:
继续配置如下路径配置项:
Location:
-> Settings
-> vi-style line editing commands
Location:
-> Linux Module Utilities
-> Simplified modutils
默认会选中“Simplified modutils”,这里我们要取消勾选!!结果如图38.2.2.5 所示:
继续配置如下路径配置项:
Location:
-> Linux System Utilities
-> mdev (16 kb) //确保下面的全部选中,默认都是选中的
结果如图38.2.2.6 所示:
最后就是使能busybox 的unicode 编码以支持中文,配置路径如下:
Location:
-> Settings
-> Support Unicode //选中
-> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中
结果如图38.2.2.7 所示:
busybox 的配置就到此结束了,大家也可以根据自己的实际需求选择配置其他的选项,不过对于初学者笔者不建议再做其他的修改,可能会出现编译出错的情况发生。
4、编译busybox
配置好busybox 以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编译结果存放到前面创建的rootfs 目录中,输入如下命令:
make install CONFIG_PREFIX=/home/zuozhongkai/linux/nfs/rootfs
COFIG_PREFIX 指定编译结果的存放目录,比如我存放到
“/home/zuozhongkai/linux/nfs/rootfs”目录中,等待编译完成。编译完成以后如图38.2.2.8 所示:
编译完成以后会在busybox 的所有工具和文件就会被安装到rootfs 目录中,rootfs 目录内容如图38.2.2.9 所示:
从图38.2.2.9 可以看出,rootfs 目录下有bin(常用命令)、sbin 和usr 这三个目录,以及linuxrc 这个文件。
前面说过Linux 内核init 进程最后会查找用户空间的init 程序,找到以后就会运行这个用户空间的init 程序,从而切换到用户态。如果bootargs 设置init=/linuxrc,那么linuxrc 就是可以作为用户空间的init 程序,所以用户态空间的init 程序是busybox 来生成的。
busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,我们继续来完善rootfs。
1、向rootfs 的“/lib”目录添加库文件
Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要向根文件系统中添加动态库。在rootfs 中创建一个名为“lib”的文件夹,命令如下:
mkdir lib
lib 文件夹创建好了,库文件从哪里来呢?lib 库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。
交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们作为初学者肯定不知道,既然我不知道那就简单粗暴的把所有的库文件都放到我们的根文件系统中。这样做出来的根文件系统肯定很大,但是我们现在是学习阶段,还做不了裁剪。这就是为什么我们推荐大家购买512MB+8GB 版本的EMMC核心版,如果后面要学习QT 的话那占用的空间将更大,不裁剪的话512MB 的NAND 完全不够用的!而裁剪又是需要经验的,我们都是初学者,哪里来的经验啊。所以我们推荐初学者购买EMMC 版核心板并不是说为了多赚大家的钱,而是从实际角度考虑的。
进入如下路径对应的目录:
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib
此目录下有很多的 * so * ( * 是通配符)和.a 文件,这些就是库文件,将此目录下所有的so和.a文件都拷贝到rootfs/lib 目录中,拷贝命令如下:
cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/lib/ -d
后面的“-d”表示拷贝符号链接,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于Windows 下的快捷方式。会链接到库ld-2.19-2014.08-1-git.so 上,输入命令“ls ld-linux-armhf.so.3 -l”查看此文件详细信息,如图38.2.3.1 所示:
从图38.2.3.1 可以看出,ld-linux-armhf.so.3 后面有个“->”,表示其是个软连接文件,链接到文件ld-2.19-2014.08-1-git.so,因为其是一个“快捷方式”,因此大小只有24B。但是,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-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-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 文件详细信息,如图38.2.3.2 所示:
从图38.2.3.2 可以看出,此时ld-linux-armhf.so.3 已经不是软连接了,而是实实在在的一个库文件,而且文件大小为724392B。继续进入如下目录中:
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib
此目录下也有很多的的so和.a 库文件,我们将其也拷贝到rootfs/lib 目录中,命令如下:
cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/lib/ -d
rootfs/lib 目录的库文件就这些了,完成以后的rootfs/lib 目录如图38.2.3.3 所示:
2、向rootfs 的“usr/lib”目录添加库文件(和上面不是一个目录)
在rootfs 的usr 目录下创建一个名为lib 的目录,将如下目录中的库文件拷贝到rootfs/usr/lib目录下:
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
将此目录下的so 和.a 库文件都拷贝到rootfs/usr/lib 目录中,命令如下:
cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/usr/lib/ -d
完成以后的rootfs/usr/lib 目录如图38.2.3.4 所示:
至此,根文件系统的库文件就全部添加好了,可以使用“du”命令来查看一下rootfs/lib 和rootfs/usr/lib 这两个目录的大小,命令如下:
cd rootfs //进入根文件系统目录
du ./lib ./usr/lib/ -sh //查看lib 和usr/lib 这两个目录的总和大小
结果如图38.2.3.5 所示:
可以看出lib 和usr/lib 这两个文件的大小分别为57MB 和67MB,加起来就是57+67=124MB。非常大!
所以正点原子的256MB 和512MB 的NAND 核心版就不是给初学者准备的,而是给大批量采购的企业准备的,还是那句话,初学者选择EMMC 版本的。
在根文件系统中创建其他文件夹,如dev、proc、mnt、sys、tmp 和root 等,创建完成以后如图38.2.4.1 所示:
目前来看,这个根文件系统好像已经准备好了,究竟有没有准备好,直接测一下就知道了!
接下来我们使用测试一下前面创建好的根文件系统rootfs。
由于烧写一次所有内核、根文件系统到板子里是很浪费时间的,为了方便测试,方法就是使用NFS 挂载,uboot 里面的bootargs环境变量会设置“root”的值,所以我们将root 的值改为NFS 的挂载路径即可,也就是板子上直接加载Ubuntu下的根文件。
uboot、zimage、dtb前面已经烧写到EMMC里面了,这里需要将其运行加载到Ubuntu的/home/zuozhongkai/linux/nfs/rootfs上。
在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.250。
< 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.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.250:/home/zuozhongkai/linux/nfs/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 挂载的根文件系统为可读可写。
启动开发板,进入uboot 命令行模式,然后重新设置bootargs 环境变量,命令如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.250:
/home/zuozhongkai/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.250:192.168.1.1:
255.255.255.0::eth0:off' //设置bootargs
saveenv //保存环境变量
设置好以后使用“boot”命令启动Linux 内核,结果如图38.3.1 所示:
从图38.3.1 可以看出,我们进入了根文件系统,说明我们的根文件系统工作了!如果没有启动进入根文件系统的话可以重启一次开发板试试。我们可以输入“ls”命令测试一下,结果如图38.3.2 所示:
可以看出ls 命令工作正常!那么是不是说明我们的rootfs 就制作成功了呢?
大家注意,在进入根文件系统的时候会有下面这一行错误提示:
can't run '/etc/init.d/rcS': No such file or directory
提示很简单,说是无法运行“/etc/init.d/rcS”这个文件,因为这个文件不存在。如图38.3.3所示
看来我们的rootfs 还是缺文件啊,没什么说的,一步一步的完善吧。
rcS 是个shell 脚本,Linux内核启动以后需要启动一些服务,而rcS 就是规定启动哪些文件的脚本文件。在rootfs 中创建/etc/init.d/rcS 文件,然后在rcS 中输入如下所示内容:
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
示例代码38.4.1.1 中的rcS 文件内容是最精简的,大家如果去看Ubuntu 或者其他大型Linux操作系统中的rcS 文件,就会发现其非常复杂。因为我们是初次学习,所以不用搞这么复杂的,而且这么复杂的rcS 文件也是借助其他工具创建的,比如buildroot 等。
创建好文件/etc/init.d/rcS 以后一定要给其可执行权限,使用如下命令给予/ec/init.d/rcS 可执行权限:
chmod 777 rcS
设置好以后就重新启动Linux 内核,启动以后如图38.4.1.1 所示:
从图38.4.1.1 可以看到,提示找不到/etc/fstab 文件,还有一些其他的错误,我们先把/etc/fstab这个错误解决了。说不定把这个问题解决以后其他的错误也就解决了。前面我们说了“mount -a”挂载所有根文件系统的时候需要读取/etc/fstab,因为/etc、fstab 里面定义了该挂载哪些文件,好了,接下来就是创建/etc/fstab 文件。
在rootfs 中创建/etc/fstab 文件,fstab 在Linux 开机以后自动配置哪些需要自动挂载的分区,格式如下:
<file system> <mount point> <type> <options> <dump> <pass>
按照上述格式,在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,结果如图38.4.2.1 所示:
从图38.4.2.1 可以看出,启动成功,而且没有任何错误提示。但是我们要还需要创建一个文件/etc/inittab。
inittab 的详细内容可以参考busybox 下的文件examples/inittab。init 程序会读取/etc/inittab这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的4 个段组成,格式如下:
<id>:<runlevels>:<action>:<process>
< id>:每个指令的标识符,不能重复。但是对于busybox 的init 来说,有着特殊意义。
对于busybox 而言用来指定启动进程的控制tty,一般我们将串口或者LCD 屏幕设置为控制tty。
< runlevels>:对busybox 来说此项完全没用,所以空着。
< action>:动作,用于指定可能用到的动作。busybox 支持的动作如表38.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。 |
< process>:具体的动作,比如程序、脚本或命令等。
参考busybox 的examples/inittab 文件,我们也创建一个/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
/etc/inittab 文件创建好以后就可以重启开发板即可,至此,根文件系统要创建的文件就已经全部完成了。
接下来就要对根文件系统进行其他的测试,比如我们自己编写的软件运行是否正常、是否支持软件开机自启动、中文支持是否正常以及能不能链接等。
我们使用Linux的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,以后我们学习Linux 驱动的时候就把所有的实验文件放到这个文件夹里面。
在ubuntu 下使用vim 编辑器新建一个hello.c 文件,在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-linux-gnueabihf-gcc hello.c -o hello
使用arm-linux-gnueabihf-gcc 将hello.c 编译为hello 可执行文件。这个hello 可执行文件究竟是不是ARM 使用的呢?使用“file”命令查看文件类型以及编码格式:
file hello //查看hello 的文件类型以及编码格式
结果如图38.5.1.1 所示:
从图38.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
可以看出,hello 这个软件运行正常,说明我们的根文件系统中的共享库是没问题的,要想终止hello 的运行,按下“ctrl+c”组合键即可。
此时大家应该能感觉到,hello 执行的时候终端是没法用的,除非使用“ctrl+c”来关闭hello,那么有没有办法既能让hello 正常运行,而且终端能够正常使用?那肯定是有的,让hello 进入后台运行就行了,让一个软件进入后台运行的方法很简单,运行软件的时候加上“&”即可,比如
./hello &
就是让hello 在后台运行。在后台运行的软件可以使用“kill -9 pid(进程ID)”命令来关闭掉,首先使用“ps”命令查看要关闭的软件PID 是多少,ps 命令用于查看所有当前正在运行的进程,并且会给出进程的PID。输入“ps”命令,结果如图38.5.1.3 所示:
从图38.5.1.3 可以看出hello 对应的PID 为166,因此我们使用如下命令关闭在后台运行的hello 软件:
kill -9 166
因为hello 在不断的输出“hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而hello 是输出。在数据流上是没有打断的,只是显示在SecureCRT 上就好像被打断了,所以只管输入“kill -9 166”即可。hello 被kill 以后会有提示,如图38.5.1.4 所示:
再去用ps 命令查看一下当前的进程,发现没有hello 了。这个就是Linux 下的软件后台运行以及如何关闭软件的方法,重点就是3 个操作:软件后面加“&”、使用ps 查看要关闭的软件PID、使用“kill -9 pid”来关闭指定的软件。
1、设置SecureCRT 使用UTF-8 编码
因为Linux 使用的编码格式为UTF-8,因此要先设置SecureCRT 的编码格式。打开Options->Session Options…,打开“Session Options”对话框,选择左侧的“Appearance”,然后在右侧的“Character encoding:”栏选择UTF-8 编码,如图38.5.2.1 所示:
设置好以后点击下方的“Ok”按钮即可,SecureCRT 我们就设置好了。
2、创建中文文件
在ubuntu 中向在rootfs 目录新建一个名为“中文测试”的文件夹,然后在SecureCRT 下查看中文名能不能显示正确。输入“ls”命令,结果如图38.5.2.2 所示:
可以看出“中文测试”这个文件夹显示正常,接着“touch”命令在“中文测试”文件夹中新建一个名为“测试文档.txt”的文件,并且使用vim 编辑器在其中输入“这是一个中文测试文件”,借此来测试一下中文文件名和中文内容显示是否正常。在SecureCRT 中使用“cat”命令来查看“测试文档.txt”中的内容,结果如图38.5.2.3 所示:
从图38.5.2.3 可以看出,“测试文档.txt”的中文内容显示正确,而且中文路径也完全正常,说明我们的根文件系统已经完美支持中文了!
在38.5.1 小节测试hello 软件的时候都是等Linux 启动进入根文件系统以后手动输入命令“./hello”来完成的。我们一般做好产品以后都是需要开机自动启动相应的软件,本节我们就以hello 这个软件为例,讲解一下如何实现开机自启动。前面我们说过了,进入根文件系统的时候会运行/etc/init.d/rcS 这个shell 脚本,因此我们可以在这个脚本里面添加自启动相关内容。添加完成以后的/etc/init.d/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 /
自启动代码添加完成以后就可以重启开发板,看看hello 这个软件会不会自动运行。结果如图38.5.3.1 所示:
从图38.5.3.1 可以看出,hello 开机自动运行了,说明开机自启动成功。
通过ping 命令来ping 一下百度的官网:www.baidu.com。输入如下命令:
ping www.baidu.com
可以看出,测试失败,提示www.baidu.com 是个“bad address”,也就是地址不对,显然我们的地址是正确的。之所以出现这个错误提示是因为www.baidu.com 的地址解析失败了,并没有解析出其对应的IP 地址。
我们需要配置域名解析服务器的IP地址,一般域名解析地址可以设置为所处网络的网关地址,比如192.168.1.1。也可以设置为114.114.114.114,这个是运营商(好像是电信的)的域名解析服务器地址。
在rootfs 中新建文件/etc/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 一下百度官网,结果如图38.5.4.2
所示:
可以看出ping 百度官网成功了!域名也成功的解析了,至此!我们的根文件系统就彻底的制作完成,这个根文件系统最好打包保存一下,防止以后做实验不小心破坏了根文件系统而功亏一篑,又得从头制作根文件系统。uboot、Linux kernel、rootfs 这三个共同构成了一个完整的Linux 系统,现在的系统至少是一个可以正常运行的系统,后面我们就可以在这个系统上完成Linux 驱动开发的学习。