鸿蒙内核源码分析(根文件系统) | 先挂到`/`上的文件系统 | 百篇博客分析OpenHarmony源码 | v66.01

572 阅读8分钟

季康子问政于孔子。孔子对曰:“政者,正也。子帅以正,孰敢不正?” 《论语》:颜渊篇

在这里插入图片描述

百篇博客系列篇.本篇为:

v66.xx 鸿蒙内核源码分析(根文件系统) | 先挂到/上的文件系统

文件系统相关篇为:

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后发生了什么

查看 kernel/liteos_a/Makefile

#执行 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的第一个分区成了根文件系统.

百万汉字注解.精读内核源码

四大码仓同步注解内核源码, >> 查看 gitee 仓库

百篇博客分析.深挖内核地基

给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 :P
与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,.xx 代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。

编译构建基础工具加载运行进程管理
编译环境
编译过程
环境脚本
构建工具
gn应用
忍者ninja
双向链表
位图管理
用栈方式
定时器
原子操作
时间管理
ELF格式
ELF解析
静态链接
重定位
进程映像
进程管理
进程概念
Fork
特殊进程
进程回收
信号生产
信号消费
Shell编辑
Shell解析
进程通讯内存管理前因后果任务管理
自旋锁
互斥锁
进程通讯
信号量
事件控制
消息队列
内存分配
内存管理
内存汇编
内存映射
内存规则
物理内存
总目录
调度故事
内存主奴
源码注释
源码结构
静态站点
时钟任务
任务调度
任务管理
调度队列
调度机制
线程概念
并发并行
系统调用
任务切换
文件系统硬件架构
文件概念
文件系统
索引节点
挂载目录
根文件系统
字符设备
VFS
文件句柄
管道文件
汇编基础
汇编传参
工作模式
寄存器
异常接管
汇编汇总
中断切换
中断概念
中断管理

鸿蒙研究站 | 每天死磕一点点,原创不易,欢迎转载,但请注明出处。