文件系统调用和Linux文件系统基础

文件系统调用和Linux文件系统基础

keywords

fdisk、LBA、CHS、MBR、super struct、directory、file、inode、inode table、block、file descriptor、file descriptor table、open file descriptor、open file table、mount point、vfsmount structure、alloc_super()、super_operations、inode_operations、dentry object、device driver、device file、mkfs、mount、mount point、I/O device、I/O interface、I/O controller…

这篇博客主要想简述隐藏在文件系统调用之下的一些过程。

0.简谈计算机

晶体管集成电路微操作机器指令汇编指令高级编程语言程序

keywords

接口、有限自动机、微程序、微指令、微操作、CISC、RISC、组合逻辑电路、时序逻辑电路、译码器、多路复用器、选择器、触发器、中断、设备中断、指令中断、(可编程)中断控制器、总线仲裁器、ISA(指令集体性架构)、编译、链接、加载、库函数、回调函数、头文件(接口视图)、操作系统I/O缓冲机制、I/O流、文件…

硬件电路基础

计算机的硬件电路十分复杂,但是万变不理其宗,对于不求甚解的程序员来说,了解一些基本的部件就行了。下面列举了一些部件名,不过知不知道这些部件对参与软件开发的程序员来说似乎关系不大。

晶体管、门电路、译码器、多路复用器、选择器、锁存器、触发器、全加器、PLA(可编程逻辑阵列)、时钟发生器

晶体管可以用来制作非门,与非门。其实这就够了,只需要逻辑非和逻辑与(或者逻辑或)就可以表示所有的逻辑函数。而锁存器则实现了数据(状态)的存储,时钟发生器的脉冲引起状态的转移。这些部件可以构造出内存+寄存器+ALU+CU+接口电路等重要的计算机内部部件。其中接口电路又可以用来连接外部设备,这样就能构成CU+ALU+内存+输入设备+输出设备的冯·诺依曼结构。

State machine

计算机从开机,到关机/死机之间,一直在各种状态之中游走转换。为什么这么说,其实控制计算机的信号都是CU(控制中心)产生的,而计算机的CU在原理上就是一个有限状态自动机【图灵机?】,自动地在实现了计算机ISA(指令集架构)的硬件设计师设计的电路中进行确定的有限个状态之间转换,CU会根据当前pc去取指令,不管pc指向哪里,然后将取回的指令放到IR(指令寄存器)中,然后进行指令的译码,接着去执行指令,如果有中断产生,就处理中断,然后回到最开始的状态,接着取指令、译码、执行…

指令周期[状态&&微操作]

计算机硬件执行一条指令的指令周期由几个不可中断宏观操作周期组成:取值周期+译码周期+执行周期+[中断周期]。其中每一个宏观操作周期占据1到多个时钟周期。计算机的控制信号是时钟驱动的,即在每个时钟周期脉冲的上升沿(或者下降沿)到来时驱动CU从一个状态转换到另一个状态,并发出对应的控制信号,然后CU保持在当前状态一个周期的时间直到下一个时钟脉冲的到来。所以一个宏观操作周期由1到多个时钟周期组成意味着包含1到多个状态。这几个宏观周期中,所有指令的取值周期和译码周期都是相同的,但是执行周期却由具体的指令所具有的操作编码来决定,所以add指令是add指令的执行周期,INT指令是INT指令的执行周期…,故不同的指令的执行周期的状态组成集合是不同的。最后还有一个中断周期,这个周期比较特殊,不是当前指令要求的,而是硬件设备产生了中断请求。如果CPU响应这个请求就会使CU进入中断周期,在中断周期之后,CU进入”下一条“指令的取值周期,否则,CU直接转到下一条指令的取值周期。CPU为了保证每一个指令的所有微操作都能不被中断的执行(指令周期的原子性),而且还能响应硬件设备的中断请求,会在执行完一条指令的所有微操作之后,转到下一条指令的取值周期之前,”检查“中断请求。如果这时候有中断请求,CPU就会在此插入一个中断周期。这里还提到了一个INT指令,INT指令也能导致中断,不过这个中断是INT指令的执行周期来负责的(INT:取值周期+译码周期+中断周期(或者叫INT的执行周期))。注意,我在中断周期之后转到的下一条指令的关键字”下一条“上加了双引号,这是因为中断周期一般会导致指令的非顺序执行,也就是说下一条指令不是源程序中的下一条指令,而是某个中断向量指定的地址的内容所指向的指令。有点绕口…

在阅读上面的内容时,容易产生一种执行一次微操作就是执行一条指令的错觉,不过微操作确实可以用”某种指令”来表示,不过这种指令不是我们通常意义上的编译之后二进制机器指令,而是一种更加底层的控制指令——微指令。一条不可中断的机器指令可由一个多条微指令组成的微程序来实现。



I/O接口电路

接口电路是设备与主机交互的桥梁。

接口电路:包括接口逻辑电路,数据寄存器,状态寄存器,控制寄存器等硬件。接口电路位于主板上,其中的各个寄存器端口都有一个确定的物理地址,主板上有一个地址译码器会对CPU发出的地址进行译码(注意,接口电路不是USB接口、网线接口等插口,接口电路是组合/时序电路)。

I/O接口电路一般被集成在相应的设备控制器中,随设备控制器接入主板总线,同时,复杂设备的内部也会存在一个设备控制器与主板上的设备控制器交互。

最终,各种类型的接口电路(例如USB controller、IDE controller、PCI controller等设备控制器中的接口电路)作为一个模块随着相应的设备控制器被集成到一块专门处理I/O的南桥芯片上,或者集成到一块单独的设备控制器芯片卡上,能够随之被插到主板上预留的插槽中从而连接到总线(例如SCSI device controller,SCSI是Small Computer System Interface的缩写,但它实现的功能却一点儿也不“small”。它能同时负责多个设备与主机的通信,包括hard drive、scanners、CD-ROM/RW driver、printers和tape driver )。常见的接口电路有UART、USART、CRT controller中的接口电路、磁盘控制器中的接口电路和PIO等。

中断

中断分为指令中断和硬件中断,指令中断的执行周期的微操作和硬件中断插入到指令周期的中断周期的微操作的作用是相同的,无外乎保存PC以及一些标志寄存器到内核栈中,然后根据中断向量地址在中断向量表(IVT/IDT)找到中断向量(中断服务程序入口地址),并将其装入PC,在完成这些微操作之后,控制自动机的状态转到取指令状态,切换到下一条指令,这条指令即为中断服务程序的第一条指令。【这个过程可能涉及到用户栈和内核栈以及中断栈之间的转换,还可能发生特权级的转换】

中断服务程序就是一幅函数调用图,这幅图包含了诸多的结点,每一个结点对应一个函数调用,所有的结点以及它们之间的相互关系构成了以中断服务入口函数为起始的函数调用图。这幅函数调用图可能非常的密,不过,在一次服务过程中并不是每一个结点的函数都将被调用。对应一次中断,实际的运行服务图只是这幅完整函数调用图的一个子集(可以理解为有些结点的函数是条件调用的,也就是说只有特定的条件被满足之后,该结点的函数才会在上一个节点的函数执行逻辑中被调用)。一个特定的运行服务图用来处理一次特定的中断请求。大多数汇编教程上的中断实验只是用来示范性地说明中断服务程序,所以往往比较简单,功能单一,忽略了实际操作系统中的中断服务程序可能很复杂的事实。而且,一幅中断服务图的各个结点的函数的实现往往也不是一个人写的,比较现实的情况是,操作系统实现者注册中断服务程序的入口函数的地址到中断向量表中,然后给出中断服务入口函数的实现,在实现中,操作系统实现者会调用一些自己没有实现的接口函数(函数指针),将这些接口函数的实现交给其他人,例如文件系统开发人员、驱动开发人员。

中断服务程序在开始调用相应的服务之前有一个十分重要的工作要做,那就是保存现场[SAVE_ALL],即将各个寄存器中的内容按照规定的顺序依次压入内核栈中。

从中断返回到用户态时有可能发生新的调度。例如在系统调用返回时,当前进程的task_struct被放在了等待队列中,等待外设数据传输的完成,这时候它的need_resched字段为1。在中断返回到用户态之前内核会检查当前task_struct的need_resched字段,如果need_resched为1,表明需要调度,那么内核会调用调度器,从可运行进程队列中选择一个进程的task_struct作为当前task_struct。内核还会在中断返回到用户态之前检查当前task_struct是否有软中断等待处理、是否有信号等待处理。在相关的处理都完事之后才能返回到用户态。

