亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

在介紹直接 I/O 之前,先來介紹下直接I/O這種機(jī)制產(chǎn)生的原因。畢竟已經(jīng)有了緩存I/O(Buffered I/O),那肯定能夠像到緩存I/O有缺陷吧,就按照這個思路來。

5分鐘搞懂Linux中直接I/O原理

 

 

什么是緩存 I/O (Buffered I/O)

 

緩存 I/O 又被稱作標(biāo)準(zhǔn) I/O,大多數(shù)文件系統(tǒng)的默認(rèn) I/O 操作都是緩存 I/O。在 linux 的緩存 I/O 機(jī)制中,操作系統(tǒng)會將 I/O 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中,也就是說,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間寫的過程就是數(shù)據(jù)流反方向。緩存 I/O 有以下這些優(yōu)點(diǎn):

  1. 緩存 I/O 使用了操作系統(tǒng)內(nèi)核緩沖區(qū),在一定程度上分離了應(yīng)用程序空間和實(shí)際的物理設(shè)備。
  2. 緩存 I/O 可以減少讀盤的次數(shù),從而提高性能。

對于讀操作:當(dāng)應(yīng)用程序要去讀取某塊數(shù)據(jù)的時候,如果這塊數(shù)據(jù)已經(jīng)在頁緩存中,那就返回之。而不需要經(jīng)過硬盤的讀取操作了。如果這塊數(shù)據(jù)不在頁緩存中,就需要從硬盤中讀取數(shù)據(jù)到頁緩存。

對于寫操作:應(yīng)用程序會將數(shù)據(jù)先寫到頁緩存中,數(shù)據(jù)是否會被立即寫到磁盤,這取決于所采用的寫操作機(jī)制:

  • 同步機(jī)制,數(shù)據(jù)會立即被寫到磁盤中,直到數(shù)據(jù)寫完,寫接口才返回;
  • 延遲機(jī)制:寫接口立即返回,操作系統(tǒng)會定期地將頁緩存中的數(shù)據(jù)刷到硬盤。所以這個機(jī)制會存在丟失數(shù)據(jù)的風(fēng)險。想象下寫接口返回的時候,頁緩存的數(shù)據(jù)還沒刷到硬盤,正好斷電。對于應(yīng)用程序來說,認(rèn)為數(shù)據(jù)已經(jīng)在硬盤中。

 

5分鐘搞懂Linux中直接I/O原理

緩存I/O的寫操作

 

緩存 I/O 的缺點(diǎn)

在緩存I/O的機(jī)制中,以寫操作為例,數(shù)據(jù)先從用戶態(tài)拷貝到內(nèi)核態(tài)中的頁緩存中,然后又會從頁緩存中寫到磁盤中,這些拷貝操作帶來的CPU以及內(nèi)存的開銷是非常大的。

對于某些特殊的應(yīng)用程序來說,能夠繞開內(nèi)核緩沖區(qū)能夠獲取更好的性能,這就是直接I/O出現(xiàn)的意義。

 

5分鐘搞懂Linux中直接I/O原理

直接I/O寫操作

 

 

直接I/O 介紹

凡是通過直接I/O方式進(jìn)行數(shù)據(jù)傳輸,數(shù)據(jù)直接從用戶態(tài)地址空間寫入到磁盤中,直接跳過內(nèi)核緩沖區(qū)。對于一些應(yīng)用程序,例如:數(shù)據(jù)庫。他們更傾向于自己的緩存機(jī)制,這樣可以提供更好的緩沖機(jī)制提高數(shù)據(jù)庫的讀寫性能。直接I/O寫操作如上圖所示。

直接I/O 設(shè)計(jì)與實(shí)現(xiàn)

