溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Linux中直接I/O原理是什么

發(fā)布時(shí)間:2021-10-27 12:08:12 來源:億速云 閱讀:119 作者:小新 欄目:系統(tǒng)運(yùn)維

這篇文章主要為大家展示了“Linux中直接I/O原理是什么”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“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)會(huì)將 I/O 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中,也就是說,數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(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ù),從而提高性能。

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

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

  • 同步機(jī)制,數(shù)據(jù)會(huì)立即被寫到磁盤中,直到數(shù)據(jù)寫完,寫接口才返回;

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

Linux中直接I/O原理是什么

緩存I/O的寫操作

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

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

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

Linux中直接I/O原理是什么

直接I/O寫操作

直接I/O 介紹

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

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

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

下面主要介紹三個(gè)函數(shù):open(),read() 以及 write()。Linux 中訪問文件具有多樣性,所以這三個(gè)函數(shù)對(duì)于處理不同的文件訪問方式定義了不同的處理方法,本文主要介紹其與直接 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)頁高速緩沖存儲(chǔ)器的時(shí)候,它打開文件的時(shí)候需要指定 O_DIRECT 標(biāo)識(shí)符。

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

  1. 調(diào)用 getname() 從進(jìn)程地址空間中讀取文件的路徑名;

  2. do_sys_open() 調(diào)用 get_unused_fd() 從進(jìn)程的文件表中找到一個(gè)空閑的文件表指針,相應(yīng)的新文件描述符就存放在本地變量 fd 中;

  3. 函數(shù) do_filp_open() 會(huì)根據(jù)傳入的參數(shù)去執(zhí)行相應(yīng)的打開操作。

下面列出了操作系統(tǒng)內(nèi)核中處理 open() 系統(tǒng)調(diào)用的一個(gè)主要函數(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í)行的過程中會(huì)調(diào)用函數(shù) nameidata_to_filp(),而 nameidata_to_filp() 最終會(huì)調(diào)用 __dentry_open() 函數(shù),若進(jìn)程指定了 O_DIRECT 標(biāo)識(shí)符,則該函數(shù)會(huì)檢查直接 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)文件打開時(shí)指定了 O_DIRECT 標(biāo)識(shí)符,那么操作系統(tǒng)就會(huì)知道接下來對(duì)文件的讀或者寫操作都是要使用直接 I/O 方式的。

下邊我們來看一下當(dāng)進(jìn)程通過 read() 系統(tǒng)調(diào)用讀取一個(gè)已經(jīng)設(shè)置了 O_DIRECT 標(biāo)識(shí)符的文件的時(shí)候,系統(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)前的操作位置后會(huì)調(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 主要是用于存放兩個(gè)內(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;  }

上邊的代碼段主要是檢查了文件指針的值,文件的大小以及所請(qǐng)求讀取的字節(jié)數(shù)目等,之后,該函數(shù)調(diào)用 generic_file_direct_io(),并將操作類型 READ,描述符 iocb,描述符 iovec,當(dāng)前文件指針的值以及在描述符 io_vec 中指定的用戶地址空間緩沖區(qū)的個(gè)數(shù)等值作為參數(shù)傳給它。當(dāng) generic_file_direct_io() 函數(shù)執(zhí)行完成,函數(shù) __generic_file_aio_read()會(huì)繼續(xù)執(zhí)行去完成后續(xù)操作:更新文件指針,設(shè)置訪問文件 i 節(jié)點(diǎn)的時(shí)間戳;這些操作全部執(zhí)行完成以后,函數(shù)返回。 函數(shù) generic_file_direct_IO() 會(huì)用到五個(gè)參數(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 的個(gè)數(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() 對(duì) WRITE 操作類型進(jìn)行了一些特殊處理。除此之外,它主要是調(diào)用了 direct_IO 方法去執(zhí)行直接 I/O 的讀或者寫操作。在進(jìn)行直接 I/O 讀操作之前,先將頁緩存中的相關(guān)臟數(shù)據(jù)刷回到磁盤上去,這樣做可以確保從磁盤上讀到的是***的數(shù)據(jù)。這里的 direct_IO 方法最終會(huì)對(duì)應(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ū)對(duì)齊的情況。本文在前邊介紹 open() 函數(shù)的時(shí)候指出,使用直接 I/O 讀寫數(shù)據(jù)的時(shí)候必須要注意緩沖區(qū)對(duì)齊的問題,從上邊的代碼可以看出,緩沖區(qū)對(duì)齊的檢查是在 __blockdev_direct_IO() 函數(shù)里邊進(jìn)行的。用戶地址空間的緩沖區(qū)可以通過 iov 數(shù)組中的 iovec 描述符確定。直接 I/O 的讀操作或者寫操作都是同步進(jìn)行的,也就是說,函數(shù) __blockdev_direct_IO() 會(huì)一直等到所有的 I/O 操作都結(jié)束才會(huì)返回,因此,一旦應(yīng)用程序 read() 系統(tǒng)調(diào)用返回,應(yīng)用程序就可以訪問用戶地址空間中含有相應(yīng)數(shù)據(jù)的緩沖區(qū)。但是,這種方法在應(yīng)用程序讀操作完成之前不能關(guān)閉應(yīng)用程序,這將會(huì)導(dǎo)致關(guān)閉應(yīng)用程序緩慢。

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

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

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

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

以上是“Linux中直接I/O原理是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI