跳至內容

儲存功能

來自華夏公益教科書,開放世界的開放書籍


儲存
檔案和目錄訪問
虛擬檔案系統
頁面快取
邏輯檔案系統
塊裝置
儲存驅動程式

儲存功能透過檔案和目錄提供對各種儲存裝置的訪問。大多數儲存都是永續性的,例如快閃記憶體、SSD 和傳統硬碟。另一種儲存是臨時的。檔案系統提供了一種抽象來將資訊組織成由唯一名稱標識的獨立資料塊(稱為檔案)。每個檔案系統型別定義自己的結構和邏輯規則,用於管理這些資訊組及其名稱。Linux 支援大量不同的檔案系統型別,包括本地和遠端、本地和來自其他作業系統的檔案系統。為了適應這種差異,核心定義了一個通用的頂層,即虛擬檔案系統(VFS)層。


Summary of the Linux kernel's storage stack
Linux 核心儲存堆疊的總結

檔案和目錄

[編輯 | 編輯原始碼]

四種基本檔案訪問系統呼叫

man 2 opendo_sys_open id - 按名稱開啟檔案並返回一個檔案描述符fd)。以下函式對 fd 進行操作。
man 2 closeclose_fd id
man 2 readksys_read id
man 2 writeksys_write id

Linux 和 UNIX 中的檔案不僅僅是持久儲存上的物理檔案。檔案介面用於訪問管道、套接字和其他偽檔案。

🔧待辦事項

man 2 readlink , man 2 symlink , man 2 link
man 3 readdirman 2 getdents
man 7 path_resolution
man 2 fcntl – 操作檔案描述符


⚙️ 檔案和目錄內部結構

linux/fs.hinc
fs/open.csrc
fs/namei.csrc
fs/read_write.csrc


📚 檔案和目錄參考

輸入/輸出,GNU C 庫
Linux 核心 2.4 內部結構中的 VFS
Unix 檔案型別


檔案鎖

[編輯 | 編輯原始碼]

檔案鎖是允許程序協調對共享檔案的訪問的機制。這些鎖有助於防止多個程序或執行緒同時嘗試訪問同一個檔案時發生衝突。

💾歷史:強制鎖定功能在 Linux 5.15 及更高版本中不再受支援,因為該實現不可靠。

⚲ API

man 8 lslocks – 列出本地系統鎖
man 3 lockf – 對開啟的檔案應用、測試或刪除 POSIX 鎖
man 2 flock – 對開啟的檔案應用或刪除 BSD 建議鎖
man 2 fcntl – 操作檔案描述符
F_SETLK id – 建議記錄鎖
F_OFD_SETLK id – 開啟檔案描述符鎖
flock id – 鎖引數


⚙️ 內部結構

linux/filelock.hinc
fs/locks.csrc
trace/events/filelock.hinc

非同步 I/O

[編輯 | 編輯原始碼]

🚀高階功能

AIO

https://lwn.net/Kernel/Index/#Asynchronous_IO
man 2 io_submit man 2 io_setup man 2 io_cancel man 2 io_destroy man 2 io_getevents
uapi/linux/aio_abi.hinc
fs/aio.csrc
io/aio ltp


io_uring

🌱自 2019 年 5 月釋出 5.1 版本以來的新功能


https://blogs.oracle.com/linux/an-introduction-to-the-io_uring-asynchronous-io-framework
https://thenewstack.io/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/
io_uring_enter id io_uring_setup id io_uring_register id
linux/io_uring.hinc
uapi/linux/io_uring.hinc
fs/.csrc
https://lwn.net/Kernel/Index/#io_uring
io_uring、SCM_RIGHTS 和引用計數迴圈
io_uring 的快速發展
io_uring 的自動緩衝區選擇
io_uring 的操作限制
io_uring、SCM_RIGHTS 和引用計數迴圈
io_uring 的重新設計的 workqueues
io_uring ltp

