根文件系统简介
根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是 FATFS、 FAT、 EXT4、 YAFFS 和 NTFS 等这样的文件系统。在这里,根文件系统并不是 FATFS 这样的文件系统代码, EXT4 这样的文件系统代码属于 Linux 内核的一部分。 Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹), 在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如 rcS, inittab 等。根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统, Linux 内核在启动的时候就会提示内核崩溃(Kernel panic)的提示。根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的 ls、 mv、 ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。在构建根文件系统之前,我们先来看一下根文件系统里面大概都有些什么内容,以 Ubuntu为例,根文件系统的目录名字为‘ /’1、 /bin 目录看到“ bin”大家应该能想到 bin 文件, bin 文件就是可执行文件。所以此目录下存放着系统需要的可执行文件,一般都是一些命令,比如 ls、 mv 等命令。此目录下的命令所有的客户都可以使用。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可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中。
BusyBox 构建根文件系统
38.2.1 BusyBox 简介上一小节说了,根文件系统里面就是一堆的可执行文件和其他文件组成的?难道我们得一个一个的从网上去下载这些文件?显然这是不现实的!那么有没有人或者组织专门干这个事呢?他们负责“收集”这些文件,然后将其打包,像我们这样的开发者可以直接拿来用。答案是有的,它就叫做 BusyBox!其名字分为“ Busy”和“ Box”,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。 BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、 mv、 ifconfig 等命令 BusyBox 都会提供。 BusyBox 就是一个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。BusyBox 可以在其官网下载到,官网地址为: /目前最新的 BusyBox 版本是 1.31.0,不过我建议大家使用我们开发板光盘里面提供的 1.29.0 版本的 BusyBox。因为笔者测试 1.29.0 版本目前还没有出现任何问题,路径为: 1、例程源码->6、 BusyBox 源码->busybox-1.29.0.tar.bz2, BusyBox 准备好以后就可以构建根文件系统了。38.2.2 编译 BusyBox 构建根文件系统一般我们在 Linux 驱动开发的时候都是通过 nfs 挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到 EMMC 或者 NAND 中。所以要在 4.2.1 小节中设置的 nfs 服务器目录中创建一个名为 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 目录中1、修改 Makefile,添加编译器同 Uboot 和 Linux 移植一样,打开 busybox 的顶层 Makefile,添加 ARCH 和 CROSS_COMPILE的值,如下所示:示例代码 38.2.2.1 Makefile 代码段164 CROSS_COMPILE ?= /usr/local/arm/gcc-linaro-4.9.4-.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-......190 ARCH ?= armCORSS_COMPILE 使用了绝对路径!主要是为了防止编译出错。2、 busybox 中文字符支持如果默认直接编译 busybox 的话,在使用 SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“ ?”,比如你的中文目录,中文文件都显示为“ ?”。不知道从哪个版本开始 busybox中的 shell 命令对中文输入即显示做了限制,即使内核支持中文但在 shell 下也依然无法正确显示。所以我们需要修改 busybox 源码,取消 busybox 对中文显示的限制,打开文件 busybox-1.29.0/libbb/printable_string.c,找到函数 printable_string,缩减后的函数内容如下:示例代码 38.2.2.2 libbb/printable_string.c 代码段12 const char* FAST_FUNC printable_string(uni_stat_t *stats, const char*str)13 {14 char *dst;15 const char *s;1617 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 }3536 #if ENABLE_UNICODE_SUPPORT37 dst = unicode_conv_to_printable(stats, str);38 #else39 {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 }第 31 和 32 行,当字符大于 0X7F 以后就跳出去了。第 45 和 46 行,如果支持 UNICODE 码的话,当字符大于 0X7F 就直接输出‘ ?’。所以我们需要对这 4 行代码进行修改,修改以后如下所示:示例代码 38.2.2.3 libbb/printable_string.c 代码段12 const char* FAST_FUNC printable_string(uni_stat_t *stats, const char*str)13 {14 char *dst;15 const char *s;1617 s = str;18 while (1) {......30 if (c < ' ')31 break;32 /* 注释掉下面这个两行代码 */33 /* if (c >= 0x7f)34 break; */35 s++;36 }3738 #if ENABLE_UNICODE_SUPPORT39 dst = unicode_conv_to_printable(stats, str);40 #else41 {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 }红色部分的代码就是被修改以后的,主要就是禁止字符大于 0X7F 以后 break 和输出‘ ?’。接着打开文件 busybox-1.29.0/libbb/unicode.c,找到如下内容:示例代码 38.2.2.4 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;10091010 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 }第 1022 行,当字符大于 0X7F 以后, *d++就为‘ ?’。第 1030 和 1031 行,当字符大于 0X7F 以后, *d 也为‘ ?’。修改示例代码 38.2.2.4,修改后内容如下所示:示例代码 38.2.2.5 libbb/unicode.c 代码段1003 static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t*stats, const char *src, unsigned width, int flags)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 }红色部分的代码就是被修改以后的,同样主要是禁止字符大于 0X7F 的时候设置为‘ ?’。 busybox 中文字符支持跟代码修改有关的就改好了,最后还需要配置 busybox来使能 unicode 码,这个稍后我们配置 busybox 的时候在设置。3、配置 busybox根我们编译 Uboot、 Linux kernel 一样,我们要先对 busybox 进行默认的配置,有以下几种配置选项:①、 defconfig,缺省配置,也就是默认配置选项。②、 allyesconfig,全选配置,也就是选中 busybox 的所有功能。③、 allnoconfig,最小配置。我们一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下 busybox:make defconfigbusybox 也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:make menuconfig配置路径如下: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 (16 kb) //确保下面的全部选中,默认都是选中的最后就是使能 busybox 的 unicode 编码以支持中文,配置路径如下:Location:-> Settings-> Support Unicode //选中-> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中busybox 的配置就到此结束了,大家也可以根据自己的实际需求选择配置其他的选项4、编译 busybox配置好 busybox 以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编译结果存放到前面创建的 rootfs 目录中,输入如下命令:make install CONFIG_PREFIX=/home/zuozhongkai/linux/nfs/rootfsCOFIG_PREFIX 指 定 编 译 结 果 的 存 放 目 录 , 比 如 我 存 放 到“ /home/zuozhongkai/linux/nfs/rootfs”目录中,等待编译完成。编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中。rootfs 目录下有 bin、 sbin 和 usr 这三个目录,以及 linuxrc 这个文件。前面说过 Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,我们继续来完善 rootfs。38.2.3 向根文件系统添加 lib 库1、向 rootfs 的“ /lib”目录添加库文件Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要先根文件系统中添加动态库。在 rootfs 中创建一个名为“ lib”的文件夹,命令如下:mkdir liblib 文件创建好了,库文件从哪里来呢? lib 库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了“ /usr/local/arm/”目录中。交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们作为初学者肯定不知道,既然我不知道那就简单粗暴的把所有的库文件都放到我们的根文件系统中。进入如下路径对应的目录:/usr/local/arm/gcc-linaro-4.9.4-.01-x86_64_arm-linux-gnueabihf/arm-linuxgnueabihf/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-.08-1-git.so 上,输入命令“ ls ld-linux-armhf.so.3 -l”查看此文件详细信息ld-linux-armhf.so.3 后面有个“ ->”,表示其是个软连接文件,链接到文件 ld-2.19-.08-1-git.so,因为其是一个“快捷方式”,因此大小只有 24B。但是, ld-linuxarmhf.so.3 不能作为符号链接,否则的话在根文件系统中执行程序无法执行!所以我们需要 ldlinux-armhf.so.3 完成逆袭,由“快捷方式”变为“本尊”,方法很简单,那就是重新复制 ld-linuxarmhf.so.3,只是不复制软链接即可,先将 rotfs/lib 中的 ld-linux-armhf.so.3 文件删除掉,命令如下:rm ld-linux-armhf.so.3然 后 重 新 进 入 到 /usr/local/arm/gcc-linaro-4.9.4-.01-x86_64_arm-linux-gnueabihf/armlinux-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 文件详细信息此时 ld-linux-armhf.so.3 已经不是软连接了,而是实实在在的一个库文件,而且文件大小为 724392B。继续进入如下目录中:/usr/local/arm/gcc-linaro-4.9.4-.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib此目录下也有很多的的*so*和.a 库文件,我们将其也拷贝到 rootfs/lib 目录中,命令如下:cp *so* *.a /home/zuozhongkai/linux/nfs/rootfs/lib/ -drootfs/lib 目录的库文件就这些了2、向 rootfs 的“ usr/lib”目录添加库文件在 rootfs 的 usr 目录下创建一个名为 lib 的目录,将如下目录中的库文件拷贝到 rootfs/usr/lib目录下:/usr/local/arm/gcc-linaro-4.9.4-.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至此,根文件系统的库文件就全部添加好了,可以使用“ du”命令来查看一下 rootfs/lib 和rootfs/usr/lib 这两个目录的大小,命令如下:cd rootfs //进入根文件系统目录du ./lib ./usr/lib/ -sh //查看 lib 和 usr/lib 这两个目录的大小38.2.4 创建其他文件夹在根文件系统中创建其他文件夹,如 dev、 proc、 mnt、 sys、 tmp 和 root 等
根文件系统初步测试
接下来我们使用测试一下前面创建好的根文件系统 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>:<gwip>:<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 rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off启动开发板,进入 uboot 命令行模式,然后重新设置 bootargs 环境变量,命令如下:setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off' //设置 bootargssaveenv //保存环境变量设置好以后使用“ boot”命令启动 Linux 内核大家注意,在进入根文件系统的时候会有下面这一行错误提示:can't run '/etc/init.d/rcS': No such file or directory提示很简单,说是无法运行“ /etc/init.d/rcS”这个文件,因为这个文件不存在。
完善根文件系统
38.4.1 创建/etc/init.d/rcS 文件rcS 是个 shell 脚本, Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。在 rootfs 中创建/etc/init.d/rcS 文件,然后在 rcS 中输入如下所示内容:示例代码 38.4.1.1 /etc/init.d/rcS 文件1 #!/bin/sh3 PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH4 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib5 export PATH LD_LIBRARY_PATH7 mount -a8 mkdir /dev/pts9 mount -t devpts devpts /dev/pts1011 echo /sbin/mdev > /proc/sys/kernel/hotplug12 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 文档。示例代码 38.4.1.1 中的 rcS 文件内容是最精简的,大家如果去看 Ubuntu 或者其他大型 Linux操作系统中的 rcS 文件,就会发现其非常复杂。因为我们是初次学习,所以不用搞这么复杂的,而且这么复杂的 rcS 文件也是借助其他工具创建的,比如 buildroot 等。创建好文件/etc/init.d/rcS 以后一定要给其可执行权限!使用如下命令给予/ec/init.d/rcS 可执行权限:chmod 777 rcS设置好以后就重新启动 Linux 内核提示找不到/etc/fstab 文件,还有一些其他的错误,我们先把/etc/fstab这个错误解决了。说不定把这个问题解决以后其他的错误也就解决了。前面我们说了“ mount -a”挂载所有根文件系统的时候需要读取/etc/fstab,因为/etc、 fstab 里面定义了该挂载哪些文件,好了,接下来就是创建/etc/fstab 文件。38.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 文件中输入如下内容:示例代码 38.4.2.1 /etc/fstab 文件1 #<file system> <mount point> <type> <options> <dump> <pass>2 proc /proc proc defaults 0 03 tmpfs /tmp tmpfs defaults 0 04 sysfs /sys sysfs defaults 0 0fstab 文件创建完成以后重新启动 Linux,启动成功,而且没有任何错误提示。但是我们要需要创建一个文件/etc/inittab。38.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 支持的动作如表 38.4.3.1 所示:动作 描述sysinit 在系统初始化的时候 process 才会执行一次。respawn 当 process 终止以后马上启动一个新的。askfirst 和 respawn 类似,在运行 process 之前在控制台上显示“ Please press Enter to activatethis console.”。只要用户按下“ Enter”键以后才会执行 process。wait 告诉 init,要等待相应的进程执行完以后才能继续执行。once 仅执行一次,而且不会等待 process 执行完成。restart 当 init 重启的时候才会执行 procee。ctrlaltdel 当按下 ctrl+alt+del 组合键才会执行 process。shutdown 关机的时候执行 process。表 38.4.3.1 动作<process>: 具体的动作,比如程序、脚本或命令等。参考 busybox 的 examples/inittab 文件,我们也创建一个/etc/inittab,在里面输入如下内容:示例代码 38.4.3.1 /etc/inittab 文件1 #etc/inittab2 ::sysinit:/etc/init.d/rcS3 console::askfirst:-/bin/sh4 ::restart:/sbin/init5 ::ctrlaltdel:/sbin/reboot6 ::shutdown:/bin/umount -a -r7 ::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,也就是关闭交换分区。/etc/initta 文件创建好以后就可以重启开发板即可,至此!根文件系统要创建的文件就已经全部完成了。
根文件系统其他功能测试
38.5.1 软件运行测试我们使用 Linux 的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“ drivers”的文件夹,以后我们学习 Linux 驱动的时候就把所有的实验文件放到这个文件夹里面。在 ubuntu 下使用 vim 编辑器新建一个 hello.c 文件,在 hello.c 里面输入如下内容:示例代码 38.5.1.1 hello.c 文件1 #include <stdio.h>2 3int 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 可执行文件。使用“ file”命令查看文件类型以及编码格式:file hello //查看 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 //执行 hellohello 执行的时候终端是没法用的,除非使用“ ctrl+c”来关闭 hello,那么有没有办法既能让 hello 正常运行,而且终端能够正常使用?那肯定是有的,让 hello 进入后台运行就行了,让一个软件进入后台的方法很简单,运行软件的时候加上“ &”即可,比如“ ./hello &”就是让 hello 在后台运行。在后台运行的软件可以使用“ kill -9 pid(进程 ID)”命令来关闭掉,首先使用“ ps”命令查看要关闭的软件 PID 是多少, ps 命令用于查看所有当前正在运行的进程,并且会给出进程的 PID。输入“ ps”命令因为 hello 在不断的输出“ hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而 hello 是输出。在数据流上是没有打断的,只是显示在 SecureCRT 上就好像被打断了,所以只管输入“ kill -9 166”即可。这个就是 Linux 下的软件后台运行以及如何关闭软件的方法,重点就是 3 个操作:软件后面加“ &”、使用 ps 查看要关闭的软件 PID、使用“ kill -9 pid”来关闭指定的软件。38.5.2 中文字符测试1、设置 SecureCRT 使用 UTF-8 编码因为 Linux 使用的编码格式为 UTF-8,因此要先设置 SecureCRT 的编码格式。打开Options->Session Options…,打开“ Session Options”对话框,选择左侧的“ Appearance”,然后在右侧的“ Character encoding:”栏选择 UTF-8 编码2、创建中文文件在 ubuntu 中向在 rootfs 目录新建一个名为“中文测试”的文件夹,然后在 SecureCRT 下查看中文名能不能显示正确。可以看出“中文测试”这个文件夹显示正常,接着“ touch”命令在“中文测试”文件夹中新建一个名为“测试文档.txt”的文件,并且使用 vim 编辑器在其中输入“这是一个中文测试文件”,借此来测试一下中文文件名和中文内容显示是否正常。38.5.3 开机自启动测试在 38.5.1 小节测试 hello 软件的时候都是等 Linux 启动进入根文件系统以后手动输入命令“ ./hello”来完成的。我们一般做好产品以后都是需要开机自动启动相应的软件,本节我们就以hello 这个软件为例,讲解一下如何实现开机自启动。前面我们说过了,进入根文件系统的时候会运行/etc/init.d/rcS 这个 shell 脚本,因此我们可以在这个脚本里面添加自启动相关内容。添加完成以后的/etc/init.d/rcS 文件内容如下:示例代码 38.5.3.1 rcS 文件代码1 #!/bin/sh2 PATH=/sbin:/bin:/usr/sbin:/usr/bin3 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib4 runlevel=S5 umask 0226 export PATH LD_LIBRARY_PATH runlevel7 mount -a9 mkdir /dev/pts10 mount -t devpts devpts /dev/pts1112 echo /sbin/mdev > /proc/sys/kernel/hotplug13 mdev -s1415 #开机自启动16 cd /drivers17 ./hello &18 cd /第 16 行,进入 drivers 目录,因为要启动的软件存放在 drivers 目录下。第 17 行,以后台方式执行 hello 这个软件。第 18 行,退出 drivers 目录,进入到根目录下。自启动代码添加完成以后就可以重启开发板,看看 hello 这个软件会不会自动运行。38.5.4 外网连接测试测试方法很简单,就是通过 ping 命令来 ping 一下百度的官网: 。输入如下命令:ping 测试失败,提示 是个“ bad address”,也就是地址不对,显然我们的地址是正确的。之所以出现这个错误提示是因为 的地址解析失败了,并没有解析出其对应的 IP 地址。我们需要配置域名解析服务器的 IP 地址,一般域名解析地址可以设置为所处网络的网关地址,比如 192.168.1.1。也可以设置为 114.114.1144.114,这个是运营商的域名解析服务器地址。在 rootfs 中新建文件/etc/resolv.conf,然后在里面输入如下内容:示例代码 38.5.4.1 resolv.conf 文件内容1 nameserver 114.114.114.1142 nameserver 192.168.1.1设置很简单, nameserver 表示这是个域名服务器,设置了两个域名服务器地址:114.114.114.114 和 192.168.1.1,大家也可以改为其他的域名服务器试试。如果使用“ udhcpc”命令自动获取 IP 地址,“ udhcpc”命令会修改 nameserver 的值,一般是将其设置为对应的网关地址。修改好以后保存退出,重启开发板!重启以后重新 ping 一下百度官网可以看出 ping 百度官网成功了!域名也成功的解析了,至此!我们的根文件系统就彻底的制作完成,这个根文件系统最好打包保存一下,防止以后做实验不小心破坏了根文件系统而功亏一篑,又得从头制作根文件系统。 uboot、 Linux kernel、 rootfs 这三个共同构成了一个完整的Linux 系统
参考文献
【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.3.pdf