linux 下的文件I/O方式

標準訪問文件的方式

Linux 中,這種訪問文件的方式是通過兩個系統調用實現的:read() write()。當應用程序調用 read()系統調用讀取一塊數據的時候,如果該塊數據已經在內存中了,那麼就直接從內存中讀出該數據並返回給應用程序;如果該塊數據不在內存中,那麼數據會被從磁盤上讀到頁高緩存中去,然後再從頁緩存中拷貝到用戶地址空間中去。如果一個進程讀取某個文件,那麼其他進程就都不可以讀取或者更改該文件;對於寫數據操作來說,當一個進程調用了 write()系統調用往某個文件中寫數據的時候,數據會先從用戶地址空間拷貝到操作系統內核地址空間的頁緩存中去,然後才被寫到磁盤上。但是對於這種標準的訪問文件的方式來說,在數據被寫到頁緩存中的時候,write()系統調用就算執行完成,並不會等數據完全寫入到磁盤上。Linux在這裏採用的是我們前邊提到的延遲寫機制( deferred writes)。

Diect IO

凡是通過直接 I/O方式進行數據傳輸,數據均直接在用戶地址空間的緩衝區和磁盤之間直接進行傳輸,完全不需要頁緩存的支持。操作系統層提供的緩存往往會使應用程序在讀寫數據的時候獲得更好的性能,但是對於某些特殊的應用程序,比如說數據庫管理系統這類應用,他們更傾向於選擇他們自己的緩存機制,因爲數據庫管理系統往往比操作系統更瞭解數據庫中存放的數據,數據庫管理系統可以提供一種更加有效的緩存機制來提高數據庫中數據的存取性能。

直接I/O 的優點

直接 I/O 最主要的優點就是通過減少操作系統內核緩衝區和應用程序地址空間的數據拷貝次數,降低了對文件讀取和寫入時所帶來的CPU 的使用以及內存帶寬的佔用。這對於某些特殊的應用程序,比如自緩存應用程序來說,不失爲一種好的選擇。如果要傳輸的數據量很大,使用直接I/O 的方式進行數據傳輸,而不需要操作系統內核地址空間拷貝數據操作的參與,這將會大大提高性能。

直接I/O 潛在可能存在的問題

直接 I/O 並不一定總能提供令人滿意的性能上的飛躍。設置直接 I/O 的開銷非常大,而直接 I/O 又不能提供緩存 I/O 的優勢。緩存 I/O 的讀操作可以從高速緩衝存儲器中獲取數據,而直接I/O 的讀數據操作會造成磁盤的同步讀,這會帶來性能上的差異, 並且導致進程需要較長的時間才能執行完;對於寫數據操作來說,使用直接I/O 需要write() 系統調用同步執行,否則應用程序將會不知道什麼時候才能夠再次使用它的I/O 緩衝區。與直接I/O 讀操作類似的是,直接I/O 寫操作也會導致應用程序關閉緩慢。所以,應用程序使用直接I/O 進行數據傳輸的時候通常會和使用異步I/O 結合使用。


Linux IO機制演變

一般來說,recv函數將會阻塞如果當前沒有數據可用,同樣,send函數將會擁塞當socket的出口隊列沒有足夠的空間來傳送信息。這些都會改變當我們處於非擁塞模式,這樣的話,如果遇到擁塞就會失敗,可以使用poll或者select函數來決定何時能接收或傳輸數據。

Socket機制有其特殊的處理異步I/O的方法,但是並不是標準的。有時稱作是“signal-based I/O”來區別於實時擴展的異步I/O。可以使用SIGIO信號來告知我們:從一個socket上讀數據和何時socket的寫隊列是可用的。分爲以下兩步:

1 建立socket的所有者,這樣信號就知道發送給哪個進程

2 當I/O操作不再擁塞時通知這個socket

處理fcntl和ioctl函數,加上參數

 

Linux的I/O機制經歷了一下幾個階段的演進:

1.同步阻塞I/O:用戶進程進行I/O操作,一直阻塞到I/O操作完成爲止。

2.同步非阻塞I/O:用戶程序可以通過設置文件描述符的屬性O_NONBLOCK,I/O操作可以立即返回,但是並不保證I/O操作成功。

3.異步事件阻塞I/O:用戶進程可以對I/O事件進行阻塞,但是I/O操作並不阻塞。通過select/poll/epoll等函數調用來達到此目的。

4.異步時間非阻塞I/O: 也叫異步I/O(AIO),用戶程序可以通過向內核發出I/O請求命令,不用等待I/O事件真正發生,可以繼續做另外的事情,等I/O操作完成,內核會通過函數回調或者信號機制通知用戶進程。這樣很大程度提高了系統吞吐量。

相關文檔:

http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt

ceph的新bluestore組件,自己實現

bluefs、blockdevice驅動層

啓動blockdevice用到的是aio庫

5、system io 和stream io

System io調用系統api,進入內核空間

Stream io: 調用c標準庫的api,數據在應用層的buffer中,不會調用系統的io api.


文件IO原子操作

在多個進程同時寫一個文件的時候,在沒有O_APPEND參數時,

只好用lseek和write兩個系統調用來實現這個功能現在假設進程A和B分別寫文件file,

A在offset爲1000處寫入100字節,B在offset爲1000處寫入50字節。

執行A得到文件尾的偏移量爲1000,此時內核轉而執行進程B,在偏移量爲1000(文件尾)寫入50字節,

現在的偏移量爲1050(文件的屬性也擴展了)。再當系統反過來執行進程A的時候,

這時A的寫入的偏移量依然爲1000,寫入100字節,這時會將進程B所寫入的50字節覆蓋。


內核很有可能在兩個系統調用之間暫停這個進程,從而進入下一個進程。

這樣任何非單一系統調用都不能被稱之爲原子操作。就以上的例子中,當我們打開文件的時候加上參數O_APPEND,那麼在每次寫之前都會自動的定位到文文件尾,

這樣我們不必在每一次write操作之前調用lseek操作。


這裏列舉pread和pwrite函數,

pread函數相當於在read函數之後加上lseek來定位偏移量。它們並不改變文件的指針

另外,如果我們要打開一個文件,同時使用O_CREAT和 O_EXCL兩個參數在打開的文件已經存在的前提下會報錯。檢查文件是否存在和創建新的文件是一個原子操作。如果我們沒有這個原子操作,那麼可能的實現方法是

if ((fd =open(pathname,O_WRONLY)) < 0) {
if (errno == ENOENT) {
  if ((fd= create(pathname,mode) < 0){
    err_sys("create error ");
  }
}  else
  err_sys(" open error ");
}

這裏有一個問題,那就是在open和create兩個操作之間內核中的其他進程或者創建了這個文件,或者往文件寫入了內容,新的創建會把文件原來的內容給擦除掉,這樣會導致文件的不一致性。

總體來講,原子操作就是這個操作可能由多步組成,或者全做或者全不做

pwrite函數原型:

ssize_t pwrite(int fildes, const void *buf, size_tnbyte,off_t offset); 


相比write,多了個lseek定位到offset指定位置的功能


sync函數和fsync函數

   傳統的UNIX系統的應用程序大部分的磁盤I/O都會經過buffer緩存或者頁緩存。當我們寫一個文件數據的時候,內核會將它寫入其中的一個緩衝區排隊,並等待在之後的某個時間寫入磁盤,這就是通常所說的延遲寫。

  sync只是將所有修改的塊都放到等待衝向磁盤的隊列中,但是並不等到這些塊寫入磁盤。

  fsync是將文件描述符fd標誌的文件的緩衝區排隊寫入磁盤,並且等待寫完後才返回。包括文件的元數據。

  fdatasync只是將數據同步到磁盤,在必要的情況下才同步元數據,比如文件大小變化了。(目前linux不支持該功能)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章