要在塊設(shè)備中執(zhí)行直接 I/O,進(jìn)程必須在打開文件的時候設(shè)置對文件的訪問模式為 O_DIRECT,這樣就等于告訴操作系統(tǒng)進(jìn)程在接下來使用 read() 或者 write() 系統(tǒng)調(diào)用去讀寫文件的時候使用的是直接 I/O 方式,所傳輸?shù)臄?shù)據(jù)均不經(jīng)過操作系統(tǒng)內(nèi)核緩存空間。使用直接 I/O 讀寫數(shù)據(jù)必須要注意緩沖區(qū)對齊( buffer alignment )以及緩沖區(qū)的大小的問題,即對應(yīng) read() 以及 write() 系統(tǒng)調(diào)用的第二個和第三個參數(shù)。這里邊說的對齊指的是文件系統(tǒng)塊大小的對齊,緩沖區(qū)的大小也必須是該塊大小的整數(shù)倍。

下面主要介紹三個函數(shù):open(),read() 以及 write()。Linux 中訪問文件具有多樣性,所以這三個函數(shù)對于處理不同的文件訪問方式定義了不同的處理方法,本文主要介紹其與直接 I/O 方式相關(guān)的函數(shù)與功能.首先,先來看 open() 系統(tǒng)調(diào)用,其函數(shù)原型如下所示:

int open(const char *pathname, int oflag, … /*, mode_t mode * / ) ;

 

當(dāng)應(yīng)用程序需要直接訪問文件而不經(jīng)過操作系統(tǒng)頁高速緩沖存儲器的時候,它打開文件的時候需要指定 O_DIRECT 標(biāo)識符。

操作系統(tǒng)內(nèi)核中處理 open() 系統(tǒng)調(diào)用的內(nèi)核函數(shù)是 sys_open(),sys_open() 會調(diào)用 do_sys_open() 去處理主要的打開操作。它主要做了三件事情:

  1. 調(diào)用 getname() 從進(jìn)程地址空間中讀取文件的路徑名;
  2. do_sys_open() 調(diào)用 get_unused_fd() 從進(jìn)程的文件表中找到一個空閑的文件表指針,相應(yīng)的新文件描述符就存放在本地變量 fd 中;
  3. 函數(shù) do_filp_open() 會根據(jù)傳入的參數(shù)去執(zhí)行相應(yīng)的打開操作。

下面列出了操作系統(tǒng)內(nèi)核中處理 open() 系統(tǒng)調(diào)用的一個主要函數(shù)關(guān)系圖。

sys_open() 
 |-----do_sys_open() 
 |---------getname() 
 |---------get_unused_fd() 
 |---------do_filp_open() 
 |--------nameidata_to_filp() 
 |----------__dentry_open()

函數(shù) do_flip_open() 在執(zhí)行的過程中會調(diào)用函數(shù) nameidata_to_filp(),而 nameidata_to_filp() 最終會調(diào)用 __dentry_open() 函數(shù),若進(jìn)程指定了 O_DIRECT 標(biāo)識符,則該函數(shù)會檢查直接 I./O 操作是否可以作用于該文件。下面列出了 __dentry_open() 函數(shù)中與直接 I/O 操作相關(guān)的代碼。

if (f->f_flags & O_DIRECT) { 
 if (!f->f_mApping->a_ops || 
 ((!f->f_mapping->a_ops->direct_IO) && 
 (!f->f_mapping->a_ops->get_xip_page))) { 
 fput(f); 
 f = ERR_PTR(-EINVAL); 
 } 
}

當(dāng)文件打開時指定了 O_DIRECT 標(biāo)識符,那么操作系統(tǒng)就會知道接下來對文件的讀或者寫操作都是要使用直接 I/O 方式的。

下邊我們來看一下當(dāng)進(jìn)程通過 read() 系統(tǒng)調(diào)用讀取一個已經(jīng)設(shè)置了 O_DIRECT 標(biāo)識符的文件的時候,系統(tǒng)都做了哪些處理。 函數(shù) read() 的原型如下所示:

ssize_t read(int feledes, void *buff, size_t nbytes) ;

操作系統(tǒng)中處理 read() 函數(shù)的入口函數(shù)是 sys_read(),其主要的調(diào)用函數(shù)關(guān)系圖如下:

sys_read() 
 |-----vfs_read() 
 |----generic_file_read() 
 |----generic_file_aio_read() 
 |--------- generic_file_direct_IO()
?

函數(shù) sys_read() 從進(jìn)程中獲取文件描述符以及文件當(dāng)前的操作位置后會調(diào)用 vfs_read() 函數(shù)去執(zhí)行具體的操作過程,而 vfs_read() 函數(shù)最終是調(diào)用了 file 結(jié)構(gòu)中的相關(guān)操作去完成文件的讀操作,即調(diào)用了 generic_file_read() 函數(shù),其代碼如下所示:

ssize_t 
generic_file_read(struct file *filp, 
char __user *buf, size_t count, loff_t *ppos) 
{ 
 struct iovec local_iov = { .iov_base = buf, .iov_len = count }; 
 struct kiocb kiocb; 
 ssize_t ret; 
 
 init_sync_kiocb(&kiocb, filp); 
 ret = __generic_file_aio_read(&kiocb, &local_iov, 1, ppos); 
 if (-EIOCBQUEUED == ret) 
 ret = wait_on_sync_kiocb(&kiocb); 
 return ret; 
}

函數(shù) generic_file_read() 初始化了 iovec 以及 kiocb 描述符。描述符 iovec 主要是用于存放兩個內(nèi)容:用來接收所讀取數(shù)據(jù)的用戶地址空間緩沖區(qū)的地址和緩沖區(qū)的大小;描述符 kiocb 用來跟蹤 I/O 操作的完成狀態(tài)。之后,函數(shù) generic_file_read() 凋用函數(shù) __generic_file_aio_read()。該函數(shù)檢查 iovec 中描述的用戶地址空間緩沖區(qū)是否可用,接著檢查訪問模式,若訪問模式描述符設(shè)置了 O_DIRECT,則執(zhí)行與直接 I/O 相關(guān)的代碼。函數(shù) __generic_file_aio_read() 中與直接 I/O 有關(guān)的代碼如下所示:

if (filp->f_flags & O_DIRECT) { 
 loff_t pos = *ppos, size; 
 struct address_space *mapping; 
 struct inode *inode; 
 
 mapping = filp->f_mapping; 
 inode = mapping->host; 
 retval = 0; 
 if (!count) 
 goto out; 
 size = i_size_read(inode); 
 if (pos < size) { 
 retval = generic_file_direct_IO(READ, iocb, 
 iov, pos, nr_segs); 
 if (retval > 0 && !is_sync_kiocb(iocb)) 
 retval = -EIOCBQUEUED; 
 if (retval > 0) 
 *ppos = pos + retval; 
 } 
 file_accessed(filp); 
 goto out; 
}

上邊的代碼段主要是檢查了文件指針的值,文件的大小以及所請求讀取的字節(jié)數(shù)目等,之后,該函數(shù)調(diào)用 generic_file_direct_io(),并將操作類型 READ,描述符 iocb,描述符 iovec,當(dāng)前文件指針的值以及在描述符 io_vec 中指定的用戶地址空間緩沖區(qū)的個數(shù)等值作為參數(shù)傳給它。當(dāng) generic_file_direct_io() 函數(shù)執(zhí)行完成,函數(shù) __generic_file_aio_read()會繼續(xù)執(zhí)行去完成后續(xù)操作:更新文件指針,設(shè)置訪問文件 i 節(jié)點(diǎn)的時間戳;這些操作全部執(zhí)行完成以后,函數(shù)返回。 函數(shù) generic_file_direct_IO() 會用到五個參數(shù),各參數(shù)的含義如下所示:

  1. rw:操作類型,可以是 READ 或者 WRITE
  2. iocb:指針,指向 kiocb 描述符 
  3. iov:指針,指向 iovec 描述符數(shù)組
  4. offset:file 結(jié)構(gòu)偏移量
  5. nr_segs:iov 數(shù)組中 iovec 的個數(shù)

函數(shù) generic_file_direct_IO() 代碼如下所示:

static ssize_t 
generic_file_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, 
 loff_t offset, unsigned long nr_segs) 
{ 
 struct file *file = iocb->ki_filp; 
 struct address_space *mapping = file->f_mapping; 
 ssize_t retval; 
 size_t write_len = 0; 
 
 if (rw == WRITE) { 
 write_len = iov_length(iov, nr_segs); 
 if (mapping_mapped(mapping)) 
 unmap_mapping_range(mapping, offset, write_len, 0); 
 } 
 
 retval = filemap_write_and_wait(mapping); 
 if (retval == 0) { 
 retval = mapping->a_ops->direct_IO(rw, iocb, iov, 
 offset, nr_segs); 
 if (rw == WRITE && mapping->nrpages) { 
 pgoff_t end = (offset + write_len - 1) 
 >> PAGE_CACHE_SHIFT; 
 int err = invalidate_inode_pages2_range(mapping, 
 offset >> PAGE_CACHE_SHIFT, end); 
 if (err) 
 retval = err; 
 } 
 } 
 return retval; 
}

函數(shù) generic_file_direct_IO() 對 WRITE 操作類型進(jìn)行了一些特殊處理。除此之外,它主要是調(diào)用了 direct_IO 方法去執(zhí)行直接 I/O 的讀或者寫操作。在進(jìn)行直接 I/O 讀操作之前,先將頁緩存中的相關(guān)臟數(shù)據(jù)刷回到磁盤上去,這樣做可以確保從磁盤上讀到的是最新的數(shù)據(jù)。這里的 direct_IO 方法最終會對應(yīng)到 __blockdev_direct_IO() 函數(shù)上去。__blockdev_direct_IO() 函數(shù)的代碼如下所示:

ssize_t 
__blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode, 
 struct block_device *bdev, const struct iovec *iov, loff_t offset, 
 unsigned long nr_segs, get_block_t get_block, dio_iodone_t end_io, 
 int dio_lock_type) 
{ 
 int seg; 
 size_t size; 
 unsigned long addr; 
 unsigned blkbits = inode->i_blkbits; 
 unsigned bdev_blkbits = 0; 
 unsigned blocksize_mask = (1 << blkbits) - 1; 
 ssize_t retval = -EINVAL; 
 loff_t end = offset; 
 struct dio *dio; 
 int release_i_mutex = 0; 
 int acquire_i_mutex = 0; 
 
 if (rw & WRITE) 
 rw = WRITE_SYNC; 
 
 if (bdev) 
 bdev_blkbits = blksize_bits(bdev_hardsect_size(bdev)); 
 
 if (offset & blocksize_mask) { 
 if (bdev) 
 blkbits = bdev_blkbits; 
 blocksize_mask = (1 << blkbits) - 1; 
 if (offset & blocksize_mask) 
 goto out; 
 } 
 
 for (seg = 0; seg < nr_segs; seg++) { 
 addr = (unsigned long)iov[seg].iov_base; 
 size = iov[seg].iov_len; 
 end += size; 
 if ((addr & blocksize_mask) || (size & blocksize_mask)) { 
 if (bdev) 
 blkbits = bdev_blkbits; 
 blocksize_mask = (1 << blkbits) - 1; 
 if ((addr & blocksize_mask) || (size & blocksize_mask)) 
 goto out; 
 } 
 } 
 
 dio = kmalloc(sizeof(*dio), GFP_KERNEL); 
 retval = -ENOMEM; 
 if (!dio) 
 goto out; 
 dio->lock_type = dio_lock_type; 
 if (dio_lock_type != DIO_NO_LOCKING) { 
 if (rw == READ && end > offset) { 
 struct address_space *mapping; 
 
 mapping = iocb->ki_filp->f_mapping; 
 if (dio_lock_type != DIO_OWN_LOCKING) { 
 mutex_lock(&inode->i_mutex); 
 release_i_mutex = 1; 
 } 
 
 retval = filemap_write_and_wait_range(mapping, offset, 
 end - 1); 
 if (retval) { 
 kfree(dio); 
 goto out; 
 } 
 
 if (dio_lock_type == DIO_OWN_LOCKING) { 
 mutex_unlock(&inode->i_mutex); 
 acquire_i_mutex = 1; 
 } 
 } 
 
 if (dio_lock_type == DIO_LOCKING) 
 down_read_non_owner(&inode->i_alloc_sem); 
 } 
 
 dio->is_async = !is_sync_kiocb(iocb) && !((rw & WRITE) && 
 (end > i_size_read(inode))); 
 
 retval = direct_io_worker(rw, iocb, inode, iov, offset, 
 nr_segs, blkbits, get_block, end_io, dio); 
 
 if (rw == READ && dio_lock_type == DIO_LOCKING) 
 release_i_mutex = 0; 
 
out: 
 if (release_i_mutex) 
 mutex_unlock(&inode->i_mutex); 
 else if (acquire_i_mutex) 
 mutex_lock(&inode->i_mutex); 
 return retval; 
}

該函數(shù)將要讀或者要寫的數(shù)據(jù)進(jìn)行拆分,并檢查緩沖區(qū)對齊的情況。本文在前邊介紹 open() 函數(shù)的時候指出,使用直接 I/O 讀寫數(shù)據(jù)的時候必須要注意緩沖區(qū)對齊的問題,從上邊的代碼可以看出,緩沖區(qū)對齊的檢查是在 __blockdev_direct_IO() 函數(shù)里邊進(jìn)行的。用戶地址空間的緩沖區(qū)可以通過 iov 數(shù)組中的 iovec 描述符確定。直接 I/O 的讀操作或者寫操作都是同步進(jìn)行的,也就是說,函數(shù) __blockdev_direct_IO() 會一直等到所有的 I/O 操作都結(jié)束才會返回,因此,一旦應(yīng)用程序 read() 系統(tǒng)調(diào)用返回,應(yīng)用程序就可以訪問用戶地址空間中含有相應(yīng)數(shù)據(jù)的緩沖區(qū)。但是,這種方法在應(yīng)用程序讀操作完成之前不能關(guān)閉應(yīng)用程序,這將會導(dǎo)致關(guān)閉應(yīng)用程序緩慢。

直接I/O 優(yōu)點(diǎn)

最大的優(yōu)點(diǎn)就是減少操作系統(tǒng)緩沖區(qū)和用戶地址空間的拷貝次數(shù)。降低了CPU的開銷,和內(nèi)存帶寬。對于某些應(yīng)用程序來說簡直是福音,將會大大提高性能。

直接I/O 缺點(diǎn)

直接IO并不總能讓人如意。直接IO的開銷也很大,應(yīng)用程序沒有控制好讀寫,將會導(dǎo)致磁盤讀寫的效率低下。磁盤的讀寫是通過磁頭的切換到不同的磁道上讀取和寫入數(shù)據(jù),如果需要寫入數(shù)據(jù)在磁盤位置相隔比較遠(yuǎn),就會導(dǎo)致尋道的時間大大增加,寫入讀取的效率大大降低。

總結(jié)

直接IO方式確實(shí)能夠減少CPU的使用率以及內(nèi)存帶寬的占用,但是有時候也會造成性能的影響。所以在使用直接IO之前一定要清楚它的原理,只有在各項(xiàng)都清晰的情況下,才考慮使用。本人只是介紹了原理,如想深入,建議參考內(nèi)核相關(guān)文檔。
 

分享到:
標(biāo)簽:Linux
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定