1.用busybox构建根文件系统
逐步构建
从空文件夹开始,逐步添加rootfs必备的东西(busybox集成了),然后烧录成镜像, 开发板启动内核用nfs方式挂载rootfs,观察启动后现象。
什么是busybox
(1)一个C语言项目,包含很多.c/.h文件。
(2)作用:
1)为了在嵌入式环境下, 构建rootfs使用的init进程应用程序。
2)为当前系统提供一套shell命令程序集, 如vi、cd、mkdir、ls等集成在一起, 所以叫busybox, 而发行版linux(如ubuntu、redhat、centOS等)中vi、cd、ls等是单独的应用程序。
(3)特点:
1)跨平台: 可被配置编译成各平台的应用程序。
如arm-linux-gcc编译busybox得到可在arm架构开发板上运行的应用程序。
2)体积小: 比发行版的(/sbin /bin /usr/sbin /usr/bin用du -h查)总和小十倍多;
一, busybox提供的shell命令是阉割版, 参数选项保留几个常用;(不用的命令免得占嵌入式系统面积)
二, busybox所有命令代码在一个程序实现,很多代码函数通用, 减少重复代码, 减少代码体积。
(如ls、cd、mkdir等命令需要操作目录的函数共用)
总结:
busybox的特点是嵌入式系统本身的要求造成的。
busybox的移植实战
(1)下载:
0)镜像- https://busybox.net/downloads/ 官方- www.busybox.net
1)源码可直接从网上下载, 因为是一个开源项目。
2)版本差异不大,新旧无所谓。
(2)解压:
下载busybox-1.24.1, linux下创建一个文件夹, 我用的是/mnt/share/, 复制压缩包到该文件夹
cd /mnt/share/my_rootfs/rootfs
tar -jxvf busybox-1.24.1.tar.bz2
(3)检查Makefile, 添加:
1)174行: CROSS_COMPILE = /opt/arm-2009q3/bin/arm-none-linux-gnueabi- (指定arm-2009q3版本的交叉编译工具链路径)
2)200行: ARCH = arm
(4)配置; busybox配置比linux kernel少, 不用分两步, 直接make menuconfig;
1)Busybox Settings--->Build Options--->
[*]Build BusyBox as a static binary(no shared libs)//静态链接
2)Busybox Settings---> Busybox Library Tuning--->
[*]vi-style line editing commands //vi风格的编辑命令行, 按y选中
[*]Fancy shell prompts
3)Linux Module Utilities--->//驱动模块化设计
[去掉]Simplified modutils ->弹出的全选 //按n去掉
[*]insmod [*]rmmod [*]lsmod [*]Oretty output [*]modprobe [*]Blacklist spoort [*]depmod
4)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
(5)编译: make ,源码目录出现busybox说明编译成功;
1)错误:一堆警告, 加最后报错sync.c: undefined reference to 'syncfs'
//一般是因为gcc版本和Makefile版本不匹配,
解决思路:避开报错, sync.c模块可能用不上,在配置时不选择编译, 先去在Makefile中查看sync.c依赖的配置项名称, 再去取消勾选
find -name "sync.c" //发现模块所在路径
cd coreutils/ //进入模块目录
vim Kbuild //配置sync.c模块的Makefile, 不知道为什么叫Kbuild
L23: lib-$(CONFIG_SYNC) +=sync.o //发现sync.c模块的配置项叫CONFIG_SYNC, 配置时去掉勾选, 就可避开模块
make menuconfig, 按/键搜索SYNC, 发现配置菜单的路径后, 按n去掉选择, LOCAL-> Coreutils ->[去掉]Sync, 去掉后重新make编译
(6)安装到默认目录初体验:
在busybox源目录下编译执行install目标,会生成一个install目录
$make install
$cd _install/
$ls -l (发现所有程序都是~/bin/busybox的符号链接)
补充: make install作用是安装软件, 最原始的安装方式。将编译生成的可执行程序及其依赖库文件、配置文件、头文件安装到当前系统的某个目录;(可以不指定安装到默认目录 (./_install), 注意安装到ubuntu根目录的话会把原有文件冲掉)
(7)选择安装到nfs服务器导出的rootfs目录:
1)ubunutu搭建nfs服务器:
apt install nfs-common //安装nfs服务
apt install nfs-kernel-server //安装nfs服务器
vim /etc/exports //末尾添加一句 /mnt/share/my_rootfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)
chmod 777 -R /mnt/share/my_rootfs/rootfs //将文件系统目录设为共享目录
exportfs -r //更新导出目录
showmount -e 或者showmount localhost -e //查看成功导出的目录
/etc/init.d/nfs-kernel-server restart //重启nfs
mount -t nfs -o nolock localhost:/mnt/share/my_rootfs/rootfs /test //挂载nfs目录测试, 进入/mnt能看到rootfs则成功
umount -v /opt //取消挂载
2)进入busybox源码目录:
$make menuconfig -> Busybox Settings ---> Installation Options(...)
----> 选中(./_install) BusyBox installation prefix , 修改为/mnt/share/my_rootfs/rootfs
make install
(8)开发板内核配置为nfs启动, uboot传参设置好主机ip和开发板ip, 启动时会自动挂载nfs服务器的共享目录;
(9)结果:
[ 1.570053] VFP support v0.3: implementor 41 architecture 3 part 30 variant c rev 2...
[ 55.129114] VFS: Mounted root (nfs filesystem) on device 0:12....
//挂载成功,执行/linuxrc成功(实质是buzybox), 注意uboot的bootargs设置nfs方式启动;
[ 55.133516] Freeing init memory: 164K //释放驱动的安装函数内存
can't run '/etc/init.d/rcS': No such file or directory
can't open /dev/tty2: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty4: No such file or directory
//虽然不断提示找不到/etc/init.d/rcS和/dev/tty2等文件,但有进入命令行 /#下。
inittab详解
(0)解决上面找不到tty问题, 就一普通配置脚本, 位置在~\2.19.根文件系统构建实验及过程详解\etc\下
(1)复制inittab到rootfs
1)赋值到/etc
cd /mnt/share/my_rootfs/rootfs mkdir etc
将提供的典型inittab复制到~/rootfs/etc/下
2)再次启动内核, 挂载rootfs看效果
3)结果: 成功启动并挂载rootfs进入了控制台命令行, 开发板对rootfs的操作会和ubuntu的同步。
(2)内容解析: 是面向busybox能解析的格式写的, 可上网查
#first:run the system script file //注释, 以行为单位,每行是独立的配置项,表示某个具体含义
::sysinit:/etc/init.d/rcS //每行的配置项格式为id:runlevels:action:process, 其中配置值可空缺
::askfirst:-/bin/sh //维持sh程序启动, 前两个:: 表示id和runlevels空缺, id:是标识名,任意4个字符内。
::ctrlaltdel:-/sbin/reboot //在对应runlevels下满足action条件时, 就会执行process路径处的程序/脚本。
#umount all filesystem
::shutdown:/bin/umount -a -r //关机时取消挂载的所有文件系统
#restart init process
::restart:/sbin/init
值得注意: 如果看了busybox源码会发现,busybox最终进入死循环,反复检查是否满足各个action条件,满足就会执行对应process。
(3)工作原理: 被/linuxrc调用起作用。
inittab在/etc下属于运行时配置文件,busybox会按一定格式解析inittab文本并执行。
(4)
action条件表;
sysinit | 系统启动前运行。所以命令行出现前打印了can't run '/etc/init.d/rcS': No such file or directory |
askfirst | 类似respawn, 进入控制台前显示'Please press Enter to active this console' |
respawn | 进程终止执行时,重新启动该进程。 |
off | 如果进程正在运行, 发出一个警告信息, 等待数秒发出SIGKILL终止进程; |
wait | 启动进程并等待处理结束,处理结束后才去处理下一条条目, 一般是init进程。 |
once | 启动进程,不等待处理结束,继续处理下一条条目。 当该进程被终止时,init 不会重新启动它。从一个运行级别进入另一个运行级别时,如果相应的进程仍在运行,init 就不会重新启动该进程。 |
boot | 只在系统启动时,init 才处理这条条目,启动相应的进程,并不等待处理结束就去处理下一条条目。当这样的进程终止时,也不会重新启动它。 |
bootwait | 系统启动后,当第一次从单用户模式进入多用户模式时才处理该条目,init 启动这样的进程,并且等待其处理结束才处理下一条条目,当该进程被终止时,也不重新启动它。 |
ondemand | 与“respawn”的功能一样,但是只适用于运行级别为 A、B、C 的条目。 |
initdefault | 指定一个默认的运行级别,如果指定了多个运行级别,其中最大的数字将是默认的运行级别。如果 inittab 文件没有包含该条目,在系统启动时会请求用户为其指定一个默认的运行级别。 |
ctratldel | 当按下Ctrl+Alt+Delete组合键时,执行相应的进程 |
shutdown | 当系统关机时,执行相应的进程 |
restart | init进程重新启动时,执行相应进程,通常执行的进程是init本身 |
ctrlaltdel | 当按下Ctrl+Alt+Delete组合键(信号)时,执行相应的进程 |
powerfail | 当 init 接到断电的信号时,处理指定的进程,但是不等待该进程处理结束。 |
powerwait | 当 init 接到断电的信号时,处理指定的进程,并且等到处理结束后才去检查其他的条目。 |
runlevel表: 指定条目适用哪个运行级别。如空, 适用于0~6运行级, 后面的N/S是sh脚本runlevel的运行级。
#无 /N | System bootup(NONE) 我的ubuntu16.04 $runlevel 结果是N 5 |
# 无 /S | Sing user mode(not to be switched to directly) |
# 0 / 0 | - 停机(执行$init 0就能关机, 千万不能把init默认设置为0 ) |
# 1 / 1 | - 单用户模式 ,root权限, 用于系统维护 |
# 2 / 2~5 | - 多用户,没有 NFS (没有网络) |
# 3 | - 完全多用户模式(标准的运行级,登录后进入控制台) |
# 4 | - reserve |
# 5 | - X11控制台, 登录后进入图图形界面 (xwindow) |
# 6 / 6 | - 系统正常关闭并重启 ($init 6 重启) |
http://blog.chinaunix.net/uid-20564848-id-73947.html
运行级别的原理:
1。在目录/etc/rc.d/init.d下有许多服务器脚本程序,一般称为服务(service)
2。在/etc/rc.d下有7个名为rcN.d的目录,对应系统的7个运行级别
3。rcN.d目录下都是一些符号链接文件,这些链接文件都指向init.d目录下的service脚本文件,命名规则为K+nn+服务名或S+nn+服务名,其中nn为两位数字。
4。系统会根据指定的运行级别进入对应的rcN.d目录,并按照文件名顺序检索目录下的链接文件
对于以K开头的文件,系统将终止对应的服务
对于以S开头的文件,系统将启动对应的服务
5。查看运行级别用:runlevel
6。进入其它运行级别用:init N
7。另外init0为关机,init 6为重启系统
2.busybox源码分析
源码目录梳理
~/Makefile | 编译器脚本 |
libbb, coreutils | 命令集实现 |
注: 参考make menuconfig看模块结构, 建好SI工程
程序入口main
(0)入口可能在:
C语言main()是程序入口: 适用于操作系统下应用程序的工作。
连接脚本来指定的程序入口: uboot/linux kernelC语言项目中。
(1)代码定位: ~/libbb/appletlib.c
busybox是linux启动后的应用程序,必然有main函数, 且是入口;
SI搜索main有一堆, 很多是其他命令被当做程序调用时的入口(通过条件编译, 生成.o文件来筛选出真正的)
(2)实现命令集原理: ls, cd等命令都是busybox的符号链接,但执行时效果各不相同。
busybox先执行其main,再识别(传参argv[0])出真正要执行的函数, 调用相应xxx_main(譬如ls_main)实现命令。(具体在L831~L838)
程序和命令的解析与执行
(1)inittab脚本:
1)代码定位: ~/init/init.c中init_main()
2)代码分析:
L1123: parse_inittab(); //解析~/etc/inittab(action和process)
//具体是然后sysinit, wait, once的process执行一遍,然后在while(1)死循环不断监听respwan, askfirst。
(2)pwd命令原理:
1)代码定位: ~/coreutils/Pwd.c的pwd_main()
2)代码分析:
L77: buf =xreadlloc_getcwd_or_warn(NULL);
//里面通过getcwd()库函数实现当前路径获取 (黑的,表示没有,从外部来,man 3 可查)
3.rcS文件
(0)回顾: 启动命令行时, 打印can't run '/etc/init.d/rcS': No such file or directory
(1)一个运行时配置sh脚本,被inittab中第一句::sysinit:/etc/init.d/rcS调用, 很多其他配置由这文件引出。
内容一句 exec /etc/init.d/rc S 执行了rc文件,
(2)代码定位: ~\2.19.根文件系统构建实验及过程详解\etc\init.d
(3)内容分析:
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
#定义PATH, 赋值为后面字符串(环境变量路径,意思是系统调用程序时,当前目录找完,去PATH指定的目录)
#实现一进入命令行能不带路径执行ls cd pwd等程序
#busybox也导出了(init.c L1074), 这里不定义也行。
runlevel=S #将系统设置为单用户模式
prevlevel=N
umask 022 #创建文件时的默认权限, 文件是666减umask, 目录是777减umask
export PATH runlevel prevlevel
# export导出PATH,变成了环境变量(任何目录下都能用$PATH引用)。
mount -a #挂载所有应被挂载的文件系统
#busybox中mount -a时, busybox查找/etc/fstab文件,其中列出了所有该挂载的文件系统(包括VFS)
echo /sbin/mdev > /proc/sys/kernel/hotplug #下面4行删除, 老师没用
mdev -s
/bin/hostname -F /etc/sysconfig/HOSTNAME
ifconfig eth0 192.168.0.106
rcS和fstab文件移植
(1)移植文件: 将/etc/init.d/rc和/etc/fstab复制到~/my_rootfs/rootfs/etc/
(2)启动内核: 设好cmdline,如果不想每次都下载内存, 就去fastboot flash kernel后, nfs方式挂载;
bootcmd=movi read kernel 30008000;mov read rootfs 30B00000 300000;bootm 30008000 30B00000;
(3)结果:
rcS文件无法发现; vi /etc/rcS打开发现每句末尾都有^M, 删除^M重启就行;(win创建的rcS, 换行符为'\r\n')。
如果是ubuntu的vi会对行尾做转换,但secureCRT行尾就会多出^M。
扩展:有时应用程序执行时, 提示文件不存在,可能是动态链接库找不到。
(4)再结果:正常启动, 但无法挂载, Eenter进入命令行;
1)测试$runlevel查看运行级别, 实际结果是unknown,原因是busybox不支持runlevel特性。
2)挂载错误:
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根目录下创建挂载点目录。
(5)再结果: 启动成功, 无错误信息, 进入挂载点目录能看到很多挂载的设备文件(CRT才看得到, ubuntu的看不到)。
rcS文件中添加上面删除的4行
(1)mdev:
1)udev的嵌入式简化版本,用来配合linux驱动工作的应用层软件和/dev目录下的设备文件应用层软件。
2)rcS文件没有配置mdev -s时,启动后/dev目录下是空的;添加2行配置项:
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s #再次启动发现/dev生成很多的设备驱动文件, 是mdev生成的。
(2)hostname: shell命令。
1)命令hostname xxx可设置当前系统的主机名为xxx,直接hostname显示主机名。
2)/bin/hostname -F /etc/sysconfig/HOSTNAME
#-F指定主机名的配置文件路径, 里面只有一句名字, 如aston210,,$hostname原本是一个ip地址, 自己创建该文件测试后变成了aston210, 但命令行提示符还没改;
(3)ifconfig:
1)ifconfig eth0 192.168.0.20 #希望开机的默认ip地址, 不改的话是nfs那时指定的192.168.0.20
4.根文件系统的一些完善
cmdline提示符(profile中配置)
(1)问题引入: 之前添加HOSTNAME配置文件, 但命令行提示符没显示。
(2)代码定位: 提供的~/etc/profile,
放入rootfs/etc/下即可, 也是被busybox(init进程)自动调用的,认名字的。
(3)内容分析:
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
(4)结果: 命令行提示符显示[@aston210 ]# , 但登录用户名没显示。原因是直接进入命令行而没有做用户登录功能。
添加用户登录界面(/bin/sh)
(0)linux程序设计原则: 用较少代码完成一个功能。
如产品确实需要复杂综合型的功能,倾向于集成很多小程序完成整个大产品, 如intttab, shell脚本。
(1)解决思路:
之前intttab配置了 ::askfirst:-/bin/sh,启动直接进入cmdline, 而不会出现登录界面。
busybox集成了登录程序 (/bin/login和/sbin/gettty) , 要在inittab用/bin/login或/sbin/getty替代/bin/sh。
(2)用户名和密码的设置
1)密码用加密文字存储,而不是用明文。
(3)解决方法: (有时忘记密码, 只能在ubuntu挂载的rootfs下做如下操作)
1)设置开机启动登录: 在inittab,去掉/bin/sh换上/bin/login,系统启动后出现login: 和password: 。
2)设置用户名和密码:
直接复制ubuntu的/etc/passwd和/etc/shadow到ubuntu挂载的/rootfs/etc/, 除root第一行, 其他行删除;
修改内容: root:x:0:0root:/root:/bin/bash->sh #busybox不支持bash
$madir /root #提供家目录
注: 大部分linux登录加密方式一样, 当前密码等于ubuntu中root用户密码。
3)注: 大部分linux登录加密方式一样, 当前密码等于ubuntu中root用户密码。
ubuntu无法开机登录root用户机制, 是通过/etc/shadow中密文口令是空白的, 来实现。
但在busybox中密文口令为空的话, 则无密码直接登录, 因为不需要普通用户, 可用passwd root设置密码。
添加用户登录界面(/bin/getty) (常用)
(0)login和/gettty差别不详,但在busybox中这两个是一样的, 都是busybox符号链接, 不用严格区分;
(1)按照上面操作, 在inittab中用getty替换login实现同样效果。
动态链接库的拷贝
(0)解决程序如何运行的问题;
(1)问题引入:写个helloworld,然后gcc hello.c hello,丢hello到开发板~/rootfs/下无法运行,;
(2)问题解析: $file hello 可见程序是x86-64,而不是arm架构, 需要用arm-linux-gcc来交叉编译。
(3)解决方法:
1)$arm-linux-gcc hello.c -o helloSatic -static 静态编译; //600多K, 成功运行,;
2)$arm-linux-gcc hello.c -o helloYnamic 动态编译; //8K, 但提示找不到程序。
动态链接时, hello程序调用printf函数,但运行时环境中没有对应的库文件。
解决方案:
1)将arm-linux-gcc的动态链接库文件(.so)复制到开发板rootfs的/lib目录下即可解决。
cp /opt/arm-2009q3/arm-none-linux-gnueabi/libc/lib/*so* /mnt/share/my_rootfs/rootfs/lib/ -rdf
注意: lib/下面有很多符号链接到本目录下的其他文件, 而cp符号链接时会复制指向实体, 但名字还是符号链接的名字,我们要保留链接本身加 -rdf;
2)结果: 现在去执行helloYnamic可以运行
3)strip工具去除库中无用信息:
动态库.so包含调试符号,运行时没用,但占用空间, 单片机嵌入式flash有限,用strip去掉。
$arm-linux-strip *so* //在/rootfs/lib/下执行, 库文件由3.8M变成了3.0M。
4)其他交叉编译工具链的动态链接库目录不一定在这,用find找:
cd /opt/arm-2009q3 (填交叉编译工具安装目录)
find -name "*libm.so*"
//发现.so文件集中在 ~/arm-none-linux-gnueabi/libc/下的usr lib armv4t thumb2
程序开机自启动
开机启动相关的功能基本都是添加到rcS文件
(0)启动模式:
1)前台运行: 程序运行时占用了控制台,此时我们无法操作;
2)后台运行: 程序运行时让出控制台, 不影响控制台的使用;
(1) 前台运行: 进入cmdline前, 程序开机自启动
1)修改rcS实现:
vi /etc/init.d/rcS //最末尾添加两行
cd /程序路径/
./hello_dynamic
2)结果: 登录界面出现前, 打印了hello world, 结束进程后进入cmdline(前台运行模式)
(2) 后台运行: 进入cmdline前, 程序开机自启动
1)修改rcS实现:
vi /etc/init.d/rcS
cd /程序路径/
./hello_dynamic & //加个&后台运行
2)结果: 执行循环打印1~100的程序, 一边打印的同时, 可以输入命令, 但输入的命令会被打印字符隔开, 而命令回车后可正常执行;
(3)开机加载驱动模块程序
真实产品的rootfs是怎样的
(0)以九鼎QT4.8的rootfs分析
(1)inittab文件:sysinit时执行rcS,shutdown时执行rcK。
(2)/etc/init.d/rcS: 开机做的工作, 里面是for i in /etc/init.d/S??* 遍历执行S开头, 至少有三个字符的文件;
(3)/etc/init.d/rcK: 关机做的工作,同样是遍历 /etc/init.d/, 结束S开头, 至少有三个字符的文件;
总结:
正式产品中的rcS和rcK都是引入,真正干活的配置脚本是/etc/init.d/S??*,;
优势是不同门类的启动/关闭工作放到不同文件中, 便于扩展。
配置文件中肯定有一个判断参数是start还是stop的代码,start时做初始化,stop时做清理工作。
5.把busybox制作成镜像并烧录启动
(0)确定nfs启动时, 文件夹格式的rootfs可用;
(1)制作ext2格式的镜像:
1)创建rootfs.ext2镜像, 并挂载到一个目录下方便访问:
cd /mnt/share/my_rootfs
dd if=/dev/zero of=rootfs.ext2 bs=1024 count=10240 //生成ext2空镜像文件, 注意大小比busybox(5.6M)要大, 这里设10M
losetup /dev/loop1 rootfs.ext2 //设置循环设备
mke2fs -m 0 /dev/loop1 10240 //10240个块, 如果loop1被使用可以使用loop2
mkdir rootfs_ext2 //创建镜像的挂载目录, 方便访问
mount -t ext2 /dev/loop1 ./rootfs_ext2/ //挂载后老师的rootfs_ext2自动生成回收站lost+found
2)将~/rootfs中的busybox内容复制到镜像目录中:
cp ./rootfs/* ./rootfs_ext2/ -rf
3)卸载镜像和虚拟文件:
umount /dev/loop1
losetup -d /dev/loop1
4)my_rootfs/下会生成rootfs.ext2, 复制到fastboot目录中烧录。
fastboot flash system \my_rootfs\rootfs.ext2
(2)启动系统进入uboot, 设置bootargs为:
set bootargs console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext2
(3)结果: 启动后和nfs方式启动的rootfs一样,rootfs制作实验圆满完成。
总结:
制作过程本身, 有文档非常简单,但侧重于不是rootfs制作本身,而是rootfs工作的原理分析。
本文内容属于朱有鹏linux嵌入式课程笔记