天天看點

Linux下的檔案操作(Linux系統調用和ANSIC檔案操作)

1、Linux系統調用

系統調用常用于 I/O 檔案操作,系統調用常用的函數有 open、 close、 read、write、 lseek、ulink 等。

  • open:打開或建立檔案
  • close:關閉檔案
  • read :從指定的檔案描述符中讀出的資料放到緩沖區,并傳回實際讀出的位元組數
  • write:把指定緩沖區的資料寫入指定的檔案描述符中,并傳回實際寫入的位元組數
  • lseek:在指定的檔案描述符中将檔案指針定位到相應的位置
  • ulink:删除檔案

open 函數:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, int perms);
參數說明:
pathname:被打開的檔案名(包括路徑名);
flags:檔案打開方式标志,其取值如下:
O_RDONLY:表示“以隻讀方式打開”;O_WRONLY:表示“以隻寫方式打開”;
O_RDWR:表示“以讀寫方式打開”;
O_CREAT:表示“若檔案不存在則建立文檔并打開”;
O_TRUNC:表示“若檔案存在,将其長度縮短為 0,屬性不變”;
O_APPEND:表示“打開檔案後檔案指針指向末尾”;
O_EXCL:和 O_CREAT 一起使用,用于在執行“O_CREAT”前判斷檔案是否存在,若存在将導緻傳回失敗(避免原檔案被覆寫)。
傳回值:
成功傳回檔案描述符fd;
失敗傳回-1。
           

close 函數:

#include <unistd.h>
int close(int fd); 
參數說明:
 fd:檔案描述符。
傳回值:
 0:成功;
 -1:出錯。
           

說明:在檔案操作過程中,每一個通過 open 打開的檔案,在檔案操作完後都需要調用close 進行關閉,否則這個檔案資源将長期被占用,影響其他程式對檔案的讀寫操作。

read 函數:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
參數說明:
 fd:檔案描述符;
 buf:存儲内容的記憶體空間(指定存儲讀出資料的緩沖區);
 count:讀取的位元組數。
傳回值:
 >0:成功讀取的位元組數;
 <0:出錯;
 0:表示遇到檔案末尾 EOF。
           

write 函數

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
參數說明:
 fd:檔案描述符;
 buf:需要寫入内容的記憶體空間(緩沖區的指針);
 count:寫入的位元組數。
傳回值:
 >0:成功寫入的位元組數;
 <0:出錯;
 0:表示遇到檔案末尾 EOF。
           

lseek 函數

#include <unistd.h>
#include <sys/types.h>
ssize_t lseek(int fd, off_t offset, int whence);
函數說明:
每一個已打開的檔案都有一個讀寫位置,當打開檔案時通常其讀寫位置是指向檔案開
頭,若是以追加的方式打開檔案(如 O_APPEND),則讀寫位置會指向檔案尾。當 read()或
write()時,讀寫位置會随之增加, lseek()便是用來控制該檔案的讀寫位置。
參數說明:
 fd:檔案描述符;
 offset:偏移量(可正可負);
 whence:取值為 SEEK_SET 時表示“檔案開頭+offset”為新讀寫位置,取值為
SEEK_CUR 時表示“目前讀寫位置+offset”為新位置,取值為 SEEK_END 時表示
“檔案結尾+offset”為新位置。
傳回值:
 >0:定位後檔案操作位置相對于檔案頭的偏移量;
 <0:出錯,通常傳回-1。
常用方式:
将讀寫位置移到檔案開頭:
lseek(fd, 0, SEEK_SET)
将讀寫位置移到檔案尾:
lseek(fd, 0, SEEK_END)
将取得目前檔案位置:
lseek(fd, 0, SEEK_CUR)
           

unlink 函數

int unlink(const char * path)
函數說明:删除檔案的一個硬連結。
參數說明:
 path:需要删除的包含檔案名的檔案路徑。
傳回值:
 成功傳回 0;
 失敗傳回-1。
           

2、ANSI C 檔案操作

ANSI C 檔案操作方法是所有作業系統通用的檔案操作方法,它的操作是被緩沖過的,被修改的檔案并不會立即反應到磁盤中,它在記憶體中開辟一個“緩沖區”,為程式中的每一個檔案操作所使用,當執行讀檔案的操作時,從磁盤檔案中将資料先讀入記憶體“緩沖區”,裝滿後再從記憶體“緩沖區”依次讀入接收的資料。這種檔案操作方式又被稱作流式檔案操作。

ANSI C 檔案操作常用的函數有 fopen、 fclose、 fread、 fwrite、 fseek、 ftell、 rewind、 fgetc、fputc、 fgets、 fputs 及 remove 等。

函數 作用
fopen 打開或建立檔案
fclose 關閉檔案
fread 從檔案中讀取一個位元組
fwrite 将資料塊寫入檔案流
fseek 移動檔案流的讀寫位置
ftell 查詢檔案的讀寫文字設定
rewind 把檔案的讀寫位置設定在檔案頭
fgetc 從文本檔案中讀取一個字元
fputc 向文本檔案中寫入一個字元
fgets 從文本檔案中讀取一個字元串(一行資料,以\n 結尾)
fputs 向文本檔案中寫入一個字元串
remove 删除檔案

fopen 函數

#include <stdio.h>
FILE * fopen(const char* path, const char * mode);
參數說明:
 path:帶檔案路徑的檔案名;
 mode:檔案打開狀态,其具體取值及對應的含義如表
傳回值:
 成功則傳回指向該流(流式檔案操作)的檔案指針(即檔案句柄) fp;
 失敗則傳回 NULL。
**說明**:檔案句柄 fp 是一個指向 FILE 結構的指針, FILE 是由系統定義的一個結構,該
結構中含有檔案名、檔案狀态和檔案目前位置等資訊。在編寫應用程式時通常不必關心 FILE
結構的細節,但調用 fclose、 fread、 fwrite、 fseek、 ftell、 rewind、 fgetc、 fputc、 fgets 及 fputs
等函數前均需要先調用 fopen 函數打開檔案,以擷取操作該檔案的句柄 fp。
           
參數 作用
r 打開隻讀檔案,檔案必須存在
r+ 打開讀寫檔案,檔案必須存在
w 打開隻寫檔案,若檔案存在則清除内容,不存在則建立該檔案
w+ 打開可讀寫檔案,若檔案存在則清除内容,不存在則建立該檔案
a 以附加方式打開隻寫檔案,若檔案不存在則建立該檔案;若存在則寫入的資料被加到檔案尾
a+ 以附加方式打開可讀寫檔案,若檔案不存在則建立該檔案;若存在則寫入的資料被加到檔案尾
備注 上面的參數後面可以再加上一個 b,表示打開的二進制檔案,而不是純文字檔案,如 rb、 w+b、 ab+等

fclose 函數

#include <stdio.h>
int fclose(FILE * stream);
參數說明:
 stream:檔案句柄。
傳回值:
 成功傳回 0;
 出錯傳回 EOF。
說明:與前面介紹的“系統調用”相類似,每一個通過 fopen 打開的檔案,在檔案操作
完後均需要調用 fclose 函數進行關閉,否則這個檔案資源将長期被占用,影響其他程式對文
件的讀寫操作。對于流式檔案寫操作,調用 fclose 還具有重要作用——讓記憶體“緩沖區”中
未及時寫入存儲媒體的資料最終寫入存儲媒體。
           

fread 函數

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
參數說明:
 ptr:指向一塊存儲空間,用來存放本次讀取到的資料;
 size:讀取檔案一條記錄的位元組數大小;
 nmemb:本次讀取檔案記錄的數目;
 stream:将要讀取的檔案流句柄。
傳回值:
 成功則傳回實際讀取到的數目 nmemb;
 出錯則傳回 EOF。
           

fwrite 函數

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
參數說明:
 ptr: 需寫入的資料位址;
 size: 寫入檔案一條記錄的位元組數大小;
 nmemb: 寫入檔案記錄的數目;
 stream: 将要寫入資料的檔案流句柄。
傳回值:
 成功則傳回實際寫入到的數目 nmemb;
 出錯傳回 EOF。
           

fseek 函數

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
參數說明:
 stream:檔案句柄;
 offset:偏移量(可正可負);
 whence:取值為 SEEK_SET 時表示“檔案開頭+offset”為新讀寫位置,取值為
SEEK_CUR 時表示“目前讀寫位置+offset”為新位置,取值為 SEEK_END 時表示
“檔案結尾+offset”為新位置。
傳回值:
 成功傳回 0;
 出錯傳回-1。
           

ftell 函數

#include <stdio.h>
int ftell(FILE *stream);
函數說明: 簡單地傳回目前位置。
參數說明:
 stream:檔案句柄。
傳回值:
 >0:檔案流目前操作位置相對于檔案頭的偏移量;
 <0:出錯。
           