在返回的最后一步也有一个十分重要的工作要做,那就是恢复现场[RESTORE_ALL],即将内核栈中的内容恢复到对应的寄存器中,然后执行iret指令将cs、eip、ss、esp、eflags恢复到中断之前的状态。之后进程就可以像从没发生过中断一样正确的往下执行后续的指令。

其它功能部件

中断控制器、总线仲裁器、DMA、I/O处理器。

分层与接口的思想

这里所谓分层就是将实现固定在某一个层,然后向其上层给出调用本层内某些功能实现的接口。而接口就是不同层之间的通信标准,要求上下层满足这个标准。好处有:

  1. 职能分离。每一层只需要专注于自己所在层的设计,如果本层需要下层提供的功能则直接调用下层给出的功能接口。
  2. 提高系统安全性和健壮性。下层能在实现中给出对来自上层的调用的安全性检查,如果不安全,则拒绝服务,返回出错消息。而且,分层之后起到了一个功能封装的作用,上层想要调用下层的服务只能通过接口,而不是随随便便就能访问下层的元素,所以分层起到了隔离的作用。
  3. 可移植性。不同平台的实现可以不同,但是只要满足接口标准,那么同样的上层软件就可以不做改变地配置在不同的平台之上,当然这是理想情况,现实往往不尽人意。中断服务程序的设计就利用了分层与接口的思想。

高级指令-汇编指令-微操作(微指令)

一条高级语言指令由1到多个汇编指令构成,一条汇编指令对应一条机器指令码,而一条机器指令码的执行又涉及到了一组不可中断的微操作(原子性)。

1. 磁盘

1.1 磁盘分区

Introduction

一块磁盘如果毫无组织那将无法使用,所以往往将一个磁盘划分为多个分区,每个分区内又可以继续分区。按照标准,每块磁盘的第一个扇区(512字节)以及每个分区的第一个扇区被单独划分出来,不被格式化,用来引导操作系统。

传统的磁盘与文件系统的应用中,一个分区就只能够被格式化成为一个文件系统,所以我们可以说一个 filesystem 对应一个 partition。但是由于新技术的利用,例如LVM与软件磁盘阵列(software raid), 这些技术可以将一个分区格式化为多个逻辑分区并格式化成多个文件系统(例如LVM),也能够将多个分区或磁盘合并为一个逻辑分区并格式化成一个文件系统(LVM, RAID)! 所以说,目前我们在格式化时已经不再说成针对 partition 来格式化了, 通常我们可以称呼一个可被挂载的文件系统介质为一个文件系统而不是一个分区!

1.2 fdisk

fdisk命令可以用来管理分区。

linux_fdisk命令详解

2. 文件系统

在用户眼里,一个文件通过一个文件描述符【整数】来表示,因为操作这个文件所需的所有信息都在打开文件系统调用结束时建立好了。

在内核眼里,一个文件【目录文件、链接文件、普通文件、设备文件…】通过inode来表示。

2.1 介绍

Linux文件系统的实现

文件系统有两方面的内容,一方面是指存储介质上进行格式化的文件系统格式,例如EXT2、FAT文件系统格式;另一方面是指操作系统为管理文件系统而编写的代码以及构建的数据结构(在Linux源代码文件的fs文件夹下有Linux自己集成的各种文件系统的源代码)。文件系统格式是基础,文件系统代码和数据结构是核心。

2.2 mkfs

用一种文件系统格式格式化某个分区、设备。

2.3 注册文件系统

解析 Linux 中的 VFS 文件系统机制

为方便文件系统的管理,在将文件系统安装到内核之前,先要注册文件系统,内核会为自带的文件系统自动注册。用户也可以自己注册所需的文件系统。所谓注册其实就是在内存中实例化一个file_system_type的结构体,这个结构体中包含了将对应文件系统类型的文件系统安装到操作系统中所需的全部信息。所以,为某种类型的文件系统注册了file_system_type结构体之后,内核就有了足够的信息可以按既定的[routine]将该类型的文件系统安装到操作系统中。内核中,所有的file_system_type结构体组成一个链表,全局指针file_systems指向链表表头。

file_system_type中一个关键的成员是mount函数指针接口【在Linux中这个接口名为get_sb】,这个函数的接口由具体的文件系统注册,通过调用这个函数,内核中就能创建安装以及管理一个文件系统所需的数据结构的实例【例如Linux的super_block和vsfmount】。

2.3.1 Ucore教学操作系统中[简单文件系统的]注册的执行路径

某个文件系统模块【代码】被加载时可以通过调用内核函数register_filesystem注册一个file_system_type:

//待注册文件系统模块须给出文件系统类型[name]和安装该类型文件系统时的安装方法[mount]
int register_filesystem(const char *name, 
                      int (*mount) (const char *devname) //安装该注册文件系统类型
                      //的文件系统时被调用,mount由具体的文件系统模块给出实现。
                      )
{
  assert(name != NULL);
  if (strlen(name) > FS_MAX_DNAME_LEN) {
      return -E_TOO_BIG;
  }

  int ret = -E_NO_MEM;
  char *s_name;
  if ((s_name = strdup(name)) == NULL) {
      return ret;
  }

//分配一个file_system_type
/* struct file_system_type { const char *name;//文件系统名【ext2、fat、ext3...】 //mount函数 int (*mount) (const char *devname); //系统已有的文件系统组成的链表 list_entry_t file_system_type_link; }; */
  struct file_system_type *fstype;
  if ((fstype = kmalloc(sizeof(struct file_system_type))) == NULL) {
      goto failed_cleanup_name;
  }

  ret = -E_EXISTS;

  lock_file_system_type_list();//上锁

  if (!check_file_system_type_name_conflict(s_name)) {
      unlock_file_system_type_list();
      goto failed_cleanup_fstype;
  }
  //初始化这个file_system_type
  fstype->name = s_name;
  fstype->mount = mount;

  //将其加入到文件系统链表中,所有的注册文件系统都在这个链表中。
  list_add(&file_system_type_list, &(fstype->file_system_type_link));

  unlock_file_system_type_list();//解锁
  return 0;

failed_cleanup_fstype:
  kfree(fstype);
failed_cleanup_name:
  kfree(s_name);
  return ret;
}

例如,简单文件系统的注册:

  if ((ret = register_filesystem("sfs", sfs_mount)) != 0) {
      panic("failed: sfs: register_filesystem: %e.\n", ret);
  }

其中sfs_mount函数来自简单文件系统:

/* * Actual function called from high-level code to mount an sfs. */

int sfs_mount(const char *devname)
{
//先将请求传递给vfs_mount,vfs_mount就像一个多入口=>多出口的结点,这里的入口是
//sfs_mount,出口自然是sfs_do_mount
  return vfs_mount(devname, sfs_do_mount);
}

2.4 mount

linux系统调用mount全过程分析

IBMdeveloperworks.解析 Linux 中的 VFS 文件系统机制[描述了VFS的基本框架]

安装文件系统。除非已经注册,在正真安装之前,需要将该类型的文件系统注册。在安装一个文件系统时,内核从file_system_type链表中查找它支持的文件系统类型,看是否支持当前需要安装的文件系统,如果不支持,自然是无法安装的,如果file_system_type链表中有对应的结构体存在,证明内核支持该类型的文件系统,可以安装它。当然,在没有找到匹配的file_system_type的情况下,安装还是有可能成功的,因为内核会启动一个守护进程,加载对应类型的文件系统模块(管理相关类型的文件系统的代码和数据结构),为该类型的文件系统建立一个file_system_type结构体,注册并添加到链表中去。文件安装之后,内核中会存在一个属于它的super_block结构体实例【若系统中有多个位于不同分区或磁盘上的同一类型的文件系统,则存在多个super_block与之对应,它们组成一个链表,同时系统中的所有超级块结构体实例组成了另一个全局的super_block链表】和vfsmount结构体实例【若同一个分区的一个文件系统被安装多次[到不同目录下],则存在多个vfsmount与之对应,但它们共用一个super_block,vfsmount成员中有指向这个super_block的指针】。vfsmount结构体实例中(直接或间接地)包含了使用该文件系统所需要的全部信息。所有的vfsmount结构体实例组成一个父子关系的拓扑结构,同时所有的vfsmount结构体实例组成了一个双向链表,全局指针vfsmntlist指向链表的表头。系统使用一个散列表mount_hashtable来加快对vfsmount的查找。

