轉(zhuǎn)載于淺談Linux內(nèi)核IO體系之磁盤IO
前言
Linux I/O體系是Linux內(nèi)核的重要組成部分,主要包含網(wǎng)絡(luò)IO、磁盤IO等。基本所有的技術(shù)棧都需要與IO打交道,分布式存儲系統(tǒng)更是如此。本文主要簡單分析一下磁盤IO,看看一個IO請求從發(fā)起到完成到底經(jīng)歷了哪些流程。
目錄
- 名詞解釋
- IO體系
- VFS層
- PageCache層
- 映射層
- 通用塊層
- IO調(diào)度層
- 設(shè)備驅(qū)動層
- 物理設(shè)備層
- FAQ
名詞解釋
-
Buffered I/O:緩存IO又叫標(biāo)準(zhǔn)IO,是大多數(shù)文件系統(tǒng)的默認(rèn)IO操作,經(jīng)過PageCache。 -
Direct I/O:直接IO,By Pass PageCache。offset、length需對齊到block_size。 -
Sync I/O:同步IO,即發(fā)起IO請求后會阻塞直到完成。緩存IO和直接IO都屬于同步IO。 -
Async I/O:異步IO,即發(fā)起IO請求后不阻塞,內(nèi)核完成后回調(diào)。通常用內(nèi)核提供的Libaio。 -
Write Back:Buffered IO時,僅僅寫入PageCache便返回,不等數(shù)據(jù)落盤。 -
Write Through:Buffered IO時,不僅僅寫入PageCache,而且同步等待數(shù)據(jù)落盤。
IO體系
我們先看一張總的Linux內(nèi)核存儲棧圖片:

Linux IO存儲棧主要有以下7層:

