1. 程式人生 > >Linux下的檔案描述符與檔案指標及其區別

Linux下的檔案描述符與檔案指標及其區別

檔案描述符

在Linux系統中一切皆檔案。如果要對某個裝置進行操作,就不得不開啟此裝置檔案,只要你開啟檔案就會獲得該檔案的檔案描述符fd(file discriptor),這個檔案描述符就是一個整數。每個程序在PCB(Process Control Block)中儲存著一份檔案描述符表,檔案描述符就是這個表的索引,每個表項都有一個指向已開啟檔案的指標。 如下圖所示。
這裡寫圖片描述
圖中,檔案描述符即為檔案描述符陣列的下標。
檔案描述符的分配規律:從當前未使用的最小的整數處開始分配 。比如說如果你開啟一個檔案系統會自動為它開啟三個檔案,分別是stdin,stdout,stderr,就是標準輸入,標準輸出,標準輸出。他們的檔案描述符分別是 0,1,2,也就是說當你的檔案開啟時它的檔案描述符就從3開始分配了,如果你把那三個檔案關閉一個,例如,關掉標準輸入,則開啟的檔案的檔案描述符將會填上0,讓後再往後分配。

檔案描述符的優點:
相容POSIX標準,許多Linux和UNIX系統呼叫都依賴於它。

檔案描述符的缺點:
不能移植到UNIX以外的系統上去,也不直觀。

概括:
每個程序在PCB(Process Control Block)即程序控制塊中都儲存著一份檔案描述符表,檔案描述符就是這個表的索引,檔案描述表中每個表項都有一個指向已開啟檔案的指標。已開啟的檔案在核心中用file結構體表示,檔案描述符表中的指標指向file結構體。
fd為開啟檔案的檔案描述符,而每個程序都有一張檔案描述表,fd檔案描述符就是這張表的索引,同樣這張表中有一表項,該表項又是指向前面提到開啟檔案的file結構體,file結構體才是核心中用於描述檔案屬性的結構體。

file結構體中的成員:
緩衝區基址,緩衝區當前指標,緩衝區大小,緩衝區剩餘位元組個數,檔案讀寫模式等。

struct   FILE   { 
char   *_ptr;//檔案輸入的下一個位置 
int    _cnt; //當前緩衝區的相對位置 
char   *_base;//指基礎位置(應該是檔案的其始位置) 
int    _flag; //檔案標誌 
int    _file; //檔案的有效性驗證 
int    _charbuf;//檢查緩衝區狀況,如果無緩衝區則不讀取 
int    _bufsiz; //檔案的大小 
char   *_tmpfname;//臨時檔名 
}; 

對檔案操作的一些函式

開啟檔案: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+” 讀寫開啟一個二進位制檔案,允許讀或在檔案末追加資料

緩衝區

緩衝機制有三種
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)//fork error;
    {
        printf("fork error!");
        return;
    }

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

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

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

和我們想的一樣,輸出五句話,等待兩秒鐘後,程式退出。
那麼如果把執行結果寫入到檔案裡呢?以下是執行結果。
這裡寫圖片描述

可以看到多出了兩句話,那麼為什麼會多出來呢? 那我們就要了解檔案的操作。檔案的操作分為兩種:

1.流式檔案操作:

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()結束,父子程序各自重新整理,都會執行一次緩衝區中的資料
所以子父程序都打了一遍,所有就多了兩句話。

檔案指標

檔案指標指向程序使用者區中的一個被稱為FILE結構的資料結構。FILE結構包括一個緩衝區和一個檔案描述符。而檔案描述符是檔案描述符表的一個索引,因此從某種意義上說檔案指標就是控制代碼的控制代碼(在Windows系統上,檔案描述符被稱作檔案控制代碼)。

通常的,任何程式執行起來之後都會預設開啟三個標準輸入輸出流:標準輸入流(鍵盤)、標準輸出 流(顯示器),標準錯誤流(顯示器)。
相應C語言的檔案指標:stdin,stdout,stderror

檔案描述符與檔案值針的區別

fd只是一個整數,在open時產生。起到一個索引的作用,程序通過PCB中的檔案描述符表找到該fd所指向的檔案指標filp。
open:檔案描述符的操作(如: open)返回的是一個檔案描述符(int fd),核心會在每個程序空間中維護一個檔案描述符表, 所有開啟的檔案都將通過此表中的檔案描述符來引用。
fopen:流(如: fopen)返回的是一個檔案指標(即指向FILE結構體的指標), FILE結構是包含有檔案描述符的,fopen可以看作是對open(fd直接操作的系統呼叫)的封裝, 它的優點是帶有I/O快取。

C語言中檔案指標與檔案描述符的相互轉換

可通過fdopen和fileno兩個函式實現。它們都包含在標頭檔案stdio.h中。

//fdoppen 函式的原型
FILE * fdopen(int filedes,const char *opentype);

第一個引數filedes是一個開啟的檔案描述符,opentype是表示開啟方式的字串,和fopen函式具有相同的取值,比如”w”或”w+”等。但是你必須保證該字串的描述和檔案實際的開啟方式是匹配的。函式fopen()就是返回開啟檔案的指標;如果操作失敗,返回空指標null。

把檔案流指標轉換成檔案描述符用fileno函式

//fileno 函式的原型
int fileno (FILE *stream);

它返回和stream檔案流對應的檔案描述符。如果失敗,返回-1。