Linux C 檔案IO

熊沐發表於2021-05-31

檔案IO

2021-05-31 12:46:14 星期一

檔案描述符:是有限資源

檔案描述符 POSIX名稱 用途 stdio流
0 STDIN_FILENO 標準輸入 stdin
1 STDOUT_FILENO 標準輸出 stdout
2 STDERR_FILENO 標準錯誤 stderr

基礎IO

open

#include <fcntl.h>

int open(const char *path, int oflag, mode_t mode);
  • path檔案的路徑和名稱
  • flag檔案的開啟模式,組合使用時需要使用位運算或
    • 基本模式
      • 只讀 只寫 讀寫
        O_RDONLY O_WRONLY O_RDWR
        00 01 02
    • 附加模式(只列常用)
      • O_APPEND:總是在檔案末尾新增資料
      • O_EXCL:配合O_CREAT標誌
        • 表明如果檔案存在則不會開啟檔案,並使open呼叫失敗,否則能夠建立並開啟檔案。這樣確保了呼叫open()的程式即為建立檔案的程式.
        • 同時不允許path是符號連結
      • O_CREAT:沒有檔案存在時會建立檔案,需要mode引數指明檔案許可權,一共9個
      • O_TRUNC:如果檔案存在為普通檔案且該程式對改檔案有寫許可權,則清空檔案內容
  • mode
    • S_IRUSR:檔案所有者有讀許可權
    • S_IWUSR:檔案所有者有寫許可權
    • S_IXUSR:檔案所有者有執行許可權
    • S_IRGRP:同組使用者有讀許可權
    • S_IWGRP:同組使用者有寫許可權
    • S_IXGRP:同組使用者有執行許可權
    • S_IROTH:其他使用者有讀許可權
    • S_IWOTH:其他使用者有寫許可權
    • S_IXOTH:其他使用者有執行許可權
  • 返回檔案描述符fd或者-1

錯誤

creat

舊版使用,新版都用open進行建立檔案

read

open返回的fd檔案描述符中讀取bytes位元組的資料到buf中,返回實際讀取的位元組數,不成功返回-1

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t bytes);
  • read普通檔案:呼叫成功返回實際讀取的位元組數,遇到檔案EOF時返回0,出現錯誤返回-1
  • read讀取終端,遇到\n即返回

注意:read系統呼叫是逐位元組讀取的,所以無法遵守C語言中的字串的規則。比如C語言中字串以\00x0)作為結束,但是read認為這裡的位元組值是0x0並繼續讀下去.所以使用read讀資料時,通常的方式是:每次讀取資料,再將的實際讀取到的size處設定成C語言認可的字串結束符,即buffer[size] = '\0';

#define MAX_READ 16

char buffer[MAX_READ + 1];
ssize_t size;
size = read(fd, buffer, MAX_READ);
if (size == -1)
    exit(0);
buffer[size] = '\0';
close(fd);

一個例子

該檔案從終端讀取一行(因為read讀終端時以\n作為結束)字元並列印出來,同時列印每一個字元

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#define MAX_READ 16

int main(int argc, char **argv)
{
    char buffer[MAX_READ + 1];
    memset(buffer, 0x23, sizeof(buffer));  // fill with #
    buffer[MAX_READ] = '\0';
    
    ssize_t numRead, i;
    numRead = read(STDIN_FILENO, buffer, MAX_READ);
    if (numRead == -1)
        perror("read");

    // buffer[numRead] = '\0';
    printf("the input data was:%s\n", buffer);
    for (i = 0; buffer[i] != '\0' && i < MAX_READ; i++)
        printf("%ld->%x\n", i, buffer[i]);
    return 0;
}
$ ./4.4
abcd
the input data was:abcd
############
0->61
1->62
2->63
3->64
4->a
5->23
6->23
...
16->0

由此可見read只是以二進位制的形式照搬資料,並不對資料進行處理,因此,對資料的處理留給了程式設計師

write

bytes位元組的buf資料寫到open返回的fd檔案描述符所指的檔案中,返回實際寫的位元組數,不成功返回-1寫入已開啟的檔案。呼叫成功並不代表已經寫入磁碟,可能先進入快取(這樣減少磁碟活動量、加快write呼叫)。

#include <unistd.h>

ssize_t write(int fd, void *buf, size_t bytes);

close

#include <unistd.h>

int close (int fd);

close函式也有錯誤處理,程式設計時也應該錯誤檢查。

lseek

核心開啟的檔案時會記錄檔案偏移量,第一位元組的偏移量為0,檔案開啟時,會將偏移量設定為0