允許對多個檔案描述符進行非阻塞訪問。

高效事件輪詢epoll


⚲ API

uapi/linux/eventpoll.hinc
man 7 epoll
man 2 epoll_createdo_epoll_create id
man 2 epoll_ctldo_epoll_ctl id
man 2 epoll_waitdo_epoll_wait id


⚙️ 內部結構

fs/eventpoll.csrc


selectpoll

💾 歷史:select 和 poll 系統呼叫源於 UNIX


⚲ API

man 2 polldo_sys_poll id
man 2 selectkern_select id


⚙️ 內部結構

fs/select.csrc

向量 I/O

[編輯 | 編輯原始碼]

🚀 高階特性

向量 I/O,也稱為分散/收集 I/O,是一種輸入輸出方法,透過該方法,單個過程呼叫可以按順序從多個緩衝區讀取資料並將其寫入單個數據流,或者從資料流讀取資料並將其寫入多個緩衝區,如緩衝區向量中定義的那樣。分散/收集指的是從給定緩衝區集中收集資料或將資料分散到給定緩衝區集中的過程。向量 I/O 可以同步或非同步執行。使用向量 I/O 的主要原因是效率和便捷性。


⚲ API

uapi/linux/uio.hinc
linux/uio.hinc
iovecid
man 2 readvdo_readv id
man 2 writevdo_writev id


⚙️ 內部結構

iov_iterid
do_readv id ↯ 呼叫層次結構
vfs_readvid
import_iovecid
ext4_file_read_iterid
lib/iov_iter.csrc


📚 參考資料

快速分散/收集 I/O,GNU C 庫
https://lwn.net/Kernel/Index/#Vectored_IO
https://lwn.net/Kernel/Index/#Scattergather_chaining

虛擬檔案系統

[編輯 | 編輯原始碼]

