Linux文件系统的制作、生成、使用

本文学习自
https://blog.csdn.net/wangweijundeqq/article/details/82533485
感谢这位大神的记录过程,让我对文件系统有了进一步的认识!

本文主要内容来自于大神的记录,本文章基本也是将他的思想稍微或基本没改变的的再次记录,学习下来。

文件系统的制作、生成、使用

提示:以下是本篇文章正文内容,下面案例可供参考
busybox的下载制作,就没放这了,具体可以看大神的链接。

1、使用busybox生成文件系统需要的四个文件

bin linuxrc sbin usr
使用nfs挂载后,系统无法系统,提示run ‘/etc/init.d/rcS’ No such file or directory
can’t open /dev/tty2:No such file or directory

2、加入一个典型的inittab后,再次挂载系统后

只提示 can’t run ‘/etc/init.d/rcS’: No such file or directory
Please press Enter to activate this console.
回车键后可以进入系统下。
注:inittab格式解析
1)inittab的工作原理就是被、linuxrc(也就是busybox)执行时所调用起作用。
2)inittab在/etc目录下,所以属于一个运行时配置文件,内容由文本组成。我们研究的重点是如何修改它
3)我们的重点是inittab的格式究竟怎样的?我们看到一个inittab后怎么去分析这个inittab对启动的影响。
4)inittab格式解析 详情:百度busybox inittab
id:runlevels:action:process
每行语句都有四个字段(id、runlevels、action和process),每个字段以冒号隔开,如果有某字段不需要配置,
不写即可(千万不能用空格代替)。
id:对busybox而言,id用来指定process的控制终端。如果第四个字段process并不是个可以交互的shell,此字段可空着不填。
runlevels:process的运行级别,在这里可填入多个运行级别,比如12345或者35等。
linux有7个运行级别,如下:
0:关机。
1:单用户字符界面
2:不具备网络文件系统(NFS)功能的多用户字符界面。
3:具有网络功能的多用户字符界面
4:保留不用
5:具有网络功能的图形用户界面
6:重新启动系统
但是,busybox将会完全忽略runlevels字段,所以可以空着不填。

#注释(linux中的习惯性思维)
:分隔符,每行代表一个独立的含义,分隔开各个部分

1,inittab内容是以行为单位的,行与行之间没有关联,每行都是一个独立的配置项,每一个配置项表示一个具体的含义
2,每一行的配置项都是由3个冒号分隔开的4个配置项共同确定的。这四个配置项就是id:runlevels:action:process。
值得注意的是有些配置项可以空缺,空缺后冒号不能空缺,所以有时候会看到连续2个冒号。
3,每一行的配置项中4个配置中最重要的是action和process,action是一个条件/状态,process是一个可被执行的程序的pathname。
合起来的意思就是:当满足action的条件时就会执行process这个程序。
注意:理解inittab的关键就是明白“当满足action的条件时就会执行process这个程序”。去分析busybox的源代码就会发现,busybox
最终会进入一个死循环,在这个死循环中去反复检查是否满足各个action条件,如果某个action的条件满足就会去执行对应的process。

action:运行process的时机或方式
action字段可以是以下值:
控制台上显示:“Please press Enter to active this console。”的信息
sysinit:不论在哪个运行级别,系统会在执行boot及bootwait之前执行process。
powerwait:当系统的供电不足时执行process,且一直等它执行完毕。
powerokwait:当系统的供电恢复正常时执行process,且一直等它执行完毕。
powerfailnow:当系统的供电严重不足时执行process。
ctrlaltdel:当用户按下【ctrl+alt+del】时执行process。
ondemand:进入ondemand执行等级时,执行process。
wait:执行process,并等待它执行完毕。
respawn:启动并监视process,若process终止则重启它。