所谓mount就是创建一些管理文件系统的数据结构并注册一些接口。有了这些基本的数据结构,文件系统就能正常使用。

2.4.1 Ucore教学操作系统中[简单文件系统的]mount的执行路径

Ucore的mount系统调用的主要任务是用磁盘上的文件系统的超级块的信息来创建以及初始化一个fs结构体的实例。Ucore的fs的作用类似于Linux的super_block的作用【维护属于该文件系统的:各种inode链表、根目录[s_root,如果s_root为null,则该文件系统是一个伪文件系统,只在内核可见,对用户不可见]、打开文件链表[s_files,在卸载文件系统时会检查该链表来判断是否可以成功卸载]、文件系统所在的块设备[s_dev/s_bdev,用于查找设备文件和驱动程序]、各种函数接口[s_op,创建、初始化、管理、更新该文件系统的inode…,接口的实现由具体的文件系统负责注册]…】,用来全局性的管理和组织一个文件系统。

用户层
//------------mount--------------
int
mount(const char *source, 
const char *target, 
const char *filesystemtype,
      const void *data)
{
    return sys_mount(source, target, filesystemtype, data);
}

//------------sys_mount--------------
int
sys_mount(const char *source, 
const char *target, 
const char *filesystemtype,
          const void *data)
{
    return syscall(SYS_mount, source, target, filesystemtype, data);
}

//------------syscall--------------
uint64_t syscall(int num, ...)
{
    va_list ap;
    va_start(ap, num);
    uint64_t a[MAX_ARGS];
    int i;
    for (i = 0; i < MAX_ARGS; i++) {
        a[i] = va_arg(ap, uint64_t);
    }
    va_end(ap);

    uint64_t ret;
asm volatile ("movq 0x00(%%rbx), %%rdi;"
              "movq 0x08(%%rbx), %%rsi;"
              "movq 0x10(%%rbx), %%rdx;"
              "movq 0x18(%%rbx), %%rcx;"
              "movq 0x20(%%rbx), %%r8;"
              "movq 0x28(%%rbx), %%r9;" "int %1":"=a" (ret)
              :"i"(T_SYSCALL), "a"(num), "b"(a)
              :"cc", "memory");
    return ret;
}

最后通过int 0x80指令陷入内核:

/* //操作系统在编译的时候静态的初始化了一张系统调用表,其中的表项是操作系统内核自带的实 //现,理论上,我们可以在操作系统被加载之后修改其中的函数指针,使其指向我们给出的函数地 //址。 static uint32_t(*syscalls[]) (uint32_t arg[]) = { ... [SYS_exit] sys_exit, [SYS_fork] sys_fork, [SYS_wait] sys_wait, [SYS_exec] sys_exec, [SYS_clone] sys_clone, [SYS_kill] sys_kill, [SYS_sleep] sys_sleep, [SYS_gettime] sys_gettime, [SYS_getpid] sys_getpid, [SYS_brk] sys_brk, [SYS_open] sys_open, [SYS_close] sys_close, [SYS_read] sys_read, [SYS_write] sys_write, ... //mount系统调用函数指针 [SYS_mount] sys_mount, [SYS_umount] sys_umount }; */

mount系统调用陷入内核,根据用户传来的系统调用号index到sys_mount函数指针,调用它。

调用level 1: sys_mount
//---level 1---

static uint32_t sys_mount(uint32_t arg[])
{
    const char *source = (const char *)arg[0];//设备名称
    const char *target = (const char *)arg[1];//安装点
    const char *filesystemtype = (const char *)arg[2];//文件系统类型
    const void *data = (const void *)arg[3];
    //参数均来自用户



    //调用do_mount...level 2.1【VFS】
    return do_mount(source, filesystemtype);
}
调用level 2.1: do_mount
//---level 2.1---

int do_mount(const char *devname, const char *fsname)
{
    int ret = -E_EXISTS;

    //锁住文件系统链表
    lock_file_system_type_list();

    list_entry_t *list = &file_system_type_list, *le = list;


    while ((le = list_next(le)) != list) {
        //从le中返回le所在的file_system_type【宿主】
        struct file_system_type *fstype =
            le2fstype(le, file_system_type_link); //Routine 1

        //一直循环查找,直到找到的文件系统类型与用户传过来的文件系统类型一样
        if (strcmp(fstype->name, fsname) == 0) {
            assert(fstype->mount);


        //----------------------------------------------
          //找到注册的file_system_type,用文件系统注册的mount接口完成
          //接下来的安装过程。
          //调用do_mount...level 3.1
            ret = (fstype->mount) (devname);
        //----------------------------------------------


            break;//跳出循环查找
        }
    }

    //解锁文件系统链表
    unlock_file_system_type_list();

    return ret;
}

Routine 1:


#define le2fstype(le, member) \

to_struct((le), struct file_system_type, member)

/* * * to_struct - get the struct from a ptr * @ptr: a struct pointer of member * @type: the type of the struct this is embedded in * @member: the name of the member within the struct * */

#define to_struct(ptr, type, member) \

((type *)((char *)(ptr) - offsetof(type, member)))
调用level 3.1: sfs_mount

这层的调用与具体的文件系统类型有关,这里我们以简单文件系统的安装为例:

当然,安装之前先得注册,注册流程上文已给出。

//---level 3.1---

/* * Actual function called from high-level code to mount an sfs. */
int sfs_mount(const char *devname)
{
//调用level 2.2
//先将请求传递给vfs_mount,vfs_mount就像一个多入口=>多出口的结点,这里的入口是
//sfs_mount,出口自然是sfs_do_mount
    return vfs_mount(devname, sfs_do_mount);
}
调用level 2.2: vfs_mount
//---level 2.2---

/* * Mount a filesystem. Once we've found the device, call MOUNTFUNC to * set up the filesystem and hand back a struct fs. */
int
vfs_mount(const char *devname,
      int (*mountfunc) (struct device * dev, struct fs ** fs_store))
{
    int ret;

    lock_vdev_list();//lock

    vfs_dev_t *vdev;
/* * Structure for a single named device. * * devname - Name of device (eg, "lhd0"). Should always be set to * a valid string. * * devnode - inode of device . * * fs - Filesystem object mounted on, or associated with, this * device. NULL if there is no filesystem. ----------------------设备文件描述-------------------------- typedef struct { const char *devname; struct inode *devnode;//[设备]文件结点【普通文件结点、目录文件结点...】 struct fs *fs; bool mountable; list_entry_t vdev_link;//设备文件描述结点 } vfs_dev_t; */

    //根据名字从设备链表中找到设备文件描述
    if ((ret = find_mount(devname, &vdev)) != 0) { //Routine 1
        goto out;
    }
    if (vdev->fs != NULL) {
        ret = -E_BUSY;
        goto out;
    }
    assert(vdev->devname != NULL && vdev->mountable);

//返回设备文件结点【从inode的联合体成员变量得到】 
/* 操作系统为每个设备维护一个vfs_dev_t[设备描述符]链表,由一个全局变量指向这个链表 */
    struct device *dev = vop_info(vdev->devnode, device);//Routine 2
/* struct device { size_t d_blocks; size_t d_blocksize; void *linux_file; void *linux_dentry; int (*d_linux_read) (struct device * dev, const char __user * buf, size_t count, size_t * offset); int (*d_linux_write) (struct device * dev, const char __user * buf, size_t count, size_t * offset); int (*d_linux_ioctl) (struct device * dev, unsigned int, unsigned long); void *(*d_linux_mmap) (struct device * dev, void *addr, size_t len, int unused1, int unused2, size_t off); int (*d_open) (struct device * dev, uint32_t open_flags); int (*d_close) (struct device * dev); int (*d_io) (struct device * dev, struct iobuf * iob, bool write); int (*d_ioctl) (struct device * dev, int op, void *data); }; */


//--------------------------------------------------
//调用sfs_do_mount... level 3.2
    if ((ret = mountfunc(dev, &(vdev->fs))) == 0) {
//-------------------------------------------------- 


        assert(vdev->fs != NULL);
        kprintf("vfs: mount %s.\n", vdev->devname);
    }

out:
    unlock_vdev_list();
    return ret;
}

Routine 1:

