1. 程式人生 > >Linux的檔案IO使用

Linux的檔案IO使用

1. Linux 中 IO 的概念介紹

所有的 I/O 操作都是通過讀檔案或者寫檔案來完成的。在這裡,把所有的外圍裝置,包括
鍵盤和顯示器,都看成是檔案系統中的檔案

2. 什麼是快取 I/O

快取 I/O 又被稱作標準 I/O,大多數檔案系統的預設 I/O 操作都是快取 I/O。在 Linux 的
快取 I/O 機制中,作業系統會將 I/O 的資料快取在檔案系統的頁快取( page cache )中,
也就是說,資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷
貝到應用程式的地址空間。快取 I/O 有以下這些優點:
快取 I/O 使用了作業系統核心緩衝區,在一定程度上分離了應用程式空間和實際的物理
裝置。


快取 I/O 可以減少讀盤的次數,從而提高效能。
當應用程式嘗試讀取某塊資料的時候,如果這塊資料已經存放在了頁快取中,那麼這塊數
據就可以立即返回給應用程式,而不需要經過實際的物理讀盤操作。當然,如果資料在應用程
序讀取之前並未被存放在頁快取中,那麼就需要先將資料從磁碟讀到頁快取中去。對於寫操作
來說,應用程式也會將資料先寫到頁快取中去,資料是否被立即寫到磁碟上去取決於應用程式
所採用的寫操作機制:如果使用者採用的是同步寫機制( synchronous writes ), 那麼資料會立
即被寫回到磁碟上,應用程式會一直等到資料被寫完為止;如果使用者採用的是延遲寫機制
( deferred writes ),那麼應用程式就完全不需要等到資料全部被寫回到磁碟,資料只要被寫
到頁快取中去就可以了。在延遲寫機制的情況下,作業系統會定期地將放在頁快取中的資料刷
到磁碟上。與非同步寫機制( asynchronous writes )不同的是,延遲寫機制在資料完全寫到磁
快取 I/O 又被稱作標準 I/O,大多數檔案系統的預設 I/O 操作都是快取 I/O。在 Linux 的
快取 I/O 機制中,作業系統會將 I/O 的資料快取在檔案系統的頁快取( page cache )中,
也就是說,資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷
貝到應用程式的地址空間。快取 I/O 有以下這些優點:
快取 I/O 使用了作業系統核心緩衝區,在一定程度上分離了應用程式空間和實際的物理
裝置。
快取 I/O 可以減少讀盤的次數,從而提高效能。

當應用程式嘗試讀取某塊資料的時候,如果這塊資料已經存放在了頁快取中,那麼這塊數
據就可以立即返回給應用程式,而不需要經過實際的物理讀盤操作。當然,如果資料在應用程
序讀取之前並未被存放在頁快取中,那麼就需要先將資料從磁碟讀到頁快取中去。對於寫操作
來說,應用程式也會將資料先寫到頁快取中去,資料是否被立即寫到磁碟上去取決於應用程式
所採用的寫操作機制:如果使用者採用的是同步寫機制( synchronous writes ), 那麼資料會立
即被寫回到磁碟上,應用程式會一直等到資料被寫完為止;如果使用者採用的是延遲寫機制
( deferred writes ),那麼應用程式就完全不需要等到資料全部被寫回到磁碟,資料只要被寫
到頁快取中去就可以了。在延遲寫機制的情況下,作業系統會定期地將放在頁快取中的資料刷
到磁碟上。與非同步寫機制( asynchronous writes )不同的是,延遲寫機制在資料完全寫到磁碟
上的時候不會通知應用程式,而非同步寫機制在資料完全寫到磁碟上的時候是會返回給應用程
序的。所以延遲寫機制本身是存在資料丟失的風險的,而非同步寫機制則不會有這方面的擔心

3 快取 I/O 的缺點

