目录
前言
具体步骤
一.编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录
二.利用交叉编译工具链,构建/lib目录
三.手工构建/etc目录
四.手工构建最简化的/dev目录
五.创建其它空目录
六.配置系统自动生成/proc目录和构建完整的/dev目录
七.制作根文件系统的映像文件
squashfs文件系统制作
jffs2文件系统制作
一些错误的记录
总结
前言
简介
FHS(Filesystem Hierarchy Standard)标准介绍
当我们在linux下输入ls / 的时候,见到的目录结构以及这些目录下的内容都大同小异,这是因为所有的linux发行版在对根文件系统布局上都遵循FHS标准的建议规定。
该标准规定了根目录下各个子目录的名称及其存放的内容:
目录名 | 存放的内容 |
/bin | 必备的用户命令,例如ls、cp等 |
/sbin | 必备的系统管理员命令,例如ifconfig、reboot等 |
/dev | 设备文件,例如mtdblock0、tty1等 |
/etc | 系统配置文件,包括启动文件,例如inittab等 |
/lib | 必要的链接库,例如C链接库、内核模块 |
/home | 普通用户主目录 |
/root | root用户主目录 |
/usr/bin | 非必备的用户程序,例如find、du等 |
/usr/sbin | 非必备的管理员程序,例如chroot、inetd等 |
/usr/lib | 库文件 |
/var | 守护程序和工具程序所存放的可变,例如日志文件 |
/proc | 用来提供内核与进程信息的虚拟文件系统,由内核自动生成目录下的内容 |
/sys | 用来提供内核与设备信息的虚拟文件系统,由内核自动生成目录下的内容 |
/mnt | 文件系统挂接点,用于临时安装文件系统 |
/tmp | 临时性的文件,重启后将自动清除 |
制作根文件系统就是要建立以上的目录,并在其中建立完整目录内容。其过程大体包括
- 编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录
- 利用交叉编译工具链,构建/lib目录
- 手工构建/etc目录
- 手工构建最简化的/dev目录
- 创建其它空目录
- 配置系统自动生成/proc目录
- 利用udev构建完整的/dev目录
- 制作根文件系统的映像文件
具体步骤
首先建立根文件系统文件 rootfs
mkdir rootfs
接下来就是生成根目录下各个子目录了
一.编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录
1、找到SDK中提供的busybox
7z x busybox.7z
cd busybox
2、修改Makefile
ARCH ?= mips
CROSS_COMPILE ?= mips-linux-gnu-
这里ARCH会从CROSS_COMPILE 提取,所以我就没有修改
备注:(如果不想修改Makefile也可以通过make menuconfig配置编译编译工具链和编译选项, Busybox Settings --->Build Options 下)
3、make menuconfig配置busybox
busybox配置主要分两部分
第一部分是Busybox Settings,主要编译和安装busybox的一些选项。这里主要需要配置:
1)、Build Options
Build BusyBox as a static binary (no shared libs),表示编译busybox时,是否静态链接C库。我们选择动态链接C库,所以这里不勾选。
2)、Installation Options ("make install" behavior)
What kind of applet links to install (as soft-links) --->(X) as soft-links,表示安装busybox时,将各个命令安装为指向busybox的软链接还是硬链接。我们选择软链接。
Installation Options ("make install" behavior) ---> (./_install) BusyBox installation prefix表示busybox的安装位置,我这里用的是默认的。
3)Busybox Library Tuning
保留Command line editing以支持命令行编辑;
保留History size以支持记忆历史命令;
选中Tab completion和Username completion以支持命令自动补全
我这里默认都是选中的。
第二部分是Applets,他将busybox的支持的几百个命令分门别类。我们只要在各个门类下选择想要的命令即可。这里我们基本保持默认设置。
1)选中Networking Utilities -- httpd下的Enable -u <user> option,以启用http服务器的功能allows the server to run as a specific user
4、编译安装
make defconfig 生成默认配置
make
make install
安装完成之后安装目录_install下生成了bin sbin usr/bin usr/sbin目录,其下包含了我们常用的命令,这些命令都是指向bin/busybox的软链接,而busybox本身的大小不到800K:
cp -r _install/* rootfs
如果没有busybox的话也可以从网上下载,修改项差别不大
从http://www.busybox.net/ 下载busybox-1.7.0.tar.bz2
tar xjvf busybox-1.7.0.tar.bz2解包
修改Makefile文件
175 ARCH ?= arm
176 CROSS_COMPILE ?= arm-linux-
二.利用交叉编译工具链,构建/lib目录
光有应用程序(命令)是不够的,因为应用程序本身需要使用C库的库函数,因此还必需制作C库,并将其放置于/lib目录
- 目标文件,如crtn.o,用于gcc链接可执行文件
- libtool库文件(.la),在链接库文件时这些文件会被用到,比如他们列出了当前库文件所依赖的其它库文件,程序运行时无需这些文件
- gconv目录,里面是各种链接脚本,在编译应用程序时,他们用于指定程序的运行地址,各段的位置等
- 静态库文件(.a),例如libm.a,libc.a
- 动态库文件 (.so、.so.[0-9]*)
- 动态链接库加载器ld-2.3.6.so、ld-linux.so.2
- 其它目录及文件
这里我们可以使用 交叉编译工具链中 现成的cp过来
找到自己的交叉编译链的安装路径
这里我们只用cp动态库就好了
如果我们只制作最简单的系统,那么我们只需要运行busybox这一个应用程序即可。
通过执行# mips-linux-gnu-readelf -a busybox | grep 'Shared'确认busybox运行时需要用到的库
- 实际的共享链接库:libLIBRARY_NAME-GLIBC_VERSION.so。当然需要拷贝。
- 主修订版本的符号链接,指向实际的共享链接库:libLIBRARY_NAME.so.MAJOR_REVISION_VERSION,程序一旦链接了特定的链接库,将会参用该符号链接。程序启动时,加载器在加载程序前,会检索该文件。所以需要拷贝。
- 与版本无关的符号链接,指向主修订版本的符号连接(libc.so是唯一的例外,他是一个链接命令行:libLIBRARY_NAME.so,是为编译程序时提供一个通用条目)。这些文件在程序被编译时会被用到,但在程序运行时不会被用到,所以不必拷贝它。
拷贝以下文件
如果有报错Kernel panic - not syncing: Requested init /linuxrc failed (error -2).
这个错误说明/lib目录下动态库还是不完整,就要检查一下是否漏cp了
关于共享库的2个符号链接的作用的特别说明:
- 当 我们使用gcc hello.c -o hello -lm编译程序时,gcc会根据-lm的指示,加头(lib)添尾(.so)得到libm.so,从而沿着与版本无关的符号链接(libm.so -> libm.so.6)找到libm.so.6并记录在案(hello的ELF头中),表示hello需要使用libm.so.6这个库文件所代表的数学库 中的库函数。而当hello被执行的时候,动态链接库加载器会从hello的ELF头中找到libm.so.6这个记录,然后沿着主修订版本的符号链接 (libm.so.6 -> libm-2.3.6.so)找到实际的共享链接库libm-2.3.6.so,从而将其与hello作动态链接。可见,与版本无关的符号链接是供编译器 使用的,主修订版本的符号链接是供动态链接库加载器使用的,而实际的共享链接库则是供应用程序使用的。
添加厂商工具命令及依赖的动态库
方法类似。mips-linux-gnu-readelf -a 工具名称 | grep 'Shared'。将缺失的库添加即可。
三.手工构建/etc目录
/etc 目录存放的是系统程序的主配置文件,因此需要哪些配置文件取决于要运行哪些系统程序。即使最小的系统也一定会运行1号用户进程init,所以我们至少要手 工编写init的主配置文件inittab。busybox的inittab文件的语法、语义与传统的SYSV的inittab有所不同。
inittab 文件中每个条目用来定义一个需要init启动的子进程,并确定它的启动方式,格式 为<id>:<runlevel>:<action>:<process>。例 如:ttySAC0::askfirst:-/bin/sh
- <id>表示子进程要使用的控制台,若省略则使用与init进程一样的控制台
- <runlevel>表示运行级别,busybox init程序这个字段没有意义
- <action>表示init进程如何控制这个子进程
- sysinit:系统启动后最先执行,只执行一次,init进程等待它结束后才继续执行其它动作
- wait:系统执行完sysinit条目后执行,只执行一次,init进程等待它结束后才继续执行其它动作
- once:系统执行完wait条目后执行,只执行一次,init进程不等待它结束
- respawn:启动完once进程后,init进程监测发现子进程退出时,重新启动它
- askfirst:启动完respawn进程后,与respawn类似,不过init进程先输出” Please press Enter to activate this console“,等用户输入回车后才启动子进程
- shutdown:当系统关机时
- restart:Busybox中配置了CONFIG_FEATURE_USE_INITAB,并且init进程接收到SIGUP信号时执行,先重新读取、解析/etc/inittab文件,再执行restart程序
- ctrlaltdel:按下ctrl+alt+del键时执行,不过在串口控制台中无法输入它
- <process>表示进程对应的二进制文件。如果前面有-号,表示该程序是“可以与用户进行交互的”
1)可以构建最简单的文件内容
我们可以制作最简单的/etc/inittab文件,其内容如下:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a –r
制作最简单的脚本程序文件/etc/init.d/rcS,其内容如下:
#!/bin/sh
ifconfig eth0 192.168.2.17
修改shell脚本文件/etc/init.d/rcS的权限,以使其可被执行:
# chmod a+x /etc/init.d/rcS
2)这里我用的是busybox 的 examples/bootfloppy/etc中的文件
etc 目录可参考系统 /etc 下的文件。其中最主要的文件包括 inittab、fstab、init.d/ rcS 文件等,这些文件最好从 busybox 的 examples/bootfloppy/etc 目录下拷贝过来,根据需求自行修改。
修改inittab文件:
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::once:/usr/sbin/telnetd -l /bin/login
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
------------------------------------------------------
修改fstab 文件 :
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
var /dev tmpfs defaults 0 0
------------------------------------------------------
修改/init.d/rcS文件:
#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
mkdir -p /var/lock
/bin/hostname -F /etc/hostname
# chmod a+x /etc/init.d/rcS, 记得赋权限
------------------------------------------------------
修改 profile文件:
#Ash profile
#vim : syntax=sh
#No core files by default
#ulimit -S -c 0 > /dev/null 2>&1
USER="`id -un`"
LOGNAME=$USER
PS1='[\u@\h \W]# '
PATH=$PATH
HOSTNAME=`/bin/hostname`
echo "hello world."
echo "Done"
export USER LOGNAME PS1 PATH
------------------------------------------------------
echo "mipsT40" > etc/hostname 内容自定,将来作为主机名
3)其他文件
复制宿主机下的 /etc/passwd grop shadow 到此目录下
同时修改 /etc/inittab
console::respawn:/sbin/getty -L console 115200 vt100 # GENERIC_SERIAL
这样就会启动后就需要输入密码才能登陆了
四.手工构建最简化的/dev目录
创建两个设备节点:
#mknod console c 5 1
#mknod null c 1 3
也可以直接从系统中拷贝过来 需要的设备文件。拷贝文件时请使用 cp –R filename。
五.创建其它空目录
根据前面所列的目录,看缺少什么就创建
六.配置系统自动生成/proc目录和构建完整的/dev目录
上面的rcS已经完成了这些操作
1、PATH=xxx
(1)首先从shell脚本的语法角度分析,这一行定义了一个变量PATH,值等于后面的字符串
(2)后面用export导出了这个PATH,那么PATH就变成了一个环境变量。
(3)PATH这个环境变量是linux系统内部定义的一个环境变量,含义是操作系统去执行程序时会默认到PATH指定的各个目录下去寻找。如果找不到就认定这个程序不存在,如果找到了就去执行它。将一个可执行程序的目录导出到PATH,可以让我们不带路径来执行这个程序。
(4)rcS中为什么要先导出PATH?就是因为我们希望一旦进入命令行下时,PATH环境变量中就有默认的/bin, /sbin, /usr/bin, /usr/sbin 这几个常见的可执行程序的路径,这样我们进入命令行后就可以ls、cd等直接使用了。
(5)为什么我们的rcS文件还没添加,系统启动就有了PATH中的值?原因在于busybox自己用代码硬编码为我们导出了一些环境变量,其中就有PATH。
2、runlevel=
(1)runlevel也是一个shell变量,并且被导出为环境变量。
(2)runlevel=S表示将系统设置为单用户模式
3、umask=
(1)umask是linux的一个命令,作用是设置linux系统的umask值。
(2)umask值决定当前用户在创建文件时的默认权限。
4、mount -a
(1)mount命令是用来挂载文件系统的
(2)mount -a是挂载所有的应该被挂载的文件系统,在busybox中mount -a时busybox会去查找一个文件/etc/fstab文件,这个文件按照一定的格式列出来所有应该被挂载的文件系统(包括了虚拟文件系统)
5、mkdir /dev/pts;mount -t devpts devpts /dev/pts
挂载虚拟内核文件设备
6、echo /sbin/mdev > /proc/sys/kernel/hotplug
当有热插拔事件产生时,内核会调用/proc/sys/kernel/hotplug文件里指定的应用程序来处理热插拔事件。
7、mdev -s
自动装配/dev目录下的设备文件
8、/bin/hostname -F /etc/hostname
指定主机名配置文件
七.制作根文件系统的映像文件
可以从以下两种文件系统中选择一种
squashfs:只读文件系统,压缩率高
jffs2:可读写文件系统,可选择压缩方式
squashfs 文件系统是一套基于 Linux 内核使用的压缩只读文件系统,压缩率高。 squashfs 具有如下特点: 优点: 压缩率高,数据(data),节点(inode)和目录(directories)都被压缩,可以节省 flash 空间,最大可以支持 4G 文件系统。 缺点: 无法写操作
squashfs 文件系统是一套基于 Linux 内核使用的压缩只读文件系统,压缩率高。
squashfs 具有如下特点:
优点: 压缩率高,数据(data),节点(inode)和目录(directories)都被压缩,可以节省 flash 空间,最大可以支持 4G 文件系统。
缺点: 无法写操作
jffs2 是 RedHat 的 David Woodhouse 在 jffs2 基础上改进的文件系统,是用于
微型嵌入式设备的原始闪存芯片的实际文件系统。jffs2 文件系统是日志结构化的可读写
的文件系统。jffs2 的优缺点如下:
优点: 使用了压缩的文件格式。最重要的特性是可读写操作。
缺点: jffs2 文件系统挂载时需要扫描整个 jffs2 文件系统,因此当 jffs2 文件系
统分区增大时,挂载时间也会相应的变长。使用 jffs2 格式可能带来少量的 Flash 空间
的浪费。这主要是由于日志文件的过度开销和用于回收系统的无用存储单元,浪费的空
间大小大致是若干个数据段 jffs2 的另一缺点是当文件系统已满或接近满时,jffs2 运行
速度会迅速降低。这是因为垃圾收集的问题
squashfs文件系统制作
# mksquashfs rootfs ./rootfs.squashfs.img -b 64K –comp xz
注:制作之前请先删除要保存 squahfs 目录里面同名的 squashfs 文件
squashfs介绍和安装_mayue_web的博客-CSDN博客_squashfs
jffs2文件系统制作
# mkfs.jffs2 –d ./rootfs -l –e 0x8000 -s 0x1000 -o rootfs.jffs2.img --pad=0x200000
mkfs.jffs2 -o root-uclibc-1.1.jffs2 -r root-uclibc-1.1 -e 0x10000 -s 0x1000 -n -l -X zlib --pad=0x10000
参数简介:
-o, --output=FILE 指定生成文件的文件名.(default: stdout)
-r, -d, --root=DIR 指定需要制作的文件夹目录名.(默认:当前文 件夹)
-e, --eraseblock=SIZE 设定擦除块的大小为(默认: 64KB)
-s, --pagesize=SIZE 节点页大小(默认: 4KB)
-n, --no-cleanmarkers 指明不添加清楚标记(nand flash 有自己的校 检块,存放相关的信息。) 如果挂载后会出现类似:CLEANMARKER node found at 0x0042c000 has totlen 0xc != normal 0x0 的警告,则加上-n 就会消失。
-l, --little-endian 指定使用小端格式
-X, --enable-compressor=COMPRESSOR_NAME 指压缩格式
-p, --pad[=SIZE] 通常用 16 进制表示输出文件系统大小,不足部 分用 0xff 补充
UBI文件系统制作
(1)mkfs.ubifs 制作 UBI 文件系统(不带卷集信息)
(2)mkfs.ubifs -e 0x1f000 -c 568 -m 2048 -d config/ -o config.ubifs -v
(3)参数简介:
-e 0x1f000 : 表示逻辑擦除块(LEB) 的 size。
-c 568 : 最多逻辑可擦除块数目为 568(568*128KB=71MB),这个可根据
ubi volume 数量来设置, 实际上是设置此卷的最大容量。(理论上要给bad block坏块预留点空间,制作的时候尽量预留一点出来,-c 指定该分区最大逻辑擦除块数量,该值随着根文件系统分区的大小和该分区的坏块数调整。)
-m 2048 : flash 最小的读写单元, 一般为 flash 的页大小。
-d config/ : 需要制作成 UBI 文件系统的源文件。
-o config.ubifs : 制作出来的镜像名称为 config.ubifs。
-v : 显示制作的详细过程。
备注:制作成功的 UBI 镜像,是没有创建 volume 的,不能直接烧录使用,需
要进行创建 volume 步骤(挂载时需要配合 ubiattach ,ubimkvol 命令使用)。
简单的脚本挂载(mtd3):
ubiattach -m 3 -d 3 /dev/ubi_ctrl
avlSize=$((((0x$(cat /proc/mtd | grep "mtd3" | cut -d ' ' -f
2)/128/1024)-20)*124*1024))
ubimkvol /dev/ubi3 -N config -s $avlSize
mount -t ubifs /dev/ubi3_0 /mnt
(2)ubinize 制作 UBI 文件系统(带有卷集信息)
ubinize -o config.img -m 2048 -p 128KiB -s 2048 ubinize.cfg -v
参数简介:
-o config.img : 制作出的镜像名称是 config.img。
-m 2048 : flash 最小的读写单元, 一般为 flash 的页大小。
-p 128KiB : 表示物理擦除块(PEB) 的 size, 一般为块大小。
-s 2048 : UBI 头部信息的最小输入输出单元, 一般与最小读写单元
(-m 参数)大小一样。
ubinize.cfg : 制作带 volume UBI 格式的镜像所需要的配置文件。
-v : 显示制作过程中的详细参数。
(3)ubinize.cfg 制作:
$ touch ubinize.cfg
$ vim ubinize.cfg
[ubifs]
mode=ubi
image=config.ubifs
vol_id=0
#vol_size=64MiB
vol_type=dynamic
vol_name=config
vol_flags=autoresize
vol_alignment=1
参数介绍:
mode=ubi : 这是个强制参数, 目前只能是 ubi
image=config.ubifs : 指定 mkfs.ubifs 制作 UBI 文件系统制作的 UBI 镜
像, 作为源文件。
vol_id=0 : 指定卷的 id 为 0。
vol_size=64MiB : 指定该卷的 size 为 64MiB, 使用该参数的时候要注
意当设置了 vol_flags=autoresize, 这个参数将不起作用, 设置成了自动分
配 size。 手动指定 size 的时候, 要注意, 参数的大小不能超过当前分区的
最大 size。
vol_type=dynamic : dynamic: 当前卷为动态卷, 是可读可写的。
static: 表示卷为静态卷, 是只读的。
vol_name=config : 指定当前卷的卷名为 config。
vol_flags=autoresize : 指定当前卷为自动分配 size, 当有一个文件系统有
两个卷的时候只能有一个为 autoresize。
vol_alignment=1 : 指定对齐方式, 默认为 1。
备注:通过 ubinize 制作的 ubi 文件系统可以直接烧录使用
UBI 内部空间开销 大致包含:
2 个可擦除块用来存储 UBI 分区表
1 个可擦除块用来做磨损均衡
1 个可擦除块用来做 atomic LEB 更新
按每 1024 个可擦除块预留 20 个可擦除块的比例,预留空间做坏块管理(不足 1024 的,按 1024 算),假设 预留 B1
每个可擦除块,预留 2 个 page size 存储 EC header 和 VID header,如果 Nand Flash 支持 sub-pages 则每 个可擦除块预留 1 个 page size 存储 EC header 和 VID header
MTD 分区中的坏块个数 B2
坏块管理开销为 MAX(B1,B2) (B1,B2 哪个大就以哪个计算)
举例 以一个 50M 的 MTD 分区,假定可擦除块的 size 为 128KiB,共 400 个可擦除块,page size 为 2KiB,不支持 sub-pages,并且无坏块,则其 UBI 预留空间为:
(20 + 4) * 128KiB + 4KiB * (400 – 20 – 4) = 4576KiB
可以用来创建 volume 的空间为: 50M – 4576KiB = 46624KiB
一些错误的记录
1、文件系统打到内核中启动时报错
VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6
问题原因
文件系统缺少 init软连接
ln -s init bin/busybox
补充:如果不是这个原因 就要检查一下 busybox 和 lib是不是对应的,busybox 必须要使用与之对应的工具链中的库才行。
2、挂载jffs2系统报错 : jffs2: jffs2_scan_eraseblock(): Magic bitmask 0x1985 not found at 0x00000018: 0x2000 instead
我遇到这个问题的原因分区没擦,在uboot里面erase擦除了就好了
jffs2系统挂载不上的常见原因 · 荔枝派Zero指南 · 看云 (kancloud.cn)
总结
参考 详解制作根文件系统_复兴之路
Uboot和系统移植(19)------- 根文件系统构建过程详解_big_C的博客