/* * Look for a mountable device named devname. * Should already hold an lock::vdev_list_sem. *设备描述符链表在内核启动早期被初始化。 */
static int find_mount(const char *devname, vfs_dev_t ** vdev_store)
{
  assert(devname != NULL);
  list_entry_t *list = &vdev_list, *le = list;
  while ((le = list_next(le)) != list) {
      vfs_dev_t *vdev = le2vdev(le, vdev_link);
      if (vdev->mountable && strcmp(vdev->devname, devname) == 0) {
          *vdev_store = vdev;
          return 0;
      }
  }
  return -E_NO_DEV;
}

Routine 2:


#define __vop_info(node, type) \

({                                                              \
struct inode *__node = (node);                              \
assert(__node != NULL && check_inode_type(__node, type));   \
&(__node->in_info.__##type##_info); \
})


#define vop_info(node, type) __vop_info(node, type)
调用level 3.2: sfs_do_mount
//---level 3.2---

/* * SFS Mount. * *传递给vfs_mount的用开完成文件安装的函数,代码实现来自简单文件系统层 */
static int sfs_do_mount(struct device *dev, struct fs **fs_store)
{

...

    //allocate fs structure 
    //Ucore的fs的作用类似于Linux的super_block的作用
/* struct fs { union { ... //简单文件系统fs struct sfs_fs __sfs_info; ... } fs_info; enum { ... fs_type_sfs_info, ... } fs_type; ... //获得该文件系统的根目录inode struct inode *(*fs_get_root) (struct fs * fs); }; */
    struct fs *fs;
    if ((fs = alloc_fs(sfs)) == NULL) {
        return -E_NO_MEM;
    }

    /* get sfs from fs.fs_info.__sfs_info */
    struct sfs_fs *sfs = fsop_info(fs, sfs);
    sfs->dev = dev;//指向文件系统所在的设备的设备结点

    int ret = -E_NO_MEM;

    void *sfs_buffer;
    if ((sfs->sfs_buffer = sfs_buffer = kmalloc(SFS_BLKSIZE)) == NULL) {
        goto failed_cleanup_fs;
    }

    /* load and check sfs's superblock */
    if ((ret = sfs_init_read(dev, SFS_BLKN_SUPER, sfs_buffer)) != 0) {
        goto failed_cleanup_sfs_buffer;
    }

    ret = -E_INVAL;

    struct sfs_super *super = sfs_buffer;

    /* Make some simple sanity checks */
    if (super->magic != SFS_MAGIC) {
        kprintf
            ("sfs: wrong magic in superblock. (%08x should be %08x).\n",
             super->magic, SFS_MAGIC);
        goto failed_cleanup_sfs_buffer;
    }
    if (super->blocks > dev->d_blocks) {
        kprintf("sfs: fs has %u blocks, device has %u blocks.\n",
            super->blocks, dev->d_blocks);
        goto failed_cleanup_sfs_buffer;
    }
    super->info[SFS_MAX_INFO_LEN] = '\0';
    sfs->super = *super;

    ret = -E_NO_MEM;

    uint32_t i;

    /* alloc and initialize hash list */
    list_entry_t *hash_list;
    if ((sfs->hash_list = hash_list =
         kmalloc(sizeof(list_entry_t) * SFS_HLIST_SIZE)) == NULL) {
        goto failed_cleanup_sfs_buffer;
    }
    for (i = 0; i < SFS_HLIST_SIZE; i++) {
        list_init(hash_list + i);
    }

...     

    /* and other fields */
    sfs->super_dirty = 0;
    sem_init(&(sfs->fs_sem), 1);
    sem_init(&(sfs->io_sem), 1);
    sem_init(&(sfs->mutex_sem), 1);
    list_init(&(sfs->inode_list));
    kprintf("sfs: mount: '%s' (%d/%d/%d)\n", sfs->super.info,
        blocks - unused_blocks, unused_blocks, blocks);

    /* Set up abstract fs calls */
    //注册来自简单文件系统模块实现的函数
    fs->fs_sync = sfs_sync;

    fs->fs_get_root = sfs_get_root; //Routine sfs_get_root

    fs->fs_unmount = sfs_unmount;
    fs->fs_cleanup = sfs_cleanup;

    *fs_store = fs;
    return 0;

    ...

    return ret;
}

Routine sfs_get_root:

/* * Get inode for the root of the filesystem. * The root inode is always found in block 1 (SFS_ROOT_LOCATION). */

static struct inode *sfs_get_root(struct fs *fs)
{
  struct inode *node;
  int ret;
  if ((ret =
       sfs_load_inode(fsop_info(fs, sfs), &node, SFS_BLKN_ROOT)) != 0) {
      panic("load sfs root failed: %e", ret);
  }
  return node;
}

Linux的安装

为了加快查找文件的速度,Linux使用比inode更加小型的数据结构dentry来组织目录树,最近比较常用的dentry会被缓存到内核内存中,通过散列的方式可以更快的查找到所需的文件。Linux允许将一个文件系统安装在另一个已安装的文件系统的某个目录【这个目录叫做安装点】之下。一个安装点被多个文件系统安装,则会对应有多个dentry,它们分别表示原文件系统的目录、各个被安装的文件系统的根目录,但是,最终只有一个dentry得以表示安装点,其他的dentry都被一层层的隐藏起来,在安装点的各个文件系统卸载的过程中,它们会依次表现出来。

Ucore没有实现将一个文件系统安装到某个目录下,它仅仅是在mount时将管理一个文件系统要用到的基本的数据结构创建出来并初始化,往后就能通过这个已经存在的数据结构来管理和组织这个文件系统。

当然,Ucore教学操作系统比不上Linux,但是其中的许多东西都是对Linux的模仿,相比Linux,Ucore显得更为简单,更容易理清每一个函数的执行路径。

2.5 unmount

卸载某个文件系统。文件的装载和卸载操作很少发生,因为除了可移动设备之外,文件系统都是在启动过程中装载,在关机时卸载。

3 内存作为磁盘的高速缓存【Page cache】

所有的数据都得要加载到内存后 CPU 才能够对该数据进行处理。如果你常常编辑一个好大的文件, 在编辑的过程中又频繁的要系统来写入到磁盘中,由于磁盘写入的速度要比内存慢很多, 因此你会常常耗在等待硬盘的写入/读取上,效率低下。

为了解决这个效率的问题,因此我们的 Linux 使用的方式是透过一个称为异步处理 (asynchronously) 的方式。所谓的异步处理是这样的:

当系统加载一个文件到内存后,如果该文件没有被更动过,则在内存区段的文件数据会被配置为干净(clean)的。 但如果内存中的文件数据被更改过了(例如你用 nano 去编辑过这个文件),此时该内存中的数据会被配置为脏的 (Dirty)。此时所有的动作都还在内存中运行,并没有写入到磁盘中! 系统会不定时的将内存中配置为『Dirty』的数据写回磁盘,以保持磁盘与内存数据的一致性。 你也可以利用sync命令来手动强迫写入磁盘。

我们知道内存的速度要比硬盘快的多,因此如果能够将常用的文件放置到内存中,这不就会添加系统性能吗? 没错!是有这样的想法:

  • 系统会将常用的文件数据放置到主存储器的缓冲区,以加速文件系统的读/写;
  • 承上,因此 Linux 的物理内存最后都会被用光!这是正常的情况!可加速系统效能;
  • 你可以手动使用 sync 来强迫内存中配置为 Dirty 的文件回写到磁盘中;
  • 若正常关机时,关机命令会主动呼叫 sync 来将内存的数据回写入磁盘内;
  • 但若不正常关机(如跳电、死机或其他不明原因),由于数据尚未回写到磁盘内, 因此重新启动后可能会花很多时间在进行磁盘检验,甚至可能导致文件系统的损毁(非磁盘损毁)。

4. 虚拟文件系统

参考References.part4.VFS

文件系统包括它的格式以及管理它的代码和数据结构,但是,不同的文件系统,他们的格式,代码,数据结构实例不尽相同,为了统一地管理各种不同的文件系统,Linux抽象出了一层文件系统通用管理层,即VFS(抽象文件系统)层。VFS提供上层调用到下层文件系统实现之间的抽象接口(API),为提供这些接口,VFS创建了一些管理文件系统并与之交互的数据结构,这些数据结构中包含了几个函数表(结构体,成员全部是函数指针),函数表中全是函数指针(回调函数),一个函数指针对应一个接口,接口的实现交给不同的文件系统的代码模块,在文件系统安装时,文件系统会初始化一些内核的数据结构【super_block、vfsmount】并将自己对接口的实现注册到内核函数表中相应的接口位置,然后,在VFS为管理一个文件系统的某个文件而创建与之相关的数据结构时【file、inode…】,会将数据结构中的函数表指针成员变量指向文件系统初始化的函数表【file_operations…】。这样,所有文件系统的上层调用(read用户库调用 –> read系统调用)先陷入中断(INT 0x80),进入到内核层,调用中断描述符表【IDT,在操作系统内核初始化的时候建立】中的0x80号中断服务入口函数(syscall),然后根据系统调用号【用户通过寄存器%eax传递到内核栈中,同时还有随其他寄存器传递过来的参数】调用系统调用函数表【可以理解为一个函数数组】中相应的函数进入到VFS层,随后调用VFS层相应函数表中相关的函数(例如,根据文件描述符fd的值找到相应的文件结构体,再在文件结构体中的函数表中(file_operations)找到read函数指针,调用这个函数),进入到具体的文件系统层(或通用文件系统层),文件系统层一般会和Linux内核中的Page cache层打交道,来获取内核缓存区的数据或者将上层传来的数据写入内核缓存区(或者跳过页缓存,使用直接I/O方式),Page cache层需要和文件系统的驱动层打交道,实现外部数据流入内核缓存区以及内核缓存区的数据流出外设。

5. 驱动程序

字符设备驱动 架构分析
浅谈Linux的设备文件

参考《Linux内核代码情景分析》第8章.设备驱动

5.1 与设备交互

I/O设备通过接口和主机交互,这里所谓的”接口“包含了连接设备到主机的硬件接口电路以及与设备打交道的驱动程序。驱动程序给系统提供了与相关类型的设备交互的功能,例如检查I/O接口电路中的状态寄存器来获取设备的状态、将内存缓冲区中的数据写入到输出端口、将输入端口的数据写到内存缓冲区。

5.2 文件系统层与设备驱动层

文件操作是对设备操作的组织与抽象,而设备操作则是对文件操作的最终实现。

文件,有自己的线性寻址空间。
设备,例如磁盘,有自己的逻辑寻址空间【逻辑块号】,还有对应的物理寻址空间【磁柱,磁面,扇区】。

应用程序眼中的文件的内容最终需要映射到具体设备的物理存储介质上,这个映射可以通过层层抽象来完成:

对于普通[常规]文件而言:文件系统将文件中的数据的地址从这个文件的线性寻址空间转换到文件所在设备的逻辑地址空间的逻辑地址,设备驱动程序将设备的逻辑地址【块号】从逻辑地址空间转换到设备的物理地址空间的物理地址【磁柱,磁面,扇区】。

对于设备文件而言:一个设备文件就对应一个设备,所以设备文件的寻址空间直接就是设备的逻辑地址空间,设备驱动程序将设备的逻辑地址【块号】从逻辑地址空间转换到设备的物理地址空间的物理地址【磁柱,磁面,扇区】。

5.3 设备文件

按照经典的UNIX箴言 “万物皆文件”,对外设的访问可利用/dev目录下的设备文件来完成。设备驱动程序支持应用程序经由设备文件与设备通信。

Linux通过设备文件对设备进行管理,一个设备文件不像普通文件通过文件名标识,而是由主、从设备号来标识。通过设备文件可以将设备分为不同的类型,例如块设备【内核将每个块设备都视为一个线性表,由按整数编号的扇区或块组成】字符设备。同一种特性【这里的特性指的是:同一种性质的外设,例如一个硬盘的所有分区属于同一特性的设备】的设备的主设备号相同,从设备号不同,主设备号+设备类型用来关联驱动程序,从设备号用来区别同特性的设备的各个个体。除极少数例外,设备文件被统一到文件系统之中,由虚拟文件系统统一进行管理,所以,在一定程度上可以将设备文件当作常规文件来看待。设备文件并不关联到任何存储设备上的数据,而是建立了与某个设备驱动程序的连接。

5.4 模块

除了内核在编译时被编译到内核地址空间中的代码之外,操作系统还提供了方法动态地将外部代码集成到已编译运行的内核之中。被集成到内核中的外部代码以模块的方式动态的加载到内核中。

模块集成到内核的方式是双向的:

  1. 模块链接到内核:模块要与内核取得联系。例如使用内核中已经确定了地址的函数以及全局变量。模块会使用它需要的内核导出的外部函数和外部全局变量【在编译时对模块来说,内核中的函数和变量都是外部引用】,这就需要内核导出外部引用的地址以便模块在加载时解析这些外部引用。模块链接到内核之后,可以保证所有的外部引用都能正确的解析,完成到这里,模块可以使用内核提供的函数和变量,但是内核无法使用模块,因为还没有为内核建立与模块之间的链接,内核访问不到模块之中的函数和数据。
  2. 内核链接上模块:内核要与模块取得联系。例如调用一个函数表中的接口。所以模块在加载到内核之后,需要进行一些初始化操作,初始化一些数据结构,将自己对接口的实现注册到函数表中相应的接口上。

6. 系统调用路径

IBMdeveloperworks.read 系统调用剖析

例如,sys_read:

代码来自ucore教学操作系统。

调用level 1: sys_read

//---level 1---

static uint32_t
sys_read(uint32_t arg[]) {
...
int fd = (int)arg[0]; //传递过来的文件描述符
void *base = (void *)arg[1]; //要读取的内容在文件中的起始位置
size_t len = (size_t)arg[2]; //读取长度
...


//-------------------------------------------------
//调用level 2.1...【VFS】
return sysfile_read(fd, base, len);//返回读取的长度 
//-------------------------------------------------


}

调用level 2.1: sysfile_read【VFS层】

//---level 2.1【VFS】---

int
sysfile_read(int fd, void *base, size_t len) {
struct mm_struct *mm = current->mm;//获取当前进程的虚拟内存空间

    ...

void *buffer;
if ((buffer = kmalloc(IOBUF_SIZE)) == NULL) {//获取一块内核buffer
return -E_NO_MEM;
}

int ret = 0;
size_t copied = 0, alen;
while (len != 0) {
if ((alen = IOBUF_SIZE) > len) {
alen = len;
}


//-------------------------------------------------
//调用level 2.2...
ret = file_read(fd, buffer, alen, &alen);//将内容拷贝到内核缓冲区buffer中
//-------------------------------------------------



if (alen != 0) {
lock_mm(mm);
{
//将内核缓冲区buffer的内容写入用户空间base处
if (copy_to_user(mm, base, buffer, alen)) {

assert(len >= alen);
base += alen, len -= alen, copied += alen;
}
else if (ret == 0) {
ret = -E_INVAL;
}
}
unlock_mm(mm);
}
if (ret != 0 || alen == 0) {
goto out;
}
}

out: //释放分配的内核缓冲区buffer
kfree(buffer);
if (copied != 0) {
return copied;
}

return ret;//返回读取的长度
}

调用level 2.2:file_read

//---level 2.2--- 【VFS】

int
file_read(int fd, void *base, size_t len, size_t *copied_store) {
int ret;

struct file *file; //文件结构体
/* struct file { enum { FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED, } status; bool readable; bool writable; int fd; off_t pos; struct inode *node;//指向【VFS层的】文件的inode atomic_t open_count; }; */  

*copied_store = 0;

//根据fd找到进程中对应的打开文件
if ((ret = fd2file(fd, &file)) != 0) { //Routine 1
return ret; //用户传来的fd没有对应的打开文件,将出错信息返回上层。
}

filemap_acquire(file);  //原子性地增加文件的引用计数,即file->open_count + 1;

//初始化一个iobuf 
struct iobuf __iob, *iob = iobuf_init(&__iob, base, len, file->pos); //Routine 2
/* struct iobuf { void *io_base; // The base addr of object off_t io_offset; // Desired offset into object size_t io_len; // The lenght of Data size_t io_resid; // Remaining amt of data to xfer }; */ 



//-------------------------------------------------
//调用level 2.3...
ret = vop_read(file->node, iob); //将文件中的内容拷贝到iob->io_base [其实就是拷贝到
//内核缓存区buffer]
//-------------------------------------------------



//从iob得到被拷贝的文件长度
size_t copied = iobuf_used(iob);  //Routine 3

if (file->status == FD_OPENED) {
file->pos += copied; //更新文件偏移
}
*copied_store = copied;

filemap_release(file);  //原子性地减少文件的引用计数,即file->open_count - 1;

return ret; //返回读取的长度
}

Routine 1:

static inline int
fd2file(int fd, struct file **file_store) {
if (testfd(fd)) {

//从fd从进程打开文件数组中索引到对应的文件
  struct file *file = get_filemap() + fd; //Routine 1.1

if (file->status == FD_OPENED && file->fd == fd) {    
*file_store = file; 
return 0;
}
}
return -E_INVAL;
}

Routine 1.1:

static struct file *
get_filemap(void) {
struct fs_struct *fs_struct = current->fs_struct;//获取当前进程打开的文件集合信息
/* //--------------------------struct fs_struct------------------------------ //进程PCB中有一个fs_struct实例,其中包含了进程打开文件集合filemap,而打开文件file在 //filemap中以文件描述符fd作为索引,file中有一个成员为inode,是VFS层用来描述一个文件的结 //构体,这个inode中又有一个成员为in_info,in_info是一个联合数据结构,可以用合理的具体文 //集系统给出的XXXfilesystem_inode来赋值,例如用struct sfs_inode实例来赋值,在 //sfs_inode中有一个成员struct sfs_disk_inode,sfs_disk_inode用来描述磁盘分区中格式化 //为sfs【简单文件系统】格式的磁盘inode结点。一个sfs文件对应一个sfs_disk_inode。 //sfs_disk_inode描述了文件的类型【目录、普通文件、链接文件】、大小、包含文件数据的数据块 //的位置、硬连接数... //是进程与文件系统交互的门户、桥梁。 struct fs_struct { struct inode *pwd;//进程工作目录 struct file *filemap;//打开文件集合,以fd作为索引 atomic_t fs_count; semaphore_t fs_sem; }; */
assert(fs_struct != NULL && fs_count(fs_struct) > 0);

return fs_struct->filemap;//返回打开文件数组
}

Routine 2:

struct iobuf *
iobuf_init(struct iobuf *iob, void *base, size_t len, off_t offset) {
iob->io_base = base; //内核缓冲区buffer地址
iob->io_offset = offset;
iob->io_len = iob->io_resid = len; 
return iob;
}

Routine3:


#define iobuf_used(iob) ((size_t)((iob)->io_len - (iob)->io_resid))

调用level 2.3: vop_read

//---level 2.3---

//-------------------------------------------------
//接口的包装,会调用简单文件系统实现的接口
#define vop_read(node, iob) (__vop_op(node, read)(node, iob)) //Routine 1
//-------------------------------------------------


/* //--------------------------struct inode------------------------------ struct inode { union { struct device __device_info; struct pipe_root __pipe_root_info; struct pipe_inode __pipe_inode_info; //简单文件系统实现的inode struct sfs_inode __sfs_inode_info; // Data structure 1 } in_info; enum { inode_type_device_info = 0x1234, inode_type_pipe_root_info, inode_type_pipe_inode_info, inode_type_sfs_inode_info, } in_type; atomic_t ref_count; atomic_t open_count; //指向inode所属的文件系统 struct fs *in_fs; // Data structure 2 //函数操作集,其中的函数都是接口,等待其他模块实现,并注册。 const struct inode_ops *in_ops; // Data structure 3 }; --------------------------Data structure 1------------------------------ // inode for sfs【简单文件系统实现的inode】 struct sfs_inode { //on-disk inode struct sfs_disk_inode *din; // Data structure 1.1 //如果ino是1,则这个sfs_inode为root sfs_inode uint32_t ino; // inode number uint32_t flags; // inode flags bool dirty; // true if inode modified int reclaim_count; // kill inode if it hits zero semaphore_t sem; // semaphore for din list_entry_t inode_link; // entry for linked-list in sfs_fs list_entry_t hash_link; // entry for hash linked-list in sfs_fs }; --------------------------Data structure 1.1------------------------------ //inode (on disk)【描述格式化为简单文件系统的磁盘分区中的inode】 struct sfs_disk_inode { union { struct { uint32_t size; //size of the file (in bytes) } fileinfo; struct { uint32_t slots; //# of entries in this directory uint32_t parent; //parent inode number } dirinfo; }; uint16_t type; //one of SYS_TYPE_* above uint16_t nlinks; // # of hard links to this file uint32_t blocks; //# of blocks uint32_t direct[SFS_NDIRECT]; // direct blocks uint32_t indirect; // indirect blocks uint32_t db_indirect; //double indirect blocks }; --------------------------Data structure 2------------------------------ struct fs { union { struct pipe_fs __pipe_info; //简单文件系统fs struct sfs_fs __sfs_info; //Data structure 2.1 #ifdef UCONFIG_HAVE_YAFFS2 struct yaffs2_fs __yaffs2_info; #endif #ifdef UCONFIG_HAVE_FATFS struct ffs_fs __ffs_info; #endif } fs_info; enum { fs_type_pipe_info = 0x5678, fs_type_sfs_info,//简单文件系统编号 #ifdef UCONFIG_HAVE_YAFFS2 fs_type_yaffs2_info, #endif #ifdef UCONFIG_HAVE_FATFS fs_type_ffs_info, #endif } fs_type; int (*fs_sync) (struct fs * fs); //获得该文件系统的根目录inode struct inode *(*fs_get_root) (struct fs * fs); int (*fs_unmount) (struct fs * fs); int (*fs_cleanup) (struct fs * fs); }; --------------------------Data structure 2.1------------------------------ //filesystem for sfs struct sfs_fs { struct sfs_super super; //on-disk superblock //Routine 2.1.1 struct device *dev; //device mounted on struct bitmap *freemap; //blocks in use are mared 0 bool super_dirty; //true if super/freemap modified void *sfs_buffer; //buffer for non-block aligned io semaphore_t fs_sem; //semaphore for fs semaphore_t io_sem; //semaphore for io semaphore_t mutex_sem; //semaphore for link/unlink and rename list_entry_t inode_list; //inode linked-list list_entry_t *hash_list; //inode hash linked-list }; --------------------------Data structure 2.1.1------------------------------ //On-disk superblock struct sfs_super { uint32_t magic; //magic number, should be SFS_MAGIC uint32_t blocks; //# of blocks in fs uint32_t unused_blocks; //# of unused blocks in fs char info[SFS_MAX_INFO_LEN + 1]; //infomation for sfs }; --------------------------Data structure 3------------------------------ struct inode_ops { unsigned long vop_magic; int (*vop_open)(struct inode *node, uint32_t open_flags); int (*vop_close)(struct inode *node); ... //vop_read函数指针 int (*vop_read)(struct inode *node, struct iobuf *iob); ... int (*vop_write)(struct inode *node, struct iobuf *iob); int (*vop_fstat)(struct inode *node, struct stat *stat); int (*vop_fsync)(struct inode *node); int (*vop_ioctl)(struct inode *node, int op, void *data); int (*vop_unlink)(struct inode *node, const char *name); int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store); }; */

Routine 1:

//调用inode实例node中的操作函数集实例in_ops中的函数指针vop_##sym;

#define __vop_op(node, sym) / 

({                                                                        /
  struct inode *__node = (node);                                          /
  ...


//-------------------------------------------------
//调用level 3.1... 【FILE SYSTEM】
  __node->in_ops->vop_##sym; / 
//具体的函数实现在node创建后需要被注册
//-------------------------------------------------

})                                                                           /

到这里貌似就跟不下去了,原因是inode的实例node在哪儿,什么时候,被谁创建?更别提inode的成员in_ops的各个函数指针被什么函数实现注册了。这些在read系统调用中看不出来,不过在read之前得有文件被打开,所以首先有一个open系统调用打开文件,然后才能对这个文件进行读写。

inode就是在open系统调用的执行途径中被创建、赋值,其成员in_ops的各个函数指针被相应的文件所在的文件系统给出的函数实现注册。简单起见,这里就不给出open的执行路径了。

调用level 3.1: sfs_read

//---level 3.1---【FILE SYSTEM】
static int sfs_read(struct inode *node, struct iobuf *iob)
{
//调用level 3.2...
//【node-->文件,iob-->内核buffer】
    return sfs_io(node, iob, 0);
}

调用level 3.2: sfs_io

//---level 3.2---

static inline int sfs_io(struct inode *node, struct iobuf *iob, bool write)
{
    //从node中取得sfs_fs和成员sfs_inode
    struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs);
    struct sfs_inode *sin = vop_info(node, sfs_inode);


    int ret;
    if ((ret = trylock_sin(sin)) != 0) {
        return ret;
    }
    size_t alen = iob->io_resid;
    ret =
//调用level 3.3...
        sfs_io_nolock(sfs, sin, iob->io_base, iob->io_offset, &alen, write);

    if (alen != 0) {
        iobuf_skip(iob, alen);
    }
    unlock_sin(sin);
    return ret;
}

