天天看點

linux 下的檔案I/O方式标準通路檔案的方式Diect IOsync函數和fsync函數

标準通路檔案的方式

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不支援該功能)

繼續閱讀