深入理解 Linux 檔案系統
===
:::info
學習目標
1. 檔案系統是的 `open`, `read`, `write`, `close` 是怎麼實作的?
2. 檔案系統掛載的目的是什麼?
3. 檔案系統是怎麼實現使用者權限的更改的?
4. 檔案系統是怎麼和加載器 (loader, `execve`) 合作的?
5. 在一個檔案系統中,為什麼一些字符設備 (character device) 會有不同的打開方式?
6. 檔案描述子 (file descriptor) 是怎麼產生的?他又是怎麼和文件產生連結的?
如果這些問題你都可以清楚地回答出來。恭喜你,這篇文章對你沒有什麼幫助,但是如果你對這些問題沒有概念,那你可以繼續閱讀來了解檔案系統的運作原理。
:::
# 目錄
- 檔案系統概述
- 檔案系統怎麼使用
- 檔案系統的結構介紹
- `jffs2` 檔案系統介紹
- NAND flash 介紹
- 深度解析 `jffs2` 檔案系統操作的實作
- `ext2` 檔案系統介紹
- 深度解析 `ext2` 檔案系統操作的實作
- 設計與實作一個檔案系統
> 聲明:因應工作需求,本文使用的 Linux 核心版本為 [`5.10.181`](https://elixir.bootlin.com/linux/v5.10.181/source) 不是最新的發行版
>
> 本文主要觀察 `ext2` 和 `jffs2`。因為 `ext2` 是基於 block device 的檔案系統而 `jffs2` 是基於 mtd device 的檔案系統。
### 實驗環境
- 設備: macbook pro m1 max
- 作業系統: parallel ubuntu debian
```
$ uname -a
Linux ubuntu-linux-20-04-desktop 5.13.0-25-generic #26~20.04.1-Ubuntu SMP Sat Jan 8 18:05:46 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
```
# 檔案系統概述
檔案系統的使用分為兩個層面,一是普通使用者的角度,二是應用程式 (application) 開發者的角度。
從普通使用者來說,檔案系統的使用無非是增(create)、刪(delete)、查(search)、改(modify)。從開發者來說,除了這幾個功用之外,需要對一些檔案系統有更深入的理解,像是怎麼樣可以繞過快取 (cache) ,怎麼樣給檔案加鎖 (lock) 等等。
本文主要已開發者的角度進行撰寫,會從用戶空間 (user space) 的使用方式慢慢深入到核心空間 (kernel space) 看看核心設計者是怎麼實作這些功能的。
使用者眼中的檔案系統檔案系統主要由檔案 (file)、目錄 (directory) 及連結 (link) 所組成
## 如何使用檔案系統
在這個章節,我們以用戶空間的開發者作為基礎說明一些基本的檔案系統機制,例如檔案讀寫、目錄的操作、許可權的處理與檔案系統的掛載
### 對檔案進行讀寫
參考程式碼 [`cp.c`](https://github.com/zoanana990/note/blob/main/fs/cp/cp.c) 這個程式碼是一個簡單的 Linux `cp` 的命令,這裡用到四個函式 `fopen`, `fread`, `fwrite`, `fclose` 這邊簡單看一下程式碼
```c=16
// 打開檔案, 以 read 的方式
source_file = fopen(argv[1], "rb");
// 不能保證一次寫完檔案,一次寫 1024 bytes
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, source_file)) > 0) {
fwrite(buffer, 1, bytes_read, target_file);
}
// 做完之後把檔案關閉
fclose(source_file);
```
直接在終端機上面 `make` 就可以編譯了,測試命令如下:
```
$ touch test
$ echo "hello" > test
$ cat test
hello
$ ./cp test hello
$ cat hello
hello
```
輸出上面所是代表複製檔案成功,程式碼裡面的 `fopen`, `fread`, `fwrite`, `fclose` 可以使用 `open`, `read`, `write`, `close` 代替,寫法也非常類似,差別在於 `fopen` 系列的是 C 語言定義的函式,而 `open` 系列的是 POSIX 標準的函式,同時也是系統呼叫 (system call)。後面的章節會介紹這些系統呼叫的實作
### 對目錄進行操作
上面這個例子只有對檔案進行操作,這個案例就是列出目錄中所有檔案,參考程式碼 [ls.c](https://github.com/zoanana990/note/blob/main/fs/ls/ls.c)
當編譯完成之後可以使用 `./ls` 進行測試。
如果想要列出每個檔案的細節,像是 `ls -a` 會需要使用到 `struct stat`,這邊不是我們的重點,可以參考 `glibc` 的實作
### 檔案系統的格式化與掛載
一班在使用格式化的時候我們會使用 `mkfs` 當空間格式化完成之後才會將檔案系統進行掛載,掛載的命令會使用 `mount`。由此可知,每個檔案系統都需要在 linux kernel 中提供 `mount` 的函式。`mount` 的部分在後面會提到,這邊先看格式化的部分。
:::warning
### 延伸閱讀
在 `/dev` 底下總是有一些奇奇怪怪的檔案和目錄,看到但是又不知道那些是什麼東西,有的時候掛載檔案系統時會用到,這邊讓進行解析。
#### Block devices
Block devices 可透過 `/dev` 中的檔案節點來存取。一個 Block 只能處理一到多個完整 Block 的 I/O 操作,大小為 512 bytes (或是更大的2的次方)。Linux 可以讓讀取 Block device 如同操作File 一樣,而不用一次讀一整個 Block,在 User Space 中使用與 Char device 基本上無差別。有差別的是在 Kernel Driver 這一層完全不同。
#### Character devices
Character devices 可以被當作位元組流 (A stream of bytes) 來被存取,就像一般的檔案 (File) 一樣。因此 Char dirver 就要負責至少 `open`, `close`, `read`, 和 `write` 的系統呼叫 (system call) 操作,有的還有提供 `ioctl` 的操作方式。常見的例子有 Text Console `/dev/console` 和 Serial Ports `/dev/ttyS0`,都是位元組流結構。Char devices 由檔案系統的節點 (Filesystem node) 來存取,像是 `/dev/tty1` 和 `/dev/lp0`。
但跟一般檔案不同的是,一般檔案可以把指標往前往後來操作檔案,但是多數 Char device 只能依序存取。當然也有例外,可以用 `mmap` 或是 `lseek` 將檔案的位置指標進行移動。
#### Block 與 Character 的差異
Block devices 是以固定大小長度來傳送轉移資料,而 Character devices 是以不定長度的字元傳送資料,而其操作方式在使用者空間大同小異。
#### `/dev` 底下常見的檔案
- `/dev/mem` 實體記憶體
- `/dev/kmem` 虛擬記憶體
- `/dev/ram0` 第 0 個 ram disk,請使用 `tmpfs` 這個已經過時了
- `/dev/mtd` 代表的是 [`mtd`](https://en.wikipedia.org/wiki/Memory_Technology_Device)
其他的去看 [devices.txt](https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt),裡面有詳細的介紹
:::
### 許可權管理
#### RWX-UGO 許可權管理
Linux 最常用的許可詮釋 RWX-UGO 的許可權管理。其中,RWX 是 Read, Write, eXecution 的縮寫,而 UGO 是 User, Droup 和 other 的縮寫。當我們在終端機輸入命令 `ls -alh` 會列出下面的資訊:
```shell
$ ls -alh
total 52K
drwxr-xr-x 1 khienh dialout 160 Dec 9 08:16 .
drwxr-xr-x 1 khienh dialout 128 Dec 9 08:14 ..
-rw-r--r-- 1 khienh dialout 107 Dec 9 08:16 Makefile
-rwxr-xr-x 1 khienh dialout 33K Dec 9 08:16 ls
-rw-r--r-- 1 khienh dialout 409 Dec 9 08:15 ls.c
```
其中, `drwxr-xr-x` 是許可權資訊、`khienh` 是所屬使用者、`dialout` 是所屬群組。
我們可以利用 `chmod` 改變我們的許可權資訊,這邊先介紹許可權資訊,以 `drwxr-xr-x` 為例:
```txt
d | rwx | r-x | r-x
```
- 第一個字母,`d` 是 `directory`、`c` 是字符裝置 (character device)、`b` 是塊裝置 (block device)、`-` 是普通的檔案
- 由左到右,第一區的 `rwx` 是代表使用者權限
- 由左到右,第二區的 `rwx` 是代表所屬群組的權限,以這個例子來說可以讀和可以執行
- 由左到右,第三區的 `rwx` 是代表其他使用者的權限
- 這些表示權限的三個字母,如果是 `-` 就代表沒有這個權限,如果有出現 `r` 代表可以讀,`w` 代表可以寫,`x` 代表可以執行。
`chmod 777 xxx` 代表賦予 `xxx` 這個檔案可讀可寫可以執行的權限,其中,`chmod` 代表的是 change mode 的縮寫。
```shell
# 增加可以執行的許可權
$ chmod +x test.bin
# 增加可讀可寫可執行的許可權
$ chmod 777 test.bin
```
定義如 [stat.h](https://elixir.bootlin.com/linux/v5.10.181/source/include/uapi/linux/stat.h#L29)
```c=29
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
```
當然還可以用 `chown` 改寫檔案的所屬使用者資訊,和 `chgrd` 還改變檔案的群組資訊等等。 RWX-UGO 的控制權就介紹到這了。
# Linux 檔案系統架構
檔案系統是 Linux 核心中的四大子系統之一(行程管理、記憶體管理、網路子系統),因此程式碼非常的繁雜。光是虛擬檔案系統的抽象層就已經多達五萬多行,還沒有包含任何一個檔案系統,下面先介紹一下 Linux 檔案系統的整體架構,如下圖所示:
```graphviz
digraph G {
rankdir=TB; // 從上到下排列
Application;
subgraph filesystem {
node [style=filled]
Virtual_File_System [label="Virtual File System" shape=rect width=6];
Page_Cache [label="Page Cache" shape=rect width=1];
Inode_Cache [label="Inode Cache" shape=rect width=1];
Directory_Cache [label="Directory Cache" shape=rect width=1];
Page_Cache -> Virtual_File_System [dir = "both"];
Virtual_File_System -> {Inode_Cache Directory_Cache} [dir = "both"];
{
rank=same;
Virtual_File_System;
Page_Cache;Inode_Cache;
}
label="filesystem"
color=blue
}
EXT2 [label="EXT2" shape=rect width=0];
JFFS2 [label="JFFS2" shape=rect width=0];
XFS [label="XFS" shape=rect width=0];
NFS [label="NFS" shape=rect width=0];
proc [label="proc" shape=rect width=0];
sysfs [label="sysfs" shape=rect width=0];
ramfs [label="ramfs" shape=rect width=0];
Buffer_Cache [label="Buffer Cache" shape=rect width=3];
Ram [label="ram" shape=rect width=3]
Device_Driver [label="Device Driver" shape=rect width=3]
Application -> Virtual_File_System;
{
rank=same;
EXT2; XFS; NFS; JFFS2; proc; sysfs; ramfs;
}
Virtual_File_System -> EXT2 [style=dashed];
Virtual_File_System -> XFS [style=dashed];
Virtual_File_System -> NFS [style=dashed];
Virtual_File_System -> JFFS2[style=dashed];
Virtual_File_System -> proc[style=dashed];
Virtual_File_System -> ramfs[style=dashed];
Virtual_File_System -> sysfs[style=dashed];
Virtual_File_System -> Buffer_Cache;
EXT2 -> Buffer_Cache;
XFS -> Buffer_Cache;
NFS -> Buffer_Cache;
ramfs->Ram;
proc->Ram;
sysfs->Ram;
JFFS2 -> Buffer_Cache;
Buffer_Cache->Device_Driver;
}
```
> 這邊有兩張圖主要是因為 github 不支援 graphviz 的顯示
>
> 後面都會採用下面這種圖
![image](https://hackmd.io/_uploads/B1H9WEGY6.png)
由上圖可以看到,由於 Linux 需要對接很多不同的檔案系統。因此,Linux 將檔案系統進行抽象為虛擬檔案系統(Virtual File System, 以下稱 VFS),虛擬檔案系統主要提供以下的功用:
1. 將所有 API 提供統一的介面。例如:`open`, `read`, `write`, `close`, `mount` 等等。
2. 很多共同的功能,例如:inode cache, page cache, directory cache 等等。
3. 規範檔案系統應該要實作哪一些功能。當檔案系統完成 VFS 規範的功能時,將他註冊到進 Linux 之後,就可以使用其功能。
下圖是一個 Linux 目錄樹,可以看到 `rootfs` 是 `ext3`。在 `mnt` 中又要掛載 `xfs` 和 `ext4`。Linux 是可以掛載不同的檔案系統的,只要你把空間格式化好之後就可以進行掛載,後面會再更詳述掛載的原理。
```graphviz
digraph G {
rankdir=TB;
root[label="/" shape=rect];
ext3[label="ext3" shape=rect style=dashed];
{rank=same; root; ext3 ext3->root};
bin[label="bin" shape=rect];
home[label="home" shape=rect];
mnt[label="mnt" shape=rect];
dev[label="dev" shape=rect];
mnt[label="mnt" shape=rect];
others[label="..." shape=rect];
others2[label="..." shape=rect];
xfs_test[label="xfs_test" shape=rect];
ext4_test[label="ext4_test" shape=rect];
ext4[label="ext4" shape=rect style=dashed];
xfs[label="xfs" shape=rect style=dashed];
root->bin[style=dashed];
bin->others2[style=dashed];
root->home[style=dashed];
root->mnt[style=dashed];
root->dev[style=dashed];
root->others[style=dashed];
mnt->xfs_test[style=dashed];
{rank=same; xfs; xfs_test; xfs->xfs_test};
mnt->ext4_test[style=dashed];
{rank=same; ext4; ext4_test; ext4->ext4_test};
}
```
### 從 `VFS` 到檔案系統
`VFS` 為使用這提供共同的介面,每一支 `API` 都會與一個核心函式進行對應,下表示比較常用的檔案系統操作。
| User space API | kernel space |
| -------- | -------- |
| `open` | [`do_sys_open`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/open.c#L1206) |
| `read` | [`ksys_read`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/read_write.c#L623) |
| `write` | [`ksys_write`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/read_write.c#L647) |
| `close` | [`__close_fd`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/open.c#L1311) |
| `mount` | [`do_mount`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/namespace.c#L3450) |
現在已經知道使用者的系統呼叫和核心函式的關係了,至於 VFS 是怎麼呼叫到具體的檔案系統呢?在後面的文章會進行說明。現在的當務之急是要把虛擬檔案系統的幾個重要的結構進行介紹。
# 檔案系統的結構
檔案系統的結構設計起來錯綜複雜,當一個行程 (process) 去開啟一個檔案的時候可以是下面這樣的
![image](https://hackmd.io/_uploads/S13l1u-tp.png)
這張圖看起來十分複雜,而實際情況又比這張圖要複雜得多,因為有很多結構是互相關連的,而這個又沒有辦法一時之前解釋清楚。只能 ***耐心的*** 將每一個結構成員看清楚,不要想走捷徑。
第一個要介紹的結構是用來註冊檔案系統的,[`file_system_type`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fs.h#L2246)
```c=2245
/* /include/linux/fs.h */
struct file_system_type {
/* 檔案系統的名稱 */
const char *name;
/* 檔案系統類型的標誌 */
int fs_flags;
/* 檔案系統保存在外部設備 */
#define FS_REQUIRES_DEV 1
...
/* 檔案系統初始化的函式給不是使用 block device 的檔案系統使用 */
int (*init_fs_context)(struct fs_context *);
const struct fs_parameter_spec *parameters;
/* 掛載用的函式 */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
void (*kill_sb) (struct super_block *);
struct module *owner;
/* 單向鏈結串列,這裡沒有使用 list API */
struct file_system_type * next;
/* 雜湊表,用來對接 super_block 用的 */
struct hlist_head fs_supers;
... /* 省略 lock 的結構 */
};
```
接下來看一下註冊函式 [`register_filesystem`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/filesystems.c#L72)
```c=59
/**
* register_filesystem - register a new filesystem
* @fs: the file system structure
*
* Adds the file system passed to the list of file systems the kernel
* is aware of for mount and other syscalls. Returns 0 on success,
* or a negative errno code on an error.
*
* The &struct file_system_type that is passed is linked into the kernel
* structures and must not be freed until the file system has been
* unregistered.
*/
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
res = -EBUSY;
else
*p = fs;
return res;
}
```
`register_filesystem` 主要是執行函式 [`find_filesystem`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/filesystems.c#L49)
```c=49
static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
/* 走訪 file_system_type 的鏈結串列 */
for (p = &file_systems; *p; p = &(*p)->next)
/* 如果這個 filesystem 已經被註冊的話就回傳這個指標 */
if (strncmp((*p)->name, name, len) == 0 &&
!(*p)->name[len])
break;
return p;
}
```
由此可知,每次註冊的 `file_system_type` 都會放到鏈結串列的尾巴。由下圖所示:
![image](https://hackmd.io/_uploads/B1q01uZYp.png)
可以透過
```shell
cat /proc/filesystems
```
查看 kernel 支援檔案系統的類型
```shell
$ cat /proc/filesystems
nodev sysfs
nodev tmpfs
nodev bdev
nodev proc
nodev cgroup
nodev cgroup2
nodev cpuset
nodev devtmpfs
nodev configfs
nodev debugfs
...
```
接下來看 `ext2` 和 `jffs2` 初始化檔案系統的過程
### 檔案系統的註冊
程式碼: [init_ext2_fs](https://elixir.bootlin.com/linux/v5.10.181/source/fs/ext2/super.c#L1647)
```c=1647
static int __init init_ext2_fs(void)
{
// 使用 slab 分配器創造記憶體空間
err = init_inodecache();
// 將檔案系統註冊到 linux 中
err = register_filesystem(&ext2_fs_type);
return 0;
}
```
1. [`init_inodecache`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/ext2/super.c#L215),這個函式是給 `ext2` 分配記憶體空間以加速檔案系統的運作,分配的方式會在記憶體篇進行說明。
2. [`register_filesystem`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/filesystems.c#L72),將 `ext2_fs_type` 加入檔案系統的鏈結串列中。
`ext2_fs_type` 其實就是一個全域變數,結構成員就是模組名稱、掛載的函式指標等等
```c=1638
static struct file_system_type ext2_fs_type = {
.owner = THIS_MODULE,
.name = "ext2",
.mount = ext2_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
```
接下來讓我們觀察 `jffs2` 的初始化流程。程式碼: [init_jffs2_fs](https://elixir.bootlin.com/linux/v5.10.181/source/fs/jffs2/super.c#L361)
```c=361
static int __init init_jffs2_fs(void)
{
int ret;
jffs2_inode_cachep = kmem_cache_create(...);
ret = jffs2_compressors_init();
ret = jffs2_create_slab_caches();
ret = register_filesystem(&jffs2_fs_type);
return 0;
}
```
可以發現流程其實差不多,都是先創造 `cache` 之後將 `struct file_system_type` 這個全域變數註冊到 `kernel` 中。
```c=352
// in /fs/jffs2/super.c
static struct file_system_type jffs2_fs_type = {
.owner = THIS_MODULE,
.name = "jffs2",
// 注意這裡已經沒有 mount 了,取而代之的是 init_fs_context
.init_fs_context = jffs2_init_fs_context,
.parameters = jffs2_fs_parameters,
.kill_sb = jffs2_kill_sb,
};
```
這邊可以看到在 `ext2` 中,有 `mount` 這個函式指標,只是在 `jffs2` 中換成了 `init_fs_context`。這兩個成員函式在 `mount` 中都會用到。
## `struct super_block`
在 Linux 中,每一個檔案系統都會需要創建 `super_block`。`super_block` 代表檔案系統的整體訊息,例如:檔案系統類型等等。
通常 block device 中的檔案系統會在開頭的一段區域存放這個檔案系統的訊息。而這一段區域對應的就是 [`struct super_block`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fs.h#L1422)
```c=1422
struct super_block {
/* 連結所有 super_block 的雙向環狀鏈結串列 */
struct list_head s_list;
/* 設備號 */
dev_t s_dev;
/* 數據塊大小,以 2 為底的對數 */
unsigned char s_blocksize_bits;
unsigned long s_blocksize;
/* 最大文件大小 */
loff_t s_maxbytes;
/* 指向這個 super_block 所屬的檔案系統 */
struct file_system_type *s_type;
/* super_block 的函式指標 */
const struct super_operations *s_op;
/* 硬碟的配額管理函式 */
const struct dquot_operations *dq_op;
const struct quotactl_ops *s_qcop;
const struct export_operations *s_export_op;
unsigned long s_flags;
unsigned long s_iflags; /* internal SB_I_* flags */
/* magic number 每一個檔案系統都有不一樣的數字 */
unsigned long s_magic;
/* super_block 的 dentry 目錄進入點 */
struct dentry *s_root;
struct rw_semaphore s_umount;
int s_count;
atomic_t s_active;
const struct xattr_handler **s_xattr;
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
/* 掛載 mount 結構體的鏈結串列,一個分區可以執行多個掛載 */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
/* 檔案系統的設備指標 */
struct block_device *s_bdev;
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
/* 雜湊表的鏈結串列,連結點是 file_system_type 的 fs_super */
struct hlist_node s_instances;
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */
struct sb_writers s_writers;
/*
* Keep s_fs_info, s_time_gran, s_fsnotify_mask, and
* s_fsnotify_marks together for cache efficiency. They are frequently
* accessed and rarely modified.
*/
void *s_fs_info; /* Filesystem private info */
/* Granularity of c/m/atime in ns (cannot be worse than a second) */
u32 s_time_gran;
/* Time limits for c/m/atime in seconds */
time64_t s_time_min;
time64_t s_time_max;
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID */
unsigned int s_max_links;
fmode_t s_mode;
struct mutex s_vfs_rename_mutex; /* Kludge */
const char *s_subtype;
/* dentry operation */
const struct dentry_operations *s_d_op; /* default d_op for dentries */
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */
/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;
/* Pending fsnotify inode refs */
atomic_long_t s_fsnotify_inode_refs;
/* Being remounted read-only */
int s_readonly_remount;
/* per-sb errseq_t for reporting writeback errors via syncfs */
errseq_t s_wb_err;
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
struct user_namespace *s_user_ns;
/*
* The list_lru structure is essentially just a pointer to a table
* of per-node lru lists, each of which has its own spinlock.
* There is no need to put them into separate cachelines.
*/
struct list_lru s_dentry_lru;
struct list_lru s_inode_lru;
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
/* 所有 inode 的鏈結串列 */
struct list_head s_inodes; /* all inodes */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
} __randomize_layout;
```
將 `file_system_type` 和 `super_block` 一起看可以得到下圖
![image](https://hackmd.io/_uploads/SJDDwNMFa.png)
`super_block` 包含兩個函式街口,`dentry_operations` 和 `super_operations`。[`super_operations`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fs.h#L1951) 的定義如下:
```c=1951
struct super_operations {
/* 創建和初始化一個 inode 節點 */
struct inode *(*alloc_inode)(struct super_block *sb);
/* 摧毀並且釋放 inode */
void (*destroy_inode)(struct inode *);
void (*free_inode)(struct inode *);
/* 標記 inode 為 dirty,代表這個 inode 被修改過 */
void (*dirty_inode) (struct inode *, int flags);
/* 將 inode 寫入硬碟, wbc 是寫入時的控制訊息 */
int (*write_inode) (struct inode *, struct writeback_control *wbc);
/* 最後一個索引節點被釋放的時候呼叫這個函式 */
int (*drop_inode) (struct inode *);
/* 刪除指定的 inode */
void (*evict_inode) (struct inode *);
/* 卸載檔案系統的時候呼叫,用來釋放 super_block */
void (*put_super) (struct super_block *);
/* 同步檔案系統所有的 dirty inode */
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
/* 獲取 dentry 的訊息 */
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
```
接下來讓我們看一下 `jffs2` 和 `ext2` 提供的這個結構體 [`jffs2_super_operations`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/jffs2/super.c#L242)
```c=242
static const struct super_operations jffs2_super_operations =
{
.alloc_inode = jffs2_alloc_inode,
.free_inode = jffs2_free_inode,
.put_super = jffs2_put_super,
.statfs = jffs2_statfs,
...
};
```
和 [`ext2_sops`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/ext2/super.c#L346)
```c=346
static const struct super_operations ext2_sops = {
.alloc_inode = ext2_alloc_inode,
.free_inode = ext2_free_in_core_inode,
.write_inode = ext2_write_inode,
.evict_inode = ext2_evict_inode,
.put_super = ext2_put_super,
.sync_fs = ext2_sync_fs,
...
};
```
這邊先感受一下結構就好,細節在後面會進行說明。
## `struct dentry`
```txt
/Users/khienh/
```
在這段路徑中總共有幾個 `dentry`? 幾個 `inode`?
共有三個 `denetry` 和三個 `inode`。看到下面的圖你就會知道什麼是 `inode` 什麼是 `dentry`
下圖是一張電腦檔案結構的圖片
![image](https://hackmd.io/_uploads/SJmOdEftT.png)
這些資料夾對應的 `dentry` 關係如下圖:
![image](https://hackmd.io/_uploads/SJft8iMFa.png)
可以看到每一個資料夾都對應一個 `dentry` ,而每一個 `dentry` 都會指向一個 `inode`。`dentry` 代表的就是目錄或是檔案的入口,而 `inode` 則是檔案本身。
可能一開始看到這樣的設計覺得很神奇,為什麼 Linux 要把 `dentry` 和 `inode` 分離呢?一來是可以支援多個檔案系統,不同的檔案系統可能在同一個目錄底下,此時可以將 `inode` 換掉得到不同的檔案。另外,`inode` 的創造是根據各個檔案系統的 `alloc_inode` 函式創建的,而 `dentry` 則是統一由虛擬檔案系統創建。
這樣的設計好處在 `open` 的時候就可以充分體會到
看完這兩張圖之後來看[程式碼](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/dcache.h#L89)
```c=89
struct dentry {
/* RCU lookup touched fields */
/* 目錄項的標誌 */
unsigned int d_flags; /* protected by d_lock */
seqcount_spinlock_t d_seq; /* per dentry seqlock */
/* 雜湊表,這種雜湊表不用 spinlock 進行保護,而是使用最後一個位元作保護 */
struct hlist_bl_node d_hash; /* lookup hash list */
/* 上一個階級的 dentry */
struct dentry *d_parent; /* parent directory */
/* 目錄向的名稱 */
struct qstr d_name;
/* 指向的實體檔案 */
struct inode *d_inode; /* Where the name belongs to - NULL is* negative */
/* 存放較短目錄的名稱 */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
/* dentry 得函式指標 */
const struct dentry_operations *d_op;
/* dentry 所在的文件系統的 super_block */
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
/* dentry 的鏈結串列,child 是同一階級的、subdirs 是下一階的 */
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
} __randomize_layout;
```
若將各個結構圖畫的完整一點會長這樣:
![image](https://hackmd.io/_uploads/By2Xg2GKa.png)
可以注意到紫色那一條線,跟目錄的 `d_subdirs` 連接到的是下一階級的 `d_child`。因此在尋找目錄底下資料夾的時候,會去走訪這一個鏈結串列,直到回到親代目錄的 `d_subdirs` 時就會發現已經走訪完成,停止走訪。這一些操作在 linux 的程式碼中屢見不鮮。
接下來看一下 [`dentry_operations`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/dcache.h#L135) 的定義:
```c=135
struct dentry_operations
/* 用在網路系統當中 */
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
/* 用來計算 dentry 的雜湊值 */
int (*d_hash)(const struct dentry *, struct qstr *);
/* 比較 dentry 名稱 */
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *);
/* 引用數 < 1 的時候呼叫 d_delete 刪除 dentry */
int (*d_delete)(const struct dentry *);
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
/* 釋放 dentry 指向的 inode */
void (*d_iput)(struct dentry *, struct inode *);
/* 設置 dentry 的名稱 */
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *);
} ____cacheline_aligned;
```
這個結構在 `jffs2` 和 `ext2` 的檔案系統中都沒有用到。
## `struct inode`
前面的 `dentry` 保存了一些檔案的基本資料,例如:文件名稱等,`dentry` 主要的目的是建立檔案系統的架構與階級。其他檔案的具體細節則是由 `inode` 進行保存。
在 linux 中,任何東西都是檔案描述子(file descriptor)。而檔案描述子本人就是 `inode`。上一小節已經將檔案的入口 `dentry` 說明完畢,現在來看一下檔案本人 [`inode`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fs.h#L610) 的定義吧。
```c=610
struct inode {
/* 文件類型與存取權限 */
umode_t i_mode;
/* 行程打開文件的方式 */
unsigned short i_opflags;
/* user id 和 group id */
kuid_t i_uid;
kgid_t i_gid;
/* 文件的屬性 */
unsigned int i_flags;
/* acl 文件權限的結構 */
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
/* 節點的函式指標 */
const struct inode_operations *i_op;
/* 節點所屬的 super_block */
struct super_block *i_sb;
/* 節點映射到的地址空間 */
struct address_space *i_mapping;
...
/* Stat data, not accessed from path walking */
/* inode 編號,每一個號碼不重複 */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;
loff_t i_size;
/* inode 時間,a: access, m: modify */
struct timespec64 i_atime;
struct timespec64 i_mtime;
struct timespec64 i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
/* inode 的大小,以 byte 來計 */
unsigned short i_bytes;
u8 i_blkbits;
u8 i_write_hint;
/* inode 的大小,以 block 來計 */
blkcnt_t i_blocks;
...
/* Misc */
/* inode 狀態 */
unsigned long i_state;
/* inode 的讀寫鎖 */
struct rw_semaphore i_rwsem;
/* inode 的被標記為 dirty 的時間,以 jiffies 為單位(比較不精準) */
unsigned long dirtied_when; /* jiffies of first dirtying */
/* inode 的被標記為 dirty 的時間 */
unsigned long dirtied_time_when;
/* inode 和雜湊表, inode_hashtable 對應 */
struct hlist_node i_hash;
/* inode 和被用儲存裝置的鏈結串列 */
struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
struct bdi_writeback *i_wb; /* the associated cgroup wb */
/* foreign inode detection, see wbc_detach_inode() */
int i_wb_frn_winner;
u16 i_wb_frn_avg_time;
u16 i_wb_frn_history;
#endif
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
struct list_head i_wb_list; /* backing dev writeback list */
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
...
/* 文件使用的函式指標 */
union {
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
void (*free_inode)(struct inode *);
};
struct file_lock_context *i_flctx;
/* 一個具體的檔案在開啟後,
* 核心會在記憶體中為此建立一個struct inode結構
* 該inode結構也會在對應的file結構體中引用,
* 其中的i_mapping域指向一個address_space結構。
* 這樣,一個檔案就對應一個address_space結構,
* 一個 address_space 與一個偏移量能夠決定一個 page cache
* 或swap cache中的一個頁面。
* */
struct address_space i_data;
struct list_head i_devices;
/* 連接設備使用 */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};
__u32 i_generation;
...
void *i_private; /* fs or device private pointer */
} __randomize_layout;
```
接下來看一下 [`inode_operations`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fs.h#L1880)。`inode_operations` 的結構是每一個檔案系統必須要實作的,而且不同的檔案系統有可能會將一個結構分成兩個變數使用,例如 `ext2` 就將這個結構分為 `ext2_file_inode_operations` 和 `ext2_dir_inode_operations` 兩個不同變數。當然這兩個變數實作的方向也不同,等等也會一一介紹。
```c=1880
struct inode_operations {
/* 在特定目錄下找到 inode */
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
/* inode 的符號連結 */
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
/* 檢查文件存取權限 */
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int);
/* vfs 透過 open 和 create 來呼叫這個函式,對 dentry 創造一個節點 */
int (*create) (struct inode *,struct dentry *, umode_t, bool);
/* 創造 inode 的 hard link */
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
/* 創造 inode 的 symbolic link */
int (*symlink) (struct inode *,struct dentry *,const char *);
/* 創造目錄 */
int (*mkdir) (struct inode *,struct dentry *,umode_t);
/* 移除目錄 */
int (*rmdir) (struct inode *,struct dentry *);
/* 創造節點 */
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
/* 節點重新命名 */
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
...
} ____cacheline_aligned;
```
結合前面幾個結構圖會長下面這樣:
![image](https://hackmd.io/_uploads/BypGPpMY6.png)
> 為了圖片美觀,有一些線必須省略
我們看一下 `ext2` 和 `jffs2` 裡面的實作,先看 [`ext2`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/ext2/namei.c#L413)的
```c=413
const struct inode_operations ext2_dir_inode_operations = {
.create = ext2_create,
.lookup = ext2_lookup,
.link = ext2_link,
.unlink = ext2_unlink,
.symlink = ext2_symlink,
.mkdir = ext2_mkdir,
.rmdir = ext2_rmdir,
...
};
const struct inode_operations ext2_special_inode_operations = {
.listxattr = ext2_listxattr,
.getattr = ext2_getattr,
.setattr = ext2_setattr,
.get_acl = ext2_get_acl,
.set_acl = ext2_set_acl,
};
```
可以發現會把一些專門設置屬性的函式指標額外的抓出來。其實 [`jffs2`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/jffs2/file.c#L65) 也是如此
```c=51
const struct inode_operations jffs2_dir_inode_operations =
{
.create = jffs2_create,
.lookup = jffs2_lookup,
.link = jffs2_link,
.unlink = jffs2_unlink,
.symlink = jffs2_symlink,
...
};
// in another file
const struct inode_operations jffs2_file_inode_operations =
{
.get_acl = jffs2_get_acl,
.set_acl = jffs2_set_acl,
.setattr = jffs2_setattr,
.listxattr = jffs2_listxattr,
};
```
## `struct file`
在看 `struct file` 之前,需要先看一下一個[行程](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/sched.h#L644)對於檔案系統的關係是什麼
```c=644
struct task_struct {
...
/* 行程目錄的訊息 */
struct fs_struct *fs;
/* 行程打開文件的訊息 */
struct files_struct *files;
...
};
```
進一步看 [`struct fs_struct`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fs_struct.h#L9) 的定義
```c=9
struct fs_struct {
int users;
spinlock_t lock;
seqcount_spinlock_t seq;
/* 存取檔案的遮罩 */
int umask;
/* 用來標示現在是不是加載器要打開檔案 */
int in_exec;
/* 表是形成現在的位置和根目錄的位置 */
struct path root, pwd;
} __randomize_layout;
```
預習一下 [`struct path`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/path.h#L8) 的結構
```c=8
struct path {
/* 這個路徑的檔案系統掛載結構,後面再說 */
struct vfsmount *mnt;
/* 這個位置的目錄項 */
struct dentry *dentry;
} __randomize_layout;
```
當然我們可以把圖畫出來了解關係
![image](https://hackmd.io/_uploads/HyOBXrrtp.png)
接下來進一步看 [`struct files_struct`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/fdtable.h#L49)
```c=49
struct files_struct {
/*
* read mostly part
*/
/* 這個物件被使用的次數 */
atomic_t count;
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt;
/* 內含一個 fdtable */
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
/* 下一個檔案描述子的編號,初始值為 0 */
unsigned int next_fd;
/* 執行 execve() 系統調用時關閉這個 bitmap */
unsigned long close_on_exec_init[1];
/* 打開的檔案描述子 bitmap */
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
```
其中,有兩個結構也是比較重要的 [`fdtable`](https://elixir.bootlin.com/linux/v5.10/source/include/linux/fdtable.h#L27) 和 [`file`]()。`fdtable` 是用來決定你的檔案描述子 (file descriptor) 的編號,每一個行程給的檔案描述子是不一樣的,因此需要去查表,找到檔案的真實編號才可以開啟檔案。
```c=27
struct fdtable {
/* 打開文件的最大數量,bitmap 決定 */
unsigned int max_fds;
/* 檔案描述子的指標陣列 */
struct file __rcu **fd; /* current fd array */
/* 指向 execve 使用的關閉文件的 bitmap */
unsigned long *close_on_exec;
/* 行程打開文件的 bitmap */
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
```
將結構圖畫出來就可以知道關係了:
![image](https://hackmd.io/_uploads/rkbROrrKT.png)
```c=916
struct file {
union {
/* 單向鏈結串列 */
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
/* 檔案路徑 */
struct path f_path;
/* 檔案指向 inode */
struct inode *f_inode; /* cached value */
/* 函式指標 */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
/* open() 系統使用的 flag */
unsigned int f_flags;
/* 以何種模式打開檔案 */
fmode_t f_mode;
struct mutex f_pos_lock;
/* 檔案當前讀寫的位置 */
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
/* 檔案的預讀結構 */
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
errseq_t f_sb_err; /* for syncfs */
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
```
## `struct mount`
[`struct mount`](https://elixir.bootlin.com/linux/v5.10/source/fs/mount.h#L40) 代表一次的掛載動作
```c=40
struct mount
/* hash table 成員 */
struct hlist_node mnt_hash;
/* 親代 mount */
struct mount *mnt_parent;
/* mount 的目錄項 (dentry 入口) */
struct dentry *mnt_mountpoint;
/* vfsmount 結構體,表示在 vfs 中掛載的資訊 */
struct vfsmount mnt;
...
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
/* 這個會和 super_block 對接 */
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
/* mount 的設備名稱 */
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
/* 將實體 mount 掛到鏈結串列中 */
struct list_head mnt_list;
/* 將實體 mount 掛到鏈結串列中 */
struct list_head mnt_expire; /* link in fs-specific expiry list */
struct list_head mnt_share; /* circular list of shared mounts */
struct list_head mnt_slave_list;/* list of slave mounts */
struct list_head mnt_slave; /* slave list entry */
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
union {
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
struct hlist_node mnt_umount;
};
struct list_head mnt_umounting; /* list entry for umount propagation */
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
...
} __randomize_layout;
```
這裡我們看一下 `mount` 和其他結構體的關係是什麼:
```c=71
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
} __randomize_layout;
```
```c=33
// 掛載點的結構
struct mountpoint {
// 雜湊表的鏈結串列
struct hlist_node m_hash;
// 指向掛載點的 directory entry (dentry)
struct dentry *m_dentry;
// 鏈結 mount 的 instance,鏈結串列的頭
struct hlist_head m_list;
// 掛載的次數
int m_count;
};
```
目前為止,已經把大部分檔案系統會用到的結構看過一遍了。後面會把前面沒有的細節補上。
# `mtd` 子系統
`mtd` 子系統用來直接存取 "raw flash",例如:`rom`,`nor flash` 和 `nand flash` 等等。其他像是記憶卡、`MMC` 等等的儲存裝置都含有 "flash translation layer"。雖然他們裡面還是有一顆 "flash",但是他們算是 block device。
其實 `mtd` 的邏輯與其他驅動的子系統一樣,就是統一函數接口,底下自己實作對應的函式,之後將他掛上 linux 提供的函式指標。
其實如果將整個架構圖在畫的清楚一些會是下面這個樣子:
![image](https://hackmd.io/_uploads/HJCUiAVFp.png)
由上圖可以發現,每一個檔案系統都會對應到不同的儲存裝置。因為一些儲存裝置的特性導致開發者特別開發與設計檔案系統,使得儲存裝置的壽命增加。
像是 `jffs2` 就是發現在那之前的 `nand flash` 都在使用驅動程式「模擬」一些 `ftl` 裝置的行為。但是那些 `ftl` 裝置他們有硬體控制器,硬體會偵測儲存區塊是不是正常的,但是 `nand flash` 不會,因此他將 `nand flash` 抽象成 `mtd device` 並且將寫的動作集中在一個區塊,並且會將 `flash` 的區塊使用分散化以增加硬體的壽命。
在上面的延伸閱讀有提到一些節點的號碼。`mtd char` 的設備號碼是 `90`,而 `mtd block` 的設備號碼是 `31`。接下來讓我們看一下 `mtd` 提供的 API。
```c=492
int mtd_point(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
void **virt, resource_size_t *phys);
int mtd_unpoint(struct mtd_info *mtd, loff_t from, size_t len);
unsigned long mtd_get_unmapped_area(struct mtd_info *mtd, unsigned long len,
unsigned long offset, unsigned long flags);
int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf);
int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf);
int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf);
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops);
int mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops);
```
這些 `API` 對應的是 [`mtd_info`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/mtd/mtd.h#L235) 結構裡面的函式指標。在 `mtd` 子系統中,主要有三個結構:`struct mtd_partition`, [`struct mtd_part`](https://elixir.bootlin.com/linux/v5.10.181/source/include/linux/mtd/mtd.h#L211) 和 `struct mtd_info` 這三個。若是我們的 linux 不支援設備樹 (device tree)則我們會需要 `mtd_partition` 這個結構指定 `flash` 分區。
```c=235
struct mtd_info {
u_char type;
uint32_t flags;
uint64_t size; // Total size of the MTD
/* "Major" erase size for the device. Naïve users may take this
* to be the only erase size available, or may use the more detailed
* information below if they desire
*/
uint32_t erasesize;
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
* it is of ECC block size, etc. It is illegal to have writesize = 0.
* Any driver registering a struct mtd_info must ensure a writesize of
* 1 or larger.
*/
uint32_t writesize;
/*
* Size of the write buffer used by the MTD. MTD devices having a write
* buffer can write multiple writesize chunks at a time. E.g. while
* writing 4 * writesize bytes to a device with 2 * writesize bytes
* buffer the MTD driver can (but doesn't have to) do 2 writesize
* operations, but not 4. Currently, all NANDs have writebufsize
* equivalent to writesize (NAND page size). Some NOR flashes do have
* writebufsize greater than writesize.
*/
uint32_t writebufsize;
uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
uint32_t oobavail; // Available OOB bytes per block
/*
* If erasesize is a power of 2 then the shift is stored in
* erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
*/
unsigned int erasesize_shift;
unsigned int writesize_shift;
/* Masks based on erasesize_shift and writesize_shift */
unsigned int erasesize_mask;
unsigned int writesize_mask;
/*
* read ops return -EUCLEAN if max number of bitflips corrected on any
* one region comprising an ecc step equals or exceeds this value.
* Settable by driver, else defaults to ecc_strength. User can override
* in sysfs. N.B. The meaning of the -EUCLEAN return code has changed;
* see Documentation/ABI/testing/sysfs-class-mtd for more detail.
*/
unsigned int bitflip_threshold;
/* Kernel-only stuff starts here. */
const char *name;
int index;
/* OOB layout description */
const struct mtd_ooblayout_ops *ooblayout;
/* NAND pairing scheme, only provided for MLC/TLC NANDs */
const struct mtd_pairing_scheme *pairing;
/* the ecc step size. */
unsigned int ecc_step_size;
/* max number of correctible bit errors per ecc step */
unsigned int ecc_strength;
/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*/
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
/*
* Do not call via these pointers, use corresponding mtd_*()
* wrappers instead.
*/
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
int (*_read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
int (*_get_fact_prot_info) (struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf);
int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_get_user_prot_info) (struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf);
int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
size_t len, size_t *retlen, u_char *buf);
int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len);
int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen);
void (*_sync) (struct mtd_info *mtd);
int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_block_isreserved) (struct mtd_info *mtd, loff_t ofs);
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
int (*_max_bad_blocks) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*_suspend) (struct mtd_info *mtd);
void (*_resume) (struct mtd_info *mtd);
void (*_reboot) (struct mtd_info *mtd);
/*
* If the driver is something smart, like UBI, it may need to maintain
* its own reference counting. The below functions are only for driver.
*/
int (*_get_device) (struct mtd_info *mtd);
void (*_put_device) (struct mtd_info *mtd);
/*
* flag indicates a panic write, low level drivers can take appropriate
* action if required to ensure writes go through
*/
bool oops_panic_write;
struct notifier_block reboot_notifier; /* default mode before reboot */
/* ECC status information */
struct mtd_ecc_stats ecc_stats;
/* Subpage shift (NAND) */
int subpage_sft;
void *priv;
struct module *owner;
struct device dev;
int usecount;
struct mtd_debug_info dbg;
struct nvmem_device *nvmem;
/*
* Parent device from the MTD partition point of view.
*
* MTD masters do not have any parent, MTD partitions do. The parent
* MTD device can itself be a partition.
*/
struct mtd_info *parent;
/* List of partitions attached to this MTD device */
struct list_head partitions;
struct mtd_part part;
struct mtd_master master;
};
```
`nand flash` 是怎麼掛上 `mtd` 子系統的呢?其實很簡單,可以觀察 `nand_scan_tail` 這隻函式就知道了
# `jffs2` 論文閱讀
> 內容來自 `redhat` 公司提供的投影片與論文
# 檔案系統的掛載
- 如果將磁碟分區 sda1 的檔案系統與根目錄的檔案系統進行連結,稱為掛載 (mount),以圖片上為例,掛載點是 `/mnt`
- 根目錄的檔案系統可以同時掛載很多不同的檔案系統,此外每一個檔案系統都可以在掛載其他檔案系統,也因此檔案系統的掛載是一個非常複雜的動作。
接下來還要看 `struct mount` 的結構體
```c
struct mountpoint {
struct hlist_node m_hash;
struct dentry *m_dentry;
struct hlist_head m_list;
int m_count;
};
```
當你在 `linux` 中下命令讓 `jffs2` 掛載到 `mtdblock8` 上時
```shell
$ mount -t jffs2 /dev/mtdblock8 /tmp/surprise
```
`Linux` 的[系統呼叫](https://elixir.bootlin.com/linux/v5.10.181/source/fs/namespace.c#L3427)中會呼叫 [`do_mount`](https://elixir.bootlin.com/linux/v5.10.181/source/fs/namespace.c#L3233) 的函式,之後開始做真正的掛載
#### syscall
```c=
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
...
kernel_type = copy_mount_string(type);
kernel_dev = copy_mount_string(dev_name);
options = copy_mount_options(data);
ret = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
// error handling, ignore
return ret;
}
```
# NAND flash
> 本節改寫自美光的技術資料 `TN-29-19 Nand flash introduction`
### Reference
- [JFFS : The Journalling Flash File System](http://linux-mtd.infradead.org/~dwmw2/jffs2.pdf)
- [The Journalling Flash File System](https://sourceware.org/jffs2/jffs2-slides-transformed.pdf)
- [UBIFS file system](http://www.linux-mtd.infradead.org/doc/ubifs.pdf)
- [圖解虛擬檔案系統的結構](https://blog.csdn.net/u012489236/article/details/124239389)
- [TN-29-19 Nand flash introduction, micron](https://user.eng.umd.edu/~blj/CS-590.26/micron-tn2919.pdf)
- [TN-29-75 Enable on-die nand flash with jffs2](https://www.micron.com/-/media/client/global/documents/products/technical-note/nand-flash/tn2975_enable_on-die-ecc_nand_jffs2.pdf)
- [Linux MTD架構下的nand flash驅動詳解](https://blog.csdn.net/Golden_Chen/article/details/89472163)
{"title":"Linux 檔案系統","description":"Outline:","contributors":"[{\"id\":\"03f294c9-4f9f-49fa-a014-87a93ce0b071\",\"add\":63413,\"del\":15690}]"}