W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
大部分 I/O 操作是通過內(nèi)核緩沖的. 一個(gè)內(nèi)核空間緩沖區(qū)的使用允許一定程度的用戶空間和實(shí)際設(shè)備的分離; 這種分離能夠使編程容易并且還可以在許多情況下有性能的好處. 但是, 有這樣的情況它對于進(jìn)行 I/O 直接到或從一個(gè)用戶空間緩沖區(qū)是有好處的. 如果正被傳輸?shù)臄?shù)據(jù)量大, 不使用一個(gè)額外的拷貝直接通過內(nèi)核空間傳輸數(shù)據(jù)可以加快事情進(jìn)展.
2.6 內(nèi)核中一個(gè)直接 I/O 使用的例子是 SCSI 磁帶驅(qū)動. 流動的磁帶能夠傳送大量數(shù)據(jù)通過系統(tǒng), 并且磁帶傳送常常是面向記錄的, 因此在內(nèi)核中緩沖數(shù)據(jù)沒有好處. 因此, 當(dāng)條件正確(用戶空間緩沖區(qū)是頁對齊的, 例如), SCSI 磁帶驅(qū)動進(jìn)行它的 I/O 而不拷貝數(shù)據(jù).
就是說, 重要的是認(rèn)識到直接 I/O 不是一直提供人們期望的性能提高. 設(shè)置直接 I/O (它調(diào)用出錯(cuò)換入并且除下相關(guān)的用戶空間)的開銷可能是不小的, 并且被緩沖的 I/O 的好處丟失了. 例如, 直接 I/O 的使用要求 write 系統(tǒng)調(diào)用同步操作; 否則應(yīng)用程序不能知道什么時(shí)間它可以重新使用它的 I/O 緩沖. 停止應(yīng)用程序直到每個(gè) write 完成可能拖慢事情, 這是為什么使用直接 I/O 的應(yīng)用程序也常常使用異步 I/O 操作的原因.
事情的真正內(nèi)涵是, 在任何情況下, 在一個(gè)字符驅(qū)動實(shí)現(xiàn)直接 I/O 常常是不必要并且可能是有害的. 你應(yīng)當(dāng)只在你確定緩沖的 I/O 的開銷確實(shí)拖慢了系統(tǒng)的情況下采取這個(gè)步驟. 還要注意, 塊和網(wǎng)絡(luò)驅(qū)動不必?fù)?dān)心實(shí)現(xiàn)直接 I/O; 這 2 種情況下, 內(nèi)核中的高級的代碼在需要時(shí)建立和使用直接 I/O, 并且驅(qū)動級別的代碼甚至不需要知道直接 I/O 在被進(jìn)行中.
實(shí)現(xiàn)直接 I/O 的關(guān)鍵是一個(gè)稱為 get_user_pages 的函數(shù), 它在 <linux/mm.h> 中定義使用下列原型:
int get_user_pages(struct task_struct *tsk,
struct mm_struct *mm,
unsigned long start,
int len,
int write,
int force,
struct page **pages,
struct vm_area_struct **vmas);
這個(gè)函數(shù)有幾個(gè)參數(shù):
tsk
一個(gè)指向進(jìn)行 I/O 的任務(wù)的指針; 它的主要目的是告知內(nèi)核誰應(yīng)當(dāng)負(fù)責(zé)任何一個(gè)當(dāng)設(shè)置緩沖時(shí)導(dǎo)致的頁錯(cuò). 這個(gè)參數(shù)幾乎一直作為 current 傳遞.
mm
一個(gè)內(nèi)存管理結(jié)構(gòu)的指針, 描述被映射的地址空間. mm_struct 結(jié)構(gòu)是捆綁一個(gè)進(jìn)程的虛擬地址空間所有的部分在一起的. 對于驅(qū)動的使用, 這個(gè)參數(shù)應(yīng)當(dāng)一直是 current->mm.
start len
start 是(頁對齊的)用戶空間緩沖的地址, 并且 len 是緩沖的長度以頁計(jì).
write force
如果 write 是非零, 這些頁被映射來寫(當(dāng)然, 隱含著用戶空間在進(jìn)行一個(gè)讀操作). force 標(biāo)志告知 get_user_pages 來覆蓋在給定頁上的保護(hù), 來提供要求的權(quán)限; 驅(qū)動應(yīng)當(dāng)一直傳遞 0 在這里.
pages vmas
輸出參數(shù). 在成功完成后, 頁包含一系列指向 struct page 結(jié)構(gòu)的指針來描述用戶空間緩沖, 并且 vmas 包含指向被關(guān)聯(lián)的 VMA 的指針. 這些參數(shù)應(yīng)當(dāng), 顯然, 指向能夠持有至少 len 個(gè)指針的數(shù)組. 任一個(gè)參數(shù)可能是 NULL, 但是你需要, 至少, struct page 指針來實(shí)際對緩沖操作.
get_user_pages 是一個(gè)低級內(nèi)存管理函數(shù), 帶一個(gè)相稱的復(fù)雜的接口. 它還要求給這個(gè)地址空間的 mmap 讀者/寫者 旗標(biāo)在調(diào)用前被以讀模式獲得. 結(jié)果是, 對 get_user_pages 常??磥硐?
down_read(¤t->mm->mmap_sem);
result = get_user_pages(current, current->mm, ...);
up_read(¤t->mm->mmap_sem);
返回值是實(shí)際映射的頁數(shù), 它可能小于請求的數(shù)目(但是大于 0).
一旦成功完成, 調(diào)用者有一個(gè)頁數(shù)組指向用戶空間緩沖, 它被鎖入內(nèi)存. 為直接在緩沖上操作, 內(nèi)核空間代碼必須將每個(gè) struct page 指針轉(zhuǎn)換為一個(gè)內(nèi)核虛擬地址, 使用 kmap 或者 kmap_atomic. 常常地, 但是, 對于可以使用直接 I/O 的設(shè)備在使用 DMA 操作, 因此你的驅(qū)動將可能想從 struct page 指針數(shù)組創(chuàng)建一個(gè)發(fā)散/匯聚列表. 我們在 "發(fā)散/匯聚映射"一節(jié)中討論如何做這個(gè).
一旦你的直接 I/O 操作完成了, 你必須釋放用戶頁. 在這樣做之前, 但是, 你必須通知內(nèi)核如果你改變了這些頁的內(nèi)容. 否則, 內(nèi)核可能認(rèn)為這些頁是"干凈"的, 意味著它們匹配一個(gè)在交換設(shè)備中發(fā)現(xiàn)的一個(gè)拷貝, 并且釋放它們不寫出它們到備份存儲. 因此, 如果你已改變了這些頁(響應(yīng)一個(gè)用戶空間寫請求), 你必須標(biāo)志每個(gè)被影響到的頁為臟, 使用一個(gè)調(diào)用:
void SetPageDirty(struct page *page);
(這個(gè)宏定義在 <linux/page-flags.h>). 進(jìn)行這個(gè)操作的代碼首先檢查來保證頁不在內(nèi)存映射的保留部分, 這部分從不被換出. 因此, 代碼常常看來如此:
if (! PageReserved(page))
SetPageDirty(page);
因?yàn)橛脩艨臻g內(nèi)存正常地不置為保留的, 這個(gè)檢查嚴(yán)格地不應(yīng)當(dāng)是必要的, 但是當(dāng)你深入內(nèi)存管理子系統(tǒng)時(shí), 最好全面并且仔細(xì).
不管這些頁是否已被改變, 它們必須從頁緩存中釋放, 或者它們一直留在那里. 這個(gè)調(diào)用是:
void page_cache_release(struct page *page);
這個(gè)調(diào)用應(yīng)當(dāng), 當(dāng)然, 在頁已被標(biāo)識為臟之后進(jìn)行, 如果需要.
增加到 2.6 內(nèi)核的一個(gè)新的特性是異步 I/O 能力. 異步 I/O 允許用戶空間來初始化操作而不必等待它們的完成; 因此, 一個(gè)應(yīng)用程序可以在它的 I/O 在進(jìn)行中時(shí)做其他的處理. 一個(gè)復(fù)雜的, 高性能的應(yīng)用程序還可使用異步 I/O 來使多個(gè)操作在同一個(gè)時(shí)間進(jìn)行.
異步 I/O 的實(shí)現(xiàn)是可選的, 并且很少幾個(gè)驅(qū)動作者關(guān)心; 大部分設(shè)備不會從這個(gè)能力中受益. 如同我們將在接下來的章節(jié)中見到的, 塊和網(wǎng)絡(luò)驅(qū)動在整個(gè)時(shí)間是完全異步的, 因此只有字符驅(qū)動對于明確的異步 I/O 支持是候選的. 一個(gè)字符設(shè)備能夠從這個(gè)支持中受益, 如果有好的理由來使多個(gè) I/O 操作在任一給定時(shí)間同時(shí)進(jìn)行. 一個(gè)好例子是流化磁帶驅(qū)動, 這里這個(gè)驅(qū)動可停止并且明顯慢下來如果 I/O 操作沒有盡快到達(dá). 一個(gè)應(yīng)用程序試圖從一個(gè)流驅(qū)動中獲得最好的性能, 可以使用異步 I/O 來使多個(gè)操作在任何時(shí)間準(zhǔn)備好進(jìn)行.
對于少見的需要實(shí)現(xiàn)異步 I/O 的驅(qū)動作者, 我們提供一個(gè)快速的關(guān)于它如何工作的概觀. 我們涉及異步 I/O 在本章, 因?yàn)樗膶?shí)現(xiàn)幾乎一直也包括直接 I/O 操作(如果你在內(nèi)核中緩沖數(shù)據(jù), 你可能常常實(shí)現(xiàn)異步動作而不必在用戶空間出現(xiàn)不必要的復(fù)雜性).
支持異步 I/O 的驅(qū)動應(yīng)當(dāng)包含 <linux/aio.h>. 有 3 個(gè) file_operation 方法給異步 I/O 實(shí)現(xiàn):
ssize_t (*aio_read) (struct kiocb *iocb, char *buffer,
size_t count, loff_t offset);
ssize_t (*aio_write) (struct kiocb *iocb, const char *buffer,
size_t count, loff_t offset);
int (*aio_fsync) (struct kiocb *iocb, int datasync);
aio_fsync 操作只對文件系統(tǒng)代碼感興趣, 因此我們在此不必討論它. 其他 2 個(gè), aio_read 和 aio_write, 看起來非常象常規(guī)的 read 和 write 方法, 但是有幾個(gè)例外. 一個(gè)是 offset 參數(shù)由值傳遞; 異步操作從不改變文件位置, 因此沒有理由傳一個(gè)指針給它. 這些方法還使用 iocb ("I/O 控制塊")參數(shù), 這個(gè)我們一會兒就到.
aio_read 和 aio_write 方法的目的是初始化一個(gè)讀或?qū)懖僮? 在它們返回時(shí)可能完成或者可能沒完成. 如果有可能立刻完成操作, 這個(gè)方法應(yīng)當(dāng)這樣做并且返回通常的狀態(tài): 被傳輸?shù)淖止?jié)數(shù)或者一個(gè)負(fù)的錯(cuò)誤碼. 因此, 如果你的驅(qū)動有一個(gè)稱為 my_read 的讀方法, 下面的 aio_read 方法是全都正確的(盡管特別無意義):
static ssize_t my_aio_read(struct kiocb *iocb, char *buffer, ssize_t count, loff_t offset)
{
return my_read(iocb->ki_filp, buffer, count, &offset);
}
注意, struct file 指針在 kocb 結(jié)構(gòu)的 ki_filp 成員中.
如果你支持異步 I/O, 你必須知道這個(gè)事實(shí), 內(nèi)核可能, 偶爾, 創(chuàng)建"異步 IOCB". 它們是, 本質(zhì)上, 必須實(shí)際上被同步執(zhí)行的異步操作. 有人可能非常奇怪為什么要這樣做, 但是最好只做內(nèi)核要求做的. 同步操作在 IOCB 中標(biāo)識; 你的驅(qū)動應(yīng)當(dāng)詢問狀態(tài), 使用:
int is_sync_kiocb(struct kiocb *iocb);
如果這個(gè)函數(shù)返回一個(gè)非零值, 你的驅(qū)動必須同步執(zhí)行這個(gè)操作.
但是, 最后, 所有這個(gè)結(jié)構(gòu)的意義在于使能異步操作. 如果你的驅(qū)動能夠初始化這個(gè)操作(或者, 簡單地, 將它排隊(duì)到它能夠被執(zhí)行時(shí)), 它必須做兩件事情: 記住它需要知道的關(guān)于這個(gè)操作的所有東西, 并且返回 -EIOCBQUEUED 給調(diào)用者. 記住操作信息包括安排對用戶空間緩沖的存取; 一旦你返回, 你將不再有機(jī)會來存取緩沖, 當(dāng)再調(diào)用進(jìn)程的上下文運(yùn)行時(shí). 通常, 那意味著你將可能不得不建立一個(gè)直接內(nèi)核映射( 使用 get_user_pages ) 或者一個(gè) DMA 映射. -EIOCBQUEUED 錯(cuò)誤碼指示操作還沒有完成, 并且它最終的狀態(tài)將之后傳遞.
當(dāng)"之后"到來時(shí), 你的驅(qū)動必須通知內(nèi)核操作已經(jīng)完成. 那通過調(diào)用 aio_complete 來完成:
int aio_complete(struct kiocb *iocb, long res, long res2);
這里, iocb 是起初傳遞給你的同一個(gè) IOCB, 并且 res 是這個(gè)操作的通常的結(jié)果狀態(tài). res2 是將被返回給用戶空間的第 2 個(gè)結(jié)果碼; 大部分的異步 I/O 實(shí)現(xiàn)作為 0 傳遞 res2. 一旦你調(diào)用 aio_complete, 你不應(yīng)當(dāng)再碰 IOCB 或者用戶緩沖.
例子代碼中的面向頁的 scullp 驅(qū)動實(shí)現(xiàn)異步 I/O. 實(shí)現(xiàn)是簡單的, 但是足夠來展示異步操作應(yīng)當(dāng)如何被構(gòu)造.
aio_read 和 aio_write 方法實(shí)際上不做太多:
static ssize_t scullp_aio_read(struct kiocb *iocb, char *buf, size_t count, loff_t pos)
{
return scullp_defer_op(0, iocb, buf, count, pos);
}
static ssize_t scullp_aio_write(struct kiocb *iocb, const char *buf, size_t count, loff_t pos)
{
return scullp_defer_op(1, iocb, (char *) buf, count, pos);
}
這些方法僅僅調(diào)用一個(gè)普通的函數(shù):
struct async_work
{
struct kiocb *iocb;
int result;
struct work_struct work;
};
static int scullp_defer_op(int write, struct kiocb *iocb, char *buf, size_t count, loff_t pos)
{
struct async_work *stuff;
int result;
/* Copy now while we can access the buffer */
if (write)
result = scullp_write(iocb->ki_filp, buf, count, &pos);
else
result = scullp_read(iocb->ki_filp, buf, count, &pos);
/* If this is a synchronous IOCB, we return our status now. */
if (is_sync_kiocb(iocb))
return result;
/* Otherwise defer the completion for a few milliseconds. */
stuff = kmalloc (sizeof (*stuff), GFP_KERNEL);
if (stuff == NULL)
return result; /* No memory, just complete now */
stuff->iocb = iocb;
stuff->result = result;
INIT_WORK(&stuff->work, scullp_do_deferred_op, stuff);
schedule_delayed_work(&stuff->work, HZ/100);
return -EIOCBQUEUED;
}
一個(gè)更加完整的實(shí)現(xiàn)應(yīng)當(dāng)使用 get_user_pages 來映射用戶緩沖到內(nèi)核空間. 我們選擇來使生活簡單些, 通過只拷貝在 outset 的數(shù)據(jù). 接著調(diào)用 is_sync_kiocb 來看是否這個(gè)操作必須同步完成; 如果是, 結(jié)果狀態(tài)被返回, 并且我們完成了. 否則我們記住相關(guān)的信息在一個(gè)小結(jié)構(gòu), 通過一個(gè)工作隊(duì)列來為"完成"而安排, 并且返回 -EIOCBQUEUED. 在這點(diǎn)上, 控制返回到用戶空間.
之后, 工作隊(duì)列執(zhí)行我們的完成函數(shù):
static void scullp_do_deferred_op(void *p)
{
struct async_work *stuff = (struct async_work *) p;
aio_complete(stuff->iocb, stuff->result, 0);
kfree(stuff);
}
這里, 只是用我們保存的信息調(diào)用 aio_complete 的事情. 一個(gè)真正的驅(qū)動的異步 I/O 實(shí)現(xiàn)是有些復(fù)雜, 當(dāng)然, 但是它遵循這類結(jié)構(gòu).
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: