跟文件系统(二)busybox构建跟文件系统
0赞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地址为多少。