提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
提示:以下是本篇文章正文内容,下面案例可供参考
一、编译 BusyBox 构建根文件系统
一般我们在 Linux 驱动开发的时候都是通过 nfs 挂载根文件系统的,当产品最终上市开卖的时候才会将根文件系统烧写到 EMMC 或者 NAND 中。所以要在 4.2.1 小节中设置的 nfs 服务器目录中创建一个名为 rootfs 的子目录(名字大家可以随意起,为了方便就用了 rootfs),比如我的电脑中“/home/zero/linux/nfs”就是我设置的 NFS 服务器目录,在nfs目录下使用如下命令创建名为 rootfs 的子目录:
mkdir rootfs
创建好的 rootfs 子目录就用来存放我们的根文件系统了。
将 busybox-1.29.0.tar.bz2 发送到 Ubuntu 中,存放位置大家随便选择。我的在/home/zero/linux/IMX6ULL/tool,然后使用如下命令将其解压:
tar -vxjf busybox-1.29.0.tar.bz2
1 修改 Makefile,添加交叉编译器
同 Uboot 和 Linux 移植一样,打开 busybox 的顶层 Makefile,进入解压的busybox-1.29.0中,添加 ARCH 和 CROSS_COMPILE的值
cd busybox-1.29.0/
vi Makefile
CORSS_COMPILE 使用了绝对路径!主要是为了防止编译出错。如何查看交叉编译器安装的绝对路径
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf$
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf // 绝对路劲
2、 busybox 中文字符支持
如果默认直接编译 busybox 的话,在使用 SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。不知道从哪个版本开始 busybox中的 shell 命令对中文输入即显示做了限制,即使内核支持中文但在 shell 下也依然无法正确显示。所以我们需要修改 busybox 源码,取消 busybox 对中文显示的限制,用vscode打开文件 busybox-1.29.0/libbb/printable_string.c,找到函数 printable_string,函数修改内容如下:
第 33 和 34 行,当字符大于 0X7F 以后就跳出去了。
第 48和 49 行,如果支持 UNICODE 码的话,当字符大于 0X7F 就直接输出‘?’。
所以我们需要对这 4 行代码进行修改,修改以后如下所示:
/* 注释掉下面这个两行代码 */
/* if (c >= 0x7f)
break; */
/* 修改下面代码 */
/*if (c < ' ' || c >= 0x7f)*/
if (c < ' ' )
主要就是禁止字符大于 0X7F 以后 break 和输出‘?’。
接着打开文件 busybox-1.29.0/libbb/unicode.c,找到如下内容:
static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t*stats, const char *src, unsigned width, int flags)
/* 修改下面一行代码 */
/* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
*d++ = (c >= ' ' ) ? c : '?';
/* 修改下面一行代码 */
/* if (c < ' ' || c >= 0x7f) */
if (c < ' ')
同样主要是禁止字符大于 0X7F 的时候设置为‘?’。 busybox 中文字符支持跟代码修改有关的就改好了,最后还需要配置 busybox来使能 unicode 码,这个稍后我们配置 busybox 的时候在设置。
3 配置 busybox
根我们编译 Uboot、 Linux kernel 一样,我们要先对 busybox 进行默认的配置,有以下几种配置选项:
①、 defconfig,缺省配置,也就是默认配置选项。
②、 allyesconfig,全选配置,也就是选中 busybox 的所有功能。
③、 allnoconfig,最小配置。
我们一般使用默认配置即可,因此使用如下命令先使用默认配置来配置一下 busybox:
make defconfig
检查有无配置成功,有.config就成功
busybox 也支持图形化配置,通过图形化配置我们可以进一步选择自己想要的功能,输入如下命令打开图形化配置界面:
make menuconfig //vscode终端里太小,去linux终端下
配置路径如下:
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 的配置就到此结束了,大家也可以根据自己的实际需求选择配置其他的选项,不过对于初学者笔者不建议再做其他的修改,可能会出现编译出错的情况发生。
4、编译 busybox
配置好 busybox 以后就可以编译了,我们可以指定编译结果的存放目录,我们肯定要将编译结果存放到前面创建的 rootfs 目录中,输入如下命令:
make install CONFIG_PREFIX=/home/zero/linux/nfs/rootfs
COFIG_PREFIX 指 定 编 译 结 果 的 存 放 目 录 , 比 如 我 存 放 到“/home/zero/linux/nfs/rootfs”目录中,等待编译完成。编译完成以后如图所示:
编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中, rootfs 目录内容如图 所示:
从图 可以看出, rootfs 目录下有 bin、 sbin 和 usr 这三个目录,以及 linuxrc 这个文件。前面说过 Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,我们继续来完善 rootfs。
二 向根文件系统添加 lib 库
1 向 rootfs 的“/lib”目录添加库文件
Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要向根文件系统中添加动态库。在 rootfs 中创建一个名为“lib”的文件夹,命令如下:
mkdir lib
lib 文件夹创建好了,库文件从哪里来呢? lib 库文件从交叉编译器中获取,前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们作为初学者肯定不知道,既然我不知道那就简单粗暴的把所有的库文件都放到我们的根文件系统中。这样做出来的根文件系统肯定很大,但是我们现在是学习阶段,还做不了裁剪。
进入根目录下,路径对应的目录:
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linuxgnueabihf/libc/lib
此目录下有很多的so(是通配符)和.a 文件,这些就是库文件,将此目录下所有的so*和.a文件都拷贝到 rootfs/lib 目录中, 拷贝命令如下:
cp *so* *.a /home/zero/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”查看此文件详细信息,如图 所示:
从图 可以看出, ld-linux-armhf.so.3 后面有个“->”,表示其是个软连接文件,链接到文件 ld-2.19-2014.08-1-git.so,因为其是一个“快捷方式”,因此大小只有 24B。但是, 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
然 后 重 新 进 入 到 /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/armlinux-gnueabihf/libc/lib 目录中,重新拷贝 ld-linux-armhf.so.3,命令如下:
cp ld-linux-armhf.so.3 /home/zero/linux/nfs/rootfs/lib/
拷贝完成以后再到 rootfs/lib 目录下查看 ld-linux-armhf.so.3 文件详细信息,如图 所示
从图 38.2.3.2 可以看出,此时 ld-linux-armhf.so.3 已经不是软连接了,而是实实在在的一个库文件,而且文件大小为 724392B。
继续进入如下目录中:
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib
此目录下也有很多的的so和.a 库文件,我们将其也拷贝到 rootfs/lib 目录中,命令如下:
cp *so* *.a /home/zero/linux/nfs/rootfs/lib/ -d
rootfs/lib 目录的库文件就这些了,完成以后的 rootfs/lib 目录如所示:
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/zero/linux/nfs/rootfs/usr/lib/ -d
完成以后的 rootfs/usr/lib 目录如图 所示:
至此,根文件系统的库文件就全部添加好了,可以使用“du”命令来查看一下 rootfs/lib 和
rootfs/usr/lib 这两个目录的大小,命令如下:
cd rootfs //进入根文件系统目录
du ./lib ./usr/lib/ -sh //查看 lib 和 usr/lib 这两个目录的大小
结果如图 所示:
可以看出lib和usr/lib这两个文件的大小分别为57MB和67MB,加起来就是57+67=124MB。非常大!所以正点原子的 256MB 和 512MB 的 NAND 核心版就不是给初学者准备的,而是给大批量采购的企业准备的,
4 创建其他文件夹
在根文件系统中创建其他文件夹,如 dev、 proc、 mnt、 sys、 tmp 和 root 等,创建完成以后
mkdir dev proc mnt sys tmp root
三 根文件系统初步测试
为了方便测试,我们采用挂在网络根文件系统,nfs。要求:
1、linux内核网络驱动要工作正常。
2、设置uboot的bootargs,也就是linux内核的命令行参数。
从ubuntu的tftpboot里面加载我们前面移植的uboot、zImage和dtb,设置bootcmd
setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmv.dtb;80800000 - 83000000'
saveenv
接下来我们使用测试一下前面创建好的根文件系统 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>
根据上面的格式 bootargs 环境变量的 root 值如下:
root/dev/nfs nfsroot=192.168.1.66:/home/zero/linux/nfs/rootfs ip=192.168.1.50:192.168.1.66: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.66:/home/zero/linux/nfs/rootfs ip=192.168.1.50:192.168.1.66:192.168.1.1:255.255.255.0::eth0:off'
=> saveenv
boot
设置好以后使用“boot”命令启动 Linux 内核,结果如图 所示:
也就是提示 文件系统为只读,这是因为booargs参数没设置全。需要在1152000 后面加rw可读可写
setenv bootargs 'console=ttymxc0,115200 rw root/dev/nfs nfsroot=192.168.1.66:/home/zero/linux/nfs/rootfs ip=192.168.1.50:192.168.1.66:192.168.1.1:255.255.255.0::eth0:off'
=> saveenv
boot
在不同平台下创建文件都会有显示
注意,在进入根文件系统的时候会有下面这一行错误提示。提示很简单,说是无法运行“/etc/init.d/rcS”这个文件,因为这个文件不存在。看来我们的 rootfs 还是缺文件啊,没什么说的,一步一步的完善吧
can't run '/etc/init.d/rcS': No such file or directory
四 完善根文件系统
1 创建/etc/init.d/rcS 文件
rcS 是个 shell 脚本, Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。在 rootfs 中创建/etc/init.d/rcS 文件,
mkdir etc
cd etc
mkdir init.d
cd init.d
vi 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
创建好文件/etc/init.d/rcS 以后一定要给其可执行权限! 使用如下命令给予/ec/init.d/rcS 可执行权限:
chmod 777 rcS
2 创建/etc/fstab 文件
在 rootfs 中创建/etc/fstab 文件, fstab 在 Linux 开机以后自动配置哪些需要自动挂载的分区,格式如下:
<file system> <mount point> <type> <options> <dump> <pass>
cd etc
vi fstab
#<file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaylts 0 0
fstab 文件创建完成以后重新启动 Linux,启动成功,而且没有任何错误提示。但是我们要还需要创建一个文件/etc/inittab。具体说明在正点原子文档 38.4.3 创建/etc/inittab 文件
#etc/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
/etc/inittab 文件创建好以后就可以重启开发板即可,至此!根文件系统要创建的文件就已经全部完成了。接下来就要对根文件系统进行其他的测试,比如我们自己编写的软件运行是否正常、是否支持软件开机自启动、中文支持是否正常以及能不能链接等。
五 根文件系统其他功能测试
1 软件运行测试
我们使用 Linux 的目的就是运行我们自己的软件,我们编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,以后我们学习 Linux 驱动的时候就把所有的实验文件放到这个文件夹里面。
在 ubuntu 下使用 vim 编辑器新建一个 hello.c 文件,肯定要在nfs/rootfs目录下,在 hello.c 里面输入如下内容:
/home/zero/linux/nfs/rootfs // 文件目录
#include <stdio.h>
int main(void)
{
while(1) {
printf("hello world!\r\n");
sleep(2);
}
return 0;
}
编写好以后就是编译,因为我们是要在 ARM 芯片上运行的,所以要用交叉编译器去编译,也就是使用 arm-linux-gnueabihf-gcc 编译,命令如下:
arm-linux-gnueabihf-gcc hello.c -o hello // 将编译过的文件命名为hello
使用 arm-linux-gnueabihf-gcc 将 hello.c 编译为 hello 可执行文件。这个 hello 可执行文件究竟是不是 ARM 使用的呢?使用“file”命令查看文件类型以及编码格式:
file hello //查看 hello 的文件类型以及编码格式
可以看出,输入“file hello”输入了如下所示信息:
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked……
hello 是个 32 位的 LSB 可执行文件, ARM 架构的,并且是动态链接的。所以我们编译出来的 hello 文件没有问题。,在开发板中输入如下命令来执行这个可执行文件:
./hello //执行 hello
可以看出, hello 这个软件运行正常,说明我们的根文件系统中的共享库是没问题的,要想终止 hello 的运行,按下“ctrl+c”组合键即可。此时大家应该能感觉到, hello 执行的时候终端是没法用的,除非使用“ctrl+c”来关闭 hello,那么有没有办法既能让 hello 正常运行,而且终端能够正常使用?那肯定是有的,让 hello 进入后台运行就行了,让一个软件进入后台的方法很简单,运行软件的时候加上“&”即可,比如“./hello &”就是让 hello 在后台运行。在后台运行的软件可以使用“kill -9 pid(进程 ID)”命令来关闭掉,首先使用“ps”命令查看要关闭的软件 PID 是多少, ps 命令用于查看所有当前正在运行的进程,并且会给出进程的 PID。输入“ps”命令,结果如图
从图 可以看出 hello 对应的 PID 为 70,因此我们使用如下命令关闭在后台运行的hello 软件:
kill -9 70
因为 hello 在不断的输出“hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而 hello 是输出。在数据流上是没有打断的,只是显示在 SecureCRT 上就好像被打断了,所以只管输入“kill -9 70”即可。 hello 被 kill 以后会有提示,如图 38.5.1.4 所示:
再去用 ps 命令查看一下当前的进程,发现没有 hello 了。这个就是 Linux 下的软件后台运行以及如何关闭软件的方法,重点就是 3 个操作:软件后面加“&”、使用 ps 查看要关闭的软件 PID、使用“kill -9 pid”来关闭指定的软件。
2 中文字符测试
1、设置 SecureCRT 使用 UTF-8 编码
因为 Linux 使用的编码格式为 UTF-8,因此要先设置 xshell的编码格式。如图 所示:
2、创建中文文件
在 ubuntu 中向在 rootfs 目录新建一个名为“中文测试”的文件夹,然后在 xshell 下查看中文名能不能显示正确。
mkdir 中文测试
cd 中文测试/
touch 测试文档.txt
gedit 测试文档.txt // 在文件里写内容
cat 测试文档.txt// 运行文件
输入“ls”命令,xshell结果如图 所示
可以看出“中文测试”这个文件夹显示正常,接着“touch”命令在“中文测试”文件夹中新建一个名为“测试文档.txt”的文件,并且使用 vim 编辑器在其中输入“这是一个中文测试文件”,借此来测试一下中文文件名和中文内容显示是否正常。在 xshell 中使用“cat”命令
来查看“测试文档.txt”中的内容,结果如图 所示
测试文档.txt”的中文内容显示正确,而且中文路径也完全正常,说明我们的根文件系统已经完美支持中文了!
3 开机自启动测试
在 38.5.1 小节测试 hello 软件的时候都是等 Linux 启动进入根文件系统以后手动输入命令“./hello”来完成的。我们一般做好产品以后都是需要开机自动启动相应的软件,本节我们就以hello 这个软件为例,讲解一下如何实现开机自启动。前面我们说过了,进入根文件系统的时候会运行/etc/init.d/rcS 这个 shell 脚本,因此我们可以在这个脚本里面添加自启动相关内容。添加完成以的/etc/init.d/rcS 文件内容如下:
结果如下:
从图 可以看出, hello 开机自动运行了,说明开机自启动成功。
4 外网连接测试
这里说的外网不是外国哪些 404 网站的连接测试,而是百度、淘宝等这些网站的测试。也就是说看看我们的开发板能不能上网,能不能和我们的局域网外的这些网站进行通信。测试方法很简单,就是通过 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,然后在里面输入如下内容:
vi resolv.conf
nameserver 114.114.114.114
nameserver 192.168.1.1
设置很简单, nameserver 表示这是个域名服务器,设置了两个域名服务器地址:114.114.114.114 和 192.168.1.1,大家也可以改为其他的域名服务器试试。如果使用“udhcpc”命令自动获取 IP 地址,“udhcpc”命令会修改 nameserver 的值,一般是将其设置为对应的网关地址。修改好以后保存退出,reboot重启开发板!重启以后重新 ping 一下百度官网,结果如图 所示:
reboot
我是开发板和电脑直连,电脑连接wifi,弹幕都说这样ping不同,是因为没加网关,用route命令添加一下就行了,怎么用route命令添加有了解的可以在评论回复一下,谢谢
客服回复:板子直连电脑,ping不通百度。这个是一个网络知识,是需要板子连路由器才能ping’通百度,板子直连电脑,现在板子只是与电脑形成一个局域网而已。