前言:一整套linux在只有内核的情况下是不能工作的,它需要由根文件系统的配置支持。同时根文件系统提供了根目录、shell命令集和linuxrc应用程序,使得系统能够从进程1中衍生出其他进程。所以rootfs对于操作系统来讲是不可缺少的。
上一篇博客中简单介绍了busybox:
(1)busybox是一个C语言写出来的项目,里面包含了很多.c文件和.h文件。这个项目可以被配置编译成各个平台下面可以运行的应用程序。我们如果用arm-linux-gcc来编译busybox就会得到一个可以在我们开发板linux内核上运行的应用程序。
(2)busybox这个程序开发出来就是为了在嵌入式环境下构建rootfs使用的,也就是说他就是专门开发的init进程应用程序。
(3)busybox为当前系统提供了一整套的shell命令程序集。譬如vi、cd、mkdir、ls等。在桌面版的linux发行版(譬如ubuntu、redhat、centOS等)中vi、cd、ls等都是一个一个的单独的应用程序。但是在嵌入式linux中,为了省事我们把vi、cd等所有常用的shell命令集合到一起构成了一个shell命令包,起名叫busybox。
总结:busybox 是用来制作嵌入式linux根文件系统的一个工具软件,它里面集成了很多基本的操作命令,在构建嵌入式Linux系统中很有用。
(1)从busybox官网下载它。
(2)将压缩包解压到你的ubuntu中。
(3)进入到busybox中,修改它的Makefile,使其对应好你的架构和交叉编译工具链。
ARCH = arm
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin//arm-none-linux-gnueabi-
(4)配置makeconfig中的内容。
Busybox Settings--->
Build Options--->
[*]Build BusyBox as a static binary(no shared libs)
Busybox Library Tuning--->
[*]vi-style line editing commands
[*]Fancy shell prompts
Linux Module Utilities--->
[ ]Simplified modutils
[*]insmod
[*]rmmod
[*]lsmod
[*]modprobe
[*]depmod
Linux System Utilities--->[*]mdev
[*]Support /etc/mdev.conf
[*]Support subdirs/symlinks
[*]Support regular expressions substitutions when renaming dev
[*]Support command execution at device addition/removal
[*]Support loading of firmwares
后面编译会报一个错误:
解决方法:在makeconfig中将sync配置为N即可。
(5)make然后make install。
make install在所有的linux下的软件中作用都是安装软件。在传统的linux系统中安装软件时都是选择源代码方式安装的。我们下载要安装的软件源代码,然后配置、编译、安装。make install的目的就是将编译生成的可执行程序及其依赖的库文件、配置文件、头文件安装到当前系统中指定。
我们在make install之前,最好指定一个安装目录:
Busybox Settings --->
Installation Options ("make install" behavior) --->
│(/root/porting_x210/rootfs/rootfs) BusyBox installation prefix
(6)安装成功。
安装成功后切换到安装目录下,即可得到bin、linuxrc、sbin、usr。
(1)尝试通过busybox生成的rootfs
上一篇博客中,我们通过自己建立了一个简易版的rootfs,其中包含了一个空的linuxrc,最终实验结果为:挂载成功,执行/linuxrc失败。
现在我们移植了busybox后,它内部包含了一个可以用的linuxrc,我们再次将将busybox生成的rootfs通过nfs去挂载,并查看结果。在启动之前,需要注意uboot中的bootarg参数配置是否正确:
nfsroot=192.168.1.141:/root/porting_x210/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200
实验结果为:挂载成功,执行/linuxrc(也就是busybox)成功,但是因为找不到/etc/init.d/rcS和/dev/tty2等文件所以一直在打印错误提示信息,而且没有进入命令行。
(2)添加inittab文件
对于没有进入命令行的问题,最重要的原因是缺少inittab文件。
inittab的工作原理就是被/linuxrc(也就是busybox)执行时所调用起作用。
inittab在/etc目录下,所以属于一个运行时配置文件,是文本格式的(内容是由一系列的遵照一个格式组织的字符组成的),实际工作的时候busybox会(按照一定的格式)解析这个inittab文本文件,然后根据解析的内容来决定要怎么工作。
在inittab文件中:
#开始的行是注释。冒号在里面是分隔符,分隔开各个部分。inittab内容是以行为单位的,行与行之间没有关联,每行都是一个独立的配置项,每一个配置项表示一个具体的含义。
每一行的配置项都是由3个冒号分隔开的4个配置值共同确定的。这四个配置值就是id:runlevels:action:process。值得注意得是有些配置值可以空缺,空缺后冒号不能空缺,所以有时候会看到连续2个冒号。
每一行的配置项中4个配置值中最重要的是action和process,action是一个条件/状态,process是一个可被执行的程序的pathname。合起来的意思就是:当满足action的条件时就会执行process这个程序(变成1个进程)。
通过分析busybox的源代码就会发现,busybox最终会进入一个死循环,在这个死循环中去反复检查是否满足各个action的条件,如果某个action的条件满足就会去执行对应的process。initab在整个操作系统工作当中一直在起作用,不是检查一次就结束了。
下面是我们的inittab代码:
::sysinit:/etc/init.d/rcS
#::askfirst:-/bin/sh
s3c2410_serial2::sysinit:/bin/login
::ctrlaltdel:-/sbin/reboot
::shutdown:/bin/umount -a -r
::restart:/sbin/init
代码解析:
::sysinit:/etc/init.d/rcS :当系统初始化,只有系统开机或重新启动的时候,/etc/init.d/rcS才会被执行一次。
::askfirst:-/bin/sh:当系统初始化,直接进入命令行,不需要登录。
s3c2410_serial2::sysinit:/bin/login:当系统初始化,只有系统开机或重新启动的时候,/bin/login才会被执行一次,然后登录用户名和密码(与上面的方式两者2选一)。
::ctrlaltdel:-/sbin/reboot:当按住ctrl+alt+delate时,系统执行/sbin/reboot。
::shutdown:/bin/umount -a -r:当关机时,执行/bin/umount -a -r,将所有的文件夹和文件都卸载。
::restart:/sbin/init:当重启时,执行/sbin/init。
为了理解busybox的工作原理,我们需要对busybox的源码进行简单的分析。
(1)busybox的工作原理
busybox入口就是main函数,其中有很多个main但是只有一个起作用了,其他的是没起作用的。真正的busybox工作时的入口是libbb/appletlib.c中的main函数。
busubox中有很多xxx_main函数,这些main函数每一个都是busybox支持的一个命令的真正入口。譬如ls_main函数就是busybox当作ls函数使用时的入口程序。
进入到busybox的bin文件中,我们可以发现ls或者cd等命令其实都是busybox的一个符号链接。那么busybox是如何实现一个程序化身万千还能各自工作的?答案就是main转xxx_main。也就是说ls或者cd等命令其实都是busybox一个程序,busybox每次执行时都是先执行其main,在main函数中识别(靠main函数的传参argv[0]来识别)我们真正要执行的函数(譬如ls)然后去调用相应的xxx_main(譬如ls_main)来具体实现这个命令。从而通过这种传参的方式实现一个busybox完成多种功能的效果。
(2)busybox对inittab的解析
inittab的解析是在busybox/init/init.c/init_main函数中。
执行逻辑是:先通过parse_inittab函数解析/etc/inittab(解析的重点是将inittab中的各个action和process解析出来),然后后面先直接执行sysinit和wait和once(注意这里只执行一遍),然后在while(1)死循环中去执行respwan和askfirst。
(3)busybox的体积优势原理
busybox实际上就是把ls、cd、mkdir等很多个linux中常用的shell命令集成在一起了。集成在一起后有一个体积优势:就是busybox程序的大小比busybox中实现的那些命令的大小加起来要小很多,因此它非常适合嵌入式这类系统。
busybox体系变小的原因主要有2个: 第一个是busybox本身提供的shell命令是阉割版的(busybox中的命令支持的参数选项比发行版中要少,譬如ls在发行版中可以有几十个-x,但是在busybox中只保留了几个常用的选项,不常用的都删除掉了);第二个是busybox中因为所有的命令的实现代码都在一个程序中实现,而各个命令中有很多代码函数都是通用的(譬如ls和cd、mkdir等命令都会需要去操作目录,因此在busybox中实现目录操作的函数就可以被这些命令共用),共用会降低重复代码出现的次数,从而减少总的代码量和体积。
/etc/init.d/rcS文件是linux的运行时配置文件中最重要的一个,其他的一些配置都是由这个文件引出来的。在inittab文件中,::sysinit:/etc/init.d/rcS实现系统启动时对rcS文件的一次调用。这里注意,将windows下编辑的文本导入到linux下要注意换行问题。
rcS脚本内容:
PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
/bin/hostname -F /etc/sysconfig/HOSTNAME
ifconfig eth0 192.168.1.20
cd /root
./hello_dynamic
cd /
脚本解析—部分1:
PATH=/sbin:/bin:/usr/sbin:/usr/bin:这一行定义了一个变量PATH,值等于后面的字符串。并且后面用export导出了这个PATH,那么PATH就变成了一个环境变量。
runlevel=S:表示将系统设置为单用户模式。
umask 022:决定当前用户在创建文件时的默认权限,设置022时创建文件的权限为644(相加为666)。
mount -a:是挂载所有的应该被挂载的文件系统,在busybox中mount -a时busybox会去查找一个文件/etc/fstab文件,这个文件按照一定的格式列出来所有应该被挂载的文件系统(包括了虚拟文件系统)。
>>当写到这里进行挂载时,出现错误mount: mounting xxx on xxx failed: No such file or directory。原因是因为根文件系统中找不到挂载点。
所谓挂载点就是我们要将目标文件系统(当然这里都是虚拟文件系统)挂载到当前文件系统中的某一个目录中,这个目录就是挂载点。
这里虚拟文件系统对应的挂载点配置在fstab文件中描述。虚拟文件系统基本上是提供与内核交互的一种通道。
解决方法: 自己在制作的rootfs根目录下创建这些挂载点目录即可。
结果: 可以看挂载时输出信息;还可以启动后去看proc和sys文件夹。
脚本解析—部分2:
echo /sbin/mdev > /proc/sys/kernel/hotplug与mdev -s:udev或mdev是用来配合linux驱动工作的一个应用层的软件,udev/mdev的工作就是配合linux驱动生成相应的/dev目录下的设备文件。/dev目录下的设备驱动文件就是mdev生成的,这就是mdev的效果和意义。
/bin/hostname -F /etc/sysconfig/HOSTNAME:这行代码指定了一个主机名配置文件(这个文件一般文件名叫hostname或者HOSTNAME)。我们可以通过命令(hostname xxx)执行后可以用来设置当前系统的主机名为xxx,直接hostname不加参数可以显示当前系统的主机名。
之前添加了/bin/hostname在/etc/sysconfig/HOSTNAME文件中定义了一个hostname(peter210),实际效果是:命令行下hostname命令查到的host名字确实是peter210。但是问题就是命令行的提示符是没有显示的。
解决方法: 在 /etc/ 目录下添加 profile文件。
profile文件工作原理:profile文件也是被busybox(init进程)自动调用的,所以是认名字的。
profile文件内容:
# 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`
export USER LOGNAME PS1 PATH
/bin/login和 /sbin/getty在用户名和密码的管理上是一样的。其实常见的所有的linux系统的用户名和密码的管理几乎都是一样的。
(1)因为登录在系统启动时只需要执行一次,所以要添加到sysinit中去。
(2)添加passwd和shadow文件
linux系统中用来描述用户名和密码的文件是passwd和shadow文件,这两个文件都在etc目录下。passwd文件中存储的是用户的密码设置,shadow文件中存储的是加密后的密码。(我们直接复制ubuntu系统中的/etc/passwd和/etc/shadow文件到当前制作的rootfs目录下,然后再做修改即可)
Tips:平时有时候我们忘记了自己的操作系统的密码,怎么办?有一种解决方法就是用其他系统(WindowsPE系统或者ubuntu的单用户模式等···)来引导启动,启动后挂载到我们的硬盘上,然后找到/etc/shadow文件,去掉密文密码后保存。然后再重启系统后密码就没了。
本实验通过编写最简单的两个打印helloworld的C程序,并分别进行静态链接编译和动态链接编译,使其实现开机自动打印helloworld来完成测试。
Makefile文件:
通过==du -h *==来查看编译后生成的可执行文件大小,可以发现动态链接文件远小于静态链接文件。
由于动态链接文件调用了printf函数,而printf函数在动态连接时要在运行时环境(开发板的rootfs)中去寻找对应的库文件(开发板rootfs中部署的动态链接库中包含了printf函数的那个库文件)。所以我们需要将arm-linux-gcc的动态链接库文件复制到开发板rootfs的/lib目录下。
我们用的arm-2009q3这个交叉编译工具链的动态链接库在 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib 目录下。通过复制命令:cp lib/so /root/porting_x210/rootfs/rootfs/lib/ -rdf来实现复制。
最后在rcS文件中添加cd /root 和./hello_dynamic和 cd /实现开机自启动。注意:默认./为前台运行模式,如果想要让出控制台,就要在./hello_dynamic后面加个&( ./xxx &),即可实现后台运行。
(1)设置bootargs为nfs启动方式,然后从主机ubuntu中做好的文件夹格式的rootfs去启动,然后看启动效果,作为将来的参照物。注:调试时候为了方便,一般都是在nfs方式下完成好之后,再制作镜像文件烧录。
(2)动手制作ext2格式的镜像
制作前要先通过du -h …/rootfs/ 查看我们完成的rootfs文件大小,这里为6.5MB,所以我们给的ext2文件大小要大于它,这里给10MB。
#block size为1024 数量为10240个,则整体ext2大小为10M
dd if=/dev/zero of=rootfs.ext2 bs=1024 count=10240
losetup /dev/loop1 rootfs.ext2
mke2fs -m 0 /dev/loop1 10240
mount -t ext2 /dev/loop1 ./ext2_rootfs/
#然后将rootfs中的所有内容复制到我们创建的ext2_rootfs(rootfs.ext2的入口)中
cp ../rootfs/* ./ -rf
#卸载
umount /dev/loop1
losetup -d /dev/loop1
#烧linux kernel
fastboot flash kernel peter/zImage
#烧rootfs
fastboot flash system peter/rootfs.ext2
#设置bootargs为从镜像中找
set bootargs console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext2