季康子问政于孔子。孔子对曰:“政者,正也。子帅以正,孰敢不正?” 《论语》:颜渊篇
百篇博客系列篇.本篇为:
v66.xx 鸿蒙内核源码分析(根文件系统) | 先挂到/
上的文件系统
文件系统相关篇为:
- v62.02 鸿蒙内核源码分析(文件概念) | 为什么说一切皆是文件
- v63.04 鸿蒙内核源码分析(文件系统) | 用图书管理说文件系统
- v64.06 鸿蒙内核源码分析(索引节点) | 谁是文件系统最重要的概念
- v65.05 鸿蒙内核源码分析(挂载目录) | 为何文件系统需要挂载
- v66.07 鸿蒙内核源码分析(根文件系统) | 先挂到
/
上的文件系统 - v67.03 鸿蒙内核源码分析(字符设备) | 字节为单位读写的设备
- v68.02 鸿蒙内核源码分析(VFS) | 文件系统和谐共处的基础
- v69.04 鸿蒙内核源码分析(文件句柄) | 你为什么叫句柄 ?
- v70.05 鸿蒙内核源码分析(管道文件) | 如何降低数据流动成本
FHS | 文件系统层次结构标准
-
在 [挂载目录篇] 中提到内核为了兼容文件系统的差异性,引出了目录树的概念,目录树是由各个文件系统像搭积木一样拼接起来的,任何文件系统只需要挂载到一个目录上就能对接进来,内核抽象出统一的挂载接口,各文件系统自己实现这些接口就行. 既然目录如此重要, 就需要规范管理, 类Unix都遵循 FHS 规范,鸿蒙同样遵循.
-
文件系统层次结构标准(英语:Filesystem Hierarchy Standard,FHS)定义了Linux操作系统中的主要目录及目录内容。FHS由Linux基金会维护。 当前版本为3.0版,于2015年发布。基本目录如下:
/ 根目录 /home 用户主文件夹 /etc 系统主要的配置文件几乎都放置在这个目录内 /root 系统管理员(root)的主文件夹 /bin 可以被 root 与一般账号所使用 /sbin 这些命令只有 root 才能够利用来“设置”系统 /lib 放置的则是在开机时会用到的函数库 /opt 用于安装第三方应用程序的 /dev 任何设备与接口设备都是以文件的形式存在于这个目录当中 /proc 一个虚拟的文件系统, 它放置的数据都是在内存当中 /sys 也是一个虚拟的文件系统,主要也是记录与内核相关的信息 /media 放置的就是可删除的设备 /mnt 暂时挂载某些额外的设备 /srv 一些网络服务启动之后,这些服务所需要取用的数据目录 /tmp 正在执行的程序暂时放置文件的地方, 系统会不定期删除 /usr “UNIX 操作系统软件资源” 所放置的目录 /usr/bin/: 绝大部分的用户可使用命令都放在这里 /usr/include/:C/C++等程序语言的头文件 header 与包含文件include放置处 /usr/lib/: 包含各应用软件的函数库、目标文件以及一些不被一般用户惯用的执行文件或脚本 /usr/local/: 系统管理员在本机自行安装下载的软件建议安装到此目录 /usr/sbin/: 非系统正常运行所需的系统命令 /usr/share/: 放置共享文件的地方 /usr/src/: 一般源码建议放置到这里 /var 该目录主要针对常态性可变动文件 /var/cache/: 应用程序本身运行过程中会产生的一些暂存文件 /var/lib/: 程序本身执行的过程中,需要的数据文件放置的目录 /var/lock/: 目录下的文件资源一次只能被一个应用程序所使用 /var/log/: 放置登录文件的目录 /var/mail/: 放置个人电子邮件信箱的目录 /var/run/: 某些程序或服务启动后的PID目录 /var/spool/: 放置排队等待其他应用程程序使用的数据
什么是根文件系统
看网上有很多的文章,但基本全是一大抄,说是内核启动时所mount的第一个文件系统,这话固然是没错, 但想重新定义下这个概念, 所谓 根文件系统 就是先挂到根目录/
上的文件系统. 核心是根目录 /
. /
目录并不必先属于哪个文件系统,否则就是先有蛋还是先有鸡的问题,所以别被蒙圈了,它跟其他文件系统没有任何区别,只是它先来,把坑/
给占了,后续来的只能挂到它下面的目录上,最终形成整颗目录树.
理解了上面,就容易明白以下几个问题:
- 一个系统可以存在多个不同的文件系统,谁做根文件系统只决于内核在启动阶段想让谁做.
- 文件系统可存在于诸多介质上,例如:硬盘(mmc),闪存(flash),内存(RAM).每种介质上有其最合适配套的文件系统,mmc一般是(fat,ext),flash包括(jffs2),内存(proc,sys,tmpfs,ramfs)
- 文件系统可简单,可复杂,只要能实现内核定义的三类接口就可以称之为文件系统,哪三类接口:
- 挂载接口:
MountOps ops
- 操作
inode
节点接口:VnodeOps *vop
- 操作
file
接口:file_operations_vfs *fop
,这个接口底层实际操作的是inode
所指向的数据块.
- 挂载接口:
- 不管怎样,内核启动后必须得有一个文件系统用于挂载到
/
下. 鸿蒙根文件系统目录结构如下:. ├── app ├── bin │ ├── init │ ├── shell │ └── tftp ├── data │ └── system │ └── param ├── etc ├── lib │ ├── libc++.so │ └── libc.so ├── system │ ├── external │ └── internal └── usr ├── bin └── lib
这些数据是怎么来的呢 ? 比如:libc.so
这种C库函数,启动后就马上需要使用的, 这需要先外部制作好,烧录到flash的指定位置. 同时注意鸿蒙制作的根文件系统并没有 /dev
目录,这个在 设备文件篇 中详细说明.
根文件系统制作过程
以liteos_a
内核为例,其提供了制作根文件系统的方法:
turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a$ make help
-------------------------------------------------------
1.====make help: get help infomation of make
2.====make: make a debug version based the .config
3.====make debug: make a debug version based the .config
4.====make release: make a release version for all platform
5.====make release PLATFORM=xxx: make a release version only for platform xxx
6.====make rootfsdir: make a original rootfs dir
7.====make rootfs FSTYPE=***: make a original rootfs img
8.====make test: make the testsuits_app and put it into the rootfs dir
9.====make test_apps FSTYPE=***: make a rootfs img with the testsuits_app in it
xxx should be one of (hi3516cv300 hi3516ev200 hi3556av100/cortex-a53_aarch32 hi3559av100/cortex-a53_aarch64)
*** should be one of (jffs2)
其中第七项 make rootfs
, FSTYPE
支持 jffs2
,vfat
文件格式系统
本篇跟踪敲下 make rootfs FSTYPE=jffs2
后发生了什么
#执行 make rootfs FSTYPE=jffs2 一切从这里开始
$(ROOTFS): $(ROOTFSDIR) #依赖于 ROOTFSDIR
$(HIDE)$(LITEOSTOPDIR)/tools/scripts/make_rootfs/rootfsimg.sh $(ROOTFS_DIR) $(FSTYPE) #制作镜像文件
$(HIDE)cd $(ROOTFS_DIR)/.. && zip -r $(ROOTFS_ZIP) $(ROOTFS) #打rootfs.zip包
解读
-
编译整个内核的目标文件
$(LITEOS_TARGET): $(__LIBS) sysroot $(HIDE)touch $(LOSCFG_ENTRY_SRC) #逐个编译子目录中的 makefile $(HIDE)for dir in $(LITEOS_SUBDIRS); \ do $(MAKE) -C $$dir all || exit 1; \ done # 生成 liteos.map $(LD) $(LITEOS_LDFLAGS) $(LITEOS_TABLES_LDFLAGS) $(LITEOS_DYNLDFLAGS) -Map=$(OUT)/$@.map -o $(OUT)/$@ --start-group $(LITEOS_LIBDEP) --end-group # $(SIZE) -t --common $(OUT)/lib/*.a >$(OUT)/$@.objsize $(OBJCOPY) -O binary $(OUT)/$@ $(LITEOS_TARGET_DIR)/$@.bin #生成 liteos.bin 文件 $(OBJDUMP) -t $(OUT)/$@ |sort >$(OUT)/$@.sym.sorted #生成 liteos.sym.sorted 文件 $(OBJDUMP) -d $(OUT)/$@ >$(OUT)/$@.asm # 生成 liteos.asm文件
-
使用
$(APPS)
编译 kernel/liteos_a/apps 目录下的各个APP
如(init
,shell
,tftp
),这些APP也称为内置到内核的APP,# 编译多个应用程序 $(APPS): $(LITEOS_TARGET) sysroot #依赖于 LITEOS_TARGET , sysroot $(HIDE)$(MAKE) -C apps all #执行apps目录下Makefile 的all目标, -C代表进入apps目录,
-
使用 tools/scripts/make_rootfs/rootfsdir.sh 创建根系统下的各个目录(
/bin
,/app
,/lib
).#创建根文件系统的各个目录 mkdir -p ${ROOTFS_DIR}/bin ${ROOTFS_DIR}/lib ${ROOTFS_DIR}/usr/bin ${ROOTFS_DIR}/usr/lib ${ROOTFS_DIR}/etc \ ${ROOTFS_DIR}/app ${ROOTFS_DIR}/data ${ROOTFS_DIR}/proc ${ROOTFS_DIR}/dev ${ROOTFS_DIR}/data/system ${ROOTFS_DIR}/data/system/param \ ${ROOTFS_DIR}/system ${ROOTFS_DIR}/system/internal ${ROOTFS_DIR}/system/external ${OUT_DIR}/bin ${OUT_DIR}/libs if [ -d "${BIN_DIR}" ] && [ "$(ls -A "${BIN_DIR}")" != "" ]; then cp -f ${BIN_DIR}/* ${ROOTFS_DIR}/bin if [ -e ${BIN_DIR}/shell ] && [ "${BIN_DIR}/shell" != "${OUT_DIR}/bin/shell" ]; then cp -f ${BIN_DIR}/shell ${OUT_DIR}/bin/shell #拷贝 shell 到根文件系统的 /bin下 fi if [ -e ${BIN_DIR}/tftp ] && [ "${BIN_DIR}/tftp" != "${OUT_DIR}/bin/tftp" ]; then cp -f ${BIN_DIR}/tftp ${OUT_DIR}/bin/tftp #拷贝 tftp 到根文件系统的 /bin下 fi fi cp -f ${LIB_DIR}/* ${ROOTFS_DIR}/lib #将c/c++ .so 库拷贝到根文件系统的 /lib cp -f ${LIB_DIR}/* ${OUT_DIR}/libs
-
使用
prepare
创建musl
目录,并将 c/c++ 库拷贝到该目录下prepare: #准备工作,创建 musl 目录,用于拷贝 c/c++ .so库 $(HIDE)mkdir -p $(OUT)/musl ifeq ($(LOSCFG_COMPILER_CLANG_LLVM), y) #使用clang-9 ,鸿蒙默认用这个编译 $(HIDE)cp -f $$($(CC) --target=$(LLVM_TARGET) --sysroot=$(SYSROOT_PATH) $(LITEOS_CFLAGS) -print-file-name=libc.so) $(OUT)/musl #将C库复制到musl目录下 $(HIDE)cp -f $$($(GPP) --target=$(LLVM_TARGET) --sysroot=$(SYSROOT_PATH) $(LITEOS_CXXFLAGS) -print-file-name=libc++.so) $(OUT)/musl #将C++库复制到musl目录下 else $(HIDE)cp -f $(LITEOS_COMPILER_PATH)/target/usr/lib/libc.so $(OUT)/musl $(HIDE)cp -f $(LITEOS_COMPILER_PATH)/arm-linux-musleabi/lib/libstdc++.so.6 $(OUT)/musl $(HIDE)cp -f $(LITEOS_COMPILER_PATH)/arm-linux-musleabi/lib/libgcc_s.so.1 $(OUT)/musl $(STRIP) $(OUT)/musl/* endif
-
tools/scripts/make_rootfs/rootfsimg.sh 生成镜像文件 rootfs_jffs2.img , 调用
mkfs.jffs2
来制作jffs2
文件格式的镜像.ROOTFS_IMG=${ROOTFS_DIR}"_"${FSTYPE}".img" JFFS2_TOOL=mkfs.jffs2 #linux 下 制作 jffs2镜像文件的工具 WIN_JFFS2_TOOL=mkfs.jffs2.exe #windows 下 制作 jffs2镜像文件的工具 chmod -R 755 ${ROOTFS_DIR} if [ -f "${ROOTFS_DIR}/bin/init" ]; then chmod 700 ${ROOTFS_DIR}/bin/init 2> /dev/null fi if [ -f "${ROOTFS_DIR}/bin/shell" ]; then chmod 700 ${ROOTFS_DIR}/bin/shell 2> /dev/null fi if [ "${FSTYPE}" = "jffs2" ]; then if [ "${system}" != "Linux" ] ; then tool_check ${WIN_JFFS2_TOOL} ${WIN_JFFS2_TOOL} -q -o ${ROOTFS_IMG} -d ${ROOTFS_DIR} --pagesize=4096 else tool_check ${JFFS2_TOOL} ${JFFS2_TOOL} -q -o ${ROOTFS_IMG} -d ${ROOTFS_DIR} --pagesize=4096 fi elif [ "${FSTYPE}" = "yaffs2" ]; then # to do fi
-
最后用 zip 命令将
rootfs
打包成rootfs.zip
,至此完成了鸿蒙根系统的制作过程. 将增加了一个out
目录,内容如下:turing@ubuntu:/home/openharmony/code-v1.1.1-LTS/kernel/liteos_a/out/hi3518ev300$ ls bin lib liteos liteos.asm liteos.bin liteos.map liteos.sym.sorted musl obj rootfs rootfs_jffs2.img rootfs.zip
rootfs
便为制作的鸿蒙根文件系统rootfs_jffs2.img
为镜像文件,可以烧到flash
中.
启动过程
这里列出启动根文件系统的相关代码
STATIC UINT32 OsSystemInitTaskCreate(VOID)
{
UINT32 taskID;
TSK_INIT_PARAM_S sysTask;
(VOID)memset_s(&sysTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
sysTask.pfnTaskEntry = (TSK_ENTRY_FUNC)SystemInit;
sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
sysTask.pcName = "SystemInit";
sysTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;
sysTask.uwResved = LOS_TASK_STATUS_DETACHED;
#if (LOSCFG_KERNEL_SMP == YES)
sysTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endif
return LOS_TaskCreate(&taskID, &sysTask);
}
解读
- 首先内核开了一个叫
SystemInit
任务来处理系统初始化代码,任务入口函数为SystemInit
SystemInit
层层调用到MountPartitions
,挂载分区.
在 设备文件篇 中将详细说明SystemInit(void) ... OsMountRootfs() AddPartitions //注册分区驱动程序 MountPartitions() #define ROOT_DEV_NAME "/dev/spinorblk0" #define ROOT_DIR_NAME "/" ret = mount(ROOT_DEV_NAME, ROOT_DIR_NAME, fsType, mountFlags, NULL);//
/dev/spinorblk0
的来源,简单的说就根文件系统烧录在nor flash
介质设备的第一个分区上,分区名称/dev/spinorblk0
只是表示一个“虚”的设备文件名而已,其背后是个实实在在的文件系统.现将它挂到/
上,结果是nor flash
的第一个分区成了根文件系统.
百篇博客分析.深挖内核地基
- 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛
- 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
基础工具 | 加载运行 | 进程管理 | 编译构建 |
---|---|---|---|
双向链表 位图管理 用栈方式 定时器 原子操作 时间管理 | ELF格式 ELF解析 静态链接 重定位 进程映像 | 进程管理 进程概念 Fork 特殊进程 进程回收 信号生产 信号消费 Shell编辑 Shell解析 | 编译环境 编译过程 环境脚本 构建工具 gn应用 忍者ninja |
进程通讯 | 内存管理 | 前因后果 | 任务管理 |
自旋锁 互斥锁 进程通讯 信号量 事件控制 消息队列 | 内存分配 内存管理 内存汇编 内存映射 内存规则 物理内存 | 总目录 调度故事 内存主奴 源码注释 源码结构 静态站点 | 时钟任务 任务调度 任务管理 调度队列 调度机制 线程概念 并发并行 CPU 系统调用 任务切换 |
文件系统 | 硬件架构 | ||
文件概念 文件系统 索引节点 挂载目录 根文件系统 字符设备 VFS 文件句柄 管道文件 | 汇编基础 汇编传参 工作模式 寄存器 异常接管 汇编汇总 中断切换 中断概念 中断管理 |
百万汉字注解.精读内核源码
鸿蒙研究站 | 每天死磕一点点,原创不易,欢迎转载,但请注明出处。