目录

前言

具体步骤 

一.编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录

二.利用交叉编译工具链,构建/lib目录

三.手工构建/etc目录 

四.手工构建最简化的/dev目录

五.创建其它空目录

六.配置系统自动生成/proc目录和构建完整的/dev目录

七.制作根文件系统的映像文件

squashfs文件系统制作

jffs2文件系统制作

UBI文件系统制作

一些错误的记录

总结

文件系统中一些目录文件的记录

/etc/inittab



前言


简介

FHS(Filesystem Hierarchy Standard)标准介绍

当我们在linux下输入ls  / 的时候,见到的目录结构以及这些目录下的内容都大同小异,这是因为所有的linux发行版在对根文件系统布局上都遵循FHS标准的建议规定。

该标准规定了根目录下各个子目录的名称及其存放的内容:

目录名存放的内容
/bin必备的用户命令,例如ls、cp等
/sbin必备的系统管理员命令,例如ifconfig、reboot等
/dev设备文件,例如mtdblock0、tty1等
/etc系统配置文件,包括启动文件,例如inittab等
/lib必要的链接库,例如C链接库、内核模块
/home普通用户主目录
/rootroot用户主目录
/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

或者自己下载busybox​​​​​​​

https://busybox.net/downloads/

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)



总结

参考 详解制作根文件系统_复兴之路-CSDN博客

Uboot和系统移植(19)------- 根文件系统构建过程详解_big_C的博客-CSDN博客

文件系统中一些目录文件的记录

/etc/inittab

::sysinit:/etc/init.d/rcS

#::respawn:-/bin/sh
#::askfirst:-/bin/sh
#::askfirst:-/bin/login
::askconsole:/bin/login

# Stuff to do before rebooting
#::ctrlaltdel:/sbin/reboot 
::shutdown:/usr/bin/killall klogd
::shutdown:/usr/bin/killall syslogd
::shutdown:/bin/sync
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

# Stuff to do when restarting the init process
::restart:/sbin/init

# Put a getty on the serial port 0
::respawn:/sbin/getty -L ttyS0 115200 vt100 

屏蔽掉串口登录栏:

::respawn:/sbin/getty -L ttyS0 115200 vt100  它指定了在特定的终端(TTYS0)上启动一个getty。这个getty将使用vt100终端类型,波特率为115200。

屏蔽掉此行,串口登录将会被屏蔽,如下,串口将不会有登录栏

给串口增加登录密码

::respawn:/sbin/getty -L ttyS0 115200 vt100  即可

部分平台可能不管用,此时可以

注释掉#console::respawn:/sbin/getty -L console 115200 vt100 # GENERIC_SERIAL

添加即可

::sysinit:/bin/hostname noname
::respawn:-/bin/login

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