Linux-文件I/O小結

首先需要複習三個知識點:

一、通過系統層析結構圖瞭解系統調用所處的位置:以下所總結的函數經常被稱爲不帶緩衝的I/O,也就是所謂的每個read和write都調用內核中的一個系統調用。

這裏寫圖片描述

在linux中,查看系統調用文件的幫助文檔需要在man指令後加上2,指定在系統調用章節查看。

二、文件描述符:
在linux中,進程是通過文件描述符(file descriptors 簡稱fd)來訪問文件的,文件描述符實際上是一個非負整數。當打開一個現有文件或創建一個新文件時,內核向進程返回一個文件描述符。當讀或者寫一個文件時,也需要將對應的文件描述符作爲參數傳給對應函數。
在程序剛啓動的時候,默認有三個文件描述符,分別是:0(代表標準輸入),1(代表標準輸出),2(代表標準出錯)。如果不修改已經打開文件,再打開一個新的文件的話,它的文件描述符就是3。
注意:POSIX標準規定,由open返回的文件描述符一定是最小的未用描述符數值。

三、內核所用的I/O的數據結構:
內核使用三種數據結構表示打開的文件:

 1. 每個進程再進程表中都有一個記錄項,記錄項中包含有一張打開文件描述符表,可將其看做一個矢量,每個描述符佔用一項。與每個文件描述符關聯的是:
     a)文件描述符標誌;
     b)指向一個文件表項的指針;
 2. 內核爲所有打開文件維持一張文件表。每個文件表項包含:
     a)文件狀態標誌;
     b)當前文件偏移量;
     c)指向該文件V節點表項的指針。

 3. 每個打開文件都有一個i節點(i_node)結構。i節點包含了文件類型和對比文件進行各種操作的函數的指針。

這裏寫圖片描述


當我們執行open操作時,就必須使得進程和文件關聯起來。按照上面的描述:在每個進程的task_struct中,有一個指向files_struct的指針*files,在files_struct中,包含一個指針數組,而每個元素都是一個指向打開文件的指針,而文件描述符則是對應指針在該指針數組的下標。所以拿到文件描述符,就可以找到在該數組的對應位置,從而找到對應的打開文件。簡化的畫一下就是下面這樣

這裏寫圖片描述

下面開始正式介紹一些系統文件操作的系統調用接口


open:(打開或創建一個文件)


man 手冊第二章中對open的描述:

這裏寫圖片描述

參數中的pathname代表要打開或創建的目標文件;flags表示打開文件時傳入的參數選項:可同時使用多個參數進行“或”運算;mode是在當創建一個文件時賦予的權限。

常用參數:
O_RDONLY:只讀打開;(read only)
O_WRONLY:只寫打開;(write only)
O_RDWR:讀,寫打開。
(以上三種打開方式必須制定一個且同時只能指定一個)
O_CREAT:若文件不存在,則創建一個新的。創建同時需要使用mode 選項來設置文件的權限
O_APPEND:追加寫

返回值:
打開成功返回對應文件的描述符;
失敗返回1。


例:

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

int main()
{
    close(1); //close stdout

    //以只寫並創建的方式打開文件,並將權限設置爲644
    int fd = open("myfile",O_WRONLY | O_CREAT, 0644);
    if(fd < 0){
        perror("open");
        return 1;
    }

    printf("fid = %d\n",fd);
    close(fd);
    return 0;
}
輸出結果:

這裏寫圖片描述

由於操作系統會默認首先打開stdin,stdout,stderr三個文件,所以我們一進來先關閉stdout,再使用open用讀加上創建的參數打開一個文件,並將權限設置爲644,打開成功後,打印fd的值,但是由於此時stdout已經關閉,myfile使用了下標爲1的位置,而printf則是默認向stdout中寫入數據,此時就變成了輸出到了myfile文件中。一定要注意文件描述符的分配規則:用當前進程中最小可用的文件描述符。


creat:(創建文件)

man 2 creat:


 int creat(const char *pathname, mode_t mode);

//返回值:成功返回爲只寫打開的文件描述符,失敗爲-1;
//該函數等價於open(pathname,O_WRONLY | O_CREAT | O_TRUNC , mode)
注:早期由於open函數第二個參數有限,不能打開一個尚未存在的文件,所以需要creat,但是現在完全可以用open函數取代creat。

close:(關閉文件)

man 2 close結果:

這裏寫圖片描述

返回值:成功返回0,失敗返回-1;
注:打開文件後若不再使用一定要記得關閉,系統中對於同一時間打開文件數目有上限,一旦達到上限,將無法再打開新的文件。但是當一個進程終止時,內核會自動關閉它所有打開的文件。

write:(寫文件)

man 2 write結果:

這裏寫圖片描述

參數介紹:
write第一個參數fd爲要寫入文件的文件描述符;
第二個參數buf爲寫的內容的首地址;
第三個count爲本次要寫入數據的字節個數。
返回值:

調用成功後,返回實際寫入數據字節數。


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

int main()
{
    int fd = open("newfile",O_WRONLY | O_CREAT,0644);
    if(fd < 0){
        perror("open");
        return 1;
    }

    int count = 3;
    const char* buf = "hello world\n";
    size_t len = strlen(buf); //字符串長度爲12
    ssize_t total = 0;

    while(count--){
        total += write(fd, buf, len);
    }
    //一共寫入三次,一次12個字節,共36字節
    printf("total write num = %ld\n",total);
    close(fd);
    return 0;
}
輸出結果:

這裏寫圖片描述


read:(讀文件)

man 2 write結果:

這裏寫圖片描述

參數介紹:
第一個參數fd爲要讀取文件的文件描述符;
第二個參數buf爲讀取內容存放緩衝區首地址;
第三個count爲緩衝區最多容納多少字節數。
返回值:

調用成功後,返回實際寫入數據字節數。


例:從上面例子創建的newfile中將之前輸入的內容都讀取出來並打印到屏幕上
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("newfile",O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }

    ssize_t total = 0;
    const char* msg = "hello world\n";
    char buf[1024] = {0};
    while(1)
    {
        ssize_t tmp = read(fd, buf, strlen(msg));
        if(tmp > 0){
            total += tmp;
            printf("%s",buf);
        }else{
            break;
        }
    }

    printf("total read num = %ld\n",total);
    close(fd);
    return 0;
}
輸出結果:

這裏寫圖片描述

注:write和read返回值類型爲ssize_t,爲有符號長整型,使用%ld格式化輸出;size_t 爲無符號長整型,%lu格式化輸出

lseek:(設置打開文件的偏移量)


       #include <sys/types.h>
       #include <unistd.h>

       off_t lseek(int fd, off_t offset, int whence);

//成功返回新的文件偏移量,出錯返回-1

參數介紹:
①fd:要設置的文件對應文件描述符;
②offset:對應解釋依靠於參數三;
③whence:
當whence爲SEEK_SET時,則將該文件的偏移量設置爲距文件開始處offset個字節;
當whence爲SEEK_CUR時,則將該文件的偏移量設置爲當前值加offset,offset可正可負;

注:可通過lseek的返回值來確定所涉及的文件是否可以設置偏移量:若文件描述符引用的是一個管道、FIFO或網絡套接字,則lseek返回-1,並將errno置爲ESPIPE。

示例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdlib.h>

int main()
{
    const char buf1[] = "aaaa";
    const char buf2[] = "bbb";
    int fd = open("file",O_WRONLY | O_CREAT , 0666);
    if(fd < 0){
        perror("open");
        exit(1);
    }

    //open success
    if(write(fd, buf1, sizeof(buf1)) != sizeof(buf1)){
        perror("write");
        exit(1);
    }
    //offset = 4
    //write success
    if(lseek(fd, 1, SEEK_SET) == -1){
        perror("lseek");
        exit(1);
    }
    //offset = 1
    if(write(fd, buf2, sizeof(buf2)) != sizeof(buf2)){
        perror("write");
        exit(1);
    }   

    return 0;
}
運行結果:

這裏寫圖片描述

在我們沒設置偏移量之前,文件偏移量爲4,更改之後偏移量爲1,也就是說之後我們再將內容寫入文件時,將直接從相對於文件開始偏移量爲1的位置開始進行寫入。

注:部分內容參考自《unix環境高級編程》

發佈了55 篇原創文章 · 獲贊 192 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章