十分重要:有時候讀檔案讀不出來,可能就是因為檔案偏移量在檔案末尾處,這時候需要重置

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
  • fd檔案描述符
  • offset表示偏移量
    • 正值為正向移動,向繼續往下讀的方向
  • wherece表示檔案讀寫指標從哪裡開始計數
    • SEEK_SET表示起始位置
    • SEEK_CUR表示當前位置
    • SEEK_END表示末尾位置的後一個位元組(這裡直接寫資料的話是恰好和檔案連線)
  • 返回新的檔案偏移量或-1(執行失敗)

檔案空洞

從檔案結尾後到新寫入的資料間的空間,他不佔用磁碟空間,直到寫入了資料。這時檔案的名義的大小可能比磁碟儲存的總量大。具體的在14節

ls -l file        檢視檔案邏輯大小

du -c file     檢視檔案實際佔用的儲存塊多少

od -c file     檢視檔案儲存的內容

unlink刪除

只是刪除path到檔案的一個連結,其檔案對於的i-node減1,為0時,改檔案才從磁碟刪除。

#include <unistd.h>

int unlink(const char *path);

iotcl

檔案和目錄

目錄是另一種檔案,只是內容是包含的檔案資訊和目錄資訊。

連結

每一個檔案對應一個inode,檔案的連結數對應inode中的連結數,記錄著這個檔案的連結數值,即指向該inode的檔案數,檔案和inode是多對一的關係。本質上rm指令呼叫系統呼叫unlink函式,將這個檔案的inode的連結數-1,為0時才真正刪除

  • 硬連結
    • 相連結的檔案總是同步
  • 軟連結
    • 理解為Windows的快捷方式
$ touch a.txt
$ echo "hello" > a.txt

$ ls -li
total 4
2104443 -rw-rw-r-- 1 dwr dwr 6 Apr 10 22:20 a.txt
# inode編號 檔案許可權 使用者 組使用者 不知道 建立月 日 時 檔名
$ ln a.txt a.txt.bak  # 建立硬連結
$ ls -li
total 8
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt.bak

$ ln -s a.txt a.txt.s  # 建立軟連結
$ ls -li
total 8
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt
2104443 -rw-rw-r-- 2 dwr dwr 6 Apr 10 22:20 a.txt.bak
2099642 lrwxrwxrwx 1 dwr dwr 5 Apr 10 22:25 a.txt.s -> a.txt
#include <unistd.h>

int link(const char *__from, const char *__to);
int symlink(const char *__from, const char *__to);

錯誤列印

perror

<errno.h>

根據設定的errno值列印對應的錯誤資訊,列印規則是先列印s中使用者定義的錯誤輸出,在列印系統呼叫錯誤的輸出提示。一定要在系統呼叫之後緊跟列印,否則會被覆蓋

void perror(const char *s);

strerror

將錯誤程式碼轉換為字串錯誤資訊。

char *strerror(int errno);

原子IO

fcntl

#include <fcntl.h>

int fcntl(int __fd, int __cmd, ...)

檔案IO緩衝

這裡是Unix系統程式設計手冊第13章內容,不全待完善

read()write()系統呼叫在操作磁碟檔案時不會直接發起磁碟訪問,而是僅僅在使用者空間緩衝區與核心緩衝區快取記憶體(kernel buffer cache)之間複製資料。write()在後續某個時刻,核心會將其緩衝區中的資料寫入(重新整理至)磁碟。

Linux 核心對緩衝區快取記憶體的大小沒有固定上限。核心會分配儘可能多的緩衝區快取記憶體頁,而僅受限於兩個因素;可用的實體記憶體總量,以及出於其他目的對實體記憶體的需求(例如,需要將正在執行程式的文字和資料頁保留在實體記憶體中)。若可用記憶體不足,則核心會將

解釋一下書中對IO系統呼叫的實驗:

  • 總用時=CPU用時+磁碟讀寫用時
  • CPU用時=使用者CPU用時(使用者模式下執行的程式碼)+系統CPU用時(核心模式(系統呼叫和資料在使用者和核心模式下傳輸)下執行的程式碼)

stdio的緩衝

C語言中IO函式可以理解為系統IO呼叫+資料緩衝,免於編寫者自己處理對資料的緩衝

int setvbuf(FILE *stream, char *buf, int modes, size_t n)

控制stdio庫函式的緩衝形式,需要最先呼叫,之後的stdio操作才有效

  • stream
    • 表示配置緩衝的檔案流
  • buf
    • 不為空則使用size大小作為緩衝區(這個buf空間應該是堆記憶體上,避免函式呼叫和返回對棧進行修改)
    • 為空則自動分配(根據mode選擇是否分配)size大小空間
  • modes
    • _IONBF:not,不緩衝,每一次呼叫stdio函式都立即呼叫系統呼叫
    • _IOLBF:line,行緩衝,遇到換行符或緩衝區滿則呼叫系統呼叫,指向終端裝置的流預設使用該模式
    • _IOFBF:file,全緩衝,指向磁碟的流預設使用該模式
  • 出錯返回非0,成功返回0

相關文章