在Linux平臺下對文件編程可以使用兩類函數:(1)Linux操作系統文件API;(2)C語言I/O庫函數。前者依賴于Linux系統調用,后者實際上與操作系統是獨立的,因為在任何操作系統下,使用C語言I/O庫函數操作文件的方法都是相同的。本章將對這兩種方法進行實例講解。
1. 文件I/O操作
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
? fd=open("/dev/globalvar", O_RDWR, S_IRUSR|S_IWUSR); ? ? //可讀寫方式打開設備文件
S_IRUSR
Permits the file's owner to read it.
S_IWUSR
Permits the file's owner to write to it.
S_IRGRP
Permits the file's group to read it.
S_IWGRP
Permits the file's group to write to it.
S_ISDIR ( ) 目錄文件
S_ISCHR ( ) 字符特殊文件
S_ISBLK ( ) 塊特殊文件
S_ISFIFO ( ) 管道或F I F O
S_ISLNK ( ) 符號連接( P O S I X . 1或S V R 4無此類型)
S_ISSOC K ( ) 套接字(P O S I X . 1或S V R 4無此類型)
S_ISREG ( ) 普通文件
2.Linux 文件 API
Linux 的文件操作 API 涉及到創建、打開、讀寫和關閉文件。
創建
int creat(const char *filename, mode_t mode);
參數mode指定新建文件的存取權限,它同umask一起決定文件的最終權限(mode&umask),其中umask代表了文件在創建時需要去掉的一些存取權限。umask可通過系統調用umask()來改變:
int umask(int newmask);
該調用將umask設置為newmask,然后返回舊的umask,它只影響讀、寫和執行權限。
打開
int open(const char *pathname, int flags);?
int open(const char *pathname, int flags, mode_t mode);
open函數有兩個形式,其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認為在當前路徑下面),flags可以去下面的一個值或者是幾個值的組合:
標志含義
O_RDONLY以只讀的方式打開文件
O_WRONLY以只寫的方式打開文件
O_RDWR以讀寫的方式打開文件
O_APPEND以追加的方式打開文件
O_CREAT 創建一個文件
O_EXEC如果使用了O_CREAT而且文件已經存在,就會發生一個錯誤
O_NOBLOCK以非阻塞的方式打開一個文件
O_TRUNC如果文件已經存在,則刪除文件的內容
? ?
O_RDONLY、O_WRONLY、O_RDWR三個標志只能使用任意的一個。
如果使用了O_CREATE標志,則使用的函數是int open(const char *pathname,int flags,mode_t mode);這個時候我們還要指定mode標志,用來表示文件的訪問權限。mode可以是以下情況的組合:
標志含義
S_IRUSR 用戶可以讀
S_IWUSR 用戶可以寫
S_IXUSR 用戶可以執行
S_IRWXU用戶可以讀、寫、執行
S_IRGRP 組可以讀
S_IWGRP 組可以寫
S_IXGRP 組可以執行
S_IRWXG 組可以讀寫執行
S_IROTH 其他人可以讀
S_IWOTH 其他人可以寫
S_IXOTH 其他人可以執行
S_IRWXO其他人可以讀、寫、執行
S_ISUID 設置用戶執行ID
S_ISGID 設置組的執行ID
除了可以通過上述宏進行“或”邏輯產生標志以外,我們也可以自己用數字來表示,Linux總共用5個數字來表示文件的各種權限:第一位表示設置用戶ID;第二位表示設置組ID;第三位表示用戶自己的權限位;第四位表示組的權限;最后一位表示其他人的權限。每個數字可以取1(執行權限)、2(寫權限)、4(讀權限)、0(無)或者是這些值的和。例如,要創建一個用戶可讀、可寫、可執行,但是組沒有權限,其他人可以讀、可以執行的文件,并設置用戶ID位。那么,我們應該使用的模式是1(設置用戶ID)、0(不設置組ID)、7(1+2+4,讀、寫、執行)、0(沒有權限)、5(1+4,讀、執行)即10705:
open("test", O_CREAT, 10705);
上述語句等價于:
open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );
如果文件打開成功,open函數會返回一個文件描述符,以后對該文件的所有操作就可以通過對這個文件描述符進行操作來實現。
讀寫
在文件打開以后,我們才可對文件進行讀寫了,Linux中提供文件讀寫的系統調用是read、write函數:
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其中參數buf為指向緩沖區的指針,length為緩沖區的大小(以字節為單位)。函數read()實現從文件描述符fd所指定的文件中讀取length個字節到buf所指向的緩沖區中,返回值為實際讀取的字節數。函數write實現將把length個字節從buf指向的緩沖區中寫到文件描述符fd所指向的文件中,返回值為實際寫入的字節數。
以O_CREAT為標志的open實際上實現了文件創建的功能,因此,下面的函數等同creat()函數:
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
定位
對于隨機文件,我們可以隨機的指定位置讀寫,使用如下函數進行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()將文件讀寫指針相對whence移動offset個字節。操作成功時,返回文件指針相對于文件頭的位置。參數whence可使用下述值:
SEEK_SET:相對文件開頭
SEEK_CUR:相對文件讀寫指針的當前位置
SEEK_END:相對文件末尾
offset可取負值,例如下述調用可將文件指針相對當前位置向前移動5個字節:
lseek(fd, -5, SEEK_CUR);
由于lseek函數的返回值為文件指針相對于文件頭的位置,因此下列調用的返回值就是文件的長度:
lseek(fd, 0, SEEK_END);
關閉
當我們操作完成以后,我們要關閉文件了,只要調用close就可以了,其中fd是我們要關閉的文件描述符:
int close(int fd);
例程:編寫一個程序,在當前目錄下創建用戶可讀寫文件“hello.txt”,在其中寫入“Hello, software weekly”,關閉該文件。再次打開該文件,讀取其中的內容并輸出在屏幕上。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define LENGTH 100
main()
{
int fd, len;
char str[LENGTH];?
fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /*創建并打開文件 */
if (fd)?
{
write(fd, "Hello, Software Weekly", strlen("Hello, software weekly")); /*寫入 Hello, software weekly字符串 */
close(fd);
}
fd = open("hello.txt", O_RDWR);
len = read(fd, str, LENGTH); /*讀取文件內容 */
str[len] = '\0';
printf("%s\n", str);
close(fd);
}
3.C語言庫函數
C庫函數的文件操作實際上是獨立于具體的操作系統平臺的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數:
創建和打開
FILE *fopen(const char *path, const char *mode);
fopen()實現打開指定文件filename,其中的mode為打開模式,C語言中支持的打開模式如下表:
標志含義
r, rb以只讀方式打開
w, wb以只寫方式打開。如果文件不存在,則創建該文件,否則文件被截斷
a, ab以追加方式打開。如果文件不存在,則創建該文件
r+, r+b, rb+ 以讀寫方式打開
w+, w+b, wh+以讀寫方式打開。如果文件不存在時,創建新文件,否則文件被截斷
a+, a+b, ab+以讀和追加方式打開。如果文件不存在,創建新文件
其中b用于區分二進制文件和文本文件,這一點在DOS、Windows系統中是有區分的,但Linux不區分二進制文件和文本文件。
讀寫
C庫函數支持以字符、字符串等為單位,支持按照某中格式進行文件的讀寫,這一組函數為:
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
char *fgets(char *s, int n, FILE *stream);
int fputs(const char *s, FILE *stream);
int fprintf(FILE *stream, const char *format, ...);
int fscanf (FILE *stream, const char *format, ...);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);
fread()實現從流stream中讀取加n個字段,每個字段為size字節,并將讀取的字段放入ptr所指的字符數組中,返回實際已讀取的字段數。在讀取的字段數小于num時,可能是在函數調用時出現錯誤,也可能是讀到文件的結尾。所以要通過調用feof()和ferror()來判斷。
write()實現從緩沖區ptr所指的數組中把n個字段寫到流stream中,每個字段長為size個字節,返回實際寫入的字段數。
另外,C庫函數還提供了讀寫過程中的定位能力,這些函數包括
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
int fseek(FILE *stream, long offset, int whence);?
等。
關閉
利用C庫函數關閉文件依然是很簡單的操作:
int fclose (FILE *stream);
例程:將第2節中的例程用C庫函數來實現。
#include <stdio.h>
#define LENGTH 100
main()
{
FILE *fd;
char str[LENGTH];
fd = fopen("hello.txt", "w+"); /*創建并打開文件 */
if (fd)
{
fputs("Hello, Software Weekly", fd); /*寫入Hello, software weekly字符串 */
fclose(fd);
}
fd = fopen("hello.txt", "r");
fgets(str, LENGTH, fd); /*讀取文件內容 */
printf("%s\n", str);
fclose(fd);
}
4.小結
Linux提供的虛擬文件系統為多種文件系統提供了統一的接口,Linux的文件編程有兩種途徑:基于Linux系統調用;基于C庫函數。這兩種編程所涉及到文件操作有新建、打開、讀寫和關閉,對隨機文件還可以定位。本章對這兩種編程方法都給出了具體的實例。
帶緩沖I/O 和 不帶緩沖I/O詳解
?以下是我對這兩者的理解:
首先要明白不帶緩沖的概念:所謂不帶緩沖,并不是指內核不提供緩沖,而是只單純的系統調用,不是函數庫的調用。系統內核對磁盤的讀寫都會提供一個塊緩沖,當用write函數對其寫數據時,直接調用系統調用,將數據寫入到塊緩沖進行排隊,當塊緩沖達到一定的量時,才會把數據寫入磁盤。因此所謂的不帶緩沖的I/O是指進程不提供緩沖功能。每調用一次write或read函數,直接系統調用。
而帶緩沖的I/O是指進程對輸入輸出流進行了改進,提供了一個流緩沖,當用fwrite函數網磁盤寫數據時,先把數據寫入流緩沖區中,當達到一定條件,比如流緩沖區滿了,或刷新流緩沖,這時候才會把數據一次送往內核提供的塊緩沖,再經塊緩沖寫入磁盤。
因此,帶緩沖的I/O在往磁盤寫入相同的數據量時,會比不帶緩沖的I/O調用系統調用的次數要少。
下面的東西是我從網上查到的對這兩者的理解,我覺得還是很到位的:
以下主要討論關于open,write等基本系統IO的帶緩沖與不帶緩沖的差別
? ? ? 帶緩存的文件操作是標準C 庫的實現,第一次調用帶緩存的文件操作函數時標準庫會自動分配內存并且讀出一段固定大小的內容存儲在緩存中。所以以后每次的讀寫操作并不是針對硬盤上的文 件直接進行的,而是針對內存中的緩存的。何時從硬盤中讀取文件或者向硬盤中寫入文件有標準庫的機制控制。不帶緩存的文件操作通常都是系統提供的系統調用, 更加低級,直接從硬盤中讀取和寫入文件,由于IO瓶頸的原因,速度并不如意,而且原子操作需要程序員自己保證,但使用得當的話效率并不差。另外標準庫中的 帶緩存文件IO 是調用系統提供的不帶緩存IO實現的。
“術語不帶緩沖指的是每個read和write都調用嗯內核中的一個系統調用。所有的磁盤I/O都要經過內核的塊緩沖(也稱內核的緩沖區高速緩 存),唯一例外的是對原始磁盤設備的I/O。既然read或write的數據都要被內核緩沖,那么術語“不帶緩沖的I/O“指的是在用戶的進程中對這兩個 函數不會自動緩沖,每次read或write就要進行一次系統調用。“--------摘自<unix環境編程>
程序中用open和write打開創建并把“hello world“寫入文件test.txt,相應用fopen和fwrite操作文件test2.txt。程序執行到open和fopen之后,sleep 15秒,這時用ls查看生成了文件沒,這時用open打開的test.txt出現了,但是fopen的test2.txt沒有;當程序執行完write和 fwrite之后,fopen的test2.txt仍然沒有出現(還是用ls查看),再用cat看test.txt,可以看到 “helloworld”;最后再關閉test.txt和test2.txt,這時test2.txt出現了,并且其內容也是“hello world“。
?? 該例子證明了open和write是不帶緩沖的,即程序一執行其io操作也立即執行,不會停留在系統提供的緩沖里,不需等到close操作完才執行。與之相比的fopen和fwrite則是帶緩沖的,(一般)要等到fclose操作完后才會執行。
??
? 相關的源碼示例如下:
?#i nclude <unistd.h>
#i nclude <iostream>
#i nclude <fcntl.h>
#i nclude <string>
#i nclude <sys/types.h>
#i nclude <sys/stat.h>
using namespace std;
int main(){
?int fd;
?FILE *file;
?char *s="hello,world\n";
?if((fd=open("test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1){
? cout<<"Error open file"<<endl;
? return -1;
?}
?if((file=fopen("test2.txt","w"))==NULL){
? cout<<"Error Open File."<<endl;
? return -1;
?}
?cout<<"File has been Opened."<<endl;
?sleep(15);
?if(write(fd,s,strlen(s))<strlen(s)){
? cout<<"Write Error"<<endl;
? return -1;
?}
?if(fwrite(s,sizeof(char),strlen(s),file)<strlen(s)){
? cout<<"Write Error in 2."<<endl;
? return -1;
?}
?cout<<"After write"<<endl;
?sleep(15);
?cout<<"After sleep."<<endl;
?close(fd);
?return 0;
}
詳情請見:http://blog.csai.cn/user1/27828/archives/2007/14285.html
以 ssize_t write(int filedes, const void *buff, size_t nbytes)和size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)來講講自己對unix系統下帶緩存的I/O和不帶緩存的I/O的區別。
?
? ? 首先要清楚一個概念,所謂的代緩存并不是指上面兩個函數的buff參數,而是指unix系統在內核中所設的緩沖存儲器。
? ? 當將數據寫到文件上時,內核先將該數據寫到緩存,如果該緩存未滿,則并不將其排入輸出隊列,直到緩存寫滿或者內核再次需要重新使用此緩存時才將其排入輸入隊列,待其到達對首,在進行實際的I/O操作,也就是此時才把數據真正寫到磁盤,這種技術叫延遲寫。
? ? 現在假設內核所設的緩存是100個字節,如果你使用write,且buff的size為10,當你要把9個同樣的buff寫到文件時,你需要調用9次write,也就是9次系統調用,此時也并沒有寫到硬盤,如果想立即寫到硬盤,調用fsync,可以進行實際的I/O操作。
? ? 標準I/O,也就是帶緩存的I/O采用FILE*,FILE實際上包含了為管理流所需要的所有信息:實際I/O的文件描述符,指向流緩存的指針(標準I /O緩存,由malloc分配,又稱為用戶態進程空間的緩存,區別于內核所設的緩存),緩存長度,當前在緩存中的字節數,出錯標志等,假設流緩存的長度為 50字節,把以上的數據寫到文件,則只需要2次系統調用(fwrite調用write系統調用),因為先把數據寫到流緩存,當其滿以后或者調用 fflush時才填入內核緩存,所以進行了2次的系統調用write。
? ? fflush將流所有未寫的數據送入(刷新)到內核(內核緩沖區),fsync將所有內核緩沖區的數據寫到文件(磁盤)。
?
? ? 不帶緩存的read和write是相對于fread/fwrite等流函數來說明的,因為fread和fwrite是用戶函數(3),所以他們會在用戶層 進行一次數據的緩存,而read/write是系統調用(2)所以他們在用戶層是沒有緩存的,所以稱read和write是無緩存的IO,其實對于內核來 說還是進行了緩存,不過用戶層看不到罷了。