1. 程式人生 > >read和write函數

read和write函數

次循環 ring 讀取 時間片 超時時間 jsb 排序 結合 通過

1、write()

函數定義:ssize_t write (int fd, const void * buf, size_t count); 

函數說明:write()會把參數buf所指的內存寫入count個字節到參數放到所指的文件內。

返回值:如果順利write()會返回實際寫入的字節數。當有錯誤發生時則返回-1,錯誤代碼存入errno中。

附加說明:

(1)write()函數返回值一般無0,只有當如下情況發生時才會返回0:write(fp, p1+len, (strlen(p1)-len)中第三參數為0,此時write()什麽也不做,只返回0。

(2)write()函數從buf寫數據到fd中時,若buf中數據無法一次性讀完,那麽第二次讀buf中數據時,其讀位置指針(也就是第二個參數buf)不會自動移動,需要程序員編程控制,

而不是簡單的將buf首地址填入第二參數即可。如可按如下格式實現讀位置移動:write(fp, p1+len, (strlen(p1)-len)。 這樣write第二次循環時變會從p1+len處寫數據到fp, 之後的也

由此類推,直至(strlen(p1)-len變為0。

(3)在write一次可以寫的最大數據範圍內(貌似是BUFSIZ ,8192),第三參數count大小最好為buf中數據的大小,以免出現錯誤。(經過筆者再次試驗,write一次能夠寫入的並不只有8192這麽多,筆者嘗試一次寫入81920000,結果也是可以,看來其一次最大寫入數據並不是8192,但內核中確實有BUFSIZ這個參數,具體指什麽還有待研究)

#include <string.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
  char *p1 = "This is a c test code";
  volatile int len = 0;
 
  int fp = open("/home/test.txt", O_RDWR|O_CREAT);
  for(;;)
  {
     int n;
 
     if((n=write(fp, p1+len, (strlen(p1)-len)))== 0)   //if((n=write(fp, p1+len, 3)) == 0) 
{ //strlen(p1) = 21 printf("n = %d \n", n); break; } len+=n; } return 0; }

此程序中的字符串"This is a c test code"有21個字符,經筆者親自試驗,若write時每次寫3個字節,雖然可以將p1中數據寫到fp中,但文件test.txt中會帶有很多亂碼。唯一正確的做法還是將第三參數設為(strlen(p1) - len,這樣當write到p1末尾時(strlen(p1) - len將會變為0,此時符合附加說明(1)中所說情況,write返回0, write結束。

2、read()

函數定義:ssize_t read(int fd, void * buf, size_t count);

函數說明:read()會把參數fd所指的文件傳送count 個字節到buf 指針所指的內存中。

返回值:返回值為實際讀取到的字節數, 如果返回0, 表示已到達文件尾或是無可讀取的數據。若參數count 為0, 則read()不會有作用並返回0。

註意:read時fd中的數據如果小於要讀取的數據,就會引起阻塞。

對一個管道的read只要管道中有數據,立馬返回,不必等待達到所請求的字節數

3、(非)阻塞I/O的概念

現在明確一下阻塞(Block)這個概念。當進程調用一個阻塞的系統函數時,該進程被置於睡眠(Sleep)狀態,這時內核調度其它進程運行,直到該進程等待的事件發生了(比如網絡上接收到數據包,或者調用sleep指定的睡眠時間到了)它才有可能繼續運行。與睡眠狀態相對的是運行(Running)狀態,在Linux內核中,處於運行狀態的進程分為兩種情況:

1、正在被調度執行。CPU處於該進程的上下文環境中,程序計數器(eip)裏保存著該進程的指令地址,通用寄存器裏保存著該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。

2、就緒狀態。該進程不需要等待什麽事件發生,隨時都可以執行,但CPU暫時還在執行另一個進程,所以該進程在一個就緒隊列中等待被內核調度。系統中可能同時有多個就緒的進程,那麽該調度誰執行呢?內核的調度算法是基於優先級和時間片的,而且會根據每個進程的運行情況動態調整它的優先級和時間片,讓每個進程都能比較公平地得到機會執行,同時要兼顧用戶體驗,不能讓和用戶交互的進程響應太慢。

如果在open一個設備時指定了O_NONBLOCK標誌,read/write就不會阻塞。以read為例,如果設備暫時沒有數據可讀就返回-1,同時置errno為EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這裏(would block,虛擬語氣),事實上並沒有阻塞而是直接返回錯誤,調用者應該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這裏死等,這樣可以同時監視多個設備:

while(true)
{
    非阻塞read(設備1);
    if(設備1有數據到達)
        處理數據; 
        
    非阻塞read(設備2);
    if(設備2有數據到達)
        處理數據; 

    ...
}

如果read(設備1)是阻塞的,那麽只要設備1沒有數據到達就會一直阻塞在設備1的read調用上,即使設備2有數據到達也不能處理,使用非阻塞I/O就可以避免設備2得不到及時處理。

非阻塞I/O有一個缺點,如果所有設備都一直沒有數據到達,調用者需要反復查詢做無用功,如果阻塞在那裏,操作系統可以調度別的進程執行,就不會做無用功了。在使用非阻塞I/O時,通常不會在一個while循環中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。

while(true)
{
    非阻塞read(設備1);
    if(設備1有數據到達)
        處理數據; 
        
    非阻塞read(設備2);
    if(設備2有數據到達)
        處理數據; 

    ...
  sleep(n);  
}

這樣做的問題是,設備1有數據到達時可能不能及時處理,最長需延遲n秒才能處理,而且反復查詢還是做了很多無用功。而select/poll/epoll 等函數可以阻塞地同時監視多個設備,還可以設定阻塞等待的超時時間,從而圓滿地解決了這個問題。

整個write過程

技術分享圖片

3.下面是整個write過程

  • glibc write是將app_buffer->libc_buffer->page_cache
  • write是將app_buffer->page_cache
  • mmap可以直接獲取page_cache直寫
  • write+O_DIRECT的話將app_buffer寫到io_queue裏面
    • io_queue一方面將寫鄰近扇區的內容進行merge,另外一方面進行排序確保磁頭和磁 盤旋轉最少。
    • io_queue的工作也需要結合IO調度算法。不過這些僅僅對於physical disk有效。
    • 對於ssd而言的話,因為完全是隨機寫,基本沒有調度算法。
  • driver(filesystem module)通過DMA寫入disk_cache之後(使用fsync就可以強制刷新)到disk上面了。
  • 直接操作設備(RAW)方式直接寫disk_cache.

O_DIRECT 和 RAW設備最根本的區別是O_DIRECT是基於文件系統的,也就是在應用層來看,其操作對象是文件句柄,內核和文件層來看,其操作是基於inode和數據塊,這些概念都是和ext2/3的文件系統相關,寫到磁盤上最終是ext3文件。而RAW設備寫是沒有文件系統概念,操作的是扇區號,操作對象是扇區,寫出來的東西不一定是ext3文件(如果按照ext3規則寫就是ext3文件)。一般基於O_DIRECT來設計優化自己的文件模塊,是不滿系統的cache和調度策略,自己在應用層實現這些,來制定自己特有的業務特色文件讀寫。但是寫出來的東西是ext3文件,該磁盤卸下來,mount到其他任何linux系統上,都可以查看。而基於RAW設備的設計系統,一般是不滿現有ext3的諸多缺陷,設計自己的文件系統。自己設計文件布局和索引方式。舉個極端例子:把整個磁盤做一個文件來寫,不要索引。這樣沒有inode限制,沒有文件大小限制,磁盤有多大,文件就能多大。這樣的磁盤卸下來,mount到其他linux系統上,是無法識別其數據的。兩者都要通過驅動層讀寫;在系統引導啟動,還處於實模式的時候,可以通過bios接口讀寫raw設備。

操作系統為了提高文件讀寫效率,在內核層提供了讀寫緩沖區。對於磁盤的寫並不是立刻寫入磁盤, 而是首先寫入頁面緩沖區然後定時刷到硬盤上。但是這種機制降低了文件更新速度,並且如果系統發生故障 的話,那麽會造成部分數據丟失。這裏的3個sync函數就是為了這個問題的。

  • sync.是強制將所有頁面緩沖區都更新到磁盤上。
  • fsync.是強制將某個fd涉及到的頁面緩存更新到磁盤上(包括文件屬性等信息).
  • fdatasync.是強制將某個fd涉及到的數據頁面緩存更新到磁盤上。

read和write函數