目录
概述
本文记录了根文件系统的一些知识点,Busybox 工具的使用和 最小根文件系统的制作。
概念
根文件系统是什么
根文件系统是特殊用途的文件系统,必须属于某种文件系统格式。那么文件系统是用来干嘛的?
- 首先,存储设备(块设备,像硬盘、 flash 等) 是分块(扇区)的,物理上底层去访问存储设备时是按照块号(扇区号)来访问的。这就很麻烦。
- 其次,文件系统是一些代码,是一套软件,这套软件的功能就是对存储设备的扇区进行管理,将这些扇区的访问变成了对目录和文件名的访问。我们在上层按照特定的目录和文件名去访问一个文件时,文件系统会将这个目录 + 文件名转换成对扇区号的访问。
- 最后,不同的文件系统的差异在于对这些扇区的管理策略和方法不同,如坏块管理、 碎片管理。
根文件系统中有什么
- 最重要的就是 linuxrc
- dev 目录下的设备文件。在 linux 中一切皆是文件,硬件设备被虚拟化成一个设备文件。
- sys 和 proc 目录。可以为空但是必须有,和驱动有关。 属于 linux 中的虚拟文件系统。
- etc 目录。存放运行时配置文件。 /etc 目录下的所有配置文件会直接或者间接的被 /linuxrc 所调用执行,完成操作系统的运行时配置。 etc 目录是制作 rootfs 的关键。
- lib 目录下放的是当前操作系统中的动态和静态链接库文件。
根文件系统的形式
-
镜像文件形式
- 使用专用工具软件制作的可供烧录的镜像文件
- 镜像中包含了根文件系统中的所有文件
- 烧录此镜像类似于对相应分区格式化。
- 镜像文件系统具有一定的格式,格式是内化的,跟文件名后缀是无关的。
-
文件夹形式
- 根文件系统其实就是一个包含特定内容的文件夹而已
- 根文件系统可由任何一个空文件夹添加必要文件构成而成
- 根文件系统的雏形就是在开发主机中构造的文件夹形成的
-
总结
-
镜像文件形式的根文件系统主要目的是用来烧录到块设备上,设备上的内核启动后去挂载它。镜像文件形式的根文件系统是由文件夹形式的根文件系统使用专用的镜像制作工具制作而成的。
-
最初在开发主机中随便 mkdir 创建了一个空文件夹,然后向其中添加一些必要的文件(包括etc 目录下的运行时配置文件、/bin 等目录下的可执行程序、 /lib 目录下的库文件等···)后就形成了一个文件夹形式的 rootfs。 然后这个文件夹形式的 rootfs 可以被 kernel 通过 nfs 方式来远程挂载使用,但是不能用来烧录块设备。我们为了将这个 rootfs 烧录到块设备中,于是用一些专用的软件工具,将其制作成可供烧录的一定格式的根文件系统镜像。
-
文件夹形式的 rootfs 是没有格式的,制作成镜像后就有了一定的 rootfs 格式了, 格式是由我们的镜像制作过程和制作工具来决定的。 每一种格式的镜像制作工具的用法都不同。
-
Busybox 简介
-
busybox是一个C语言写出来的项目,里面包含了很多.c文件和.h文件。
-
busybox 这个程序开发出来就是为了在嵌入式环境下构建根文件系统(以下简称 rootfs)使用的,也就是说他就是专门开发的 init 进程应用程序。
-
busybox 为当前系统提供了一整套的 shell 命令程序集。譬如 vi、cd、mkdir、ls 等。在桌面版的 linux 发行版(譬如 ubuntu、redhat等)中 vi、cd、ls 等都是一个一个的单独的应用程序。但是在嵌入式 linux 中,为了省事我们把 vi、cd 等所有常用的 shell 命令集合到一起构成了一个 shell 命令包,起名叫 busybox。
-
Busybox 在编写过程中对文件大小进行了优化,并考虑了系统资源有限(比如内存等)的情况。与一般的 GNU 工具集动辄几 M 的体积相比,动态链接的 Busybox 只有几百 K,即使是采用静态链接也只有1 M 左右。Busybox 按模块设计,可以很容易地加入、去除某些命令,或增减命令的某些选项。
什么是 linuxrc
- linuxrc 是一个可执行的应用程序。 是应用层的,和内核源码一点关系都没有。
- linuxrc 如果是静态编译连接的那么直接可以运行;如果是动态编译连接的那么我们还必须给他提供必要的库文件才能运行。但是因为我们 linuxrc 这个程序是由内核直接调用执行的, 因此用户没有机会去导出库文件的路径,因此实际上 linuxrc 没法动态连接,一般都是静态连接的。
- linuxrc 执行时引出用户界面。操作系统启动后在一系列的自检运行配置之后,最终会给用户一个操作界面(cmdline 或 GUI),这个用户操作界面就是由 /linuxrc 带出来的。用户界面等很多事并不是在 /linuxrc 程序中负责的,用户界面有自己专门的应用程序,但是用户界面的应用程序是直接或者间接的被 linuxrc 调用执行的。用户界面程序和其他的应用程序就是进程 2、3、4 ···,这就是我们说的进程 1(init 进程,也就是 /linuxrc)是其他所有应用程序进程的祖宗进程。
- linuxrc 负责系统启动后的配置。操作系统启动起来后也不能直接用,要配置下。操作系统启动后的应用层的配置(一般叫运行时配置,英文简写 etc),是为了让我们的操作系统用起来更方便,更适合个人的爱好或者实用性。
VFS 简介
-
VFS 是 linux 内核的一种设计理念、设计机制。全拼 vitrual file system,叫虚拟文件系统。
-
具体的一些文件系统如 FAT、 NTFS、 ext2、 ext3、 jffs2、 yaffs2、 ubi 等主要设计目的是为了管理块设备(硬盘、 Nand···)
-
VFS 是借鉴了文件系统的设计理念(通过文件系统将底层难以管理的物理磁盘扇区式访问,转换成目录 + 文件名的方式来访问),将硬件设备的访问也虚拟化成了对目录 + 文件的访问。所以有了 VFS 后我们可以通过设备文件(如/dev/mmcblk0p2) 的方式来访问系统中的硬件设备。
-
VFS 将对硬件设备的访问和对普通文件的访问给接口统一化了(linux 中一切皆是文件)。VFS 成了一个隔离层,隔离了下层的不同文件系统的差异性,对上层应用提供一个统一的接口。
-
VFS 将不同文件系统和下层硬件设备(块设备)驱动之间的细节也给屏蔽了。不同类型的文件系统在本身设计时是 不用考虑各种不同的硬件设备的具体操作差异的,这里有一个类似于 VFS 的设计理念。
-
VFS 机制和 rootfs 挂载与其他文件系统的挂载都是有关联的。内核中有一些 sys proc 这种虚拟文件系统,也是和 VFS 机制有关。 /dev/目录下的设备文件都和 VFS 有关。
Busybox 工具
Busybox 目录结构
目录 | |
---|---|
applets | 主要是实现applets框架的文件 |
applets_sh | 一些有用的脚本,例如:dos2unix、unix2dos等 |
archival | 与压缩有关的命令源文件,例如:bzip2、gzip等 |
configs | 自带的一些默认配置文件 |
console-tools | 与控制台相关的一些命令,例如:setconsole |
coreutils | 常用的核心命令,例如:cat、rm等 |
editors | 常用的编辑命令,例如:vi、diff等 |
findutils | 用于查找的命令,例如:find、grep等 |
init | init进程的实现源文件 |
networking | 与网络相关的命令,例如:telnetl、arp等 |
shell | 与shell相关的实现,例如:ash、msh等 |
util-linux | Linux下常用的命令,主要是与文件系统相关的,例如:mkfs_ext2等 |
Menuconfig 选项说明
Busybox Settings ---> # BusyBox的通用配置,一般采用默认值即可。
Archival Utilities ---> # 压缩、解压缩相关工具。
Coreutils ---> # 最基本的命令,如cat、cp、ls等。
Console Utilities ---> # 控制台相关命令。
Debian Utilities ---> # Debian操作系统相关命令。
Editors ---> # 编辑工具,如vi、awk、sed等。
Finding Utilities ---> # 查找工具,如find、grep、xargs。
Init Utilities ---> # BusyBox init相关命令。
Login/Password Management Utilities ---> # 登陆、用户账号/密码等方面的命令。
Linux Ext2 FS Progs ---> # ext2文件系统的一些工具。
Linux Module Utilities ---> # 加载/卸载模块等相关的命令。
Linux System Utilities ---> # 一些系统命令。
Miscellaneous Utilities ---> # 一些不好分类的命令,如crond、crontab。
Networking Utilities ---> # 网络相关的命令和工具。
Print Utilities ---> # print spool服务及相关工具。
Mail Utilities ---> # mail相关命令。
Process Utilities ---> # 进程相关命令,如ps、kill等。
Runit Utilities ---> # runit程序。
Shells ---> # shell程序。
System Logging Utilities ---> # 系统日志相关工具,如syslogd、klogd。
编译 BusyBox
-
解压 Busybox 源码后,进入 Busybox 目录,打开图像界面配置菜单:
make menuconfig
-
进入配置菜单后,进行相关配置。我们可以静态或者动态编译Busybox,选择动态编译,使得Busybox可执行文件更小,选项开关如下:
[ ] Build BusyBox as a static binary (no shared libs)
-
经过上诉步骤之后,这个时候选择配置界面的 Exit 退出,保存刚刚的配置,之后就可以看到在源代码目录下多了一个 .config 文件。.config 配置文件里面的内容记录了我们刚刚选中的功能。如下所示:
CONFIG_PLATFORM_LINUX=y
每一个都是名值对的形式,名称是一个环境变量,后面的值如果为 Y 代表选中,注释行代表裁减掉的功能。
-
开始编译并安装
make make install
生成的文件将默认安装到目录 _install。
使用 BusyBox 进行调试
BusyBox 编译成功后,在 目录 _install 下可以看到生成的 文件,我们可以继续添加文件,做成一个最小根文件系统。或是直接操作 Busybox 这个可执行文件,用法如下:
./BusyBox 命令 [参数]
其中 命令 是编译 BusyBox 时选中的命令,用法同 Linux 下的命令,只是加上 ./BusyBox 。比如我们在编译时选中的 flashcp 命令,编译成功后,我们把 BusyBox 放到开发板中,就可以直接使用新加入的 flashcp 命令了,而不用重新编译和烧录根文件系统到开发板中:
./BusyBox flashcp /tmp/nfs/usr.sqsh4 /dev/mtd7
完善根文件系统
添加 inittab 文件
inittab 被 /linuxrc(也就是 busybox)执行时所调用而起作用。存放在/etc 目录下,属于一个运行时配置文件,是文本格式的。实际工作的时候 busybox 会(按照一定的格式)解析这个 inittab文本文件,然后根据解析的内容来决定要怎么工作。inittab 的格式在 busybox 中定义的。
将 inittab 文件放到到我们制作的 rootfs 的根目录下的 /etc/ 目录下,启动并且挂载 rootfs 进入了控制台命令行。当前制作的最小 rootfs 就成功了。典型的 inittab :
#/etc/inittab # 井号是注释
::sysinit:/etc/init.d/rcS # 系统启动以后运行 /etc/init.d/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,也就是关闭交换分区。
-
冒号在里面是分隔符,分隔开各个部分。以行为单位的,行与行之间没有关联,每一行的配置项都是由 3 个冒号分隔开的 4 个配置值共同确定的( id : runlevels : action : process )。有些配置值可以空缺,空缺后冒号不能空缺。
-
action 是一个条件/状态,process 是一个可被执行的程序的 pathname。合起来的意思就是:当满足 action 的条件时就会执行 process 这个程序:busybox 最终会进入一个死循环,在这个死循环中去反复检查是否满足各个 action 的条件,如果某个 action 的条件满足就会去执行对应的 process。
动作 描述 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。
- inittab 的解析是在 busybox/init/init.c/init_main 函数中。执行逻辑是:先通过 parse_inittab 函数解析 /etc/inittab(解析的重点是将 inittab 中的各个 action 和 process 解析出来),然后后面先直接执行 sysinit 和 wait 和 once(只执行一遍),然后在 while(1) 死循环中去执行 respwan 和 askfirst。
添加登陆系统
-
之前添加了 /bin/hostname 在 /etc/sysconfig/HOSTNAME 文件中定义了一个 hostname,实际效果是:命令行下 hostname 命令查到的 host 名字正确。但是命令行的提示符是没有显示的。这个问题的解决就要靠profile 文件。将提供的 profile 文件放入 /etc/ 目录下即可。添加之后命令行提示符前面显示:
[@aston210 ]#
-
profile 文件也是被 busybox(init 进程)自动调用的,所以是认名字的。
-
在 intttab 中有一个配置项 ::askfirst:-/bin/sh,这个配置项作用就是当系统启动后就去执行 /bin/sh,执行这个就会出现命令行。因此我们这样的安排就会直接进入命令行而不会出现登录界面。我们要出现登录界面,就不能直接执行/bin/sh,而应该执行一个负责出现登录界面并且负责管理用户名和密码的一个程序,busybox 中也集成了这个程序(就是/bin/login 和/sbin/gettty),因此我们要在 inittab 中用 /bin/login 或者 /sbin/getty 去替代 /bin/sh:
: :askfirst : -/bin/sh # 启动后按回车不用登陆直接进入命令行 #: : respawn : -/bin/sh # 可去除按回车的步骤。自己进入命令行 : : sysinit : /bin/login # 启动后进入用户登陆程序,
-
passwd 文件中存储的是用户的密码设置,shadow 文件中存储的是加密后的密码。我们直接复制 ubuntu 系统中的/etc/passwd 和/etc/shadow 文件到当前制作的 rootfs 目录下,然后再做修改即可。修改方法:只保留 root 那一行,如
root:x:0:0:root:/root:/bin/sh # passwd 文件,最后是 sh root :tyLxf271Ytqok:0:0:99999:7::: # shadow 文件:123456 的密文
-
重置密码
- ubuntu 中,普通用户登陆后可以使用 su passwd root 给 root 用户设置密码。
- busybox 中没有普通用户,直接使用 passwd root 给 root 用户设置密码。
- 平时有时候我们忘记了自己的操作系统的密码,怎么办?有一种解决方法就是用其他系统( WindowsPE 系统或者 ubuntu 的单用户模式等···)来引导启动,启动后挂载到我们的硬盘上,然后找到 /etc/shadow 文件,去掉密文密码后保存。然后再重启系统后密码就没了。
-
getty 实战
-
inittab 中最常见的用于登录的程序不是 /bin/login,反而是 /sbin/getty。在 busybox 中这两个是一样的,都是 busybox 的符号链接而已,因此不用严格区分这两个。
-
我们可以在 inittab 中用 getty 替换 login 程序来实现同样的效果。
s3c2410_serial2::respawn:/sbin/getty -L s3c2410..... 115200 vt100
-
添加 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 # 挂载所有的文件系统,需存在/etc/fstab 文件
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug # 这两行跟/dev 有关
mdev -s
/bin/hostname -F /etc/sysconfig/HOSTNAME # 设置主机名为 HOSTNAME 写的名字
/etc/init.d/rcS 文件是 linux 的运行时配置文件中最重要的一个,其他的一些配置都是由这个文件引出来的。这个文件可以很复杂也可很简单,里面可以有很多的配置项。可以通过修改 rcS 实现开机自启动,例如添加:
runlevel=S # 表示将系统设置为单用户模式
umask 022 # 设置 linux 系统的 umask 值
ifconfig eth0 192.168.1.10 # 设置 IP,默认为 bootcmd 设置的 IP 地址
-
正式产品中的 rcS 是一个引入,不是真正干活的。真正干活的/etc/init.d/中的其他脚本。
-
创建好文件/etc/init.d/rcS 以后一定要给其可执行权限!
-
当提示文件不存在时,可以通过开发板 vi 查看该文件,看看是不是结尾多出来了一个 ^M,有的话删掉即可。这是因为 rcS 文件在 windows 下创建时,行尾换行符为'\r\n',这个换行符 ubuntu 的 vi 自动进行优化,但是开发板的 vi 却没有,所以只能以^M 显示。
-
启发:shell 脚本文件如果格式不对,运行时可能会被提示文件不存在。有时候一个应用程序执行时也会提示文件不存在,问题可能是这个程序所调用的一个动态链接库找不到。
umask 测试
-
umask 是 linux 的一个命令,作用是设置 linux 系统的 umask 值。umask 值决定当前用户在创建文件时的默认权限。
-
测试结果:
- umask 是 022 的时候,默认 touch 创建一个文件的权限是 644
- umask 是 044 的时候,默认 touch 创建一个文件的权限是 622
- umask 是 444 的时候,默认 touch 创建一个文件的权限是 222
-
总结:umask 的规律就是:umask 值和默认创建文件的权限值加起来是 666。
PATH 测试
- PATH=xxx:这一行定义了一个变量 PATH,值等于后面的字符串。后面用 export 导出了这个 PATH,那么 PATH 就变成了一个环境变量。
- 测试结果:我们的 rcS 文件还没添加,系统启动就有了 PATH 中的值?原因在于 busybox 自己用代码硬编码为我们导出了一些环境变量,其中就有 PATH。因此看不出什么差别。
runlevel 测试
- runlevel 也是一个 shell 变量,并且被导出为环境变量。
- 测试结果:runlevel 执行结果一直是 unknown,因为 busybox 并不支持 runlevel 这个特性。
mount 测试
-
mount命令是用来挂载文件系统的。mount -a 是挂载所有的应该被挂载的文件系统,在 busybox中 mount -a 时 busybox 会去查找一个文件 /etc/fstab 文件,这个文件按照一定的格式列出来所有应该被挂载的文件系统(包括了虚拟文件系统)
-
测试结果:挂载时全部出错:
mount: mounting proc on /proc failed: No such file or directory mount: mounting sysfs on /sys failed: No such file or directory mount: mounting tmpfs on /var failed: No such file or directory mount: mounting tmpfs on /tmp failed: No such file or directory mount: mounting tmpfs on /dev failed: No such file or directory
-
原因:根文件系统中找不到挂载点。所谓挂载点就是我们要将目标文件系统(当然这里都是虚拟文件系统)挂载到当前文件系统中的某一个目录中,这个目录就是挂载点。
-
解决方案:自己在制作的 rootfs 根目录下创建这些挂载点目录即可。
mdev 测试
- mdev 是 udev 的嵌入式简化版本,udev/mdev 是用来配合 linux 驱动工作的一个应用层的软件,udev/mdev 的工作就是配合 linux 驱动生成相应的 /dev 目录下的设备文件。
- 在 rcS 文件中没有启动 mdev 的时候,/dev 目录下启动后是空的;在 rcS 文件中添加上 mdev 有关的 2 行配置项后,再次启动系统后发现/dev 目录下生成了很多的设备驱动文件。
- /dev 目录下的设备驱动文件就是 mdev 生成的,这就是 mdev 的效果和意义。
hostname 测试
- hostname 是 linux 中的一个 shell 命令。命令(hostname xxx)执行后可以用来设置当前系统的主机名为 xxx,直接 hostname 不加参数可以显示当前系统的主机名。
- /bin/hostname -F /etc/sysconfig/HOSTNAME -F 来指定了一个主机名配置文件(这个文件一般文件名叫 hostname 或者 HOSTNAME),可以这这个配置文件里更改主机名。
- 需要加入 profile 文件才能在命令行前显示相关前缀。
动态链接库的拷贝
静态链接:arm-linux-gcc hello.c -o hello_satic -static //加 -static 命令即可
动态链接:arm-linux-gcc hello.c -o hello_dynamic
实验结果:静态编译连接后生成的 hello_satic 已经可以成功运行。而动态编译链接程序不能执行。
分析:静态链接时,把需要的库文件都链接到了一起,可以独立运行,但是体积大(10 倍)
Linux下运行程序时,无论程序是动态编译还是静态编译,都需要一些基本库的支持。若是根文件系统中没有任何库文件,直接运行程序将提示找不到可执行文件,即使该可执行文件就在当前目录下。为了完善根文件系统,我们需要继续 在 rootfs 中创建 /lib 目录,并添加必要的库文件。在不知道需要哪些库文件的情况下,我们可以直接复制 gcc 中的动态库文件。步骤如下:
-
找到动态链接库文件:从交叉编译工具链文件夹中寻找,可使用 find -name “libm.so”定位到目标目录。一般安装的编译工具链目录下,如:/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib ( 仅供参考)
-
复制动态链接库到 roots/lib 目录下。
cp lib/*so* /rootfs/lib/ -rdf # -rdf:保留符号链接而非实体
-
使用 strip 工具去掉库中符号信息:动态链接库 so 文件中包含了调试符号信息,这些符号信息在运行时是没用的(调试时用的),占用空间。在传统的嵌入式系统中 flash 空间是有限的,为了节省空间常常把这些符号信息去掉。这样节省空间并且不影响运行。
arm-linux-strip *so* # 执行后从 30 M 缩小到了 3.8 M
最小 rootfs 目录结构
- dev 目录。在 linux 中一切皆是文件,因此一个硬件设备也被虚拟化成一个设备文件来访问,在 linux 系统中 /dev/xxx 就表示一个硬件设备
- sys 和 proc 目录。该目录在最小 rootfs 中也是,属于 linux 中的虚拟文件系统。
目录 | 说明 | 补充 |
---|---|---|
linuxrc | 最重要 | 必须存在 |
bin | 可执行文件,最主要的是 busybox | 必须存在 |
sbin | 可执行文件,链接指向 /bin/busybox | 必须存在 |
usr | 系统用户所有的一些文件的存放地,busybox 安装时默认创建 | 可以删除 |
etc | 存放配置文件,被 /linuxrc 所调用执行 | 必须存在 |
lib | 存放的是当前操作系统中的动态和静态链接库文件。 | 必须存在 |
dev | 设备文件 | 必须存在 |
sys | 虚拟文件系统,不可省略的,但是只要创建了空文件夹即可 | 必须存在 |
proc | 虚拟文件系统,不可省略的,但是只要创建了空文件夹即可 | 必须存在 |
mnt | 用来挂载 | 可以删除 |