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 庫的,因此也就不可能用於內核空間的驅動程序中對設備的操作。