歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> 文件系統調用和Linux文件系統基礎

文件系統調用和Linux文件系統基礎

日期:2017/3/3 11:35:55   编辑: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到多個匯編指令構成,一條匯編指令對應一條機器指令碼,而一條機器指令碼的執行又涉及到了一組不可中斷的微操作(原子性)。

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:
[code]//待注冊文件系統模塊須給出文件系統類型[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;
}

例如,簡單文件系統的注冊:
[code]  if ((ret = register_filesystem("sfs", sfs_mount)) != 0) {
      panic("failed: sfs: register_filesystem: %e.\n", ret);
  }

其中sfs_mount函數來自簡單文件系統:
[code]/*
* 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…,接口的實現由具體的文件系統負責注冊]…】,用來全局性的管理和組織一個文件系統。
用戶層
[code]//------------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指令陷入內核:
[code]/*
//操作系統在編譯的時候靜態的初始化了一張系統調用表,其中的表項是操作系統內核自帶的實
//現,理論上,我們可以在操作系統被加載之後修改其中的函數指針,使其指向我們給出的函數地
//址。
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
[code]//---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
[code]//---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:
[code]
#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
這層的調用與具體的文件系統類型有關,這裡我們以簡單文件系統的安裝為例:
當然,安裝之前先得注冊,注冊流程上文已給出。
[code]//---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
[code]//---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:
[code]/*
* 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:
[code]
#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
[code]//---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:
[code]/*
* 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 模塊

除了內核在編譯時被編譯到內核地址空間中的代碼之外,操作系統還提供了方法動態地將外部代碼集成到已編譯運行的內核之中。被集成到內核中的外部代碼以模塊的方式動態的加載到內核中。
模塊集成到內核的方式是雙向的:
模塊鏈接到內核:模塊要與內核取得聯系。例如使用內核中已經確定了地址的函數以及全局變量。模塊會使用它需要的內核導出的外部函數和外部全局變量【在編譯時對模塊來說,內核中的函數和變量都是外部引用】,這就需要內核導出外部引用的地址以便模塊在加載時解析這些外部引用。模塊鏈接到內核之後,可以保證所有的外部引用都能正確的解析,完成到這裡,模塊可以使用內核提供的函數和變量,但是內核無法使用模塊,因為還沒有為內核建立與模塊之間的鏈接,內核訪問不到模塊之中的函數和數據。
內核鏈接上模塊:內核要與模塊取得聯系。例如調用一個函數表中的接口。所以模塊在加載到內核之後,需要進行一些初始化操作,初始化一些數據結構,將自己對接口的實現注冊到函數表中相應的接口上。

6. 系統調用路徑

IBMdeveloperworks.read 系統調用剖析
例如,sys_read:
代碼來自ucore教學操作系統。

調用level 1: sys_read

[code]//---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層】

[code]//---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

[code]//---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:
[code]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:
[code]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:
[code]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:
[code]
#define iobuf_used(iob)            ((size_t)((iob)->io_len - (iob)->io_resid))

調用level 2.3: vop_read

[code]//---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:
[code]//調用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

[code]//---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

[code]//---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

[code]//---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
[code]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
[code]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

[code]//---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

[code]//---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接口層】

[code]//---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【設備層】

[code]//實現
/*
//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

[code]//---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 【驅動層】

[code]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
Copyright © Linux教程網 All Rights Reserved