1. 程式人生 > >Linux:基礎IO(cIO庫函式詳細介紹)(IO系統呼叫介面詳細介紹)(兩者關係:檔案描述符和檔案指標)

Linux:基礎IO(cIO庫函式詳細介紹)(IO系統呼叫介面詳細介紹)(兩者關係:檔案描述符和檔案指標)

目錄

c系統中的庫函式:

fopen:開啟檔案

fclose:關閉檔案

fwrite:向檔案寫入一個數據塊

fread:讀寫

fprintf:格式化輸出到一個流/檔案中

fseek:移動/跳轉 到當前 讀取/寫入位置

fgets:獲取字串

fput:把字串寫入到指定的流( stream) 中,但不包括空字元。

rewind改變內部指標

程式碼演示

IO系統呼叫介面

open

close

read

write

lseek

程式碼演示:

系統呼叫和庫函式的關係

兩者關係

檔案描述符fd

檔案指標


c系統中的庫函式:

在c中,檔案操作都是由庫函式實現的,標頭檔案都為<stdio.h>主要是分為讀和寫兩張操作

fopen:開啟檔案

函式原型: FILE * fopen(const char *path,cost char *mode)
作用:開啟一個檔案,返回指向該檔案的指標
引數說明:

                 第一個引數為開啟檔案的路徑及檔名
                 第二個引數表示開啟的方式
開啟方式:

  • r:只讀方式開啟,檔案必須存在
  • r+:可讀寫,必須存在
  • rb+:開啟二進位制檔案,可以讀寫
  • rt+:開啟文字檔案,可讀寫
  • w:只寫,檔案存在則檔案長度清0,檔案不存在則建立該檔案
  • w+:可讀寫,檔案存在則檔案長度清0,檔案不存在則建立該檔案
  •  上述如r、w、a在其後都可以加一個b,表示以二進位制形式開啟檔案

返回值:檔案打開了返回一個指向該開啟檔案的指標(FILE結構),開啟失敗返回NULL,並且將錯誤程式碼存在errno


fclose:關閉檔案

函式原型:int fclose(FILE *stream)

功能:關閉一個檔案流,使用fclose就可以把緩衝區內最後剩餘的資料輸出到磁碟檔案中,並釋放檔案指標和有關的緩衝區


fwrite:向檔案寫入一個數據塊

函式原型:size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

返回值:返回實際寫入的資料塊數目

引數說明:

  •    buffer:是一個指標,對fwrite來說,是要獲取資料的地址;
  •    size:要寫入內容的單位元組數;
  •    count:要進行寫入size位元組的個數;
  •    stream:目標檔案指標;

返回值:實際寫入的資料項個數count。


fread:讀寫

函式原型:    size_t fread(void* buff,size_t size,size_t count,FILE* stream)
引數說明:  從檔案中讀入資料到指定的地址中

  •     buff:   接收資料的的指標(buff),就是資料的儲存地址
  •     size:單個元素的大小(單位為位元組)
  •     count: 引數的元素個數     
  •     stream:資料的檔案指標,指向檔案內部資料

返回值:讀取的總資料元素個數


fprintf:格式化輸出到一個流/檔案中

函式原型:  int fprintf( FILE *stream, const char *format, [ argument ]...)
                   fprintf()函式根據指定的格式(format)向輸出流(stream)寫入資料(argument)
引數說明:

  •     stream:檔案指標
  •     format:輸出格式
  •     第三個:附加引數列表

規定符:

  • %d, %i 十進位制有符號整數
  • %u 十進位制無符號整數
  • %f 浮點數
  • %s 字串
  • %c 單個字元
  • %p指標的值  

fseek:移動/跳轉 到當前 讀取/寫入位置

函式原型:int fseek(FILE *stream,long offset,int framewhere)

作用:重定位檔案內部的指標

引數是說明:

  • 第一個為檔案指標,
  • 第二個是指標的偏移量,
  • 第三個是指標偏移起始位置

返回值:重定位成功返回0,否則返回非零值
需要注意的是該函式不是重定位檔案指標,而是重定位檔案內部的指標,讓指向檔案內部資料的指標移到檔案中我們感興趣的資料上,重定位主要是這個目的。
說明:執行成功,則stream指向以fromwhere為基準,偏移offset個位元組的位置。執行失敗(比方說offset偏移的位置超出了檔案大小),則保留原來stream的位置不變
起始點有三個常量值:

  •   SEEK_SET 0   檔案開頭
  •   SEEK_CUR 1   檔案當前位置
  •   SEEK_END 2   檔案末尾

fgets:獲取字串

函式原型:char *fgets(char *str, int num, FILE *fp)
返回值:返回第一個引數buf