明白各个action什么意思
在busybox-1.24.1中init.c定义了以下action
sysinit:指定的进程在访问控制台之前执行,这样的条目仅用于对某些设备的初始化,目的是为了是init在这样的设备上向
用户提问有关运行级别的问题,init需要等待进程运行结束后才继续。
respawn:如果process字段指定的进程不存在,就启动该进程,init不会等待处理结束,而是继续扫描inittab文件。当该进程
被终止时,init将重新启动它。如果相应的进程已经存在,init就忽略该条目并继续扫描inittab文件。
askfirst:类似respawn,主要用途是减少系统上执行的终端应用程序的数量。它将会促使init在控制台上显示:“Pleasse
press Enter to active this console“的信息,并在重新启动进程之前等待用户按下”enter“键
wait:告诉init必须等到相应的进程执行完成之后才能继续执行。
once:启动进程,不会等待处理结束,而是继续处理下一条条目。当该进程被终止时,init不会重新启动它。从一个运行级别进入
另一个运行级别时,如果相应的进程仍在运行,init就不会重新启动该进程。
ctratldel:当按下ctrl+alt+delete组合键时,执行相应的进程。
shutdown:当系统关机时,执行相应的进程。
restart:当init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身。
boot:只在系统启动时,init才处理这条条目,启动相应的进程,并不等待处理结束就去处理下一条条目。当这样的进程终止时,也不会
重新启动它。
bootwait:系统启动后,当第一次从当用户模式进入多用户模式时才处理该条目,init启动这样的进程,并且等待其处理结束才
处理下一条条目,当该进程被终止时,也不重新启动它。
off:如果相应的进程正在运行,那么就发出一个警告信号,等待数秒后,再发出信号SIGKILL强制终止该进程。如果相应的进程
不存在就忽略该条目。
initdefault:指定一个默认的运行级别,如果指定了多个运行级别,其中最大的数字将是默认的运行级别。如果inittab文件没有
包含该条目,在系统启动时会请求用户为其指定一个默认的运行级别。
powerwait:当初接到断电的信号时,处理指定的进程,并且等到处理结束后才去检查其它的条目
powerfail:当init接到断电的信号时,处理指定的进程,但是不等待该进程处理结束。

我们添加的典型的inittab内容,分析如下
#first the system script file
::sysinit:/etc/init.d/rcS
//在控制台初始化之前执行rcS

::askfirst:-/bin/sh
//执行控制台时的打印信息

::ctrlaltdel:-/sbin/reboot
//同时按住3键可以重启

#umount all filesystem
::shutdown:/bin/umount -a -r //关机时结束挂载

//重启时启动init
#restart init process
::restart:/sbin/init

四、busybox源码分析

busybox是linux启动起来后工作的一个应用程序,因此其中必然有main函数,而且main就是入口。

五、rcS文件介绍1

/etc/init.d/rcS文件是linux的运行时配置文件中最重要的一个,其它的一些配置都是由这个文件引出来的。
这个文件可以很复杂也可以很简单,里面可以由很多的配置项。
可以看出,rcS就是一个脚本文件。

#!/bin/sh 需要继续添加环境变量,在后面:/new即可
PATH=/sbin:/bin:/usr/sbin:/usr/bin

runlevel=S
prevlevel=N

umask 022

export PATH runlevel prevlevel

mount -a

1、PATH=XXX

1)首先从shell脚本的语法角度分析,这一行定义了一个变量PATH,值等于后面的字符串
2)后面用export导出了这个PATH,那么PATH就变成了一个环境变量。
3)PATH这个环境变量是linux系统内部定义的一个环境变量,含义是操作系统去执行程序时会默认到PATH指定的各个目录
下去寻找。如果找不到就认定这个程序不存在,如果找到了就去执行它。将一个可执行程序的目录导出到PATH,可以让
我们不带路径来执行这个程序。
4)rcS中为什么要先导出PATH?
就是因为我们希望一旦进入命令行下时,PATH环境变量中就有默认的/bin /sbin /usr/bin /usr/sbin 这几个常见的
可执行程序的路径,这样我们进入命令行后就可以ls、cd等直接使用了。
5)为什么我们的rcS文件还没添加,系统启动就有了PATH中的值?
原因在于busybox自己用代码硬编码为我们导出了一些环境变量,其中就有PATH。

2、runlevel=

1)runlevel也是一个shell变量,并且被导出为环境变量。
2)runlevel这个环境变量到底有什么用?
linux操作系统自从开始启动至启动完毕需要经历几个不同的阶段,这几个阶段就叫做runlevel。例如init 0就是关机,init 6就是重启。
linux系统有7个运行级别(runlevel)
运行级别0:系统停止状态,系统默认运行级别不能设为0,否则不能正常启动
运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登录
运行级别2:多用户状态(没有NFS)
运行级别3:完全的多用户状态(有NFS),登录后进入控制台命令行模式
运行级别4:系统未使用,保留
运行级别5:X11控制台,登录后进入图形GUI模式
运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动
3)runlevel=S表示将系统设置为单用户模式

3、umask=

1)umask是linux的一个命令,作用是设置linux系统的umask值。
2)umask值决定当前用户在创建文件时的默认权限。

4、mount -a

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

六、rcS文件实战

