根文件系统的构建(busybox)

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)复制inittabrootfs

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程序启动, 前两个:: 表示idrunlevels空缺, 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 #创建文件时的默认权限, 文件是666umask, 目录是777umask

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 静态编译; //600K, 成功运行,;

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嵌入式课程笔记

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值