引數說明:

  •          str: 儲存從檔案讀取出來的字串      
  •          fp: 待讀檔案的檔案指標
  •          num: 表示從檔案中讀出的字串不超過 n-1個字元。在讀入的最後一個字元後加上串結束標誌'\0'

fput:把字串寫入到指定的流( stream) 中,但不包括空字元。

函式原型: int  fputs(char * str,FILE * stream)
引數說明:
     str:這是一個數組,包含了要寫入的以空字元終止的字元序列。
     stream:指向 FILE 物件的指標,該 FILE 物件標識了要被寫入字串的流
返回值:該函式返回一個非負值,如果發生錯誤則返回 EOF(-1)。

ftell:用於得到檔案位置指標當前位置相對於檔案首的偏移位元組數
函式原型:long ftell(FILE *stream)
函式功能:
使用fseek函式後再呼叫函式ftell()就能非常容易地確定檔案的當前位置。


rewind改變內部指標

函式原型: void rewind(FILE *stream);
函式功能:將檔案內部的指標重新指向一個流的開頭系統呼叫
注意: 不是檔案指標而是檔案內部的位置指標,隨著對檔案的讀寫檔案的位置指標(指向當前讀寫位元組)向後移動。而檔案指標是指向整個檔案,如果不重新賦值檔案指標不會改變。


程式碼演示

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
    FILE* fp = fopen("test","w");
    if(!fp)
    {   
        perror("fopen");
        return -1; 
    }   

    const char* tmp = "WJF-handsome\n";
   
    fwrite(tmp ,1,strlen(tmp),fp);
    
    fclose(fp);
    return 0;  
}

 

輸出方式

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
    const char* tmp = "hello f\n";

    fwrite(tmp,strlen(tmp),1,stdout);
  
    printf("hello p\n");

    fprintf(stdout,"hello fp\n");
  return 0;
}

 

 

IO系統呼叫介面

標頭檔案:#include <sys/types.h>    #include <sys/stat.h>    #include <fcntl.h>

open

函式原型:

    int open(const char * pathname, int flags);
    int open(const char * pathname, int flags, mode_t mode);
引數說明:
pathname:開啟路徑
flags:開啟方式
             O_RDONLY 以只讀方式開啟檔案
             O_WRONLY 以只寫方式開啟檔案
             O_RDWR 以可讀寫方式開啟檔案.

             上述三種旗標是互斥的, 也就是不可同時使用, 但可與下列的旗標利用OR(|)運算子組合.

  •  O_CREAT 若欲開啟的檔案不存在則自動建立該檔案.
  •  O_EXCL 如果O_CREAT 也被設定, 此指令會去檢查檔案是否存在. 檔案若不存在則建立該檔案, 否則將導致開啟檔案錯誤. 此外, 若                         
  • O_CREAT 與O_EXCL 同時設定, 並且欲開啟的檔案為符號連線, 則會開啟檔案失敗.
  • O_NOCTTY 如果欲開啟的檔案為終端機裝置時, 則不會將該終端機當成程序控制終端機.
  • O_TRUNC 若檔案存在並且以可寫的方式開啟時, 此旗標會令檔案長度清為0, 而原來存於該檔案的資料也會消失.
  • O_APPEND 當讀寫檔案時會從檔案尾開始移動, 也就是所寫入的資料會以附加的方式加入到檔案後面.
  • O_NONBLOCK 以不可阻斷的方式開啟檔案, 也就是無論有無資料讀取或等待, 都會立即返回程序之中.
  • O_NDELAY 同O_NONBLOCK.
  • O_SYNC 以同步的方式開啟檔案.
  • O_NOFOLLOW 如果引數pathname 所指的檔案為一符號連線, 則會令開啟檔案失敗.
  • O_DIRECTORY 如果引數pathname 所指的檔案並非為一目錄, 則會令開啟檔案失敗

mode:許可權

引數mode 則有下列數種組合, 只有在建立新檔案時才會生效, 此外真正建檔案時的許可權會受到umask 值所影響, 因此該檔案許可權應該為 (mode-umaks).

  • S_IRWXU00700 許可權, 代表該檔案所有者具有可讀、可寫及可執行的許可權.
  • S_IRUSR 或S_IREAD, 00400 許可權, 代表該檔案所有者具有可讀取的許可權.
  • S_IWUSR 或S_IWRITE, 00200 許可權, 代表該檔案所有者具有可寫入的許可權.
  • S_IXUSR 或S_IEXEC, 00100 許可權, 代表該檔案所有者具有可執行的許可權.
  • S_IRWXG 00070 許可權, 代表該檔案使用者組具有可讀、可寫及可執行的許可權.
  • S_IRGRP 00040 許可權, 代表該檔案使用者組具有可讀的許可權.
  • S_IWGRP 00020 許可權, 代表該檔案使用者組具有可寫入的許可權.
  • S_IXGRP 00010 許可權, 代表該檔案使用者組具有可執行的許可權.
  • S_IRWXO 00007 許可權, 代表其他使用者具有可讀、可寫及可執行的許可權.
  • S_IROTH 00004 許可權, 代表其他使用者具有可讀的許可權
  • S_IWOTH 00002 許可權, 代表其他使用者具有可寫入的許可權.
  • S_IXOTH 00001 許可權, 代表其他使用者具有可執行的許可權.


