歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux文件操作學習:系統調用和標准I/O庫

Linux文件操作學習:系統調用和標准I/O庫

日期:2017/3/3 16:10:15   编辑:關於Linux

一、什麼是文件

在講述文件操作之前,我們首先要知道什麼是文件。看到這個問題你可能會感覺到可笑,因為對於用過計算機的人來說,文件是最簡單不過的概念了,例如一個文本是一個文件,一個work文檔是一個文件等。但是在Linux中,文件的概念還遠不止於這些,在Linux中,一切(或幾乎一切)都是文件。文件包括很多的內容,例如:大家知道的普通文件是文件,目錄也是一個文件,設備也是一個文件,管道也是一個文件等等。對於目錄、設備這些的操作也可以完全等同於對純文本文件的操作,這也是Linux非常成功的特性之一吧。

二、系統調用

1、文件描述符

文件描述符是一些小數值,你可以通過它們訪問的打開的文件設備,而有多少文件描述符可用取決於系統的配置情況。但是當一個程序開始運行時,它一般會有3個已經打開的文件描述符,就是

0:標准輸入

1:標准輸出

2:標准錯誤

那些數學(即0、1、2)就是文件描述符,因為在Linux上一切都是文件,所以標准輸入(stdin),標准輸出(stdout)和標准錯誤(stderr)也可看作文件來對待。

2、系統調用常用函數

A、open系統調用

open函數的原型為:

int open(const char *path, int oflags);

int open(const char *path, int oflags, mode_t mode);

path,是包括路徑的完整文件名,oflags是文件訪問模式(即是什麼方式打開文件,只讀、只寫還是可讀並可寫等),mode用於設定文件的訪問權限。具體的可選參數,可以自己查看手冊頁,這裡不一一詳述。

open建立了一條到文件或設備的訪問路徑,如果調用成功,返回一個可以被read、write等其他系統調用的函數使用的文件描述符,而且這個文件描述是唯一的,不與任何其他運行中的進程共享,在失敗時返回-1,並設置全局變量errno來指明失明的原因。

B、write系統調用

write函數的原型為:

size_t write(int fildes, const void *buf, size_t nbytes);

write的作用是把緩沖區buf的前nbytes個字節寫入到文件描述符fildes關聯的文件中,返回實際寫入的字節數。返回0表示沒有寫入任何數據,返回-1表示調用中出現了錯誤,錯誤代碼保存在errno中。

注:fildes一定要是在open調用中返回的創建的文件描述符,或者是0、1、2等標准輸入、輸出或標准錯誤。

C、read系統調用

read函數的原型為:

size_t read(int fildes, void *buf, size_t nbytes);

read系統調用的作用是從與文件描述符相關的文件裡讀入nbytes個字節的數據,並把它們放到數據區buf中,返回讀入的字節數,失敗時返回-1。

D、close系統調用

close調用的函數原型為:

int close(int fildes);

close函數的作用是終於文件描述符fildes一其對應的文件之間的關聯。

E、例子

說了這麼多,我就給出一個完整的例子吧,就是從一個數據文件(裡面有1M個‘0’字符)逐個復制到別一個文件。文件名為copy_system.c,代碼如下:

#include <unistd.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdlib.h>  
      
int main()  
{  
    char c = '\0';  
    int in = -1, out = -1;  
          
    //以只讀方式打開數據文件  
    in = open("Data.txt", O_RDONLY);  
    //以只寫方式創建文件,如果文件不存在就創建一個新的文件  
    //文件屬主具有讀和寫的權限  
    out = open("copy_system.out.txt", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);  
    while(read(in, &c, 1) == 1)//讀一個字節的數據  
        write(out, &c, 1);//寫一個字節的數據  
      
    //關閉文件描述符  
    close(in);  
    close(out);  
    return 0;  
}

三、標准I/O庫

有過C編程經歷的人都會知道stdio頭文件,它就是C語言的標准IO庫,在標准IO庫中,與底層文件描述符相對應的是流,它被實現為指向結構FILE的指針。IO庫的函數有很多,為了與前面的內容對應,這裡還是只講與前面四個函數相對應的函數,其他的函數,你可以查一查手冊頁。

A、fopen庫函數

fopen庫函數的原型為:

FILE* fopen(const char *filename, const char *mode);

它與底層系統調用open類似,成功時返回一個非空指針。失敗時返回NULL。

B、fread庫函數

fread庫函數的原型為:

size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);

它與底層調用read相似,其作用是從stream讀取nitems個長度為size的數據到ptr所指向的緩沖區中。返回值是成功讀到緩沖區中的記錄個數。

注:stream為用fopen函數返回的文件結構指針。

C、fwrite庫函數

fwrite庫函數的原型:

size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);

它與底層調用write相似,其作用是從ptr指向的緩沖區中讀取nitems個長度為size到數據,並把它們寫到stream所對應的文件中。

D、fclose庫函數

fclose庫函數的原型為:

int fclose(FILE *stream);

它與系統調用close相似,其作用是關閉指定的文件流stream。

例子

同樣地,下面是前一個例子的另一個實現版本,它實現的功能與先前的例子一樣,不過使用的是標准I/O庫,而不是系統調用,文件名為copy_stdio.c代碼如下:

#include <stdio.h>  
#include <stdlib.h>  
      
int main()  
{  
    int c = 0;  
    FILE *pfin = NULL;  
    FILE *pfout = NULL;  
      
    //以只讀方式打開數據文件  
    pfin = fopen("Data.txt", "r");  
    //以只寫方式打開復制的新文件  
    pfout = fopen("copy_stdio.out.txt", "w");  
          
    while(fread(&c, sizeof(char), 1, pfin))//讀數據  
        fwrite(&c, sizeof(char), 1, pfout);//寫數據  
    //關閉文件流  
    fclose(pfin);  
    fclose(pfout);  
    return 0;  
}

當然這裡你也可以用其他的庫函數來完成工作,如:用fgetc代替fread,用fputc代替fwrite等。

四、文件描述符和文件流的關系

每個文件流都對應一個底層文件描述符,你可以把底層輸入輸出操作與高層文件流操作混合使用,但是一般不要這樣做,因為數據緩沖的後果難以預料。我們可以通過調用fileno函數(原型為:int fileno(FILE *stream))來確定文件流使用的底層文件描述符,它返回指向文件流的文件描述符。相反地,你可以通過調用函數fdopen(原型為FILE* fdopen(int fildes, const char* mode))來在一個已經打開的文件描述符上創建一個新的文件流,mode參數與fopen函數的完全一樣,同時它必須符合該文件在最初打開時所設定的訪問模式。

但是在Linux下的編程,系統調用用得比較多一些,因為很多時候系統調用能提供更多的靈活性和更加強大的功能,有些操作是一定要使用系統調用,例如,創建文件讀寫鎖時就一定要使用系統調用。

 

五、系統調用與標准I/O的性能比較

就拿本例子中的代碼來比較,兩個例子編譯後生成的可執行文件的文件名分別為:copy_system.exe和copy_stdio.exe,在Linux下用time命令來測試其運行時間如下:

從測試結果可以看出,系統調用的效率比庫函數要低很多。為什麼呢?

因為使用系統調用會影響系統的性能。與函數調用相比,系統調用時,Linux必須從運行用戶代碼切換到執行內核代碼,然後再返回用戶代碼,所以系統調用的開銷要比普通函數調用大一些。然而也是有辦法減少這種開銷的,就是在程序中盡量減少系統調用的的次數,並且讓每次系統調用完成盡量多的工作。

而庫函數為什麼做同樣的事情效率卻會高這麼多呢?這是因為庫函數在數據滿足數據塊長度(或buffer長度)要求時才安排執行底層系統調用,從而減少了系統調用的次數,也讓每次的系統調用做了盡量多的事情,所以效率就比較高。

六、提高系統調用的簡單方法舉例

用回每一個例子(coy_system.c)的代碼,略加修改就能提高我們的效率,例如一次讀1024個字節,修改後保存文件名為copy_system2.c,代碼如下:

#include <unistd.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdlib.h>  
      
int main()  
{  
    char buff[1024];  
    int in = -1, out = -1;  
    int nread = 0;  
      
    in = open("Data.txt", O_RDONLY);  
    out = open("copy_system2.out.txt", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);  
    //一次讀寫1024個字節  
    while((nread = read(in, buff, sizeof(buff))) > 0)  
        write(out, buff, nread);  
      
    close(in);  
    close(out);  
    return 0;  
}

生成的可執行文件為copy_system2.exe,使用time命令查看其執行時間,如下:

比較下可以看出,其性能改善了一個數量級,其效率甚至比用庫函數一個一個字符復制來來得高效,至少在我的機子上是這樣。

Copyright © Linux教程網 All Rights Reserved