本人刚毕业大学生,对驱动移植于开发了解尚浅,有任何疑问和问题可以在评论区指出
根文件系统介绍
尽管内核是linux的核心,但文件却是用户与操作系统交互所采用的主要工具。这对linux来说尤其如此,这是因为在UNIX传统中,它使用文件I/O机制管理硬件设备和数据文件。
根文件系统是控制权从linux内核转移到用户空间的一个桥梁。linux内核就类似于一个黑匣子,只向用户提供各种功能的接口,但是功能的具体实现不可见,用户程序通过对这些功能接口的不同整合实现不同的功能需求。
文件系统是操作系统用于明确存储设备或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。文件系统由三部分组成:文件系统的接口,对对象操作和管理的软件集合,对象及属性。
从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。
什么是根文件系统
根文件系统首先是内核启动时所安装的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
展开来细说就是,根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本和服务加载到内存中去运行。
文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没有办法真正的启动Linux操作系统的,会出现无法加载文件系统的错误。
为什么根文件系统很重要
根文件系统之所以在前面加一个”根“,说明它是加载其它文件系统的”根“,那么如果没有这个根,其它的文件系统也就没有办法进行加载的。
根文件系统包含系统启动时所必须的目录和关键性的文件,以及使其他文件系统得以挂载所必要的文件。例如:
- init进程的应用程序必须运行在根文件系统上;
- 根文件系统提供了根目录“/”;
- linux挂载分区时所依赖的信息存放于根文件系统/etc/fstab这个文件中;
- shell命令程序必须运行在根文件系统上,譬如ls、cd等命令;
根文件件系统功能
-
存储操作系统文件:根文件系统存储操作系统核心文件,如内核文件、设备驱动程序和基本系统工具。这些文件是操作系统正常运行所必需的。
-
包含启动脚本:根文件系统中通常包含启动脚本,用于在系统启动时执行必要的初始化和配置。这些脚本负责加载内核、初始化设备、挂载其他文件系统等。
-
提供系统配置信息:根文件系统中存储了系统的基本配置信息,如网络配置、用户帐户、权限设置等。这些配置文件和目录用于定义系统的行为和属性。
-
容纳用户空间程序:根文件系统包含了操作系统的用户空间程序和工具,如shell、文本编辑器、系统管理工具等。这些程序使用户能够与操作系统进行交互和管理。
-
管理其他文件系统:根文件系统负责管理其他文件系统的挂载和卸载。它可以包含挂载点用户可以通过挂载其他文件系统来扩展存储空间或添加额外的功能。
根文件系统组成
最小目录
- /etc/:存储重要的配置文件。
- /bin/:存储常用且开机时必须用到的执行文件。
- /sbin/:存储着开机过程中所需的系统执行文件。
- /lib/:存储/bin/及/sbin/的执行文件所需的链接库,以及Linux的内核模块。
- /dev/:存储设备文件。
根文件系统启动流程
[一文讲解Linux内核中根文件系统挂载流程 - 简书.pdf](PDF\根文件系统\一文讲解Linux内核中根文件系统挂载流程 - 简书.pdf)
-
文件系统的挂载需要提供挂载点(挂载目录),linux内核在初始化时会初始化一个虚拟的“/”目录用于根文件系统的挂载
-
若init程序存在的物理磁盘设备在内核访问时已准备就绪,内核便可以直接挂载根文件系统并运行init程序,实现kernel空间到用户空间的跳转。
-
首先,内核根据配置利用过渡根文件系统或默认设置初始化“/”目录
-
然后检测初始化的“/”目录中是否存在指定应用程序(默认为init程序),
- 如果存在,则直接执行该init程序;
- 否则,根据配置挂载实际根文件系统。
-
当指定do_skip_initramfs参数为1时,表示内核忽略过渡根文件系统,直接以default_rootfs初始化“/”目录,并直接挂载实际根文件系统。
如果此时存在过渡根文件系统,并且过渡根文件系统同样被配置为一个具有实际功能的文件系统(不仅仅起过渡作用),那么可以通过cmdline的skip_initramfs指引内核转移到具有不同功能的用户空间中,如android的recovery模式和boot模式。
-
无论内核中是否存在实际的initramfs,内核总是会首先以initramfs的格式视图对其进行解析。
initramfs存在于内核的所占内存,释放到“/”目录之后,原initramfs所占内存不会被释放
-
initrd解析
-
unpack_to_rootfs(initrd)
- cpio格式的initrd也直接解压并释放到“/”目录中,initrd由uboot加载进内存,当initrd被释放到“/”目录之后,initrd所占的内存可以被释放以节约内存空间。
- 若initrd并不是cpio格式,则以image-initrd的格式对其进行解析,此时只是将image-initrd的内容转移到“/initrd.image”,并释放initrd的占用空间,具体的处理过程在prepare_namespace中的load_initrd中。
-
-
检查初始化程序是否存在
- 在无论哪种形式完成“/”目录的初始化之后,检查“/”目录中是否包含初始化init程序
- 如果有则直接运行该程序并跳转到用户空间。该init程序的具体执行流程由用户决定。
- 如果不是最终的根文件系统,用户可通过init程序安装实际根文件系统的驱动并挂载真正的根文件系统。
- 在无论哪种形式完成“/”目录的初始化之后,检查“/”目录中是否包含初始化init程序
-
挂载实际根文件系统
-
如果“/”目录下不存在初始化程序,则尝试在内核中直接挂载根文件系统,分为两种情况:
-
根文件系统存在于MTD/UBI设备,驱动程序在内核初始化阶段已安装,可直接挂载;
因为其驱动程序为内核必须,所以在内核初始化时便会加载。此时将根文件系统将被挂载到“/root”下
-
根文件系统存在其他存储设备中,以过渡根文件系统安装存储设备的驱动,最后由内核挂载根文件系统。
-
-
-
执行用户初始化程序
-
此时根文件系统已经挂载完成,则查找根文件系统中的初始化程序并执行
当“/”目录下不存在init程序时,内核也会从sbin/etc/bin等其他目录下查找默认的可执行程序。
-
常用目录
- /bin目录
该目录下存放所有用户都可以使用的、基本的命令,这些命令在挂接其它文件系统之前就可以使用,所以/bin目录必须和根文件系统在同一个分区中。
/bin目录下常用的命令有:cat,chgrp,chmod,cp,ls,sh,kill,mount,umount,mkdir,mknod,test等,我们在利用Busybox制作根文件系统时,在生成的bin目录下,可以看到一些可执行的文件,也就是可用的一些命令。 - /sbin 目录
该目录下存放系统命令,即只有管理员能够使用的命令,系统命令还可以存放在/usr/sbin,/usr/local/sbin目录下,/sbin目录中存放的是基本的系统命令,它们用于启动系统,修复系统等,与/bin目录相似,在挂接其他文件系统之前就可以使用/sbin,所以/sbin目录必须和根文件系统在同一个分区中。
/sbin目录下常用的命令有:shutdown,reboot,fdisk,fsck等,本地用户自己安装的系统命令放在/usr/local/sbin目录下。 - /dev目录
该目录下存放的是设备文件,设备文件是Linux中特有的文件类型,在Linux系统下,以文件的方式访问各种设备,即通过读写某个设备文件操作某个具体硬件。比如通过"dev/ttySAC0"文件可以操作串口0,通过"/dev/mtdblock1"可以访问MTD设备的第2个分区。 - /etc目录
该目录下存放着各种配置文件,对于PC上的Linux系统,/etc目录下的文件和目录非常多,这些目录文件是可选的,它们依赖于系统中所拥有的应用程序,依赖于这些程序是否需要配置文件。在嵌入式系统中,这些内容可以大为精减。 - /lib目录
该目录下存放共享库和可加载(驱动程序),共享库用于启动系统。运行根文件系统中的可执行程序,比如:/bin /sbin 目录下的程序。 - /home目录
用户目录,它是可选的,对于每个普通用户,在/home目录下都有一个以用户名命名的子目录,里面存放用户相关的配置文件。 - /root目录
根用户的目录,与此对应,普通用户的目录是/home下的某个子目录。 - /usr目录
/usr目录的内容可以存在另一个分区中,在系统启动后再挂接到根文件系统中的/usr目录下。里面存放的是共享、只读的程序和数据,这表明/usr目录下的内容可以在多个主机间共享,这些主要也符合FHS标准的。/usr中的文件应该是只读的,其他主机相关的,可变的文件应该保存在其他目录下,比如/var。/usr目录在嵌入式中可以精减。 - /var目录
与/usr目录相反,/var目录中存放可变的数据,比如spool目录(mail,news),log文件,临时文件。 - /proc目录
这是一个空目录,常作为proc文件系统的挂接点,proc文件系统是个虚拟的文件系统,它没有实际的存储设备,里面的目录,文件都是由内核临时生成的,用来表示系统的运行状态,也可以操作其中的文件控制系统。 - /mnt目录
用于临时挂载某个文件系统的挂接点,通常是空目录,也可以在里面创建一引起空的子目录,比如/mnt/cdram /mnt/hda1 。用来临时挂载光盘、硬盘。 - /tmp目录
用于存放临时文件,通常是空目录,一些需要生成临时文件的程序用到的/tmp目录下,所以/tmp目录必须存在并可以访问。
制作根文件系统
主流根文件系统制作工具
- busybox
- 仅构建一些常用的命令和文件,需要手动创建如:lib库、etc目录下文件
- 需要移植第三方库
- 构建出的根文件系统默认没有用户名和密码
- buildroot
- 包含了busybox的功能
- 内部集成了各种软件,需要什么软件就选择什么软件,不需要移植
- 支持图形化配置,make menuconfig
- yocto
- 过于复杂,需要时间久
制作根文件系统
使用buildroot制作根文件系统
-
打开根文件系统图形化配置界面
make menconfig
- 注意:解压时不要使用
sudo
- 注意:解压时不要使用
-
配置根文件系统
-
配置
Target options
- 配置内容
Target Architecture (AArch64 (little endian)) ---> //目标架构arm64,小端 Target Architecture Variant (cortex-A55) ---> //目标架构 Floating point strategy (FP-ARMv8) ---> //浮点策略 MMU Page Size (64KB) ---> //mmu大小 Target Binary Format (ELF) ---> //指定生成的二进制文件格式(可执行、可连接)
-
配置
Toolchain
- 配置内容,主要是配置交叉编译工具链
Toolchain (Custom toolchain) ---> //工具链类型,外部工具链 Toolchain origin (Pre-installed toolchain) ---> //工具链类型,自定义工具链 (/usr/bin/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu) Toolchain path //指定工具链路径 ($(ARCH)-none-linux-gnu) Toolchain prefix //指定工具链前缀 External toolchain gcc version (9.x) ---> //外部工具链使用的内核头文件系列版本 External toolchain kernel headers series (4.20.x) ---> //外部工具链使用的内核头文件系列版本 External toolchain C library (glibc) ---> //使用的C库类型 [*] Toolchain has SSP support? //工具链是否使用堆栈包含 [*] Toolchain has SSP strong support? //工具链是否强力支持堆栈包含 [*] Toolchain has RPC support? //工具链是否支持远程过程调用 [*] Toolchain has C++ support? //工具链是否支持C++
-
配置
System configuration
- 配置内容,如开发板名字,欢迎与、用户名、密码等
(NXP-imx) System hostname //平台名字,自行设置 (Welcome to NXP-imx93) System banner //欢迎语 Init system (BusyBox) ---> //使用 busybox /dev management (Dynamic using devtmpfs + mdev) ---> //使用 mdev [*] Enable root login with password //使能登录密码 (123987) Root password //登录密码为 123987
-
配置
Filesystem images
- 配置内容,最终制作的根文件系统是什么格式
-> Filesystem images -> [] ext2/3/4 root filesystem //如果是 EMMC 或 SD 卡的话就用 ext3/ext4 -> ext2/3/4 variant = ext4 //选择 ext4 格式 -> [] ubi image containing an ubifs root filesystem //如果使用 NAND 的话就用 ubifs
-
禁止编译linux内核和uboot
-> Kernel
-> [ ] Linux Kernel //不要选择编译 Linux Kernel 选项!-> Bootloaders
-> [ ] U-Boot //不要选择编译 U-Boot 选项! -
配置
Target packages
此选项用于配置要选择的第三方库或软件、比如 alsa-utils、ffmpeg、iperf 等工具
-
-
编译buildroot
sudo make //一定要加sudo ,不能通过-jx指定多核编译
-
移植根文件系统检测是否好用
-
配置第三方库
-
使能 alsa-lib
输入 make menuconfig,打开 buildroot 配置界面,配置路径如下:Target packages -> Libraries -> Audio/Sound -> -*- alsa-lib —> 此配置项下的文件全部选中
-
使能 alsa-utils
接下来使能 alsa-utils,路径如下:
Target packages -> Audio and video applications -> alsa-utils 此目录下的软件全部选中
-
使用busybox制作根文件系统
-
设置交叉编译器
修改makefile
CROSS_COMPILE ?= /usr/bin/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux- AECH ?= arm64
-
添加中文字符支持
打开文件busybox-1.29.0/libbb/printable_string.c,将函数printable_string()中的部分程序注释掉,修改后的函数内容如下:
/********** printable_string.c代码段 **********/ const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str) { char *dst; const char *s; s = str; while (1) { ...... if (c < ' ') break; /* 注释掉下面这个两行代码,禁止字符大于0X7F以后 break */ // if (c >= 0x7f) // break; s++; } #if ENABLE_UNICODE_SUPPORT dst = unicode_conv_to_printable(stats, str); #else { char *d = dst = xstrdup(str); while (1) { unsigned char c = *d; if (c == '\0') break; /* 修改下面代码,禁止字符大于0X7F以后输出‘?’ */ // if (c < ' ' || c >= 0x7f) if( c < ' ') *d = '?'; d++; } ...... } #endif return auto_string(dst); }
打开文件busybox-1.29.0/libbb/unicode.c,修改如下内容:
/********** unicode.c代码段 **********/ static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags) { char *dst; unsigned dst_len; unsigned uni_count; unsigned uni_width; if (unicode_status != UNICODE_ON) { char *d; if (flags & UNI_FLAG_PAD) { d = dst = xmalloc(width + 1); ...... /* 修改下面一行代码 */ // *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; *d++ = (c >= ' ') ? c : '?'; src++; } *d = '\0'; } else { d = dst = xstrndup(src, width); while (*d) { unsigned char c = *d; /* 修改下面一行代码 */ // if (c < ' ' || c >= 0x7f) if(c < ' ') *d = '?'; d++; } } ...... return dst; }
-
打开根文件系统图形化配置界面
make menconfig
-
设置Settings -> Build static binary (no shared libs)
用于选择是静态编译还是动态编译,静态编译不需要库文件,编译出来的库很大;动态编译要求根文件系统中有库文件,编译出来的 busybox 小很多。这里不使用静态编译,保持默认不选
-
设置Settings -> vi-style line editing commands
-
配置Linux Module Utilities -> Simplified modutils(不需选中)
-
配置Linux System Utilities -> mdev (16 kb)(确保全选)
-
设置Settings -> Support Unicode,使能busybox的unicode编码以支持中文
-
编译
-
-
添加lib库
-
rootfs中创建一个lib库,lib库从交叉编译器中获取
cd /usr/bin/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/lib
-
将目录下所有的so和**.a**文件拷贝到rootfs/lib中
cp *so* *.a ~/rootfs/lib/ -d //-d表示拷贝符号链接 //特殊库文件:ld-linux-armhf.so.3(软连接文件,即快捷方式) 的处理 rm ld-linux-armhf.so.3 //先删除rootfs/lib中的这个软链接 //然后重新拷贝ld-linux-armhf.so.3 cp /usr/bin/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/lib/ld-linux-armhf.so.3 .
-
进入交叉编译器的"lib64"目录,将此目录下所有的so和.a 库文件拷贝到 rootfs/lib 目录中
cd /usr/bin/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/lib64 cp *so* *.a ~/rootfs/lib/ -d //-d表示拷贝符号链接
-
-
创建其他文件夹
mkdir dev proc mnt sys tmp root
移植根文件系统
使用nfs挂载根文件系统
注意
1. 从Ubuntu17.04开始,nfs默认只支持协议3和协议4,而kernel中默认支持协议2。
2. 端口需要看手册进行选择,imx93的端口为:`console=ttyLP0,115200 `
配置nfs启动环境变量
setenv bootargs 'root=/dev/nfs nfsroot=192.168.0.125:/opt/imx93/rootfs/rootfs,tcp rw ip=192.168.0.200:192.168.0.125:192.168.0.1:255.255.255.0::eth0:off init=/linuxrc console=ttyLP0,115200'
- root=/dev/nfs:指定根文件系统的类型为NFS。这意味着根文件系统将通过NFS协议来获取
- nfsroot:指定NFS根文件系统的位置
- 192.168.0.125是NFS服务器的IP地址
- /opt/imx93/rootfs/rootfs是服务器上要共享的路径
- tcp表示使用TCP协议进行NFS连接
- rw:表示将NFS根文件系统以读写方式挂载。这允许设备对根文件系统进行读取和写入操作
- ip:指定设备的网络配置
- init:指定用于初始化系统的初始化进程
- console:控制设备的控制台输出
init启动流程
什么是init进程
- 操作系统中不可缺少的程序之一
- 由内核启动的用户级进程
- 内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以init始终是第一个进程
init作用什么
-
进程管理:init进程负责启动系统中的其他进程,并按需监控和终止它们。它可以从配置文件或脚本中读取指令,根据定义的启动顺序和依赖关系来启动和停止其他进程。
-
进程间通信(IPC):init进程通常也负责处理进程间通信,例如管道、信号和套接字等。它可以创建和管理这些通信通道,以便进程之间能够相互交换数据和通知。
-
系统初始化和资源管理:init进程在系统启动时负责初始化各种子系统和设备。它可以加载驱动程序、配置网络接口、挂载文件系统等。此外,init进程还负责管理系统的资源分配,如内存、CPU等。
-
启动服务和守护进程:init进程可以启动系统中的服务和守护进程,如网络服务、数据库服务等。它会监控这些进程的运行状态,并在需要时重新启动它们。
-
运行启动脚本:init进程会读取系统的启动脚本(如/etc/inittab或/etc/init.d),并根据脚本内容启动其他用户级进程和服务。
-
启动系统服务:init进程会启动各种系统服务,如网络服务、文件系统服务、定时任务等。这些服务通过配置文件进行管理,init进程会根据配置文件的设置启动相应的服务。
-
监控进程:init进程会监控其他进程的运行状态,如果某个进程意外退出或异常终止,init进程将负责重新启动它。
-
处理关机和重启:当系统收到关机或重启命令时,init进程会接收并处理这些信号,安全地关闭系统中的进程,并执行适当的关闭操作。
init启动流程
init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终用户空间
linux中的所有进程都是由init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。
init进程怎样创建其他进程
init进程通常使用fork()系统调用创建子进程,并使用exec()系统调用加载新的可执行程序替换子进程的内存空间,从而创建新的进程。这些子进程可能是系统服务、守护进程或其他用户级进程。
开机自启动设置
步骤:
-
编写源码,并使用交叉编译工具链进行编译,并放到开发板上
-
给予可执行文件权限:
chmod a+x <可执行文件名>
-
设置开启启动文件
/etc/init.d/rcS
/etc/init.d/rc.local
/etc/profile
/usr/etc/rc.local -
添加可执行文件
cd / //路径 ./<可执行文件>
守护进程执行流程
什么是守护进程
守护进程也就是通常所说的 Daemon 进程,它是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统启动时开始执行,在系统关闭时终止。Linux 中很多系统服务都是通过守护进程实现的。
怎样制作守护进程
-
创建一个子进程,让父进程退出
-
目的:让子进程变成孤儿进程,后台进程,同时被Init进程收养
pid=fork()
-
-
创建一个新会话,将进程设置为会话组组长
-
目的:让子进程成为会话组组长,为了让子进程完全脱离终端
setsid();
-
-
修改路径为根目录
-
目的:原因进程运行的路径不能被删除或卸载
chdir();
-
-
重设权限掩码 umask;
-
目的:增大进程创建文件时权限,提高灵活性;umask(0)
umask();
-
-
关闭多余文件描述符
close();