The 虛擬檔案系統 (VFS) 是一個抽象層,位於具體的邏輯檔案系統之上。VFS 的目的是允許客戶端應用程式以統一的方式訪問不同型別的邏輯檔案系統。例如,VFS 可以用來透明地訪問本地和 網路儲存 裝置,而客戶端應用程式不會察覺到差異。它可以用來彌合 Windows、經典 Mac OS/macOS 和 Unix 檔案系統之間的差異,以便應用程式可以訪問這些型別本地檔案系統上的檔案,而無需知道它們正在訪問哪種型別的檔案系統。VFS 指定了核心和邏輯檔案系統之間的介面(或“契約”。因此,只需滿足契約,就可以輕鬆地為核心新增對新檔案系統型別的支援。

🔧 TODO: vfsmount id, vfs_create id, vfs_read id, vfs_write id

📚 VFS 參考資料

VFS doc
Linux 核心 2.4 內部結構中的 VFS


邏輯檔案系統

[編輯 | 編輯原始碼]

一個 檔案系統(或檔案系統)用於控制資料的儲存和檢索方式。如果沒有檔案系統,儲存區域中的資訊將是一個很大的資料主體,沒有辦法區分一個資訊片段的結束和下一個資訊片段的開始。透過將資料分成單獨的片段,並給每個片段一個名稱,可以輕鬆地分離和識別資訊。每個資料組被稱為“檔案”。用來管理資訊組及其名稱的結構和邏輯規則被稱為“檔案系統”。

有很多不同型別的檔案系統。每個檔案系統都有不同的結構和邏輯,以及速度、靈活度、安全性、大小等方面的特性。有些檔案系統是為特定應用程式而設計的。例如,ISO 9660 檔案系統專門為光碟而設計。

檔案系統可以在許多不同型別的儲存裝置上使用。每個儲存裝置都使用不同型別的介質。如今最常用的儲存裝置是 SSD。曾經使用過的其他介質包括硬碟、磁帶、光碟和 。在某些情況下,計算機的主記憶體(RAM)用於建立短期使用的臨時檔案系統。原始儲存被稱為塊裝置。

Linux 支援許多不同的檔案系統,但塊裝置上系統磁碟的常用選擇包括 ext* 系列(如 ext2ext3ext4)、XFSReiserFSbtrfs。對於沒有 快閃記憶體轉換層 (FTL) 或 記憶體技術裝置 (MTD) 的原始快閃記憶體,有 UBIFSJFFS2YAFFS 等等。 SquashFS 是一種常見的壓縮只讀檔案系統。NFS 和其他網路 FS 在段落 網路儲存 中有更詳細的描述。


⚲ Shell 介面

cat /proc/filesystems
ls /sys/fs/
man 8 mount
man 8 umount
man 8 findmnt
man 1 mountpoint
man 1 df


基礎設施 ⚲ API 函式 register_filesystem id 註冊結構體 file_system_type id 並將它們儲存在連結列表中 ⚙️ file_systems id。函式 ext4_init_fs id 註冊 ext4_fs_type id檔案系統開啟的操作被稱為掛載:ext4_mount id


⚙️ 內部結構

fs/namespace.csrc
man 2 mount
do_mountid
linux/buffer_head.hinc
super_blockid
sb_breadid
fssrc
fs/ext4/ext4.hsrc
ext4_sb_breadid


📚 參考資料

filesystems doc
核心維基:EXT4btrfsReiser4RAIDXFS

頁快取

[編輯 | 編輯原始碼]

頁快取或磁碟快取是來自輔助儲存裝置(如硬碟驅動器)的記憶體頁的透明快取。作業系統在主記憶體的未使用部分中保留頁快取,從而可以更快地訪問快取頁的內容並提高整體效能。頁快取由核心實現,對應用程式來說大多是透明的。

通常,所有未直接分配給應用程式的物理記憶體都被作業系統用於頁面快取。由於記憶體否則將處於閒置狀態,並且在應用程式請求時可以輕鬆回收,因此通常沒有相關的效能損失,作業系統甚至可能會將此類記憶體報告為“空閒”或“可用”。頁面快取還有助於寫入磁碟。在將資料寫入磁碟期間已修改的主記憶體中的頁面被標記為“髒”,並且必須在它們可以被釋放之前重新整理到磁碟。當發生檔案寫入時,會查詢支援特定塊的頁面。如果它已經在頁面快取中找到,則寫入將完成到主記憶體中的該頁面。否則,如果寫入完全落在頁面大小邊界上,則該頁面甚至不會從磁碟讀取,而是被分配並立即標記為髒。否則,將從磁盤獲取該頁面(或這些頁面),並進行請求的修改。

並非所有快取的頁面都可以寫入,因為程式程式碼通常被對映為只讀或寫時複製;在後一種情況下,對程式碼的修改僅對程序本身可見,不會寫入磁碟。


⚲ API

man 2 fsyncdo_fsync id
man 2 sync_file_rangeksys_sync_file_range id
man 2 syncfssync_filesystem id

📚 參考資料

wb_workfnid
address_spaceid
do_writepagesid
linux/writeback.hinc
mm/page-writeback.csrc
頁快取


更多

DAX 的未來 - 直接訪問繞過快取
Linux 核心 2.4 內部機制中的 Linux 頁面快取

零複製

[編輯 | 編輯原始碼]

🚀高階功能

將資料寫入儲存和讀取是非常佔用資源的操作。複製記憶體也是一項耗時且佔用 CPU 的操作。一組避免複製操作的方法被稱為零複製。零複製方法的目標是在系統內進行快速高效的資料傳輸。

第一個也是最簡單的方法是管道,由 shell 中的運算子 "|" 呼叫。而不是將資料寫入臨時檔案並讀取,而是透過管道有效地傳遞資料,繞過儲存。第二種方法是tee


⚲ 系統呼叫

man 2 pipe2
man 2 tee, man 1 tee
man 2 sendfile
man 2 copy_file_range
man 2 splice
man 2 vmsplice


⚲ API 和 ⚙️ 內部機制

man 2 pipe2do_pipe2 id - 建立管道
使用 pipe_fs_type id, pipefifo_fops id
man 2 teedo_tee id- 複製管道內容
呼叫 link_pipe id
man 2 sendfiledo_sendfile id - 在檔案描述符之間傳輸資料,輸出可以是套接字。用於網路儲存 和伺服器。
呼叫:do_splice_direct id, splice_direct_to_actor id
man 2 copy_file_rangevfs_copy_file_range id - 在檔案之間傳輸資料
呼叫自定義 remap_file_range idnfs42_remap_file_range id
或自定義 copy_file_range idfuse_copy_file_range id
do_splice_direct id
man 2 splicedo_splice id - 將資料拼接進/出管道。
關於哪一端是管道,有三種情況
  1. do_splice_from id - 只有輸入是管道
    呼叫 iter_file_splice_write id 或自定義 splice_write id
    default_file_splice_write id: write_pipe_buf id, splice_from_pipe id, __splice_from_pipe id
  2. do_splice_to id - 只有輸出是管道。
    呼叫 generic_file_splice_read id 或自定義 splice_read id
    default_file_splice_read id: kernel_readv id
  3. splice_pipe_to_pipe id - 兩者都是管道
man 2 vmsplice
vmsplice_to_pipe id – 將使用者頁面拼接進管道
vmsplice_to_user id – 將管道拼接進使用者頁面


⚲ API

linux/splice.hinc


⚙️ 內部結構

fs/pipe.csrc
fs/splice.csrc


🔧 TODO: zerocopy_sg_from_iter id 從 iov_iter 構建零複製 skb 資料報。用於 tap_get_user idtun_get_user id.

skb_zerocopyid

skb_zerocopy_iter_dgramid


📚 參考資料

man 7 pipe
man 7 fifo
splice 和管道 doc
管道 API doc
splice(系統呼叫)
LTP: pipe ltp, pipe2 ltp, tee ltp, sendfile ltp, copy_file_range ltp, splice ltp, vmsplice ltp

塊裝置層

[編輯 | 編輯原始碼]

Linux 儲存基於塊裝置。

塊裝置提供對硬體的緩衝訪問,始終允許讀取或寫入任何大小的塊(包括單個字元/位元組),並且不受對齊限制的影響。它們通常用於表示諸如硬碟之類的硬體。


⚲ 介面

linux/genhd.hinc
linux/blk_types.hinc
bio id – 塊層和下層的主要 I/O 單位
linux/bio.hinc
block_deviceid
alloc_disk_node id 分配 gendisk id
add_diskid
device_add_diskid
block_device_operationsid
register_blkdevid
bio id - 塊層和下層(即驅動程式和堆疊驅動程式)的主要 I/O 單位
requestid
request_queueid


⚙️ 內部結構。

blocksrc
block_classid


👁 示例

drivers/block/brd.c src - 小型 RAM 支援的塊裝置驅動程式
drivers/block/null_blksrc


裝置對映器

[編輯 | 編輯原始碼]

裝置對映器是核心提供的框架,用於將物理塊裝置對映到更高級別的“虛擬塊裝置”。它構成了 LVM2、軟體 RAID 和 dm-crypt 磁碟加密的基礎,並提供額外的功能,例如檔案系統快照。

裝置對映器透過將資料從虛擬塊裝置(由裝置對映器本身提供)傳遞到另一個塊裝置來工作。資料也可以在傳輸過程中進行修改,例如,在裝置對映器提供磁碟加密的情況下進行修改。

需要建立新對映裝置的使用者空間應用程式透過 libdevmapper.so 共享庫與裝置對映器通訊,該庫反過來向 /dev/mapper/control 裝置節點發出 ioctl。

裝置對映器提供的功能包括線性、條帶化和錯誤對映,以及加密和多路徑目標。例如,兩個磁碟可以透過一對線性對映(每個磁碟一個)連線到一個邏輯卷中。另一個例子是,crypt 目標使用 Linux 核心的加密 API 對透過指定裝置的資料進行加密。

以下對映目標可用

cache - 允許透過使用固態驅動器 (SSD) 作為硬碟驅動器 (HDD) 的快取來建立混合卷
crypt - 使用 Linux 核心的加密 API 提供資料加密
delay - 將讀取和/或寫入到不同的裝置延遲(用於測試)
era - 以類似於線性目標的方式執行,同時跟蹤在使用者定義的時間段內寫入的塊
error - 模擬所有對映塊的 I/O 錯誤(用於測試)
flakey - 模擬週期性的不可靠行為(用於測試)
linear - 將連續的塊範圍對映到另一個塊裝置
mirror - 對映映象邏輯裝置,同時提供資料冗餘
multipath - 透過使用其路徑組支援多路徑裝置的對映
raid - 提供與 Linux 核心的軟體 RAID 驅動程式 (md) 的介面
snapshotsnapshot-origin - 用於建立 LVM 快照,作為基礎複製寫入方案的一部分
striped - 將資料跨物理裝置條帶化,以條帶數量和條帶化塊大小作為引數
zero - 等效於 /dev/zero,所有讀取都返回零塊,寫入被丟棄

📚 參考資料

裝置對映器
裝置對映器 doc
linux/device-mapper.hinc
drivers/mdsrc
https://lwn.net/Kernel/Index/#Device_mapper

多佇列塊 I/O 排隊

[編輯 | 編輯原始碼]

blk-mq API 透過利用多個佇列進行並行處理來提高 IO 效能,從而解決了傳統單佇列設計中的瓶頸。它使用軟體佇列來排程、合併和重新排序請求,並使用硬體佇列與裝置直接介面。如果硬體資源有限,請求將暫時排隊以供以後排程。

⚲ 介面

linux/blk-mq.hinc
blk_mq_hw_ctx id – 硬體排程佇列


⚙️ 內部結構

block/blk-mq.hsrc
blk_mq_ctx id – 軟體暫存佇列
block/blk-mq.csrc
...


📖 參考資料

多佇列塊 I/O 排隊機制 (blk-mq) doc

I/O 排程程式

[編輯 | 編輯原始碼]

I/O 排程(或磁碟排程)是核心選擇的方法,用於決定以何種順序將塊 I/O 操作提交到儲存卷。I/O 排程通常需要處理硬碟驅動器,這些驅動器對放置在遠離磁碟磁頭當前位置的請求(此操作稱為查詢)的訪問時間很長。為了最大限度地減少對系統性能的影響,大多數 I/O 排程程式實現了電梯演算法的變體,該演算法重新排序傳入的隨機排序請求,以便以最小的機械臂/磁頭移動來訪問相關資料。

可以使用特定塊裝置的 sysfs 檔案系統中的 /sys/block/<block_device>/queue/scheduler 檔案在執行時切換特定塊裝置使用的 I/O 排程程式。某些 I/O 排程程式還具有可調引數,可以透過 /sys/block/<block_device>/queue/iosched/ 中的檔案進行設定。


⚲ 介面

linux/elevator.hinc
函式 elv_register id 註冊結構體 elevator_type id.
elevator_queueid


⚙️ 內部結構

block/elevator.csrc
block/Kconfig.ioschedsrc
block/bfq-iosched.csrc
block/kyber-iosched.csrc
block/mq-deadline.csrc
include/trace/events/block.hinc


📖 參考資料

I/O 排程
電梯演算法
切換排程程式 doc
BFQ - 預算公平排隊 doc
截止日期 IO 排程程式可調引數 doc
https://www.cloudbees.com/blog/linux-io-scheduler-tuning/
https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers

📖 參考資料

塊裝置 doc
切換排程程式 doc
BFQ - 預算公平排隊 doc
截止日期 IO 排程程式可調引數 doc
Kyber I/O 排程程式可調引數 doc
多佇列塊 I/O 排隊機制 (blk-mq) doc


📚 進一步閱讀

https://lwn.net/Kernel/Index/#Block_layer
塊裝置 ML
LDD3:塊驅動程式
LDD1:載入塊驅動程式
ULK3 第 14 章。塊裝置驅動程式

儲存 驅動程式

[編輯 | 編輯原始碼]

🔧待辦事項


⚙️ 內部結構

drivers/scsi src - 小型計算機系統介面
drivers/virtiosrc
drivers/sdio src - 安全數字輸入輸出
drivers/nvmem src - 非易失性儲存裝置,如 EEPROMEfuse
drivers/mtd src - 用於🤖嵌入式裝置的記憶體技術裝置

NVM Express 驅動程式提供了對計算機 非易失性儲存器 的訪問。本地儲存透過 PCI Express 匯流排連線。PCI NVMe 裝置驅動程式的入口點是 nvme_init id。遠端儲存驅動程式稱為目標,本地 代理 驅動程式稱為主機。 結構 連線遠端目標和本地主機。結構可以基於 RDMATCP光纖通道 協議。


⚲ API

nvme-cli
uapi/linux/nvme_ioctl.hinc
linux/nvme.hinc


⚙️ 內部實現:

drivers/nvmesrc

主機 drivers/nvme/host src

⚲ 介面

drivers/nvme/host/nvme.hsrc
nvme_init_ctrl id 初始化 NVMe 控制器結構 nvme_ctrl id,包含操作 nvme_ctrl_ops id
nvme_scan_work id 的一個子程式使用 device_add_disk id 新增一個新的磁碟。


nvme_init id - 本地 PCI nvme 模組初始化。
nvme_probeid
nvme_init_ctrlid ...
nvme_pci_ctrl_opsid
nvme_core_init id - 模組初始化。


結構

⚲ 介面

drivers/nvme/host/fabrics.hsrc
nvmf_register_transport id 註冊 nvmf_transport_ops id
nvmf_init id - 結構模組初始化。

⚙️ 內部實現

nvmf_init id - 結構模組初始化。
nvmf_miscid
nvmf_dev_fopsid
nvmf_dev_writeid
nvmf_create_ctrl id 繫結 nvmf_transport_ops id


目標 drivers/nvme/target src

⚲ 介面: drivers/nvme/target/nvmet.h src

nvmet_register_transport id 註冊 nvmet_fabrics_ops id
nvmet_init id - 模組初始化。
fcloop_init id - 環回測試模組初始化,可用於測試 NVMe-FC 傳輸介面。


基於結構的 NVMe
TCP RDMA 光纖通道
主機模組
nvme_tcp_init_moduleid nvme_rdma_init_moduleid nvme_fc_init_moduleid
結構協議
linux/nvme-tcp.hinc linux/nvme-rdma.hinc linux/nvme-fc.hinc

linux/nvme-fc-driver.hinc

目標模組
nvmet_tcp_initid nvmet_rdma_initid nvmet_fc_init_moduleid


👁 例如: nvme_loop_init_module id nvme 環回

nvme_loop_transport id - 結構操作
nvme_loop_create_ctrlid
nvme_loop_create_io_queuesid
nvme_loop_ops id - 目標操作
nvme_loop_add_portid
nvme_loop_queue_responseid

附錄

[edit | edit source]

🚀 高階

man 1 pidstat – 報告任務統計資訊。
/proc/self/io – 程序的 I/O 統計資訊(參見 man 5 proc)。


💾 歷史儲存驅動程式

drivers/ata src - 並行 ATA


📖 關於儲存的更多閱讀

bcc/ebpf 儲存和檔案系統工具
華夏公益教科書