在快取 I/O 機制中,DMA 方式可以將資料直接從磁碟讀到頁快取中,或者將資料從頁緩
存直接寫回到磁碟上,而不能直接在應用程式地址空間和磁碟之間進行資料傳輸,這樣的話,
資料在傳輸過程中需要在應用程式地址空間和頁快取之間進行多次資料拷貝操作,這些資料拷
貝操作所帶來的 CPU 以及記憶體開銷是非常大的。
對於某些特殊的應用程式來說,避開作業系統核心緩衝區而直接在應用程式地址空間和磁
盤之間傳輸資料會比使用作業系統核心緩衝區獲取更好的效能

在所有的 linux 系統中,如果需要對檔案的進行操作,只要包含如下 4 個頭檔案即可。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
上面四個標頭檔案中包含了開啟,關閉,建立,讀檔案,寫檔案的函式,還有標誌位,以及
在不同 32 位以及 64 位系統下資料長度的巨集變數定義。

1 open函式

使用 open 函式的時候會返回一個檔案控制代碼,檔案控制代碼是檔案的唯一識別符 ID。對檔案的
操作必須從讀取控制代碼開始。
先來看一下函式 open 的兩個原型。
int open(const char *path, int oflags);
int open(const char *path, int oflags,mode_t mode);
兩個引數的 open 函式主要用於建立檔案(和create、write、read函式一起使用)
open 函式可以建立一個到檔案或者裝置的訪問路徑。在開啟或建立檔案時可以制定檔案
的屬性及使用者的許可權等各種引數。
第一個引數 path 表示:路徑名或者檔名。路徑名為絕對路徑名,例如開發板中的 led
驅動的裝置幾點/dev/leds。
第二個引數 oflags 表示:開啟檔案所採取的動作。

下面三個選項是必須選擇其中之一的。
O_RDONLY 檔案只讀
O_WRONLY 檔案只寫
O_RDWR 檔案可讀可寫
下面是可以任意選擇的。
O_APPEND 每次寫操作都寫入檔案的末尾
O_CREAT 如果指定檔案不存在,則建立這個檔案
O_EXCL 如果要建立的檔案已存在,則返回 -1,並且修改 errno 的值
O_TRUNC 如果檔案存在,並且以只寫/讀寫方式開啟,則清空檔案全部內容
O_NOCTTY 如果路徑名指向終端裝置,不要把這個裝置用作控制終端。
O_NONBLOCK 如果路徑名指向 FIFO/塊檔案/字元檔案,則把檔案的開啟和後繼 I/O
設定為非阻塞模式(nonblocking mode)
O_NDELAY 和 O_NONBLOCK 功能類似,呼叫 O_NDELAY 和使用的
O_NONBLOCK 功能是一樣的。
第三個引數 mode 表示:設定建立檔案的許可權(一般為0777 可讀可寫)。
S_IRUSR,S_IWUSER,S_IXUSR,S_IRGRP,S_IWGRP,S_IXGRP,S_IROTH,S_IWOTH,S_IXOTH.
其中 R:讀,W:寫,X:執行,USR:檔案所屬的使用者,GRP:檔案所屬的組,OTH:其
他使用者。第三個引數可以直接使用引數代替。

下面是簡單的open.c

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

void main()
{
 int fd;
 char *leds="/dev/leds";    //節點存在
 char *test1="/bin/test1";  //節點不存在
 char *test2="/bin/test2";  //節點不存在

 if((fd=open(leds,O_RDWR|O_NOCTTY|O_NDELAY))<0)   //列印正確
   printf("open %s failed!\n",leds);
 printf("\n%s fd is %d \n",leds,fd);

 if((fd=open(test1,O_RDWR,0777))<0)    //列印錯誤
  printf("open %s failed!\n",test1);
 printf("%s fd is %d\n",test1,fd);
 
 if((fd=open(test2,O_RDWR|O_CREAT,0777))<0)  //列印正確 表明建立成功
   printf("open %s failed!\n",test2);
 printf("%s fd is %d\n",test2,fd);
}

在linux 下使用交叉編譯 arm-none-linux-gnueabi-gcc -o open open.c -static

2.creat函式