调用level 3.3: sfs_io_nolock

//---level 3.2---

static int
sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf,
          off_t offset, size_t * alenp, bool write)
{
    struct sfs_disk_inode *din = sin->din;

    off_t endpos = offset + *alenp, blkoff;
    *alenp = 0;

...
//定义两个函数指针
    int (*sfs_buf_op) (struct sfs_fs * sfs, void *buf, size_t len,
               uint32_t blkno, off_t offset);
    int (*sfs_block_op) (struct sfs_fs * sfs, void *buf, uint32_t blkno,
                 uint32_t nblks);

...
//------------------------------------------------
//调用level 3.4
        sfs_buf_op = sfs_rbuf, sfs_block_op = sfs_rblock;   
//------------------------------------------------
...

    int ret = 0;
    size_t size, alen = 0;
    uint32_t ino;
    uint32_t blkno = offset / SFS_BLKSIZE;//起始块号
    uint32_t nblks = endpos / SFS_BLKSIZE - blkno;//结束块与起始块之差:块数

//因为文件起始位置一定位于起始块的某个部分,所以第一个块的读取size<=SFS_BLKSIZE,单独
//处理
    if ((blkoff = offset % SFS_BLKSIZE) != 0) {

//从起始块读取的文件内容的大小为:(SFS_BLKSIZE - blkoff)
        size =
            (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
            goto out;
        }
        if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0) {
            goto out;
        }
        alen += size;
        if (nblks == 0) {
            goto out;
        }
//读取起始块,修改相关内容。
        buf += size, blkno++, nblks--; 
    }

//除起始块之后[不包括结束块]的其他块都是一整块的,所以读取的内容大小为:SFS_BLKSIZE
    size = SFS_BLKSIZE;
    while (nblks != 0) {

//根据sfs[sfs_fs]和sin[sfs_inode]得到文件起始数据块号在磁盘中的块号:ino 
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
//Routine 1
            goto out;
        }

        if ((ret = sfs_block_op(sfs, buf, ino, 1)) != 0) {
            goto out;
        }
        alen += size, buf += size, blkno++, nblks--;
    }

//结束块的读取size<=SFS_BLKSIZE,单独处理
    if ((size = endpos % SFS_BLKSIZE) != 0) {
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
            goto out;
        }
        if ((ret = sfs_buf_op(sfs, buf, size, ino, 0)) != 0) {
            goto out;
        }
        alen += size;
    }

out:
    *alenp = alen;
    if (offset + alen > din->fileinfo.size) {
        din->fileinfo.size = offset + alen;
        sin->dirty = 1;
    }
    return ret;
}

Routine 1

static int
sfs_bmap_load_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, uint32_t index,
           uint32_t * ino_store)
{
  struct sfs_disk_inode *din = sin->din;
  assert(index <= din->blocks);
  int ret;
  uint32_t ino;
  bool create = (index == din->blocks);

//取得磁盘块号:ino
  if ((ret = sfs_bmap_get_nolock(sfs, sin, index, create, &ino)) != 0) {
//Routine 1.1
      return ret;
  }
  assert(sfs_block_inuse(sfs, ino));
  if (create) {
      din->blocks++;
  }
  if (ino_store != NULL) {
      *ino_store = ino;
  }
  return 0;
}