返回值:若所有欲核查的許可權都通過了檢查則返回0 值, 表示成功, 只要有一個許可權被禁止則返回-1.

close

函式原型:

int close(int fildes);

作用:close系統呼叫用於“關閉”一個檔案,close呼叫終止一個檔案描述符fildes以其檔案之間的關聯。檔案描述符被釋放,並能夠重新使用。

返回值:close成功返回1,出錯返回-1

read

函式原型:

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

引數說明:

  • fildes:檔案描述符,標識要讀取的檔案。如果為0,則從標準輸入讀資料。類似於scanf()的功能。
  • *buf:緩衝區,用來儲存讀入的資料。
  • nbytes:要讀取的字元數。
  • 返回值:size_t返回成功讀取的字元數,它可能會小於請求的位元組數。
     

write

函式原型:

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

引數說明:

  • fildes:檔案描述符,標識了要寫入的目標檔案。例如:fildes的值為1,就像標準輸出寫資料,也就是在顯示屏上顯示資料;如果為 2 ,則想標註錯誤寫資料。
  • *buf:待寫入的檔案,是一個字串指標。
  • nbytes:要寫入的字元數。
  • 函式返回值:size_t  返回成功寫入檔案的字元數。
     

lseek

函式原型:

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

引數說明:

  • fd是開啟的檔案描述符
  • offset是與參考偏移的位置
  • whence是檔案參考的位置

它一共有三個位置

  • SEEK_SET 檔案開始,如果使用此偏移,那麼檔案定位到offset的位置
  • SEEK_CUR 檔案讀寫指標的當前位置
  • SEEK_END 檔案結尾

lseek返回值是檔案讀寫指標移動之後的位置,-1表示失敗

a,取得檔案當前偏移位置可以如下: f_offset = lseek(fd, 0, SEEK_CUR);

b,檔案大小可以通過下面: f_len = lseek(fd, 0, SEEK_END);

c,使用lseek函式移動好讀寫指標之後,使用read,write即可往檔案裡面讀寫資料。
 

程式碼演示:

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

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

    char* buf[1024];
    
    ssize_t s = read(fd,buf,12);//1從哪讀2放在那3讀多少
   
    printf("%s\n",buf);
    
    close(fd);
   
    return 0;
}

 

系統呼叫和庫函式的關係

兩者關係

f開頭的函式,都是對系統呼叫的封裝,方便二次開發

檔案描述符fd

檔案描述符實質就是一個整數,當做檔案的ID,在系統中標識檔案

一個程序要對所有開啟的檔案進行管理,先將檔案描述起來,然後組織進行管理,程序對檔案進行描述的結構叫:file(struct file)

程序使用了一個結構體來組織這些描述符,而檔案描述符就是這結構體陣列下標

Linux程序預設會開啟三個檔案描述符:

  • 標準輸入0
  • 標準輸出1
  • 標準錯誤2
  • 0,1,2一般對應的物理裝置有:鍵盤,顯示器,顯示器
stdin stdout stderr FILE* 檔案流指標
0 1 2 int 檔案描述符

 

每個程序在PCB(Process Control Block)中儲存著一份檔案描述符表,檔案描述符就是這個表的索引,每個表項都有一個指向已開啟檔案的指標。

檔案指標

C語言中使用檔案指標做為I/O的控制代碼。檔案指標指向程序使用者區中的一個被稱為FILE結構的資料結構。

FILE結構包括一個緩衝區和一個檔案描述符。(庫函式是系統呼叫的封裝,所以檔案流指標包含檔案描述符)

而檔案描述符是檔案描述符表的一個索引,因此從某種意義上說檔案指標就是控制代碼的控制代碼(在Windows系統上,檔案描述符被稱作檔案控制代碼)。

 

當我們開啟檔案時,作業系統在記憶體中要建立相應的資料結構來描述目標檔案,於是就有了file結構體

表示一個已經開啟的檔案物件,而程序執行open系統呼叫,所有必須要讓程序和檔案關聯起來,

每個程序都有一個*file指標,指向一張表files_struct ,這個最重要的就是包含一個指標陣列,

每個元素都是一個指向開啟檔案的指標。

所以本質上檔案描述符就是該陣列的下標,只要拿著檔案描述符,就能找到對應檔案