weiqi7777

跟文件系统(二)busybox构建跟文件系统

0
阅读(2540)

rootfs有两种格式:nfs方式启动的文件夹形式的rootfs和用来烧录的镜像形式的rootfs。

一、busybox移植

1、busybox下载

busybox是一个开源项目,源代码可以网上下载。官网:https://busybox.net/downloads/,有多个版本。

下载完毕后,在linux下解压。得到以下的一些目录和文件。

2、修改Makefile

修改ARCH=arm , 表示架构是ARM

修改CROSS_COMPILE=arm-none-linux-gnueabi- , 表示使用的编译器

3、使用make menuconfig进行配置

busybox setting->build options,将下面开关打开,表示将busybox以静态方式进行编译。

busybox setting->busybox library tuning下的下面两个开关打开。第一个是选择vi风格的命令行。

linux module utilities下的simplified modutils给取消掉,然后将多出来的选项,全部选择。

Linux system utilities下的以下选择都选上。

4、make进行busybox的编译

配置完毕后,使用make命令进行编译。

编译的时候会报错。

sync.c:(.text.sync_main+0x78): undefined reference to `syncfs'

collect2: ld returned 1 exit status

这种报错的原因,可能是使用的编译器会当初编译这代码的编译器版本不一致。

有两种方式解决:一种是看源代码,去解决。这个就比较难了。

还有一种比较简单,直接绕过这个错误,不编译这个文件。

使用find命令,查找这个文件,是在哪一个目录下。可以看到,文件是在coreutils目录下。

然后去看该目录下的Kbuild文件。找到对应的CONFIG名字。是CONFIG_SYNC。

然后打开busybox目录下的.config文件。然后将对应行注释掉即可。

重新编译,没有报错。如果还有错的话,可以参考上述的方法解决。

编译完成后,在busybox目录下,会生成busybox目录。

5、make install安装

执行make install进行安装。其实就是执行busybox顶层目录下的一个目标install。但是要指定安装目录。如果不指定的话,那么就会安装在当前目录下的_install目录下。

在该目录下,有多个目录。对于linuxrc,是指向bin目录下busybox。

进入到bin目录,查看命令。可以看到这个目录下有很多个命令,但是每个命令都是指向busybox。

可以通过参数指定安装在指定目录下,通过CONFIG_PREFIX参数来指定安装目录。

make install CONFIG_PREFIX=/root/rootfs install

然后在该目录下,就会有生成的文件。

以上,就是使用busybox生成的根文件系统的目录。

make install在所有的linux下的都是安装软件。在linux系统中,可以选择使用源代码方式安装。安装的命令就是make install。make install的目的就是将编译生成的可执行程序以及依赖的库文件、配置文件、头文件安装到当前系统中特定或指定的目录下。

二、linux kernel挂载根文件系统

用linux kernel挂载busybox生成的根文件系统。此时设置开发板的bootarg,指向这个nfs文件,那么开发板的linux启动的时候,就会挂载这个nfs根文件系统。并且有了linuxrc,所以能够挂载成功,并成功执行linuxrc程序(也就是busybox)。

bootargs=root=/dev/nfs nfsroot=192.168.1.141:/root/rootfs/ ip=192.168.1.88:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200

启动开发板,挂载根文件成功。但是会打印can't信息。因为找不到/etc/init.d/rcS和/dev/tty2等文件

[ 3.486366] eth0: link down

[3.487839] ADDRCONF(NETDEV_UP): eth0: link is not ready

[3.740275] eth0: link up, 100Mbps, full-duplex, lpa 0x4DE1

[3.744479] ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready

[4.494296] IP-Config: Complete:

[4.495865] device=eth0, addr=192.168.1.88, mask=255.255.255.0, gw=192.168.1.1,

[4.503492] host=192.168.1.88, domain=, nis-domain=(none),

[4.509400] bootserver=192.168.1.141, rootserver=192.168.1.141, rootpath=

[4.516961] DBUG_PORT must not use AFC!

[4.520719] Looking up port of RPC 100003/2 on 192.168.1.141

[4.531002] Looking up port of RPC 100005/1 on 192.168.1.141

[4.550389] VFS: Mounted root (nfs filesystem) on device 0:12.

[4.554821] Freeing init memory: 540K

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这个文件是linuxrc程序执行时,会使用到的一个文件。

三、busybox源代码分析

1、inittab配置文件

在/etc目录下,需要一个inittab配置文件。这个文件是帮助init(busybox中)完成系统配置的主要内容。

在之前制作的根文件系统中创建etc目录,并且在/etc下添加这样的一个文件。

并写入以下的内容:

#first: run the system script file

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

::askfirst:-/bin/sh

::ctrlaltdel:-/bin/reboot

#umount all filesystem

::shutdown:/bin/umount -a -r

#restart init process

::restart:/sbin/init

启动开发板,挂载该nfs根文件系统,打印以下信息。

按下回车键后,就会出现命令行了。然后就可以输入linux命令,执行了。

2、inittab的工作原理

inittab的工作原理就是被/linuxrc(也就是busybox)执行时所调用起作用。inittab在/etc目录下,属于一个运行时配置文件,是文本格式的。实际工作的时候,busybox会按照一定的格式来解析这个inittab文本文件,然后根据解析的内容来决定要怎么工作。

3、配置文件解析

这个配置文件,是要按照一定的格式编写的。

#开始的行是注释

冒号是分隔符,分隔开各个部分。

inittab内容以行为单位,行与行之间没有关联,每行都是一个独立的配置项,每一个配置项表示一个独立的意思。

每一行的配置项都是由3个冒号分隔开的4个配置值来共同确定的。这四个配置值是<id>:<runlevels>:<action>:<process> 。有的配置值可以空缺。

每一行的配置项的4个配置值中最重要的是action和process。action是一个条件/状态,process是一个可被执行的程序的pathname。意思就是,当满足action的条件时,就会执行process这个程序。

inittab配置文件各字段的含义如下:

<id>: id字段与通常的inittab中的含义不同,它代表的是这个语句中process执行所在的tty设备,内容就是/dev目录中tty设备的文件名。由于是运行process的tty设备的文件名,所以也不能像通常的inittab那样要求每条语句id的值唯一。

<runlevels>: busybox不支持runlevel,所以此字段完全被忽略。

<action>: 为下列这些值之一:

sysinit, respawn, askfirst, wait,once, restart, ctrlaltdel, shutdown

其含义与通常的inittab的定义相同。特别提一下askfirst,它的含义与respawn相同,只是在运行process前,会打出一句话 "please press Enter to active this console",然后等用户在终端上敲入回车键后才运行process。

sysinit

为init提供初始化命令行的路径

respawn

每当相应的进程终止执行便重新启动

askfirst

类似respawn,不过它的主要用途是减少系统上执行的终端应用程序的数量。它将会促使init在控制台上显示"Please press Enter to activate this console."的信息,并在重新启动进程之前等待用户按下Enter键

wait

告诉init必须等到相应的进程完成才能继续执行

once

仅执行相应的进程一次,而且不会等待它完成

ctrlaltdel

当按下Ctrl-Alt-Delete组合键时,执行相应的进程

shutdown

当系统关机时,执行相应的进程

restart

当init重新启动时,执行相应的进程。

<process>:

指定要运行的process的命令。

内容

说明

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

控制台在启动之前,执行/etc/init.d/rcS程序

::askfirst:-/bin/sh

控制台启动时,执行/bin/sh程序

::ctrlaltdel:-/bin/reboot

按下ctrl+alt+del,执行/bin/reboot程序

::shutdown:/bin/umount -a -r

关机时,执行/bin/umount程序,并传入两个参数, -a –r

::restart:/sbin/init

重启,执行/sbin/init程序

四、busybox源代码分析

4、busybox中main函数全解析

busybox入口就是main函数,其中有很多个main函数,但是只有一个起作用了,其他的是没有起作用的。查看这个带main函数的源文件,有没有生成.o,即可知道这个main函数是否会起作用。

5、程序入口

入口是libbb/appletlib.c中的main函数。

busybox中有很多xxx_main函数,这些main函数每一个都是busybox支持的一个命令的真正入口。如ls_main函数就是busybox当做ls命令使用时的入口程序。

ls或者cd命令,其实都是busybox一个程序,但是实际执行时的效果却是各自的效果,busybox是如何实现一个程序化身万千还能各自工作的?其实就是main函数转xxx_main函数。也就是busybox每次执行时都是先执行main,在main函数中识别(靠main函数的传参argv[0]来识别)真正需要执行的函数(如ls),然后去调用相应的xxx_main(如ls_main)来具体实现这个命令

命令,保存在applet_name字符串中。然后调用run_applet_and_exit函数,进行命令的执行。

调用find_applet_by_name函数,将命令转化为对应的整数,然后调用run_applet_no_and_exit函数进行执行。

busybox内部,将命令和一个整数对应了起来。

6、inittab解析与执行

inittab的解析是在busybox/inti/init.c中的init_main函数中解析的。

该函数,只能是在进程1来执行。这也是为什么inittab是进程1.

设置控制台,将目录更改为/。设置sid。

设置4个环境变量。默认用户是root用户。

调用parse_inittab函数,进行inittab解析。

在parse_inittab函数中,首先是打开/etc/inittab文件。

然后调用new_init_action函数,将inittab文件中的各个action和process,保存在链表中,以供后面来执行。

执行sysint和wait和once(这些都是执行一遍)。

然后再while 1死循环中去执行respawn和askfirst。

7、pwd命令执行路径分析

该命令的执行时通过pwd_main函数来执行的。

该pwd_main不需要传参,因为pwd命令后面是不用加参数的。

调用xrealloc_getcwd_or_warn函数,得到当前目录,保存在字符串buf中,然后调用puts函数将字符串打印出来。

而xrealloc_getcwd_or_warn函数,其实就是调用linux内核提供的库函数getcwd函数,来获取当前位置。

可以通过man getcwd来获取到该函数的具体用法。

五、busybox的体积优势原理

busybox实际上就是把ls,cd,mkdir等很多个Linux中常用的shell命令集成在一起。集成在一起后又一个体积优势:就是busybox程序的大小比busybox中实现的那些命令的大小加起来还小很多。

busybox体积变小的原因主要有两个:第一个是busybox本身提供的shell命令是阉割版的,也就是busybox命令支持的参数选项比发行版的命令支持的参数选项要少。(如ls命令,在发行版中有几十个选项参数,但是在busybox中只保留了几个常用的选项,不常用的都删除掉了)。

第二个是busybox中,所有的命令的实现代码都在一个程序中实现,而各个命令中有跟很多代码函数都是通用的(如ls和cd,mkdir等命令都会需要去操作目录,因此在busybox中实现目录操作的函数就可以被这些命令共用),共用会减低重复代码出现的次数,从而减少总的代码量和体积。

busybox的体积优势是嵌入式系统半身的要求和特点造成的。

六、rcS文件介绍

1、/etc/init.d/rcS

该文件是linux的运行时配置文件最重要的一个,其他的一些配置是由这个文件引出来的。这个文件可以很复杂,也可以很简单。里面有很多的配置项。

在busybox的初始化代码中(调用parse_inittab函数),有调用这个rcS文件。

这个INIT_SCRIPT,其实就是这个rcS文件。

# define INIT_SCRIPT "/etc/init.d/rcS"

注意:在window下创建该文件,会出现换行符的问题,因此该文件拷贝到根文件系统中后,要保证这个文件的换行符只是\n,而不是window下的\r\n。如果换行符是\r\n,那么在kernel挂在该根文件系统,就会出现,can't find /etc/init.d/rcS。

2、配置项

这个rcS是一个shell脚本,里面有很多配置项:

第一行, #!/bin/sh, 表示这个脚本使用/bin/sh解释器进行脚本的解释执行。

#!/bin/sh

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.77

定义PATH , runlevel , prevlevel变量,使用export导出,那么这些变量就是环境变量。

2.1、PATH=xxx

定义PATH变量,后面用export导出,那PATH就变成了环境变量。

PATH这个环境变量是linux系统内部定义的一个环境变量,含义是操作系统去执行程序时会默认到PATH指定的各个目录下去寻找。如果找不到就认定这个程序不存在,如果找到了就去执行它。

将一个可执行程序的目录导出到PATH,可以让我们不带路径来执行这个程序。

rcS为什么要先导出PATH?因为我们希望一旦进入命令行下,PATH环境中就有默认的/bin,/sbin,/usr/bin,/usr/sbin,这几个常见的可执行程序的路径,这样我们进入命令行后就可以ls,cd等直接使用了。

rcS文件还没添加,系统启动就有了PATH的值?原因在于busybox自己用代码硬编码为我们导出了一些环境变量,其中就有PATH。

在busybox的init.c的init_main函数中,有以下几行代码,设置了几个环境变量,其中就有PATH。

    /* Make sure environs is set to something sane */

    putenv((char *) "HOME=/");

    putenv((char *) bb_PATH_root_path);

    putenv((char *) "SHELL=/bin/sh");

    putenv((char *) "USER=root"); /* needed? why? */

在libbb.h中,有定义bb_PAYH_root_path,其实就是/bin /sbin /usr/bin /usr/sbin

extern const char bb_PATH_root_path[] ALIGN1; /* "PATH=/sbin:/usr/sbin:/bin:/usr/bin" */

2.2、runlevel

runlevel也是一个shell变量,并且导出为环境变量

runlevel变量的作用:Linux操作系统自从开始启动至启动完毕需要经历几个不同的阶段,这几个阶段就叫做runlevel,同样,当linux操作系统关闭时也要经历另外几个不同的runlevel。设置runlevel,是为了linux系统避免不必要的重启动。

Linux系统有7个运行级别(runlevel)
运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动
运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆
运行级别2:多用户状态(没有NFS)
运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式
运行级别4:系统未使用,保留
运行级别5:X11控制台,登陆后进入图形GUI模式
运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动

S, s  Single user mode

    runlevel=s,表示将系统设置为单用户模式

2.3、umask

umask是linux的一个命令,作用是设置linux系统的umash值。

umash值决定当前创建文件的默认权限。

2.4、mount –a

mount命令是用来挂载文件系统的。

mount –a是挂在所有的应该被挂载的文件系统,在busybox中mount –a时busybox会去查找一个文件/etc/fstab文件,这个文件按照一定的格式列出了所有应该被挂载的文件系统(包括了虚拟文件系统)

这个文件的格式是统一的。

# /etc/fstab: static file system information.

#

# Use 'vol_id --uuid' to print the universally unique identifier for a

# device; this may be used with UUID= as a more robust way to name devices

# that works even if disks are added and removed. See fstab(5).

#

#     <file system>     <mount point>     <type>     <options>     <dump>     <pass>

    proc             /proc             proc     defaults     0         0

    sysfs             /sys             sysfs     defaults     0         0

    tmpfs             /var             tmpfs     defaults     0         0

    tmpfs             /tmp             tmpfs     defaults     0         0

    tmpfs             /dev             tmpfs     defaults     0         0

在根文件系统中,要保证/etc/fstab文件中,列出的挂载点都要存在,也就是要在根文件系统中,要创建这些文件,否则就会出现umount的提示信息。

2.5、mdev

mdev是udev的嵌入式简化版本,udev/mdev是用来配合linux驱动工作的一个应用层的软件,udev/mdev的工作就是配合linux驱动生成相应的/dev目录下的设备文件。

本来,在dev目录下,是空目录。但是当rcS文件中,添加下面两行后:

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

在dev目录下,就会生成很多设备文件。这些设备文件,就是mdev生成的。

2.6、hostname

hostname是linux中的一个shell命令。命令(hostname xxx)执行后可以用来设置当前系统的主机名为xxx,直接hostname不加参数可以显示当前系统的主机名。

设置了主机名,就可以在命令行中,显示主机名。

没有设置主机名的时候,使用hostname命令,会打印当前的IP地址。

往rcS文件加入以下参数:

/bin/hostname -F /etc/sysconfig/HOSTNAME

在etc下创建目录sysconfig, 然后再sysconfig目录下创建HOSTNAME文件,往这个文件中写入你想要设置的主机名。

然后重新启动linux,使用hostname命令,会打印设置的主机名。

2.7、ifconfig

设置当前linux的ip地址。使用ifconfig命令,将linux的ip地址设置为192.168.1.77。

ifconfig eth0 192.168.1.77

在rcS文件中使用ifconfig,就可以设置linux启动后的IP地址为多少。