Routine 1.1

static int
sfs_bmap_get_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, uint32_t index,
          bool create, uint32_t * ino_store)
{
  struct sfs_disk_inode *din = sin->din;
  int ret;
  uint32_t ent, ino;

//case1:文件起始块号index落在磁盘inode直接索引范围之内
  if (index < SFS_NDIRECT) {

//由磁盘inode结构体中的direct数组直接索引得到磁盘块号:ino
      if ((ino = din->direct[index]) == 0 && create) {//若文件在磁盘上没有这个文
//件块

//那就分配一个,并将磁盘块号传给ino
          if ((ret = sfs_block_alloc(sfs, &ino)) != 0) {
              return ret;
          }
//更新直接索引项
          din->direct[index] = ino;
          sin->dirty = 1;
      }
      goto out;
  }

//case2:文件起始块号落在磁盘inode二级索引范围之内
  index -= SFS_NDIRECT;
  if (index < SFS_BLK_NENTRY) {
      ent = din->indirect;
      if ((ret =
           sfs_bmap_get_sub_nolock(sfs, &ent, index, create,
                       &ino)) != 0) {
          return ret;
      }
      if (ent != din->indirect) {
          assert(din->indirect == 0);
          din->indirect = ent;
          sin->dirty = 1;
      }
      goto out;
  }

//case3:文件起始块号落在磁盘inode三级索引范围之内
  index -= SFS_BLK_NENTRY;
  ent = din->db_indirect;
  if ((ret =
       sfs_bmap_get_sub_nolock(sfs, &ent, index / SFS_BLK_NENTRY, create,
                   &ino)) != 0) {
      return ret;
  }
  if (ent != din->db_indirect) {
      assert(din->db_indirect == 0);
      din->db_indirect = ent;
      sin->dirty = 1;
  }
  if ((ent = ino) != 0) {
      if ((ret =
           sfs_bmap_get_sub_nolock(sfs, &ent, index % SFS_BLK_NENTRY,
                       create, &ino)) != 0) {
          return ret;
      }
  }

out:
  *ino_store = ino;//得到最终的磁盘块号,传递给上层
  return 0;
}

调用level 3.4: sfs_rbuf和sfs_rblock

//---level 3.4---

int
sfs_rbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno,
     off_t offset)
{
    assert(offset >= 0 && offset < SFS_BLKSIZE
           && offset + len <= SFS_BLKSIZE);
    int ret;
    lock_sfs_io(sfs);
    {
        if ((ret =
             sfs_rwblock_nolock(sfs, sfs->sfs_buffer, blkno, 0,
                    1)) == 0) {
            memcpy(buf, sfs->sfs_buffer + offset, len);
        }
    }
    unlock_sfs_io(sfs);
    return ret;
}


int sfs_rblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks)
{
//调用level 3.5
    return sfs_rwblock(sfs, buf, blkno, nblks, 0);
}

调用level 3.5: sfs_rwblock

//---level 3.5---

static int
sfs_rwblock_nolock(struct sfs_fs *sfs, void *buf, uint32_t blkno, bool write,
           bool check)
{
    assert((blkno != 0 || !check) && blkno < sfs->super.blocks);
    struct iobuf __iob, *iob =
        iobuf_init(&__iob, buf, SFS_BLKSIZE, blkno * SFS_BLKSIZE);

  //调用level 4.1
    return dop_io(sfs->dev, iob, write);
}

调用level 4.1: dop_io【io接口层】

//---level 4.1---

//调用level 5.1
#define dop_io(dev, iob, write) ((dev)->d_io(dev, iob, write))


//接口
/* struct device { #endif size_t d_blocks; size_t d_blocksize; void *linux_file; void *linux_dentry; int (*d_linux_read) (struct device * dev, const char __user * buf, size_t count, size_t * offset); int (*d_linux_write) (struct device * dev, const char __user * buf, size_t count, size_t * offset); // new ioctl int (*d_linux_ioctl) (struct device * dev, unsigned int, unsigned long); void *(*d_linux_mmap) (struct device * dev, void *addr, size_t len, int unused1, int unused2, size_t off); int (*d_open) (struct device * dev, uint32_t open_flags); int (*d_close) (struct device * dev); int (*d_io) (struct device * dev, struct iobuf * iob, bool write); int (*d_ioctl) (struct device * dev, int op, void *data); }; */

调用level 5.1: disc0_io【设备层】