1)将rcS加入,问题解决找不到rcS的问题
还会存在挂载点找不到的错误。
所谓挂载点就是我们要将目标文件系统(当然这里都是虚拟文件系统)挂载到当前文件系统的某一个目录中,这个目录就是挂载点。
解决方案:就是自己在制作rootfs根目录下创建这些挂载点即可。
2)这个错误告诉我们:shell脚本文件如果格式不对,运行时可能会被提示文件不存在。
3)扩展讲一个:有时候一个应用程序执行时也会提示文件不存在,问题可能是这个程序所调用的一个动态库找不到。
4)测试结果:PATH本来在busybox中就已经用代码导出过了,所以rcS中再次导出没有任何明显的现象,因此看不出什么差别;
runlevel实际执行结果一直是unknown,问题在于busybox并不支持runlevel这个特性。

2、umask测试

1)umask是022的时候,默认touch创建一个文件的权限是644

2)umask是044的时候,默认touch创建一个文件的权限是622

3)umask是444的时候,默认touch创建一个文件的权限是222

总结:umask的规律就是:umask值和默认创建文件的权限值加起来是666

3、mount测试

1)验证是否挂载成功,可以看挂载时输出信息;还可以启动后去看proc和sys文件夹,如果有文件出现则证明挂载成功了,
如果没东西就证明失败了。
这些文件在串口终端汇总查看才有,但是linux主机服务器中是ls不到的。

七、rcS文件介绍2

1、mdev

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

2)在rcS文件中没有启动mdev的时候,/dev目录下启动后是空的;
在rcS文件中添加以下与mdev有关的2行配置项后,
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
再次启动系统后发现/dev目录下生成了很多的设备驱动文件。
3)/dev目录下的设备驱动文件就是mdev生成的,这就是mdev的效果和意义。

2、hostname

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

2)/bin/hostname -F /etc/sysconfig/HOSTNAME
指定了一个主机名配置文件(这个文件一般文件名叫hostname或者HOSTNAME)
根据/bin/hostname -F /etc/sysconfig/HOSTNAME,在etc下创建sysconfig
在sysconfig下创建HOSTNAME,直接输入主机名:WUDNIU
重启,发现没有显示用户名和hostname,怎么解决?
注意:不设置HOSTNAME,默认的是一个ip地址。
添加profile文件后,即可显示用户名和hostname。

3、ifconfig

1)有时候我们希望开机后进入命令行时ip地址就是个指定的地址,这时候就可以在rcS文件中ifconfig eth0 192.168.1.10

八、profile文件和用户登录理论

解决上节的实验:没有显示用户名和hostname,
etc/profile这个文件主要用来修改文件系统左侧的用户名

1、profile文件添加

1)在/etc/目录下添加profile文件
2)添加了之后的实验现象,命令行提示符前面显示:[@WUDANIU ]#
结论是:第一,profile文件起了作用,hostname显示出来了
第二,登录用户名没显示出来。
原因就是我们直接进入了命令行而没有做登录。等我们添加了用户登录功能,并且登录成功这个问题就能解决。
3)profile文件工作原理:profile文件也是被busybox(init进程)自动调用的,所以是认名字的。

2、如何看到用户登录界面

1)linux中有个原则:用一个小程序来实现一个功能,不要把程序写的特别大。
如果我们的产品确实需要很复杂的综合功能,那么需要把小程序集合起来,比如shell脚本。
2)因为我们之前inittab中有一个配置项::askfirst:-/bin/sh,这个配置项作用就是当系统启动后就去执行/bin/sh,执行
这个就会出现命令行。因此我们这样的安排就会直接进入命令行而不会出现登录界面。
3)我们要出现登录界面,就不能直接执行/bin/sh,而应该执行一个负责出现登录界面并且负责管理用户名和密码的一个程序,
busybox中也集成了这个程序(就是/bin/login和/sbin/gettty),因此我们要在inittab中用/bin/login或者/sbin/getty去
替代/bin/sh。

3、用户名和密码的设置

1)用户名和密码的设置是和登录程序有关联的,但是/bin/login和/sbin/getty在用户名和密码的管理上是一样的。其实
常见的所有的linux系统的用户名和密码的管理几乎都是一样的。
2)密码一般都是用加密文字的,而不是用明文。意思就是系统中的密码肯定是在系统中的一个专门用来存密码的文件中存储的,
用明文存密码有风险,因此linux系统都是用密文来存储密码的。关于密文密码的使用下节再讲。

九、用户登录实战

1、添加/bin/login到sysinit

1)在etc/inittab文件中修改,去掉/bin/sh,换上/bin/login,则系统启动后出现登录界面。可以输入用户名和密码。
注释掉::askfirst:-/bin/sh
在注释的下面添加ttyPS0::sysinit:-/bin/login
注意:此处的ttyPS0是选择串口0作为tty,在/dev中有这个文件
2)从新启动,出现用户登录界面,但死活密码不对。

