[IMX][UBoot] 17.Linux 根文件系统
目录
1.BusyBox 简介
2.修改 Makefile
3.添加中文支持
4.配置 BusyBox
5.编译 BusyBox
6.添加库文件
6.1.向 /lib 目录添加库文件
6.2.向 /usr/lib 目录添加库文件
7.创建其他目录
8.根文件系统测试
9.添加启动脚本
10.添加文件系统脚本
11.添加配置脚本
12.库文件测试
13.开机自启动测试
14.添加域名解析配置
Linux 中所有目录、文件、驱动等均被组织成一个树状结构,根文件系统 rootfs 就是该文件树的根节点
根文件系统包含了操作系统运行所需的基本组件和数据 (包含驱动、库文件等),位于 Linux 的根目录 / 下
根文件系统是内核启动时挂载的第一个文件系统,系统引导程序会在根文件系统挂载后,将其中的基础服务、脚本等加载至内存中运行,根文件系统一般包含以下部分:
-
核心组件:构建根文件系统所需的软件包、系统库、编译器、Shell 等,提供基本的系统功能,如文件管理、网络访问、进程管理、设备管理等;
-
标准目录结构:符合 Linux 标准文件系统层次结构规范的目录结构,包括 /bin、/sbin、/usr、/etc、/proc 等目录,用于存放系统关键部件的信息、配置、库和命令;
-
设备驱动:各种硬件的驱动程序和内核模块,如串口驱动、网卡驱动等,使 Linux 能够正确地访问硬件设备;
-
网络工具和协议:如 SSH、FTP、HTTP、DHCP 等,提供连接其他网络设备、数据交换和网络管理的能力;
-
调试工具:如 gdb、strace、top 等,帮助定位和解决系统中的各种问题和异常;
根目录 / 中包含以下子目录:
其中常用目录的含义如下:
-
/bin:可执行文件,如 ls 、cd 等命令,所有用户均可使用;
-
/dev:设备文件,如 /dev/ttymxc0 表示串口 0,通过串口 0 收发数据就是读写文件 /dev/ttymxc0;
-
/etc:配置文件,如 SSH 的配置文件 init.d、MySQL 的配置文件 my.cnf 等;
-
/lib:共享库文件,一些命令和用户编写的程序会使用这些库;
-
/mnt:临时挂载目录,可创建空的子目录,如 /mnt/sd、/mnt/usb 等,用于管理挂载的 SD卡、U 盘等设备;
-
/proc:系统启动后将该目录作为 proc 文件系统 (虚拟文件系统) 的挂载点,无实际存储,一般保存系统运行信息相关的临时文件;
-
/usr:软件资源,存放软件;
-
/var:存放一些可改变的数据;
-
/sbin:可执行文件,该目录下的文件或命令只有管理员能使用,主要用于系统管理;
-
/sys:系统启动后作为 sysfs 文件系统的挂载点,sysfs 是一个类似 proc 文件系统的特殊文件系统,sysfs 是基于 ram 的文件系统,没有实际的存储设备,该目录是系统设备管理的重要目录,其通过一定的组织结构向用户提供详细的内核数据结构信息;
-
/opt:可选的文件、软件存放区,由用户选择将哪些文件或软件放到该目录中;
1.BusyBox 简介
BusyBox 是一个集成了大量 Linux 命令和工具的软件,使用该软件可以制作基础的根文件系统
BusyBox 的官网为:BusyBox
点击 Download Source 进入下载页面,在该页面中点击所需的版本开始下载:
Linux 驱动开发一般通过 NFS 挂载文件系统,在虚拟机的 NFS 服务器目录中新建 /rootfs 目录:
2.修改 Makefile
修改 BusyBox 的 Makefile,设置目标架构 ARCH 和交叉编译器 CROSS_COMPILE:
// busybox-1.29.0/Makefile
CROSS_COMPILE ?= arm-linux-gnueabihf-
ARCH ?= arm
3.添加中文支持
BusyBox 默认不支持中文,因此会导致串口中文字符输出为 ?,修改源码取消中文限制:
// busybox-1.29.0/libbb/printable_string.c
const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{...while (1) {...// 注释下面两行,避免字符编码大于 0x7F 时跳出// if (c >= 0x7f)// break;...}...#else{...while (1) {...// 修改判断条件,避免字符编码大于 0x7F 时跳出// if (c < ' ' || c >= 0x7f)if( c < ' ')
----------------------------------------------------------------------------------// /home/alientek/mx/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)
{...if (unicode_status != UNICODE_ON) {...if (flags & UNI_FLAG_PAD) {...while ((int)--width >= 0) {...// 修改判断条件,避免字符编码大于 0x7F 时将字符设置为 ?// *d++ = (c >= ' ' && c < 0x7f) ? c : '?';*d++ = (c >= ' ') ? c : '?';...} else {...while (*d) {...// 修改判断条件,避免字符编码大于 0x7F 时将字符设置为 ?// if (c < ' ' || c >= 0x7f)if (c < ' ')
以上修改的主要目的是,防止字符编码大于 0x7F 时将字符设置为 ?
4.配置 BusyBox
BusyBox 可以使用以下三种配置:
-
defconfig:默认配置;
-
allyesconfig:全选配置,编译全部功能;
-
allnoconfig:最小配置,仅编译必备的功能模块;
一般使用默认配置,使用以下命令编译配置文件:
make defconfig
BusyBox 支持图形化配置,使用以下命令打开图形化配置界面:
make menuconfig
通过图形化配置使能动态编译,配置路径如下:
Settings--> Build static binary (no shared libs)
该选项控制静态编译还是动态编译,静态编译不需要库文件,但是生成的文件较大,动态编译需要库文件支持,但是编译出来的文件较小,这里禁用静态编译 (会导致 DNS 出问题):
选择 VI 风格的编辑命令,配置路径如下:
Settings--> vi-style line editing commands
不使用简化的模块,配置路径如下:
Linux Module Utilities--> Simplified modutils
支持模块化编译 (选中所有支持选项),配置路径如下:
Linux System Utilities--> mdev (16 kb)
使能 unicode 编码以支持中文,配置路径如下:
Settings--> Support Unicode--> Check $LC_ALL, $LC_CTYPE and $LANG environment variables
5.编译 BusyBox
配置完成后编译 BusyBox,指定编译结果存放的目录为之前新建的 /rootfs 目录,编译命令如下:
makemake install CONFIG_PREFIX=/home/alientek/linux/nfs/rootfs
编译完成后会在该目录中生成 /bin、/sbin、/usr 三个目录以及 linuxrc 文件:
Linux 的 init 进程会查找用户空间的 init 程序,通过运行该程序切换至用户态,bootargs 可以将 linuxrc 作为用户空间的 init 程序,因此,用户空间的 init 程序实际上由 BusyBox 生成
6.添加库文件
Linux 中的应用程序可以使用静态库或动态库,静态库会直接链接至应用程序中,因此生成的程序体积较大,而依赖动态库的程序占用空间较小,但是无法独立运行,Linux 中的大部分系统程序或工具均依赖于动态库,因此在制作完根文件系统后,需要在指定目录中添加动态库文件
6.1.向 /lib 目录添加库文件
在 /rootfs 中创建 /lib 目录:
mkdir lib
库文件从交叉编译器中获取,进入交叉编译器所在的目录:
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib
该目录存放动态库文件和静态库文件:
使用以下命令将这些库文件拷贝至 /rootfs 的 /lib 目录中:
cp *so* *.a /home/alientek/linux/nfs/rootfs//lib/ -d
其中 -d 选项表示需要拷贝符号链接,/lib 目录中的 ld-linux-armhf.so.3 为软链接,链接至 ld-2.19-2014.08-1-git.so,因此,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/alientek/linux/nfs/rootfs/lib/
可以看到,拷贝完成后的 ld-linux-armhf.so.3 不再是一个软链接,而是一个实际的文件:
进入以下目录:
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib
将该目录中的库文件拷贝至 /rootfs/lib 中:
cp *so* *.a /home/alientek/linux/nfs/rootfs/lib/ -d
6.2.向 /usr/lib 目录添加库文件
在 /rootfs/usr 中新建 /lib 目录,然后进入以下目录:
cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
将该目录中的库文件拷贝至新建的 /rootfs/usr/lib 目录中:
cp *so* *.a /home/alientek/linux/nfs/rootfs/usr/lib/ -d
至此所有的库文件添加完成
7.创建其他目录
在 /rootfs 中创建其他目录,如 /dev、/proc、/mnt、/sys、/tmp、/root 等,创建完成后 /rootfs 包含以下目录:
8.根文件系统测试
通过 NFS 挂载制作好的根文件系统,测试能否正常运行
uboot 中的 bootargs 环境变量会设置参数 root 的值,因此需要将参数 root 的值改为 NFS 挂载,格式如下:
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 地址,存放 rootfs 主机的 IP 地址,虚拟机的 IP 地址为 172.17.40.88;
-
root-dir:存放根文件系统的路径,虚拟机中 rootfs 的存放路径为 /home/alientek/linux/nfs/rootfs;
-
nfs-options:其他 NFS 选项,一般不设置;
-
client-ip:客户端的 IP 地址,即开发板的 IP 地址,开发板上的 Linux 系统启动后使用该 IP 地址,需要保证和 PC 位于同一网段内,开发板的 IP 地址设置为 172.17.40.10;
-
gw-ip:客户端的网关地址,开发板的网关地址设置为 172.17.40.1;
-
netmask:客户端的子网掩码,开发板的子网掩码设置为 255.255.255.0;
-
hostname:主机名称,一般不设置;
-
device:网卡名称,一般为 eth0、eth1 等,正点原子的开发板使用 ENET2 网卡,名称为 eth0;
-
autoconf:自动配置,一般不使用 (将值设置为 off);
-
dns0-ip:DNS0 服务器的 IP 地址,不使用;
-
dns1-ip:DNS1 服务器的 IP 地址,不使用;
根据以上配置,将环境变量 bootargs 中参数 root 的值设置为:
root=/dev/nfs nfsroot=172.17.40.88:/home/alientek/linux/nfs/rootfs,proto=tcp rw ip=172.17.40.10:172.17.40.88:172.17.40.1:255.255.255.0::eth0:off
-
proto=tcp 表示使用 TCP 协议;
-
rw 表示 NFS 挂载的根文件系统可读可写;
重启开发板后进入 uboot 命令行模式,设置环境变量 bootargs 的值:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=172.17.40.88:/home/alientek/linux/nfs/rootfs,proto=tcp rw ip=172.17.40.10:172.17.40.88:172.17.40.1:255.255.255.0::eth0:off::'saveenv
设置完成后,使用 boot 命令启动 Linux 内核,检查根文件系统是否成功挂载:
从图中可以看出,根文件系统已成功挂载,提示缺少 /etc/init.d/rcS 启动脚本
9.添加启动脚本
rcS 是一个 Bash Shell 脚本,系统启动时调用该脚本设置环境变量、挂载文件系统、启动基本服务等
在 /rootfs 中新建 /etc/init.d/rcS,启动脚本的内容如下:
#!/bin/shPATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATHmount -a
mkdir /dev/pts
mount -t devpts devpts /dev/ptsecho /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
-
环境变量 PATH 保存可能存在可执行文件的目录;
-
环境变量 LD_LIBRARY_PATH 保存库文件所在目录;
-
通过 export 将环境变量 PATH 和 LD_LIBRARY_PATH 导出为全局变量;
-
mount -a 挂载所有的系统文件,这些文件由 /etc/fstab 脚本指定;
-
mkdir /dev/pts 创建 /dev/pts 目录;
-
mount -t devpts devpts /dev/pts 将伪终端设备 devpts 挂载至 /dev/pts 目录;
-
最后两行命令设置使用 mdev 管理热插拔设备,Linux 会在 /dev 目录中自动创建设备节点;
以上为精简的启动脚本,复杂的启动脚本依靠 BuildRoot 制作
使用以下命令为启动脚本赋予可执行权限:
chmod +x rcS
10.添加文件系统脚本
/etc/fstab 脚本用于存放文件系统的相关信息,Linux 启动时通过该脚本获取需要自动挂载的分区,其格式如下:
<file system> <mount point> <type> <options> <dump> <pass>
-
file system:要挂载的特殊设备,可以是块设备,如 /dev/sda 等;
-
mount point:挂载点;
-
type:文件系统类型,如 ext2、ext3、proc、romfs、tmpfs 等;
-
options:挂载选项,一般使用默认选项 defaults (包含 rw、suid、 dev、 exec、 auto、 nouser、async);
-
dump:是否允许备份,一般设置为 0,表示不允许备份;
-
pass:是否进行磁盘检查,一般不在 fstab 中挂载根目录,因此设置为 0;
按照以上配置,脚本 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
11.添加配置脚本
/etc/inittab 脚本用于初始化系统设置,其中定义了系统启动时的运行级别、系统初始化过程、控制台进程及电源故障处理等,系统管理员可以控制系统启动流程、切换运行级别、自定义服务的启动和停止等,init 程序会读取该文件,并设置各个程序的运行级别或执行相应的操作,该文件内容的格式如下:
<id>:<runlevels>:<action>:<process>
-
id:指令标识符,不能重复,BusyBox 根据 ID 指定启动进程的控制台,一般将串口或 LCD 设置为控制台 tty;
-
runlevels:运行等级;
-
action:指定 process 可能执行的操作,BusyBox 支持的操作如下:
-
process:具体的操作,如运行程序、执行脚本等;
参考 BusyBox 的 /examples/inittab 文件,创建 /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
-
::sysinit:/etc/init.d/rcS 指定系统启动后运行 rcS 脚本;
-
console::askfirst:-/bin/sh 将 console (ttymxc0) 作为终端控制台;
-
::restart:/sbin/init 指定重启时运行 /sbin/init;
-
::ctrlaltdel:/sbin/reboot 指定按下 ctrl+alt+del 组合键时运行 /sbin/reboot 重启系统;
-
::shutdown:/bin/umount -a -r 指定关机时运行 /bin/umount 卸载各个文件系统;
-
::shutdown:/sbin/swapoff -a 指定关机时运行 /sbin/swapoff 禁用交换分区 swap;
至此 Linux 系统必备的相关脚本准备完成,重启开发板检查是否可以正常运行
12.库文件测试
程序编译时一般使用动态库,在之前的操作中已经将相关库文件添加至 rootfs 中,使用以下程序测试库文件是否可以正常工作,在 rootfs 中创建 /drivers 目录并在其中新建 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
在开发板上执行该程序,输出结果如下图所示:
程序运行正常,表示添加的库文件没有问题,也可以使用以下命令让 hello 程序在后台运行:
./hello &
这样在 hello 程序运行时,终端仍然可以使用,关闭程序时需要使用 ps -a 查询对应的 PID,然后通过 kill -9 PID 终止程序的运行:
13.开机自启动测试
在 /etc/init.d/rcS 脚本的末尾添加命令,实现开机时自动运行 hello 程序:
#!/bin/shPATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATHmount -a
mkdir /dev/pts
mount -t devpts devpts /dev/ptsecho /sbin/mdev > /proc/sys/kernel/hotplug
mdev -scd /drivers
./hello &
cd /
14.添加域名解析配置
在 rootfs 中新建文件 /etc/resolv.conf,其中的内容设置为:
nameserver 114.114.114.114
nameserver 172.17.40.1
nameserver 表示域名服务器,上面的代码设置了两个域名服务器
使用 udhcpc 命令自动获取域名服务器时,会自动修改 nameserver 的值