rewind 函數

#include <stdio.h>
void rewind(FILE *stream);
函數說明:把檔案指針位置設定為 0,即把檔案指針設定到檔案的起始位置。
參數說明:
 stream:檔案句柄。
傳回值:
 無。
           

fgetc 函數

#include <stdio.h>
int fgetc(FILE *stream);
函數說明:從檔案中讀取一個字元。
參數說明:
 stream:檔案句柄。
傳回值:
 >0:成功讀取字元内容;
 EOF:出錯。
           

fputc 函數

#include <stdio.h>
int fputc(int ch, FILE *stream);
函數說明:向檔案中寫入一個字元。
參數說明:
 ch:需要寫入檔案的字元内容;
 stream:檔案句柄。
傳回值:
 >0:成功寫入的字元值;
 EOF:出錯。
           

fgets 函數

#include <stdio.h>
char * fgets(char *s, int n, FILE *stream);
函數說明:從檔案中讀取一行字元串。
參數說明:
 s:讀取字元串的存儲記憶體指針;
 n:讀取字元串記憶體的大小;
 stream:檔案句柄。
傳回值:
 非空:成功;
 NULL:出錯。
           

fputs 函數

#include <stdio.h>
int fputs(char *string, FILE *stream);
函數說明:向檔案中寫入一個字元串。
參數說明:
 string:需要寫入檔案的字元串内容;
 stream:檔案句柄。
傳回值:
 0:成功
 <0:出錯
           

remove 函數

int remove(const char *path);
參數說明:
 path:需要删除的包含檔案名的檔案路徑。
傳回值:
 成功傳回 0;
 失敗傳回-1。
           

fflush 函數

由于緩沖區的存在,是以流中的資料與對應檔案的資料可能不一緻,當系統掉電時,檔案若沒有及時 close(),緩沖區的資料就會丢失,為了同步緩沖區,此時可以調用 fflush()函數實作。

#include <stdio.h>
int fflush(FILE *stream);
參數說明:
 stream:檔案句柄。
傳回值:
 成功傳回 0;
 失敗傳回 EOF。
執行個體:
fflush 函數應用示例代碼如程式清單,對應已編譯出來的程式檔案為附件中的
“e10”。示例程式用來用 while 死循環模拟掉電狀态,打開或建立目前目錄下的“./test_2”
檔案并寫入“Hello,world!”字元串。當調用 fflush 函數後,檔案成功寫入“Hello,world!”
字元串, 否則将未寫入該字元串。

#include <stdio.h>
#define Sync_Test_On 0
int main(void)
{
	FILE *fp;
	int iCount;
	char *str = "Hello world!";
	fp = fopen("./test_2", "w"); // 打開檔案以獲得操作該檔案的句柄
	iCount = fwrite(str, 12, 1, fp);
	printf("%d block(12 bytes per block) read, check it out!\n", iCount);
	if (Sync_Test_On)
	{
		fflush(fp);
		while(1);
	}
	else
	{
		while(1);
	}
	fclose(fp);
}
說明: Sync_Test_On 為 0 時,程式沒有調用 fflush(),運作 e10 時,程式進入 while 循環,
用“Ctrl” +“C”強制退出程式(模拟掉電狀态),用 cat test_2 檢視檔案,資料沒有寫入;
Sync_Test_On 為 1 時,程式調用 fflush(),運作 e10,程式進入 while, 用“Ctrl” +“C”強制
退出程式, 然後用 cat test_2 檢視檔案,資料同步。
           

setvbuf 函數

由于緩沖區的存在,為了同步緩沖區,可以調用 fflush()實作。但是當掉電時,未調用fflush()時,此時緩沖區的資料仍舊會丢失。而 setvbuf()函數可以修改緩沖區的工作模式,可以很好的解決這一問題。

#include <stdio.h>
int setvbuf(FILE *stream,char *buf,int mode,size_t size);
參數說明:
 stream:檔案句柄。
 buf:如果 buf 未 NULL,由編譯器決定如何建立流的緩沖區,否則其應該指向一段
大小為 size 的記憶體。
 mode:指定了緩沖區的類型, _IOFBF(全緩沖), _IOLBJ(行緩沖), _IONBF(無緩沖)。
 size:指定了緩沖區的大小。
如果指定一個不帶緩沖區的流,則忽略 buf 和 size 參數。
傳回值:
 成功傳回 0;
 失敗傳回非零值。
           

應用示例:

setvbuf 函數應用示例代碼如程式清單,對應已編譯出來的程式檔案為附件中的“e11”。示例程式用來用 while 死循環模拟掉電狀态,打開或建立目前目錄下的“./test_3”

檔案并寫入“Hello,world!”字元串。當調用 setvbuf 函數将緩沖區的類型設定為_IONBF 後,

檔案可以正常寫入“Hello,world!”字元串, 否則将未寫入該字元串。

#include <stdio.h>
#define Sycn_Test_On 1
int main(void)
{
FILE *fp;
int iCount;
char *str = "Hello world!";
fp = fopen("./test_3", "w"); // 打開檔案以獲得操作該檔案的句柄
if(Sycn_Test_On){
setvbuf(fp,NULL,_IONBF,0);
}
iCount = fwrite(str, 12, 1, fp);
printf("%d block(12 bytes per block) read, check it out!\n", iCount);
while(1);
fclose(fp);
}
說明:測試方法同 fflush()一緻; setvbuf()函數如果要設定流的緩沖區,則函數必須在打
開檔案後立即調用,一旦操作了流,就不能再調用此函數了,否則結果不可預知。非緩沖的
檔案操作通路方式,每次對檔案進行一次讀寫操作時,都需要使用 linux 系統調用來處理此
操作,執行一次 linux 系統調用将涉及到 CPU 狀态的切換,即從使用者空間切換到核心空間,
實作程序上下文的切換,這将損耗一定的 CPU 時間,頻繁的磁盤通路對程式的執行效率會
造成較大的影響。
           

setbuf 函數

#include <stdio.h>
int setbuf(FILE *stream,char *buf);
參數說明:
 stream:檔案句柄。
 buf:參數 buf 須指向一個長度為 BUFSIZ 的緩沖區,如果将 buf 設定為 NULL,則
關閉緩沖區。
傳回值:
 成功傳回 0;
 失敗傳回非零值。
說明: setbuf 用法和 setvbuf 用法類似, 不再贅述。
           

3、Linux 系統調用和 ANSI C 檔案操作的差別

Linux 下對檔案操作有兩種方式:

  • Linux 系統調用
  • ANSI C檔案操作

Linux 系統調用實際上就是指最底層的一個調用,在 linux 程式設計裡面就是底層調用, 面向的是硬體。而 ANSI C 則是庫函數調用,是面向的是應用開發的,相當于應用程式的 API(應用程式接口)。采用這樣的方式有很多種原因:

  • 雙緩沖技術的實作;
  • 可移植性的考慮;
  • 底層調用本身的一些性能缺陷(如頻繁擦寫影響檔案存儲媒體的壽命);
  • 讓API也可以有具體分層和專門的應用方向。

Linux 系統調用

Linux 系統調用是作業系統相關的,是以一般沒有跨作業系統的可移植性。Linux 系統調用發生在核心空間,是以如果在使用者空間的一般應用程式中使用系統調用來進行檔案操作,會有使用者空間到核心空間切換的開銷。事實上,即使在使用者空間使用 ANSIC 檔案操作,因為檔案總是存在于存儲媒體上,是以不管是讀寫操作,都是對硬體(存儲器)的操作,都必然會引起 Linux 系統調用。也就是說, ANSI C 檔案操作實際上是通過系統調用來實作的。例如 C 庫函數 fwrite()就是通過 write()系統調用來實作的。

這樣的話,使用庫函數也有系統調用的開銷,為什麼不直接使用系統調用呢?這是因為讀寫檔案通常是大量的資料(這種大量是相對于底層驅動的系統調用所實作的資料操作機關而言),這時,使用 ANSI C 檔案操作就可以大大減少系統調用的次數。這一結果又緣于緩沖區技術。在使用者空間和核心空間,對檔案操作都使用了緩沖區,例如用 fwrite 寫檔案,都是先将内容寫到使用者空間緩沖區,當使用者空間緩沖區滿或者寫操作結束時,才将使用者緩沖區

的内容寫到核心緩沖區,同樣的道理,當核心緩沖區滿或寫結束時才将核心緩沖區内容寫到檔案對應的硬體媒介。

ANSI C 檔案操作

ANSI C 檔案操作通常用于應用程式中對一般檔案的通路。ANSI C 檔案操作是系統無關的,是以可移植性好, 由于 ANSI C 檔案操作是基于 C 庫的,是以也就不可能用于核心空間的驅動程式中對裝置的操作。

繼續閱讀