1. 程式人生 > >FILE結構體與fd檔案識別符號

FILE結構體與fd檔案識別符號

FILE結構體

我們通常對檔案進行操作時,都會使用一些與檔案相關的函式,比如:

開啟檔案:FILE *fopen(const char *path,const char* mode)
關閉檔案:int fclose(FILEE *fp)
讀檔案:size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)
寫入檔案:size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE* stream)

這裡提一下,上面這些函式都是通過流的形式來進行的,是和快取區打交道的東西,下面說到緩衝機制會詳細說。

先拿fopen來說,函式的返回值是FILE*,這是一個檔案指標型別,
第一個引數是檔案的路徑,第二個引數是開啟的方式。
如下:

“r”:開啟只讀檔案,若不存在,則會報錯。
“w”:開啟只寫檔案,如果檔案內有內容則會被直接清空,若不存在,則會自動建立。
“a”:以追加方式開啟只寫檔案,若檔案不存在,則會自動建立,如果檔案存在,則在內容的最後進行寫入。
諸如"a+","w+","r+"則都是以可讀寫的方式,不在贅述
“b”:以二進位制的方式。
例:“ab+” 讀寫開啟一個二進位制檔案,允許讀或在檔案末追加資料

說完這個文題,就該正式說下 FILE這個結構體了,FILE這個結構體包含了檔案操作的基礎屬性,其中重要的就是關於這個fd(檔案識別符號)的東西,因為在底層,作業系統是不會通過什麼檔案指標來查詢到檔案,而是通過檔案識別符號來操作的,所以,fd是FILE結構體很重要的東西。所以我們重點來看這個。

首先,檔案識別符號以一系列整數,它是從0開始,依次遞增的。但是一般的檔案識別符號都是從3開始的,這是因為系統預設開啟三個流(stdin,stdout,stderr)它們的識別符號依次為0,1,2,所以正常的檔案識別符號從3開始,但是當我們比如關掉0後,檔案識別符號就會先填滿0,然後繼續向後累加。所以,它的機制是從最低的空位開始。

每個程序在PCB(Process Control Block)即程序控制塊中都儲存著一份檔案描述符表(struct files_struct ),檔案描述符就是這個表的索引,檔案描述表中每個表項都有一個指向已開啟檔案的指標,已開啟的檔案在核心中用file結構體表,檔案描述符表中的指標指向file結構體。
如下圖:
這裡寫圖片描述

檔案描述符與檔案指標的區別
檔案描述符:在linux系統中開啟檔案就會獲得檔案描述符,它是個很小的正整數。每個程序在PCB(Process Control Block)中儲存著一份檔案描述符表,檔案描述符就是這個表的索引,每個表項都有一個指向已開啟檔案的指標。檔案指標:C語言中使用檔案指標做為I/O的控制代碼。檔案指標指向程序使用者區中的一個被稱為FILE結構的資料結構。FILE結構包括一個緩衝區和一個檔案描述符。而檔案描述符是檔案描述符表的一個索引,因此從某種意義上說檔案指標就是控制代碼的控制代碼(在Windows系統上,檔案描述符被稱作檔案控制代碼)。

緩衝區

首先,緩衝機制有三種

  1. 行緩衝:遇到\n換行即從緩衝區重新整理
  2. 全緩衝:當緩衝區資料填滿時重新整理
  3. 無緩衝:直接將資料輸出,不經過緩衝區

當然緩衝機制是可以更改的,你可以呼叫setbuf()setvbuf()函式來進行,不過我們重點用下面這個程式來介紹緩衝機制。

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

int main()
{
    char* msg = "I am fwrite\n";
    char* msg1 = " I am write\n";

    printf("I am Printf\n");
    fwrite(msg,1,strlen(msg),stdout);
    write(1,msg1,strlen(masg1));

    pid_t id = fork();
    if(id < 0)
    {
        printf("fork error!");
        return;
    }

    else(id == 0)
    {
        printf("I am child,pid:%d\n",getpid()); 
    }

    else
    {
        printf("I am father,pid:%d\n",getpid());
        sleep(3);
    }
    return 0;
}

執行結果如下圖:
這裡寫圖片描述

結果是不是和預料一樣,列印了5句話,那麼我們試下將執行結果寫入一個檔案試試。

這裡寫圖片描述
那麼這個結果呢?意不意外?為什麼5個輸出語句,卻打印出來了7句。更要注意的是,到底是哪兩句多打了。
這裡我們就要來分析一下了,先把文章開頭沒說的知識點說一下,就是對檔案的操作大致是兩種:

一、流式檔案操作:
fopen() 開啟流
fclose() 關閉流
fputc() 寫一個字元到流中
fgetc() 從流中讀一個字元
fseek() 在流中定位到指定的字元
fputs() 寫字串到流
fgets() 從流中讀一行或指定個字元
fprintf() 按格式輸出到流
fscanf() 從流中按格式讀取
feof() 到達檔案尾時返回真值
ferror() 發生錯誤時返回其值
rewind() 復位檔案定位器到檔案開始處
remove() 刪除檔案
fread() 從流中讀指定個數的字元
fwrite() 向流中寫指定個數的字元
tmpfile() 生成一個臨時檔案流
tmpnam() 生成一個唯一的檔名
這些函式都是先將資料交給緩衝區,然後在進行資料的一系列操作。

二、直接I/O檔案操作
open() 開啟一個檔案並返回它的控制代碼
close() 關閉一個控制代碼
lseek() 定位到檔案的指定位置
read() 塊讀檔案
write() 塊寫檔案
eof() 測試檔案是否結束
filelength() 取得檔案長度
rename() 重新命名檔案
chsize() 改變檔案長度
直接的I/O是不經過緩衝區的。

還有一點,當資料直接往顯示器輸出時,採用行緩衝,而一旦將資料寫入檔案,則由行緩衝變為全緩衝。(這裡的緩衝概念上面提過了,不贅述)

接下來仔細分析程式:

  1. write不經過緩衝區,所以首先被打印出來(雖然程式中先定義printf和fwrite)
  2. 由於printf和fwrite是全緩衝,所以此時這兩個輸出結果在緩衝區中
  3. fork()之後,產生子程序,程式碼共享,資料各自持有一份
  4. fork()結束,父子程序各自重新整理,都會執行一次緩衝區中的資料

這個經典的例子如果搞明白,想必對這塊的知識理解能更加透徹。

以上是個人目前對這塊知識淺顯的認知,真心希望可以得到更多學習上的指點。