VFS層
我們通常使用open、read、write等函數(shù)來編寫Linux下的IO程序。接下來我們看看這些函數(shù)的IO棧是怎樣的。在此之前我們先簡單分析一下VFS層的4個對象,有助于我們深刻的理解IO棧。
VFS層的作用是屏蔽了底層不同的文件系統(tǒng)的差異性,為用戶程序提供一個統(tǒng)一的、抽象的、虛擬的文件系統(tǒng),提供統(tǒng)一的對外API,使用戶程序調(diào)用時無需感知底層的文件系統(tǒng),只有在真正執(zhí)行讀寫操作的時候才調(diào)用之前注冊的文件系統(tǒng)的相應(yīng)函數(shù)。
VFS支持的文件系統(tǒng)主要有三種類型:
- 基于磁盤的文件系統(tǒng):Ext系列、XFS等。
- 網(wǎng)絡(luò)文件系統(tǒng):NFS、CIFS等。
- 特殊文件系統(tǒng):/proc、裸設(shè)備等。
VFS主要有四個對象類型(不同的文件系統(tǒng)都要實(shí)現(xiàn)):
- superblock:整個文件系統(tǒng)的元信息。對應(yīng)的操作結(jié)構(gòu)體:
struct super_operations。 - inode:單個文件的元信息。對應(yīng)的操作結(jié)構(gòu)體:
struct inode_operations。 - dentry:目錄項,一個文件目錄對應(yīng)一個dentry。對應(yīng)的操作結(jié)構(gòu)體:
struct dentry_operations。 - file:進(jìn)程打開的一個文件。對應(yīng)的操作結(jié)構(gòu)體:
struct file_operations。
關(guān)于VFS相關(guān)結(jié)構(gòu)體的定義都在include/linux/fs.h里面。
superblock
superblock結(jié)構(gòu)體定義了整個文件系統(tǒng)的元信息,以及相應(yīng)的操作。
https://github.com/torvalds/linux/blob/v4.16/fs/xfs/xfs_super.c#L1789
static const struct super_operations xfs_super_operations = {
......
};
static struct file_system_type xfs_fs_type = {
.name = "xfs",
......
};
inode
inode結(jié)構(gòu)體定義了文件的元數(shù)據(jù),比如大小、最后修改時間、權(quán)限等,除此之外還有一系列的函數(shù)指針,指向具體文件系統(tǒng)對文件操作的函數(shù),包括常見的open、read、write等,由i_fop函數(shù)指針提供。
文件系統(tǒng)最核心的功能全部由inode的函數(shù)指針提供。主要是inode的i_op、i_fop字段。
struct inode {
......
// inode 文件元數(shù)據(jù)的函數(shù)操作
const struct inode_operations *i_op;
// 文件數(shù)據(jù)的函數(shù)操作,open、write、read等
const struct file_operations *i_fop;
......
}
在設(shè)置inode的i_fop時候,會根據(jù)不同的inode類型設(shè)置不同的i_fop。我們以xfs為例:
https://github.com/torvalds/linux/blob/v4.16/fs/xfs/xfs_iops.c#L1266
https://github.com/torvalds/linux/blob/v4.16/fs/inode.c#L1980
如果inode類型為普通文件的話,那么設(shè)置XFS提供的xfs_file_operations。
如果inode類型為塊設(shè)備文件的話,那么設(shè)置塊設(shè)備默認(rèn)提供的def_blk_fops。
void xfs_setup_iops(struct xfs_inode *ip)
{
struct inode *inode = &ip->i_vnode;
switch (inode->i_mode & S_IFMT) {
case S_IFREG:
inode->i_op = &xfs_inode_operations;
// 在IO棧章節(jié)會分析一下xfs_file_operations
inode->i_fop = &xfs_file_operations;
inode->i_mapping->a_ops = &xfs_address_space_operations;
break;
......
default:
inode->i_op = &xfs_inode_operations;
init_special_inode(inode, inode->i_mode, inode->i_rdev);
break;
}
}
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
......
if (S_ISBLK(mode)) {
// 塊設(shè)備相應(yīng)的系列函數(shù)
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
}
......
}
dentry
dentry是目錄項,由于每一個文件必定存在于某個目錄內(nèi),我們通過路徑查找一個文件時,最終肯定找到某個目錄項。在Linux中,目錄和普通文件一樣,都是存放在磁盤的數(shù)據(jù)塊中,在查找目錄的時候就讀出該目錄所在的數(shù)據(jù)塊,然后去尋找其中的某個目錄項。
struct dentry {
......
const struct dentry_operations *d_op;
......
};
在我們使用Linux的過程中,根據(jù)目錄查找文件的例子無處不在,而目錄項的數(shù)據(jù)又都是存儲在磁盤上的,如果每一級路徑都要讀取磁盤,那么性能會十分低下。所以需要目錄項緩存,把dentry放在緩存中加速。
VFS把所有的dentry放在dentry_hashtable哈希表里面,使用LRU淘汰算法。
file
用戶程序能接觸的VFS對象只有file,由進(jìn)程管理。我們常用的打開一個文件就是創(chuàng)建一個file對象,并返回一個文件描述符。出于隔離性的考慮,內(nèi)核不會把file的地址返回,而是返回一個整形的fd。
struct file {
// 操作文件的函數(shù)指針,和inode里面的i_fop一樣,在open的時候賦值為i_fop。
const struct file_operations *f_op;
// 指向?qū)?yīng)inode對象
struct inode *f_inode;
// 每個文件都有自己的一個偏移量
loff_t f_pos;
......
}
file對象是由內(nèi)核進(jìn)程直接管理的。每個進(jìn)程都有當(dāng)前打開的文件列表,放在files_struct結(jié)構(gòu)體中。
struct files_struct {
......
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
......
};
fd_array數(shù)組存儲了所有打開的file對象,用戶程序拿到的文件描述符(fd)實(shí)際上是這個數(shù)組的索引。
IO棧
https://github.com/torvalds/linux/blob/v4.16/fs/read_write.c#L566
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
......
ret = vfs_read(f.file, buf, count, &pos);
......
return ret;
}
SYSCALL_DEFINE3(write, unsigned int, fd, char __user *, buf, size_t, count)
{
......
ret = vfs_write(f.file, buf, count, &pos);
......
return ret;
}
由此可見,我們經(jīng)常使用的read、write系統(tǒng)調(diào)用實(shí)際上是對vfs_read、vfs_write的一個封裝。
size_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
......
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else
ret = do_sync_read(file, buf, count, pos);
......
}
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
......
if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);
else
ret = do_sync_write(file, buf, count, pos);
......
}
我們發(fā)現(xiàn),VFS會調(diào)用具體的文件系統(tǒng)的實(shí)現(xiàn):file->f_op->read、file->f_op->write。
對于通用的文件系統(tǒng),Linux封裝了很多基本的函數(shù),很多文件系統(tǒng)的核心功能都是以這些基本的函數(shù)為基礎(chǔ),再封裝一層。接下來我們以XFS為例,簡單分析一下XFS的read、write都做了什么操作。
https://github.com/torvalds/linux/blob/v4.16/fs/xfs/xfs_file.c#L1137
const struct file_operations xfs_file_operations = {
......
.llseek = xfs_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
// 異步IO,在之后的版本中名字為read_iter、write_iter。
.aio_read = xfs_file_aio_read,
.aio_write = xfs_file_aio_write,
.mmap = xfs_file_mmap,
.open = xfs_file_open,
.fsync = xfs_file_fsync,
......
};
這是XFS的f_op函數(shù)指針表,我們可以看到read、write函數(shù)直接使用了內(nèi)核提供的do_sync_read、do_sync_write函數(shù)。
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
......
ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
......
}
ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
......
ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
......
}
這兩個函數(shù)最終也是調(diào)用了具體文件系統(tǒng)的aio_read和aio_write函數(shù),對應(yīng)XFS的函數(shù)為xfs_file_aio_read和xfs_file_aio_write。
xfs_file_aio_read和xfs_file_aio_write雖然有很多xfs自己的實(shí)現(xiàn)細(xì)節(jié),但其核心功能都是建立在內(nèi)核提供的通用函數(shù)上的:xfs_file_aio_read最終會調(diào)用generic_file_aio_read函數(shù),xfs_file_aio_write最終會調(diào)用generic_perform_write函數(shù),這些通用函數(shù)是基本上所有文件系統(tǒng)的核心邏輯。
接下來便要進(jìn)入PageCache層的相關(guān)邏輯了,我們先簡單概括一下讀寫多了哪些事情。
generic_file_aio_read:
- 根據(jù)文件偏移量計算出要讀取數(shù)據(jù)在PageCache中的位置。
- 如果命中PageCache則直接返回,否則觸發(fā)磁盤讀取任務(wù),會有預(yù)讀的操作,減少IO次數(shù)。
- 數(shù)據(jù)讀取到PageCache后,拷貝到用戶態(tài)Buffer中。
generic_perform_write:
- 根據(jù)文件偏移量計算要寫入的數(shù)據(jù)再PageCache中的位置。
- 將用戶態(tài)的Buffer拷貝到PageCache中。
- 檢查PageCache是否占用太多,如果是則將部分PageCache的數(shù)據(jù)刷回磁盤。
使用Buffered IO時,VFS層的讀寫很大程度上是依賴于PageCache的,只有當(dāng)Cache-Miss,Cache過滿等才會涉及到磁盤的操作。
塊設(shè)備文件
我們在使用Direct IO時,通常搭配Libaio使用,避免同步IO阻塞程序。而往往Direct IO + Libaio應(yīng)用于裸設(shè)備的場景,盡量不要應(yīng)用于文件系統(tǒng)中的文件,這時仍然會有文件系統(tǒng)的種種開銷。
通常Direct IO + Libaio使用的場景有幾種:
- write back journal,journal也是裸設(shè)備。
- 不怎么依賴文件系統(tǒng)的絕大部分功能,僅僅是讀寫即可,可直接操作裸設(shè)備。
上面基本都是普通文件的讀寫,我們通常的使用場景中還有一種特殊的文件即塊設(shè)備文件(/dev/sdx),這些塊設(shè)備文件仍然由VFS層管理,相當(dāng)于一個特殊的文件系統(tǒng)。當(dāng)進(jìn)程訪問塊設(shè)備文件時,直接調(diào)用設(shè)備驅(qū)動程序提供的相應(yīng)函數(shù),默認(rèn)的塊設(shè)備函數(shù)列表如下:
const struct file_operations def_blk_fops = {
......
.open = blkdev_open,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = blkdev_aio_read,
.aio_write = blkdev_aio_write,
.mmap = generic_file_mmap,
.fsync = blkdev_fsync,
......
};
使用Direct IO + Libaio + 裸設(shè)備時,VFS層的函數(shù)指針會指向裸設(shè)備的def_blk_fops。因為我們通常使用DIO+Libaio+裸設(shè)備,所以我們簡單分析一下Libaio的IO流程。
Libaio提供了5個基本的方法,只能以DIO的方式打開,否則可能會進(jìn)行Buffered IO。
io_setup, io_cancal, io_destroy, io_getevents, io_submit
Linux內(nèi)核AIO的實(shí)現(xiàn)在https://github.com/torvalds/linux/blob/v4.16/fs/aio.c,我們簡單分析一下io_submit的操作。
SYSCALL_DEFINE3(io_submit, aio_context_t, ctx_id, long, nr,
struct iocb __user * __user *, iocbpp)
{
return do_io_submit(ctx_id, nr, iocbpp, 0);
}
long do_io_submit(aio_context_t ctx_id, long nr,struct iocb __user *__user *iocbpp, bool compat){
...
for (i=0; i<nr; i++) {
ret = io_submit_one(ctx, user_iocb, &tmp, compat);
}
...
}
static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb,struct iocb *iocb, bool compat){
...
ret = aio_run_iocb(req, compat);
...
}
static ssize_t aio_run_iocb(struct kiocb *req, bool compat){
...
case IOCB_CMD_PREADV:
rw_op = file->f_op->aio_read;
case IOCB_CMD_PWRITEV:
rw_op = file->f_op->aio_write;
...
}
可以發(fā)現(xiàn),最終也是調(diào)用f_op的aio_read函數(shù),對應(yīng)于文件系統(tǒng)的文件就是xfs_file_aio_read函數(shù),對應(yīng)于塊設(shè)備文件就是blkdev_aio_read函數(shù),然后進(jìn)入通用塊層,放入IO隊列,進(jìn)行IO調(diào)度。由此可見Libaio的隊列也就是通用塊層之下的IO調(diào)度層中的隊列。
PageCache層
在HDD時代,由于內(nèi)核和磁盤速度的巨大差異,Linux內(nèi)核引入了頁高速緩存(PageCache),把磁盤抽象成一個個固定大小的連續(xù)Page,通常為4K。對于VFS來說,只需要與PageCache交互,無需關(guān)注磁盤的空間分配以及是如何讀寫的。
當(dāng)我們使用Buffered IO的時候便會用到PageCache層,與Direct IO相比,用戶程序無需offset、length對齊。是因為通用塊層處理IO都必須是塊大小對齊的。
Buffered IO中PageCache幫我們做了對齊的工作:如果我們修改文件的offset、length不是頁大小對齊的,那么PageCache會執(zhí)行RMW的操作,先把該頁對應(yīng)的磁盤的數(shù)據(jù)全部讀上來,再和內(nèi)存中的數(shù)據(jù)做Modify,最后再把修改后的數(shù)據(jù)寫回磁盤。雖然是寫操作,但是非對齊的寫仍然會有讀操作。
Direct IO由于跳過了PageCache,直達(dá)通用塊層,所以需要用戶程序處理對齊的問題。
臟頁刷盤
如果發(fā)生機(jī)器宕機(jī),位于PageCache中的數(shù)據(jù)就會丟失;所以僅僅寫入PageCache是不可靠的,需要有一定的策略將數(shù)據(jù)刷入磁盤。通常有幾種策略:
- 手動調(diào)用fsync、fdatasync刷盤,可參考淺談分布式存儲之sync詳解。
- 臟頁占用比例超過了閾值,觸發(fā)刷盤。
- 臟頁駐留時間過長,觸發(fā)刷盤。
Linux內(nèi)核目前的做法是為每個磁盤都建立一個線程,負(fù)責(zé)每個磁盤的刷盤。
預(yù)讀策略
從VFS層我們知道寫是異步的,寫完P(guān)ageCache便直接返回了;但是讀是同步的,如果PageCache沒有命中,需要從磁盤讀取,很影響性能。如果是順序讀的話PageCache便可以進(jìn)行預(yù)讀策略,異步讀取該P(yáng)age之后的Page,等到用戶程序再次發(fā)起讀請求,數(shù)據(jù)已經(jīng)在PageCache里,大幅度減少IO的次數(shù),不用阻塞讀系統(tǒng)調(diào)用,提升讀的性能。
映射層
映射層是在PageCache之下的一層,由多個文件系統(tǒng)(Ext系列、XFS等,打開文件系統(tǒng)的文件)以及塊設(shè)備文件(直接打開裸設(shè)備文件)組成,主要完成兩個工作:
- 內(nèi)核確定該文件所在文件系統(tǒng)或者塊設(shè)備的塊大小,并根據(jù)文件大小計算所請求數(shù)據(jù)的長度以及所在的邏輯塊號。
- 根據(jù)邏輯塊號確定所請求數(shù)據(jù)的物理塊號,也即在在磁盤上的真正位置。
由于通用塊層以及之后的的IO都必須是塊大小對齊的,我們通過DIO打開文件時,略過了PageCache,所以必須要自己將IO數(shù)據(jù)的offset、length對齊到塊大小。
我們使用的DIO+Libaio直接打開裸設(shè)備時,跳過了文件系統(tǒng),少了文件系統(tǒng)的種種開銷,然后進(jìn)入通用塊層,繼續(xù)之后的處理。
通用塊層
通用塊層存在的意義也和VFS一樣,屏蔽底層不同設(shè)備驅(qū)動的差異性,提供統(tǒng)一的、抽象的通用塊層API。
通用塊層最核心的數(shù)據(jù)結(jié)構(gòu)便是bio,描述了從上層提交的一次IO請求。
https://github.com/torvalds/linux/blob/v4.16/include/linux/blk_types.h#L96
struct bio {
......
// 要提交到磁盤的多段數(shù)據(jù)
struct bio_vec *bi_io_vec;
// 有多少段數(shù)據(jù)
unsigned short bi_vcnt;
......
}
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
所有到通用塊層的IO,都要把數(shù)據(jù)封裝成bio_vec的形式,放到bio結(jié)構(gòu)體內(nèi)。
在VFS層的讀請求,是以Page為單位讀取的,如果改Page不在PageCache內(nèi),那么便要調(diào)用文件系統(tǒng)定義的read_page函數(shù)從磁盤上讀取數(shù)據(jù)。
const struct address_space_operations xfs_address_space_operations = {
......
.readpage = xfs_vm_readpage,
.readpages = xfs_vm_readpages,
.writepage = xfs_vm_writepage,
.writepages = xfs_vm_writepages,
......
};
IO調(diào)度層
Linux調(diào)度層是Linux IO體系中的一個重要組件,介于通用塊層和塊設(shè)備驅(qū)動層之間。IO調(diào)度層主要是為了減少磁盤IO的次數(shù),增大磁盤整體的吞吐量,會隊列中的多個bio進(jìn)行排序和合并,并且提供了多種IO調(diào)度算法,適應(yīng)不同的場景。
Linux內(nèi)核為每一個塊設(shè)備維護(hù)了一個IO隊列,item是struct request結(jié)構(gòu)體,用來排隊上層提交的IO請求。一個request包含了多個bio,一個IO隊列queue了多個request。
struct request {
......
// total data len
unsigned int __data_len;
// sector cursor
sector_t __sector;
// first bio
struct bio *bio;
// last bio
struct bio *biotail;
......
}
上層提交的bio有可能分配一個新的request結(jié)構(gòu)體去存放,也有可能合并到現(xiàn)有的request中。
Linux內(nèi)核目前提供了以下幾種調(diào)度策略:
- Deadline:默認(rèn)的調(diào)度策略,加入了超時的隊列。適用于HDD。
- CFQ:完全公平調(diào)度器。
- Noop:No Operation,最簡單的FIFIO隊列,不排序會合并。適用于SSD、NVME。
塊設(shè)備驅(qū)動層
每一類設(shè)備都有其驅(qū)動程序,負(fù)責(zé)設(shè)備的讀寫。IO調(diào)度層的請求也會交給相應(yīng)的設(shè)備驅(qū)動程序去進(jìn)行讀寫。大部分的磁盤驅(qū)動程序都采用DMA的方式去進(jìn)行數(shù)據(jù)傳輸,DMA控制器自行在內(nèi)存和IO設(shè)備間進(jìn)行數(shù)據(jù)傳送,當(dāng)數(shù)據(jù)傳送完成再通過中斷通知CPU。
通常塊設(shè)備的驅(qū)動程序都已經(jīng)集成在了kernel里面,也即就算我們直接調(diào)用塊設(shè)備驅(qū)動驅(qū)動層的代碼還是要經(jīng)過內(nèi)核。
spdk實(shí)現(xiàn)了用戶態(tài)、異步、無鎖、輪詢方式NVME驅(qū)動程序。塊存儲是延遲非常敏感的服務(wù),使用NVME做后端存儲磁盤時,便可以使用spdk提供的NVME驅(qū)動,縮短IO流程,降低IO延遲,提升IO性能。
物理設(shè)備層
物理設(shè)備層便是我們經(jīng)常使用的HDD、SSD、NVME等磁盤設(shè)備了。
FAQ
1、write返回成功數(shù)據(jù)落盤了嗎?
Buffered IO:write返回數(shù)據(jù)僅僅是寫入了PageCache,還沒有落盤。
Direct IO:write返回數(shù)據(jù)僅僅是到了通用塊層放入IO隊列,依舊沒有落盤。
此時設(shè)備斷電、宕機(jī)仍然會發(fā)生數(shù)據(jù)丟失。需要調(diào)用fsync或者fdatasync把數(shù)據(jù)刷到磁盤上,調(diào)用命令時,磁盤本身緩存(DiskCache)的內(nèi)容也會持久化到磁盤上。
2、write系統(tǒng)調(diào)用是原子的嗎?
write系統(tǒng)調(diào)用不是原子的,如果有多線程同時調(diào)用,數(shù)據(jù)可能會發(fā)生錯亂??梢允褂?code>O_APPEND標(biāo)志打開文件,只能追加寫,這樣多線程寫入就不會發(fā)生數(shù)據(jù)錯亂。
3、mmap相比read、write快在了哪里?
mmap直接把PageCache映射到用戶態(tài),少了一次系統(tǒng)調(diào)用,也少了一次數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)的拷貝。
mmap通常和read搭配使用:寫入使用write+sync,讀取使用mmap。
4、為什么Direct IO需要數(shù)據(jù)對齊?
DIO跳過了PageCache,直接到通用塊層,而通用塊層的IO都必須是塊大小對齊的,所以需要用戶程序自行對齊offset、length。
5、Libaio的IO棧?
write()--->sys_write()--->vfs_write()--->通用塊層--->IO調(diào)度層--->塊設(shè)備驅(qū)動層--->塊設(shè)備
6、為什么需要 by pass pagecache?
當(dāng)應(yīng)用程序不滿Linux內(nèi)核的Cache策略,有更適合自己的Cache策略時可以使用Direct IO跳過PageCache。例如Mysql。
7、為什么需要 by pass kernel?
當(dāng)應(yīng)用程序?qū)ρ舆t極度敏感時,由于Linux內(nèi)核IO棧有7層,IO路徑比較長,為了縮短IO路徑,降低IO延遲,可以by pass kernel,直接使用用戶態(tài)的塊設(shè)備驅(qū)動程序。例如spdk的nvme,阿里云的ESSD。
8、為什么需要直接操作裸設(shè)備?
當(dāng)應(yīng)用程序僅僅使用了基本的read、write,用不到文件系統(tǒng)的大而全的功能,此時文件系統(tǒng)的開銷對于應(yīng)用程序來說是一種累贅,此時需要跳過文件系統(tǒng),接管裸設(shè)備,自己實(shí)現(xiàn)磁盤分配、緩存等功能,通常使用DIO+Libaio+裸設(shè)備。例如Ceph FileStore的Journal、Ceph BlueStore。