2、添加passwd和shadow文件

1)为什么用户名和密码不对?因为我们根本没有为root用户设置密码。
2)linux系统中用来描述用户名和密码的文件是passwd和shadow文件,这两个文件都在etc目录下。passwd文件中存储的是
用户的密码设置,shadow文件中存储的是加密后的密码。
3)我们直接复制ubuntu系统中的/etc/passwd和/etc/shadow文件到当前制作的rootfs目录下,然后再做修改即可。
修改1:在rootfs文件夹下要为root用户创建一个/root目录
修改2:sudo vim etc/passwd把其它非root用户的信息全删除
修改3:在passwd中把默认脚本改为sh(ubuntu中默认是bash,busybox不支持bash,支持sh)
修改4:sudo vim etc/shadow,把其它非root用户的信息全删除,把root用户的密码信息全部删除

4)/etc/passwd和/etc/shadow修理好后,因为shadow中的加密口令被我们删除了,是空的则默认无密码直接登录。

3、重置密码实践

1)ubuntu刚装好的时候默认登录是普通用户登录的,默认root用户关闭的。普通用户的密码是装系统的的时候设置的,
普通用户登录后可以使用su passwd root给root用户设置密码,设置了密码后root用户才可以登录。
2)其实这个原因就是root用户在/etc/shadow文件中加密口令是空白的。所以是不能登录的。
3)busybox中因为没有普通用户,所以做法是:
默认root用户如果加密口令是空的则默认无密码直接登录。等我们登录了之后还是可以用passwd root给root用户设置密码。

4、getty实战

1)大家后面做项目会发现,inittab中最常见的用于登录的程序不是/bin/login,反而是/sbin/getty。

2)这两个的差别不详,但是在busybox中这两个是一样的。这两个其实都是busybox的符号链接而已。因此不用严格区分这两个
3)我们可以在inittab中用getty替换login程序来实现同样的效果。
在inittab中改成:将/bin/login相关的注释掉,添加如下

十、动态链接库的拷贝

1、静态编译链接helloworld程序并执行

1)任务:自己写一个helloworld程序,然后交叉编译链接,然后丢到开发板根文件系统中,开机后去运行。
2)C程序如果使用gcc来编译则可以在ubuntu中运行,但是不能在开发板中运行;
要在开发板运行需要用arm-linux-gcc来交叉编译,但是这个时候就不能在主机ubuntu中运行了。
我们可以用file xx命令来查看一个elf可执行程序是哪个架构的。
直接在/rootfs/root下创建Makefile脚本,将动态和静态链接helloworld的命令都写入:
3)静态链接:arm-linux-gnueabi-gcc helloworld.c -o hello_static -static
4)实验结果:静态编译链接后生成的hello_static已经可以成功运行。

2、动态编译链接helloworld程序并执行

1)动态链接:arm-linux-gnueabi-gcc helloworld.c -o hello_dynamic
2)实验结果:-sh:./hello_dynamic:not found运行时提示找不到程序。
3)错误分析:动态链接的helloworld程序中调用了printf函数,而printf函数在动态链接时要在运行时环境(开发板的rootfs)
中去寻找对应的库文件(开发板rootfs中部署的动态链接库中包含了printf函数的那个库文件)。如果找到了则printf函数就会
被成功解析,然后hello_dynamic程序就会被执行;如果找不到则程序就不能被执行,命令行会提示错误信息-sh:./hello_dynamic:not found
4)解决方案:
将arm-linux-gnueabi-gcc的动态链接库文件复制到开发板rootfs的/lib目录下即可解决。

3、找到并复制动态链接库文件到rootfs中

1)我们用的arm-linux-gnueabi-gcc这个交叉编译工具链的动态链接库在/usr/arm-linux-gnueabi/lib目录下。
其它的一些交叉编译工具链中动态链接库的目录不一定在这里,要去找一下。找的方法就是find -name “xxx”命令。
2)复制动态链接库到rootfs/lib目录下。复制时要注意参数用-rdf,主要目的就是符号链接复制过来还是符号链接。
复制命令:sudo cp lib/so /home/rootfs/wudaniu_rootfs/lib/ -rdf

3)现在再去测试./hello_dynamic看看是否可以运行,实验结果是可以运行。

4、使用strip工具去掉库中符号信息

动态链接库so文件中包含了调试符号信息,这些符号信息在运行时是没用的(调试时用的),这些符号会占用一定空间。
在传统的嵌入式系统中flash空间是有限的,为了节省空间常常把这些符号信息去掉。这样节省空间并且不影响运行。
去掉符号信息的命令:arm-linux-strip so
实际操作后发现库文件由5.6M变成了5.5M,节省了0.1M的空间。

十一、开机自启动与主流rcS格式介绍

1、修改rcS实现开机自启动

1)开机自启动指的是让一些应用程序能够开机后自动执行
2)开机自启动的实现原理就是在开机会自动执行的脚本rcS中添加上执行某个程序的语句代码即可
譬如,我们想要将上节讲的动态链接的helloworld程序实现开机自动启动
只需要在sudo vim /etc/init.d/rcS中,在最后添加以下命令即可保存重启即可。

cd /root //先进入helloworld程序的目录,执行之后,再次退出
./hello_dynamic
cd …

2、前台运行与后台运行

1)程序运行时占用了当前的控制台,因此这个程序不结束我们都无法使用控制台,这就叫前台运行。默认执行程序
就是前台运行的。
2)后台运行就是让这个程序运行,并且同时让出控制台。这时候运行的程序还能照常运行而且还能够不影响当前控制台的使用。
3)让一个程序后台运行的方法就是./xxx &

3、开机装载驱动等其它开机自动执行

如果驱动是模块化的,没有在zImage中加载。我们希望开机装载驱动,解决办法就是在rcS文件中添加对应的启动命令即可。

4、实际开发中rootfs的rcS是怎样的

1)我们以教程来分析
2)分析inittab发现:sysinit执行rcS,shutdown时执行rcK。
3)分析/etc/init.d/rcS和rcK文件发现,rcS和rcK都是去遍历执行/etc/init.d目录下的S开头的脚本文件,
区别是rcS传参是start,rcK传参是stop。
4)由此可以分析出来,正式产品中的rcS和rcK都是一个引入,而不是真正干活的。真正干活的配置脚本是/etc/init.d/S??*。
这些文件中肯定有一个判断参数是start还是stop,然后start时去做一些初始化,stop时做一些清理工作。

十二、制作ext2格式的镜像并烧录启动

1、确定文件夹格式的rootfs可用

1)设置bootargs为nfs启动方式,然后从主机ubuntu中做好的文件夹格式的rootfs去启动,然后看启动效果,作为将来的参照物。

2、动手制作ext2格式的镜像

1、在当前的rootfs文件夹创建一个mkdir ext2_rootfs,用于我们的挂载目录

依次输入以下命令:(在此创建了一个脚本)

#!/bin/sh
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/

挂载好了之后,进入ext2_rootfs中,会出现lost+found文件。

2)向ext2_rootfs中复制内容,用sudo cp …/wudaniu_rootfs/* ./ -rf

3)接下来就是:在挂载目录的上一层目录下进行清理卸载

sudo umount /dev/loop1
sudo losetup -d /dev/loop1

4)完成后得到的rootfs.ext2就是我们做好的rootfs镜像。拿去烧录即可。
将该rootfs.ext2文件拷贝到windows中,进行下面的烧录。
sudo cp rootfs.ext2 /tftpboot/ -f

3、烧录镜像并设置合适的bootargs

1)使用tftp烧录到flash中,
烧录完成后重启系统
2)设置bootargs为:
setenv bootargs ‘console=ttyPS0,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext2’

3)启动后发现现象和之前nfs方式启动挂载rootfs后一样的,至此rootfs制作实验圆满完成。

4、总结

1)到这里就将rootfs的制作步骤和原理已经完全弄完了
2)对rootfs工作的原理以及如何从零开始够建根文件系统,有了更清晰深刻的认识和积累。
3)总体来说,对rootfs的移植可以分为三部分:文件系统介绍、busybox的移植、根文件系统构建。linux文件系统
发展很快,不断会有新的安装源码在官网上更新,但是总体原理以及全局设计观念是不会变化的,因此,关键是要掌握
其方法,特别是要了解各种文件系统的特点以及其应用场合。

实验制作的文件系统

将文件系统下载到DDR的0x6000000处
tftpboot 0x6000000 rootfs.ext2
从第二个分区的0x300000512的偏移地址处,擦除0x5000,即10M大小空间
mmc erase 0x300000 0x5000 0x5000的意思为0x5000=20480,20480
512=10M,mmc的块为512字节。
从DDR的0x6000000处写到mmc的0x300000起始地址,写0x5000大小的数据
mmc write 0x6000000 0x300000 0x5000

你可能感兴趣的:(学习,linux,嵌入式,arm,经验分享)