關於 creat 函式,首先這個單詞並不是表示建立的意思,建立的英文單詞是“create”,
這是早期的一個小的拼寫錯誤,卻一直沿用下來。
在介紹 open 函式的時候,可以看到 open 函式有兩種形式,一個是兩個引數一個是三個
引數,早期的時候 open 只有三個引數的形式,三個引數的形式會導致 open 函式無法開啟一
個未建立的檔案,也就是無法建立檔案,所以就有了這個 creat 函式。
現在 creat 函式可以完全用 open 替代,考慮到在閱讀程式碼的時候可能會碰到,所以簡單
介紹一下。
creat 函式原型如下。
int creat(const char * pathname, mode_t mode);
creat 函式只有兩個引數,引數的含義和 open 類似。
大家看到這個函式的時候知道它是建立檔案的就成,在寫程式碼的時候完全可以用 open 代
替。

3.close函式

任何一個檔案在操作完成之後都需要關閉,這個時候需要呼叫 close 函式。
呼叫 close 函式之後,會取消 open 函式建立的對映關係,控制代碼將不再有效,佔用的空間
將被系統釋放。
close 函式在標頭檔案“#include <unistd.h>”中,close 函式的使用和引數都比較簡單.
int close(int fd);
引數 fd,使用 open 函式開啟檔案之後返回的控制代碼。
返回值,一般很少使用 close 的返回值。

4.write函式

對檔案進行寫操作,write 函式使用的比較多
write 函式在標頭檔案“#include <unistd.h>”中。
函式原型為 ssize_t write(int fd,const void buf,size_t count)
引數 fd,使用 open 函式開啟檔案之後返回的控制代碼。
引數
buf,需要寫入的資料。
引數 count,將引數*buf 中最多 count 個位元組寫入檔案中。
返回值為 ssize 型別,出錯會返回-1,其它數值表示實際寫入的位元組數。

write.c

#include<stdio.h>

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>



void main()
{
  int fd;
  char *testwriter="/bin/testwrite";
  ssize_t length_w;
  char buffer_writer[]="Hello Write Function!";

  if((fd=open(testwriter,O_RDWR|O_CREAT,0777))<0)
    printf("open %s failed\n",testwriter);
  
  //將Buffer 寫入
  length_w=write(fd,buffer_writer,strlen(buffer_writer));
  if(length_w==-1)
   perror("write");
  else
   printf("Write Function OK!\n");

 close(fd);    //將檔案關閉
}

執行之後將會看到 “Write Function OK!”。

5.read函式

對檔案進行寫操作,read 函式使用的比較多。
read 函式在標頭檔案“#include <unistd.h>”中。
函式原型為 ssize_t read(int fd,void buf,size_t len)
引數 fd,使用 open 函式開啟檔案之後返回的控制代碼。
引數
buf,讀出的資料儲存的位置。
引數 len,每次最多讀 len 個位元組。
返回值為 ssize 型別,出錯會返回-1,其它數值表示實際寫入的位元組數,返回值大於 0 小
於 len 的數值都是正常的。

read.c

#include<stdio.h>

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

#define MAX_SIZE 1000



void main()
{
 int fd;
 ssize_t length_w,length_r=MAX_SIZE,ret;
 char *testwrite="/bin/testwrite";
 char buffer_write[]="Hello Read Function!";
 char buffer_read[MAX_SIZE];

 if((fd=open(testwrite,O_RDWR|O_CREAT,0777))<0) //使用 open 函式開啟或者新建"/bin/testwrite"檔案
  printf("open %s is failed!",testwrite);
 
 length_w=write(fd,buffer_write,strlen(buffer_write)); //使用 write 函式將 buffer 中的內容寫到"/bin/testwrite"檔案中。
 if(length_w==-1)
  perror("write");
 else
  printf("Write Function OK!\n");
 
 close(fd);

  if((fd=open(testwrite,O_RDWR|O_CREAT,0777))<0)
   printf("open %s is failed!",testwrite);
  if(ret=read(fd,buffer_read,strlen(buffer_write))) //使用 read 函式,將"/bin/testwrite"檔案中的內容讀出來
   perror("read");
 printf("file content is %s \n",buffer_read); //使用列印函式 printf 列印 read 函式讀出的資料
 close(fd); //呼叫 close 函式關閉開啟的檔案,程式結束

}