//实现
/* //for disk2_device: static void disk0_device_init(struct device *dev) { memset(dev, 0, sizeof(*dev)); static_assert(DISK0_BLKSIZE % SECTSIZE == 0); if (!ide_device_valid(DISK0_DEV_NO)) { panic("disk0 device isn't available.\n"); } dev->d_blocks = ide_device_size(DISK0_DEV_NO) / DISK0_BLK_NSECT; dev->d_blocksize = DISK0_BLKSIZE; dev->d_open = disk0_open; dev->d_close = disk0_close; dev->d_io = disk0_io; dev->d_ioctl = disk0_ioctl; sem_init(&(disk0_sem), 1); static_assert(DISK0_BUFSIZE % DISK0_BLKSIZE == 0); if ((disk0_buffer = kmalloc(DISK0_BUFSIZE)) == NULL) { panic("disk0 alloc buffer failed.\n"); } } */

//--------------level 5.1【设备层】--------------

static int disk0_io(struct device *dev, struct iobuf *iob, bool write)
{
    off_t offset = iob->io_offset;
    size_t resid = iob->io_resid;

    uint32_t blkno = offset / DISK0_BLKSIZE;//起始磁盘块号
    uint32_t nblks = resid / DISK0_BLKSIZE;//buffer的磁盘块的数量

    // don't allow I/O that isn't block-aligned 
    if ((offset % DISK0_BLKSIZE) != 0 || (resid % DISK0_BLKSIZE) != 0) {
        return -E_INVAL;
    }

    // don't allow I/O past the end of disk0
    if (blkno + nblks > dev->d_blocks) {
        return -E_INVAL;
    }

    // read/write nothing ? 
    if (nblks == 0) {
        return 0;
    }

    lock_disk0();
    while (resid != 0) {
        size_t copied, alen = DISK0_BUFSIZE;
        if (write) {    
            iobuf_move(iob, disk0_buffer, alen, 0, &copied);
            assert(copied != 0 && copied <= resid
                   && copied % DISK0_BLKSIZE == 0);
            nblks = copied / DISK0_BLKSIZE;

            //------------------------------------
            //调用level 5.2
            disk0_write_blks_nolock(blkno, nblks);
            //------------------------------------


        } else {
            if (alen > resid) {
                alen = resid;
            }
            nblks = alen / DISK0_BLKSIZE;


            disk0_read_blks_nolock(blkno, nblks);


            iobuf_move(iob, disk0_buffer, alen, 1, &copied);
            assert(copied == alen && copied % DISK0_BLKSIZE == 0);
        }
        resid -= copied, blkno += nblks;
    }
    unlock_disk0();
    return 0;
}

调用level 5.2: disk0_write_blks_nolock

//---level 5.2---

static void disk0_write_blks_nolock(uint32_t blkno, uint32_t nblks)
{
    int ret;
    uint32_t sectno = blkno * DISK0_BLK_NSECT, nsecs =
        nblks * DISK0_BLK_NSECT;
    if ((ret =

         //调用level 6.1【驱动层】
         ide_write_secs(DISK0_DEV_NO, sectno, disk0_buffer, nsecs)) != 0) {

        panic("disk0: write blkno = %d (sectno = %d), nblks = %d (nsecs = %d): 0x%08x.\n",
             blkno, sectno, nblks, nsecs, ret);
    }
}

调用level 6.1: ide_write_secs 【驱动层】

int
ide_write_secs(unsigned short ideno, uint32_t secno, const void *src,
           size_t nsecs)
{
    assert(nsecs <= MAX_NSECS && VALID_IDE(ideno));
    assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS);

    if(ide_devices[ideno].ramdisk)
        return ramdisk_write(&ide_devices[ideno], secno, src, nsecs);
    unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno);

    lock_channel(ideno);

    ide_wait_ready(iobase, 0);//轮询

    // generate interrupt
    outb(ioctrl + ISA_CTRL, 0);
    outb(iobase + ISA_SECCNT, nsecs);
    outb(iobase + ISA_SECTOR, secno & 0xFF);
    outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF);
    outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF);
    outb(iobase + ISA_SDH,
         0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF));
    outb(iobase + ISA_COMMAND, IDE_CMD_WRITE);

    int ret = 0;
    for (; nsecs > 0; nsecs--, src += SECTSIZE) {
        if ((ret = ide_wait_ready(iobase, 1)) != 0) {
            goto out;
        }
        outsl(iobase, src, SECTSIZE / sizeof(uint32_t));
    }

out:
    unlock_channel(ideno);
    return ret;
}

References

part0.Into a Computer

Control Unit Operation

Introduction to Computer Engineering

Introduction to Computer Systems

part1.Hard Disk

Hard disks

Hard Drive Format

Disk Access

Linux Partition HOWTO[HOWTO series]

Partition Table

Linux System Administrators Guide.Chapter 5. Using Disks and Other Storage Media.partitions

part2.File System

Filesystems.Tips and Tricks [You can download a free PDF version]

Ext2 File System [You can download a free PDF version]

Ext4 Disk Layout

12 Scribe Notes - File Systems

Lab5: filesystem ext2

part3.File System Implementation

Internal Representation of Files

The File system

IBMdeveloperworks.Anatomy of the Linux file system

Writing a Simple File System

Design and Implementation of the Second Extended Filesystem

Inode and its structure in linux

Classical Unix File System

CS372H Spring 2012 Lab 5: File System

Hard Link Soft Symbolic Links

part4.VFS

The Virtual Filesystem[信息量大,参考价值大]

IBMdeveloperworks.解析 Linux 中的 VFS 文件系统机制[描述了VFS的基本框架]

IBMdeveloperworks.从文件 I/O 看 Linux 的虚拟文件系统[配图好]

The Linux Virtual File System[VFS系统中的各种数据结构]

par5.File System Call

Linux内核读文件流程

File System Calls

Linux system programming: Open file, read file and write file

File I/O

Linux C编程一站式学习.CH28&CH29

part6.I/O

6.1 Architecture

深入理解计算机系统.第六章.存储器层次结构

Addressing IO devices

DMA

Input/output controller.A brief view

uwm.Input-Output Interface

Input / Output

Difference between port mapped and memory mapped access

I/O Controller Hub

6.2 Interrupt

Interrupts[PDF]

sys_call_table [系统调用表]

设备管理.ch7.第一讲.北京大学

Interrupts and Interrupt Handling

8259A PIC Microcontroller

Exercise #9: Interrupt Service Routines

6.3 Device driver

字符设备驱动 架构分析
princeton.edu.I/O Device and Drivers

Device Drivers

Hardware Interaction, Windows Perspective in C programming

USB Device Driver Functional Model

CSDN.中断服务子程序是不是就是驱动程序?

6.4 A lot more

Linux设备驱动–块设备(一)之概念和框架

UC Berkeley.CS61cl Lab 25 - Input-Output

IBMdeveloperworks.read 系统调用剖析[此文试着捋清read文件操作所经历的各个层]

IBMdeveloperworks.使用异步 I/O 大大提高应用程序的性能

UART Design and Programming

UTSA.edu.Operating Systems Notes: USP Chapter 4 UNIX I/O

Devices

CSDN.Read 系统调用在用户空间中的处理过程

File and Device I/O using System Calls

part7.进程和文件系统的交互

General Programming Concepts: Writing and Debugging Programs

part8.基础阅读

C语言中的#号和##号的作用

linux中的设备名称和设备号

linux文件系统简介

鸟哥的私房菜.Linux 磁盘与文件系统管理

Unix for beginners

Finding a File in an EXT2 File System

CS560 CLASS NOTES [华盛顿大学 Operating Systems Course]

part9.扩展阅读

How To Reinstall GRUB2 – Chroot Into A Linux Partition

IBMdeveloperworks.如何恢复 Linux 上删除的文件,第 1 部分

深入理解Linux内核[对初学者不太友好的一本Linux经典参考书籍]

Stackexchange.Designing USB device

Large Disk HOWTO.Disk Access [HOWTO series]

Filesystems HOWTO [HOWTO series]

part10.资料

逻辑与计算机设计基础

计算机系统组成与体系结构

Intel微处理器与外设大学教程

Intel微处理器

计算机系统系统架构与操作系统的高度集成

计算机系统概论

Operating System Development Series

计算机组成结构化方法

计算机组成原理.唐朔飞

Linux内核源代码情景分析

Unix高级环境编程

Github.Awesome Courses

Emory University.CS355: Computer Organization/Architecture II

UC Berkeley.EECS Course WEB Sites

UWM.CS Course Home Pages

PC Architecture [计算机内部组成的简单介绍]

Micro-controller Learning Modules

Baiduwenku.移动硬盘:从接口到芯片

Programmable Keyboard/Display Interface - Intel 8279

你可能感兴趣的:(linux,文件系统)