本次实验在ubuntu环境下进行。
busybox的官方链接https://busybox.net/
找到某个版本的busybox,点击git
使用git clone
将busybox源码复制到Ubuntu下
默认的busybox源码,是不支持中文字符显示的
需要修改以下几个文件
1、libbb/printable_string.c
文件
显示中文,需要解除对0x7f
字符数值的限制,原来的代码是将超过0x7f
的字符,重新赋值为?
,所以看到的中文字符,全部会显示为?
const char* FAST_FUNC printable_string2(uni_stat_t *stats, const char *str)
{
char *dst;
const char *s;
s = str;
while (1) {
unsigned char c = *s;
if (c == '\0') {
/* 99+% of inputs do not need conversion */
if (stats) {
stats->byte_count = (s - str);
stats->unicode_count = (s - str);
stats->unicode_width = (s - str);
}
return str;
}
if (c < ' ')
break;
/* if (c >= 0x7f) //解除中文字符限制
break;
*/
s++;
}
#if ENABLE_UNICODE_SUPPORT
dst = unicode_conv_to_printable(stats, str);
#else
{
char *d = dst = xstrdup(str);
while (1) {
unsigned char c = *d;
if (c == '\0')
break;
/* if (c < ' ' || c >= 0x7f)
*d = '?';
*/
if (c < ' ') //解除中文字符限制
*d = '?';
d++;
}
if (stats) {
stats->byte_count = (d - dst);
stats->unicode_count = (d - dst);
stats->unicode_width = (d - dst);
}
}
#endif
return auto_string(dst);
}
2、libbb/unicode.c
文件
在unicode_conv_to_printable2
函数下,解除对数值大于0x7f
字符的限制。
//截取部分代码
static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
{
char *dst;
unsigned dst_len;
unsigned uni_count;
unsigned uni_width;
if (unicode_status != UNICODE_ON) {
char *d;
if (flags & UNI_FLAG_PAD) {
d = dst = xmalloc(width + 1);
while ((int)--width >= 0) {
unsigned char c = *src;
if (c == '\0') {
do
*d++ = ' ';
while ((int)--width >= 0);
break;
}
// *d++ = (c >= ' ' && c < 0x7f) ? c : '?';
*d++ = (c >= ' ') ? c : '?';
src++;
}
*d = '\0';
} else {
d = dst = xstrndup(src, width);
while (*d) {
unsigned char c = *d;
// if (c < ' ' || c >= 0x7f)
if (c < ' ')
*d = '?';
d++;
}
}
if (stats) {
stats->byte_count = (d - dst);
stats->unicode_count = (d - dst);
stats->unicode_width = (d - dst);
}
return dst;
}
dst = NULL;
uni_count = uni_width = 0;
dst_len = 0;
while (1) {
busybox默认支持三种自动配置方式
make defconfig
make allyesconfig
make allnoconfig
一般情况下,我们使用默认配置。
当然也可以选择手动配置,与大部分的linux SDK一样,支持menuconfig
图形配置界面
make menuconfig
可以通过配置选项来选择/取消一些应用工具。
比如linux下常见的编辑器,vi
,可以通过Editors–>vi来选择/取消
取消选择静态库编译,即选择动态库, 静态库会有较大的文件生成。
在
Settings --->
Build static binary (no shared libs)
Settings --->
vi-style line editing commands
不知为何,大家都这么做,疑问暂时保存在这里。
在
Linux Module Utilities --->
Simplified modutils
支持mdev相关的内容,默认配置下,已经勾选。
Linux System Utilities --->
mdev
Support /etc/mdev.conf
...
...
Support daemon mode
Settings --->
[*] Support Unicode
[*] Check $LC_ALL, $LC_CTYPE and $LANG environment variables
与uboot、linux kernel的编译类似,不同cpu架构下的rootfs需要对应的工具链来编译,才可以适用到对应架构下
可以通过修改busybox目录下的Makefile
,指定编译工具链和架构。
以编译arm架构为例
vim Makefile
若没有指定arch,makefile会自带获取uname
中的架构名称。也可以手动指定ARCH ?= arm
# 工具链需要对应ubuntu环境下安装的工具链,如果没有对应工具链,需要先安装工具链
CROSS_COMPILE ?= arm-linux-gnueabihf-
ifneq ($(CROSS_COMPILE),)
SUBARCH := $(shell echo $(CROSS_COMPILE) | cut -d- -f1 | sed 's:^.*/::g')
else
SUBARCH := $(shell uname -m)
endif
SUBARCH := $(shell echo $(SUBARCH) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ )
ARCH ?= $(SUBARCH)
或者在编译的时候,传入指定工具链和架构
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
测试rootfs,可以将开发板上的rootfs通过nfs挂载到ubuntu上,这样可以方便修改,不用重新制作镜像,烧录。
busybox编译,支持将编译的产物输出到指定的目录下。
CONFIG_PREFIX=<路径>
的形式,需要先创建输出文件夹。
使用make install
进行编译安装
make install ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- CONFIG_PREFIX=/home/xxx/rootfs/
编译完成后,可以看到rootfs目录下的文件,具备了基本的rootfs文件夹
ls /home/xxx/rootfs/
bin linuxrc sbin usr
还需要添加一些c库,因为编译rootfs的时候选择了非静态编译。
C库是从交叉编译工具链中复制而来
# 首先创建 lib
mkdir lib
# 复制交叉编译工具链中的C库到lib目录下,工具链下的libc/lib/*so*, *.a, -d表示带符号拷贝
cp /opt/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/*so* /opt/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/*.a ./lib/ -d
# 复制工具链下的lib/*so* ,*.a 到rootfs的lib/
cp /opt/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/lib/*.so /opt/gcc-arm-linux-gnueabihf/lib/*.a ./lib -d
# 删除linux armhf.so,是个软连接文件,需要复制实际文件到rootfs目录下
ls -l ld-linux-armhf.so.3
ld-linux-armhf.so.3 -> ld-2.19-2014.08-1-git.so* # 实际上是个软连接
#拷贝真实的ld-linux-armhf.so.3, 暂时不知道为何
rm ld-linux-armhf.so.3
cp /opt/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib/ld-2.19-2014.08-1-git.so ld-linux-armhf.so.3
# 最后拷贝工具链的/usr/lib/*.so ,*.a到rootfs的/usr/lib目录下
mkdir usr/lib
cp /opt/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib/*.so /opt/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib/*.a ./usr/lib/ -d
到这里rootfs就准备完成,查看一下rootfs的大小,总共127M,太大了
du -sh rootfs
4.0K rootfs/sbin
724K rootfs/bin
60M rootfs/lib
4.0K rootfs/usr/sbin
4.0K rootfs/usr/bin
67M rootfs/usr/lib
67M rootfs/usr
127M rootfs/
还需要准备一些空文件夹,给rootfs挂载后使用
mkdir dev # 用于给kernel挂载设备
mkdir etc # 用于装载一些配置
mkdir tmp # 用于装载临时生成的文件
mkdir proc # 用于进程相关存储
mkdir sys # 用于device、class等驱动创建节点目录
进入uboot,配置rootfs,通过nfs挂载rootfs。
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>
192.168.31.90
。/home/allen/Desktop/rootfs/
root=/dev/nfs nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs,proto=tcp rw
ip=192.168.1.251:192.168.1.250: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.250:
/home/zuozhongkai/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.251:192.168.1.250:192.168.1.1:
255.255.255.0::eth0:off' //设置 bootargs
saveenv //保存环境变量
安装nfs
sudo apt-get install nfs-kernel-server
配置nfs目录
vim /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
/home/allen/Desktop/ *(rw,sync,no_root_squash) # 这里设置/home/allen/Desktop/作为nfs共享目录。
(rw,sync,no_root_squash)
设置rw权限。
测试mount是否成功
showmount -e
Export list for allen:
/home/allen/Desktop *
windows尝试挂载nfs
打开电脑cmd
mount 192.168.xx.90:/home/allen/Desktop
挂载成功
打开我的电脑可以看到
到此nfs已经挂载成功,就可以尝试在uboot中通过nfs来挂载rootfs了。
设置bootargs,roofs属性
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.31.90:/home/allen/Desktop/rootfs,nfsvers=3,proto=tcp ip=192.168.31.25:192.168.31.90:192.168.31.1:255.255.255.0::eth0:off'
亿个小细节:
重点是nfsvers=3
,ubuntu20.04,如果不指定nfsvers=3会出现无法连接到nfs的问题。
启动内核检查是否挂载rootfs成功。
[ 3.604707] fec 20b4000.ethernet eth0: Freescale FEC PHY driver [SMSC LAN8710/LAN8720] (mii_bus:phy_addr=20b4000.ethernet:01, irq=-1)
[ 3.644131] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
[ 6.684452] fec 20b4000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx
[ 6.704091] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 6.724370] IP-Config: Complete:
[ 6.727636] device=eth0, hwaddr=40:a9:22:34:68:68, ipaddr=192.168.31.25, mask=255.255.255.0, gw=192.168.31.1
[ 6.738042] host=192.168.31.25, domain=, nis-domain=(none)
[ 6.744063] bootserver=192.168.31.90, rootserver=192.168.31.90, rootpath=
[ 6.751343] gpio_dvfs: disabling
[ 6.754661] can-3v3: disabling
[ 6.757988] ALSA device list:
[ 6.760971] #0: wm8960-audio
[ 6.805225] VFS: Mounted root (nfs filesystem) readonly on device 0:14.
[ 6.814381] devtmpfs: mounted
[ 6.818079] Freeing unused kernel memory: 532K (80b43000 - 80bc8000)
can't run '/etc/init.d/rcS': No such file or directory
可以看到log上获取到了网络, IP地址设置正常,看到logVFS: Mounted root (nfs filesystem) readonly on device 0:14.
说明rootfs挂载正常。
rootfs挂载后,会执行linuxrc
drwxrwxr-x 2 1000 1000 4096 May 16 2022 bin
drwxr-xr-x 6 0 0 2980 Jan 1 00:00 dev
drwxrwxr-x 2 1000 1000 4096 May 16 2022 etc
drwxrwxr-x 2 1000 1000 4096 May 16 2022 lib
lrwxrwxrwx 1 1000 1000 11 May 16 2022 linuxrc -> bin/busybox
drwxrwxr-x 2 1000 1000 4096 May 16 2022 proc
drwxrwxr-x 2 1000 1000 4096 May 16 2022 sbin
drwxrwxr-x 2 1000 1000 4096 May 16 2022 tmp
drwxrwxr-x 5 1000 1000 4096 May 16 2022 usr
可以看到linuxrc -> bin/busybox
,会执行/etc/inittab
脚本,然后在调用/etc/init.d/rcS
文件配置一些基本的shell变量与环境
/etc/init.d/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
第 1 行,表示这是一个 shell 脚本。
第 3 行,PATH 环境变量保存着可执行文件可能存在的目录,这样我们在执行一些命令或
者可执行文件的时候就不会提示找不到文件这样的错误。
第 4 行,LD_LIBRARY_PATH 环境变量保存着库文件所在的目录。
第 5 行,使用 export 来导出上面这些环境变量,相当于声明一些“全局变量”。
第 7 行,使用 mount 命令来挂载所有的文件系统,这些文件系统由文件/etc/fstab 来指定,
所以我们一会还要创建/etc/fstab 文件。
第 8 和 9 行,创建目录/dev/pts,然后将 devpts 挂载到/dev/pts 目录中。
第 11 和 12 行,使用 mdev 来管理热插拔设备,通过这两行,Linux 内核就可以在/dev 目录
下自动创建设备节点。关于 mdev 的详细内容可以参考 busybox 中的 docs/mdev.txt 文档。
/etc/fstab
主要是配置一些基本的文件系统挂载信息
示例:
#
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
用 defaults,也就是默认选项,defaults 包含了 rw、suid、 dev、 exec、 auto、 nouser 和 async。
其他的分区从 2 开始。一般不在 fstab 中挂载根目录,因此这里一般设置为 0。
配置后,重新启动linux,报错就没有了
[ 6.737030] device=eth0, hwaddr=40:a9:22:34:68:68, ipaddr=192.168.31.25, mask=255.255.255.0, gw=192.168.31.1
[ 6.747440] host=192.168.31.25, domain=, nis-domain=(none)
[ 6.753457] bootserver=192.168.31.90, rootserver=192.168.31.90, rootpath=
[ 6.760738] gpio_dvfs: disabling
[ 6.764056] can-3v3: disabling
[ 6.767381] ALSA device list:
[ 6.770363] #0: wm8960-audio
[ 6.819245] VFS: Mounted root (nfs filesystem) readonly on device 0:14.
[ 6.834257] devtmpfs: mounted
[ 6.837956] Freeing unused kernel memory: 532K (80b43000 - 80bc8000)
ls /sys也可以看到驱动device都挂载到sys下
/ # ls sys/
block/ class/ devices/ fs/ kernel/ power/
bus/ dev/ firmware/ fsl_otp/ module/
/proc目录下进程也正常。
/ # cat /proc/
1/ 5/ 87/ driver/ net/
10/ 6/ 88/ execdomains pagetypeinfo
100/ 61/ 89/ fb partitions
101/ 62/ 9/ filesystems self/
102/ 67/ 90/ fs/ softirqs
11/ 68/ 91/ interrupts stat
12/ 69/ 92/ iomem swaps
13/ 7/ 93/ ioports sys/
14/ 70/ 99/ irq/ sysrq-trigger
15/ 71/ asound/ kallsyms sysvipc/
16/ 72/ buddyinfo key-users thread-self/
17/ 73/ bus/ keys timer_list
18/ 74/ cgroups kmsg tty/
19/ 75/ cmdline kpagecount uptime
2/ 76/ config.gz kpageflags version
20/ 77/ consoles loadavg vmallocinfo
21/ 8/ cpu/ locks vmstat
22/ 82/ cpuinfo meminfo zoneinfo
23/ 83/ crypto misc
24/ 84/ device-tree/ modules
3/ 85/ devices mounts
4/ 86/ diskstats mtd
inittab 的详细内容可以参考 busybox 下的文件 examples/inittab。init 程序会读取/etc/inittab
这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组
成,格式如下:
<id>:<runlevels>:<action>:<process>
简单来说,inittab可以理解为预设一些指令,执行指令时进而执行一些动作。
四个选项的说明如下:
<id>: 每个指令的标识符,不能重复。但是对于 busybox 的 init 来说,<id>有着特殊意义。
对于 busybox 而言<id>用来指定启动进程的控制 tty,一般我们将串口或者 LCD 屏幕设置为控
制 tty。
<runlevels>:对 busybox 来说此项完全没用,所以空着。
<action>:动作,用于指定<process>可能用到的动作。
<process>:具体的动作,比如程序、脚本或命令等。
action | 作用 |
---|---|
sysint | 系统启动完成后,开始初始化,对应sysint的process 会执行一次 |
respawn | 当respawn对应的process,终止后,会立刻重新执行一次process ,与openwrt的procd守护进程配置类似,也有respawn配置 |
askfirst | 和 respawn 类似,在运行 process 之前在控制台上显示“Please press Enter to activate this console.”。只要用户按下“Enter”键以后才会执行 process。 |
wait | 在init过程,要等wait对应的process执行完成后,才继续init |
once | 仅执行一次,而且不会等待 process 执行完成 |
restart | 当执行重启的时候才会执行 process |
ctrlaltdel | 键盘按下ctrl + alt + del组合键,触发process执行 |
shutdown | 系统关机时,执行对应的process |
/etc/inittab 示例:
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r 7 ::shutdown:/sbin/swapoff -a
当执行shutdown时,就会执行命令/bin/umount -a -r 7 ::shutdown:/sbin/swapoff -a
至此整个rootfs就完成!完结撒花。
1、rootfs是linux重要的一环,可以通过nfs挂载,或直接在存储空间挂载。
2、rootfs启动后,需要经历几个阶段,linuxrc、/etc/inittab、/etc/init.d/rcS、/etc/fstab。需要了解三个文件的作用,inittab保存一些<动作:执行>
指令的绑定,rcS启动后自动执行,配置一些环境变量,或添加自定义启动程序,类似/etc/rc.local。 fstab是配置基本的文件系统、tmpfs、proc、sysfs等
3、制作rootfs可以通过busybox,或直接用buildroot制作。