久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

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

161次閱讀
沒有評論

共計 7757 個字符,預計需要花費 20 分鐘才能閱讀完成。

這篇文章主要為大家展示了“Linux 中直接 I / O 原理是什么”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓丸趣 TV 小編帶領大家一起研究并學習一下“Linux 中直接 I / O 原理是什么”這篇文章吧。

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

緩存 I/O 又被稱作標準 I/O,大多數文件系統的默認 I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操作系統會將 I/O 的數據緩存在文件系統的頁緩存(page cache)中,也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。寫的過程就是數據流反方向。緩存 I/O 有以下這些優點:

緩存 I/O 使用了操作系統內核緩沖區,在一定程度上分離了應用程序空間和實際的物理設備。

緩存 I/O 可以減少讀盤的次數,從而提高性能。

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

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

同步機制,數據會立即被寫到磁盤中,直到數據寫完,寫接口才返回;

延遲機制:寫接口立即返回,操作系統會定期地將頁緩存中的數據刷到硬盤。所以這個機制會存在丟失數據的風險。想象下寫接口返回的時候,頁緩存的數據還沒刷到硬盤,正好斷電。對于應用程序來說,認為數據已經在硬盤中。

緩存 I / O 的寫操作

緩存 I/O 的缺點

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

對于某些特殊的應用程序來說,能夠繞開內核緩沖區能夠獲取更好的性能,這就是直接 I / O 出現的意義。

直接 I / O 寫操作

直接 I /O 介紹

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

直接 I /O 設計與實現

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

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

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

當應用程序需要直接訪問文件而不經過操作系統頁高速緩沖存儲器的時候,它打開文件的時候需要指定  O_DIRECT  標識符。

操作系統內核中處理 open() 系統調用的內核函數是  sys_open(),sys_open() 會調用  do_sys_open() 去處理主要的打開操作。它主要做了三件事情:

調用 getname() 從進程地址空間中讀取文件的路徑名;

do_sys_open() 調用 get_unused_fd() 從進程的文件表中找到一個空閑的文件表指針,相應的新文件描述符就存放在本地變量 fd 中;

函數 do_filp_open() 會根據傳入的參數去執行相應的打開操作。

下面列出了操作系統內核中處理 open() 系統調用的一個主要函數關系圖。

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

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

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); } }

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

下邊我們來看一下當進程通過 read() 系統調用讀取一個已經設置了 O_DIRECT 標識符的文件的時候,系統都做了哪些處理。函數 read() 的原型如下所示:

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

操作系統中處理 read() 函數的入口函數是 sys_read(),其主要的調用函數關系圖如下:

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

函數 sys_read() 從進程中獲取文件描述符以及文件當前的操作位置后會調用 vfs_read() 函數去執行具體的操作過程,而 vfs_read() 函數最終是調用了 file 結構中的相關操作去完成文件的讀操作,即調用了 generic_file_read() 函數,其代碼如下所示:

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; }

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

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; }

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

rw:操作類型,可以是 READ 或者 WRITE

iocb:指針,指向 kiocb 描述符

iov:指針,指向 iovec 描述符數組

offset:file 結構偏移量

nr_segs:iov 數組中 iovec 的個數

函數 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; }

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

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; }

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

直接 I /O 優點

*** 的優點就是減少操作系統緩沖區和用戶地址空間的拷貝次數。降低了 CPU 的開銷,和內存帶寬。對于某些應用程序來說簡直是福音,將會大大提高性能。

直接 I /O 缺點

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

以上是“Linux 中直接 I / O 原理是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注丸趣 TV 行業資訊頻道!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-25發表,共計7757字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 万州区| 新民市| 黔西县| 平度市| 商丘市| 兰西县| 滦南县| 德令哈市| 嵩明县| 晴隆县| 金堂县| 达日县| 尼玛县| 贵阳市| 镇江市| 湘潭县| 赞皇县| 重庆市| 城口县| 准格尔旗| 南靖县| 南丰县| 维西| 同心县| 巴青县| 信宜市| 西藏| 集安市| 平果县| 宜兴市| 尼木县| 昌图县| 尚志市| 三台县| 蒙山县| 赤壁市| 五峰| 溆浦县| 平陆县| 海淀区| 南乐县|