本节目录如下所示。
Linux文件系统是用于管理和存储的一系列的文件的集合,按照功能上包含如下内容。
对于文件系统,可能接触过的有busybox、buildroot、yocto、openwrt,类似桌面端的debian、ubuntu、armbian、麒麟等。回顾构建和使用这些系统的经历,有问题一直在困扰我,为什么使用这些工具能够构建可用的文件系统,它们实现的原理是什么,为什么按照这些的步骤就可以实现可用的文件系统。busybox,buildroot,debian, openwrt等它们是否有什么联系?在理解这个问题以前,无论是构建文件系统,还是交叉编译软件,可以说迷雾重重,直到系统总结这个问题,才开始有了主动思考,分析并解决问题的能力。
这里用一句话解释,文件系统是被内核访问的,由目录和文件构成的树结构的集合体。它设计上是为内核提供可以对外的访问接口,而在Linux中”一切皆文件的思想”指导下,这些接口又以文件的形式展示。基于这些信息,就可以将文件系统进一步解构,包含如下结构。
理解了上述这些,就可以进一步去掌握文件系统各目录的功能,具体如下。
上面是文件系统的目录结构和功能说明,对于用户来说,主要接触的目录为/dev、/sys、/proc目录。关于rootfs目录的标准定义参考网址: http://www.pathname.com/fhs
文件系统的构建过程,就是创建上述目录,并在对应目录下添加相应文件,库或者程序的过程。而上面提到文件系统的构建过程,本质上也是实现这些目录和文件,当然方法可能千差万别,既有手动编译构建,也有下载安装。理解了文件系统的本质,那么构建系统和交叉编译的问题就有另外的思路去理解。
构建系统的步骤可以分为如下:
至此,一个基于命令行的,可命令行访问的最小文件系统构建完成。BusyBox的构建就是基于此逻辑。不过这样构建的系统只支持最基本的功能,如etc启动配置,shell命令(ls, cd,mv)等,参考章节: Linux Shell命令说明。如果想支持更多的功能,就需要去找源码单独编译移植,然后将动态库和程序安装到相应的目录中,前面的交叉编译就是讲述了这个过程,参考章节: 嵌入式Linux平台交叉编译。可以看到在这里,知识终于串联在一起。
基于BusyBox的系统构建仅提供必要的命令工具和文件,系统中需要的目录,动态库和配置文件都需要手动创建或添加。这种方式会十分麻烦,而且不同版本或库的兼容性问题处理也十分繁杂,只能用于了解系统构建过程,在实践中很少使用。为了解决这类问题,就有技术人员发起项目,通过脚本工具链,以BusyBox这类方案为基础,集合Linux平台常用工具,配合界面化的管理,实现通过配置命令的方式直接生成打包完整的文件系统,这就是buildroot的框架本质。当然buildroot项目支持更加全面,不仅包含以busybox,systemd的基础系统,还支持扩展添加python,lua等软件,通过类似内核Kconfig的图形化配置,兼顾了自动化构建和可裁剪系统的优点,对于低性能嵌入式soc方案,目前就是最优选择。
Buildroot是通用的方案,如果应用需要很多适配硬件的配置项的调整,这也是一大难点。因此部分厂商参考这个思路,根据自家平台进一步整理个性化,包含私有的配置,然后打包以SDK形式发布,允许用户编译使用。以NXP为例,发布的Yocto系统就是按照这个思路设计的。当然,有些芯片厂商或者方案商也会根据行业的特殊需求定制对应的系统,例如Openwrt就服务于软路由实现,自带网络层转发管理和远程Web控制,其内部也集成下载工具,支持通过opkg安装离线和本地软件,是为路由应用专门定制的os系统。
BusyBox,Buildroot,Yocto和Openwrt这些文件系统的构建都涉及到环境构建和编译,不仅构建需要耗费大量的时间,而且文件系统的编译也十分依赖系统环境的支持,开发者不同版本的系统环境以及库支持的千差万别,如何解决编译的兼容问题也是构建平台的大麻烦。那么有没有将需要的二进制执行文件,脚本,配置文件压缩后放置在指定地址,用户只需要下载解压,执行安装既可以生成,不需要编译就可以完善系统呢,类似桌面端的Ubuntu使用iso安装,apt更新版本。这当然存在,嵌入式端也有同样的解决方案,Debian文件系统就是基于此方法构建的(Ubuntu属于Debian的进一步整合)。当然还有其它的文件系统,如Armbian,宝塔os等,虽然构建方式不同,也更加复杂,但仍然属于这类操作方式;理解了这些,就可以更清晰的构建文件系统。
内核在启动的最后阶段,会查找并执行init对应程序,之后会挂载文件系统,通过脚本执行必要的初始化,进入shell命令行。这时就进入熟悉的命令行界面,可以通过串口进行打印访问。对于嵌入式Linux平台,主流文件系统启动方式分为SysVinit和Systemd。
Sysvinit,即System V风格的init系统,是早期Linux发行版中使用的默认init系统。init进程是Linux系统中的第一个用户级进程,其进程号始终为1,负责启动其他用户级进程或服务,系统。这里以编译的Buildroot文件系统启动流程来说明执行流程。
/etc/inittab是Linux系统中一个非常重要的配置文件,它用于定义init进程的行为,格式:
| identifier(标识符) | run_level(运行级别) | action(动作关键字) | process(要执行的shell命令)|
identifier(标识符) :用于唯一标识/etc/inittab文件中的每一个登记项。它通常是一个简短的字符串,最多为4个字符
run_level(运行级别) :指定相应的登记项适用于哪一个运行级。运行级别是系统的一种状态,表示系统当前运行的模式。在该字段中,可以同时指定一个或多个运行级,其中各运行级分别以数字0、1、2、3、4、5、6或字母a、b、c表示,且无需对其进行分隔。如果为空,则相应的登记项将适用于所有的运行级
0: 关机状态
1: 单用户模式,只有系统管理员能够登录
2: 多用户模式,但不支持网络
3: 完全的多用户模式,支持网络,是大多数服务器的默认模式
4: 用户自定义的运行级别,通常不使用
5: 图形用户界面模式,启动X-Window系统
6: 重启系统
action(动作关键字): 用于指定init命令或进程对相应进程(在“process”字段定义)所实施的动作。具体的动作包括:
sysinit :系统初始化,只在系统启动或重新启动时执行一次
respawn :如果相应的进程不存在,则启动该进程;如果进程终止,则重新启动该进程
askfirst :与respawn类似,但在运行进程前会打印提示信息,等待用户敲入回车后再执行
wait :启动进程并等待其结束,然后再处理/etc/inittab文件中的下一个登记项
once :启动相应的进程,但不等待该进程结束便继续处理/etc/inittab文件中的下一个登记项;当该进程死亡时,init也不重新启动该进程
ondemand :与respawn的功能相同,但只用于运行级为a、b或c的登记项
ctrlaltdel :当用户按下Ctrl+Alt+Del组合键时执行对应的进程
shutdown :用于关闭系统执行的动作
powerfail、powerwait、powerokwait、powerfailnow :与电源故障相关的动作
boot和bootwait :在引导过程中执行的进程
off :如果相应的进程正在运行,则发出警告信号,等待20秒后强行终止该进程
initdefault :设置系统的默认运行级别
process(要执行的shell命令) :指定要执行的进程和它的命令行。任何合法的shell语法都适用于该字段
这里以Buildroot生成的inittab说明。
# Buildroot中的/etc/inittab文件
::sysinit:/bin/mount -t proc proc /proc # 创建了一个到内核数据结构的接口
::sysinit:/bin/mount -o remount,rw / # 重新挂载一个已经挂载的文件系统,改变其挂载选项为可读写
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a # 用于根据/etc/fstab文件挂载所有未挂载的文件系统
::sysinit:/bin/mkdir -p /run/lock/subsys
::sysinit:/sbin/swapon -a # 用于根据/etc/fstab文件启用所有标记为交换空间的设备或文件
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname # 指定/etc/hostname作为系统hostname来源
# now run any rc scripts
::sysinit:/etc/init.d/rcS # 执行rcS脚本
# Put a getty on the serial port
console::respawn:/sbin/getty -L console 0 vt100 # 指向界面显示/dev/console
ttymxc0::respawn:/sbin/getty -L ttymxc0 0 vt100 # 指向串口显示/dev/ttymxc0
# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot
# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a # 移除swap空间
::shutdown:/bin/umount -a -r # 移除/etc/fstab挂载的文件系统
如果需求实现root用户直接登录。
# 串口请求修改如下
# Put a getty on the serial port
ttymxc0::respawn:-/bin/sh
/etc/fstab文件是Linux系统中用于定义和管理文件系统的挂载信息的配置文件。详细解释如下。
挂载点(Mount Point):文件系统挂载的位置,即它在目录树中的路径。挂载点可以是任何一个空目录
文件系统类型(File System Type):指定文件系统类型,它告诉内核如何处理该分区。
Backup Operation(dump 参数):指定是否启用dump备份程序。通常这个参数的值为0或者1,0表示不备份,1表示备份
Pass Order(fsck 参数):决定系统在启动时使用fsck工具检查文件系统的顺序。这个数字值是用来指定系统启动时是否检查文件系统的完整性以及检查的顺序。通常这个参数的值为0、1或2:
# <file system> <mount pt> <type> <options> <dump> <pass>
/dev/root / ext2 rw,noauto 0 1
proc /proc proc defaults 0 0
devpts /dev/pts devpts defaults,gid=5,mode=620,ptmxmode=0666 0 0
tmpfs /dev/shm tmpfs mode=0777 0 0
tmpfs /tmp tmpfs mode=1777 0 0
tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0
sysfs /sys sysfs defaults 0 0
#!/bin/sh
# 执行/etc/init.d目录下以S*开头的脚本进行执行,以.sh结尾的直接执行,其它带start参数执行
# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i start
;;
esac
done
对于rcS中执行的脚本,主要包含seedrng, syslogd, klogd, sysctrl, network等执行流程。
/etc/network/interfaces文件是Linux系统中用于配置网络接口的一个关键配置文件,特别是在基于Debian的Linux发行版(如Ubuntu)中。该文件包含了网络接口的设置信息,如IP地址、子网掩码、网关、DNS服务器等。系统管理员通过编辑此文件来配置或修改网络接口的设置。
auto eth0
iface eth0 inet static
address 192.168.1.100
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 8.8.8.8 8.8.4.4
auto eth0
iface eth0 inet dhcp
auto lo
iface lo inet loopback
账户信息保存在/etc/passwd中, 包含了所有系统用户账户以及每个用户的基本配置信息,口令信息一般保存在/etc/shadow中。
# /etc/passwd说明
freedom:x:1000:1000:,,,:/home/freedom:/bin/bash
登录用户名:用户口令:用户UID:组UID:备注:$HOME:用户启动得shell
# /etc/shadow说明
freedom:$$$$$$$$$$$$$$$$$$:19831:0:99999:7:::
登录用户名:用户口令:密码最后修改时间:最小密码效期:最大密码效期:密码警告周期:密码不活跃周期:账户到期日期:保留字段
# /etc/group说明
netdev:x:116:freedom
组名:加密密码:组ID:用户列表
systemd是Linux系统中的一个系统和服务管理器,它负责在系统启动时启动系统服务、管理系统进程和资源,以及在系统运行时提供各种系统管理功能。systemd的设计目标是提供一个高效、可靠、灵活的系统管理框架,以取代传统的SysV init系统。
systemd的主要特点和功能包含:
systemd系统启动流程通常包括以下几个阶段.
接下来systemd就会启动系统服务单元,执行相应的启动程序,关于systemd服务格式,如下所示。
[Unit]
Description: service的简单描述
Requires(可选): 设置服务的强依赖性,表示当前服务启动之前必须启动的其他服务
After(可选):指定服务启动的顺序,表示当前服务应该在哪些服务之后启动
Before(可选):与After相反,指定服务启动的顺序,表示当前服务应该在哪些服务之前启动
Wants(可选):设置服务的弱依赖性,表示当前服务启动之后可能还需要启动的其他服务
Conflicts(可选):定义服务间的冲突关系,表示如果当前服务启动,则指定的服务不能启动
[Service]
Type:服务进程启动类型,如simple、forking、oneshot、dbus、notify和idle等
ExecStart:指定启动服务时要运行的命令或脚本的绝对路径。
ExecStartPre(可选):在ExecStart之前运行的命令或脚本
ExecStartPost(可选):在ExecStart之后运行的命令或脚本
ExecStop:指定停止服务时要运行的命令或脚本
ExecReload(可选):指定重新加载服务配置时要运行的命令或脚本
Restart:指定服务在失败或停止时是否自动重启,以及重启的策略(如always、on-failure等)
RestartSec(可选):在服务重启之前等待的时间间隔
EnvironmentFile(可选):指定环境配置文件的路径。
[Install]
WantedBy:指定服务应该与哪个目标(target)一起启动,通常设置为multi-user.target,表示服务将在多用户模式下启动
Also(可选):指定在安装本服务时还要安装的其他相关服务
Alias(可选):为服务指定别名,方便使用systemctl命令进行管理
这边举例实现一个简单的服务,实现开机自启。
首先,创建一个名为my-service.service的systemd服务文件,内容如下:
[Unit]
Description=My Custom Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/my-service.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
然后,将该文件保存到/etc/systemd/system/目录下。
接下来,启动并启用该服务:
sudo systemctl start my-service
sudo systemctl enable my-service
这样,my-service就会在系统启动时自动启动,并且在服务失败时会自动重启。
下面以主流的文件系统构建方法,展示如何实现用于产品运行的系统平台,具体如下所示。
直接开始下一章节说明: 基于busybox构建文件系统