开源鸿蒙内核源码分析系列 | 根文件系统 | 先挂到`/`上的文件系统

开源鸿蒙内核源码分析系列 | 根文件系统 | 先挂到`/`上的文件系统

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:

(https://gitee.com/weharmony/kernel_liteos_a_note/blob/master/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的第一个分区成了根文件系统。

百文说内核 | 抓住主脉络

子曰:“诗三百,一言以蔽之,曰‘思无邪’。”——《论语》:为政篇。百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在开源鸿蒙内核源码加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。百篇博客系列思维导图结构如下:

根据上图的思维导图,我们未来将要和大家一一分享以上大部分关键技术点的博客文章。

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

如果大家觉得看文章不过瘾,想直接撸代码的话,可以去下面四大码仓围观同步注释内核源码:

gitee仓

https://gitee.com/weharmony/kernel_liteos_a_note

github仓 :

https://github.com/kuangyufei/kernel_liteos_a_note

codechina仓

https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note

coding仓

https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files

写在最后

我们最近正带着大家玩嗨OpenHarmony。如果你有用OpenHarmony开发的好玩的东东,或者有对OpenHarmony的深度技术剖析,想通过我们平台让更多的小伙伴知道和分享的,欢迎投稿,让我们一起嗨起来!有点子,有想法,有Demo,立刻联系我们:

合作邮箱:zzliang@atomsource.org