文件系统调用和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_umou
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值