Unix程式設計:檔案I/O操作及檔案描述符
Unix系統中大多數檔案I/O需要用到以下五個函式:open,read,write,lseek以及close。這些函式通常被稱為不帶緩衝的I/O(這些函式都是在核心中執行,它們直接對核心快取區進行讀寫)。
檔案描述符
對於核心而言,所有開啟的檔案都通過檔案描述符引用,檔案描述符是一個非負整數,當開啟一個現有檔案或建立一個新檔案時,核心向程序返回一個檔案描述符。
open 函式:呼叫open函式可以開啟或建立一個檔案
#include <fcntl.h> int open(const char *pathname, int oflag, .../*mode_t mode*/); //返回值:若成功則返回檔案描述符,若出錯則返回-1 /* pathname:要開啟或建立檔案的名字 oflags:檔案開啟的方式 第三個引數僅當open建立新檔案時才使用 */
已開啟的檔案在核心中,用file結構體表示.
open函式具有處理符號連結(指向一個檔案的間接指標,也就是檔案的索引的索引)的功能,如果用open開啟檔案時,如果傳遞給open函式的路徑名指定了一個符號連結,那麼open函式跟隨連結到達你所指定的檔案,若此符號連結所指向的檔案並不存在,則open返回出錯。
creat 函式:建立一個新檔案
#include <fcntl.h> int creat(const char *pathname, mode_t mode); //返回值:若成功則返回為只寫開啟的檔案描述符,若出錯則返回-1 /*該函式等效於 open(pathname, O_RDWR|O_CREAT|O_TRUNC, mode); creat 函式存在不足,只能以只寫方式開啟所建立的檔案,現在一般呼叫open函式來建立*/
close 函式:關閉一個已經開啟的檔案
#include <unistd.h>
int close(int filedes);
//返回值:若成功則返回0,若出錯則返回-1
lseek 函式:
每個開啟的檔案都有一個與其相關聯的“當前檔案偏移量”。用以度量從檔案開始處計算的位元組數。通常,讀、寫操作都是從當前檔案偏移量處開始,並使偏移量增加所讀寫的位元組數。系統預設開啟一個檔案,偏移量指定為零,除非指定O_APPEND選項
我們可以呼叫lseek 顯式地為一個開啟的檔案設定其偏移量
lseek 僅將當前的檔案偏移量記錄在核心中,它並不引起任何I/O操作,然後,該偏移量用於下一個讀或寫操作。#include <unistd.h> off_t lseek(int filedes, off_t offset, int whence); //返回值:若成功則返回新的檔案偏移量,若出錯則返回-1 /* 引數offset 的解釋與引數whence的值有關,偏移量可以為負值,whence的值: SEEK_SET:距檔案開始處offset 個位元組 SEEK_CUR:當前值加offset SEEK_END:檔案長度加offset */
read 函式:從開啟檔案中讀資料
#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);
//返回值:若成功則返回讀到的位元組數,若已到檔案結尾則返回0,若出錯則返回-1
/*從filedes關聯的檔案中讀取nbytes的位元組數到buf中,然後返回實際讀的位元組數*/
讀操作從檔案的當前偏移量處開始,在成功返回之前,該偏移量將增加實際讀到的位元組數,read 有時實際讀到的位元組數會少於要求讀的位元組數。write 函式:向開啟的檔案寫資料
#include <unistd.h>
ssize_t write(int filedes, const void *buf, size_t nbytes);
//返回值:若成功則返回已寫的位元組數,若出錯則返回-1
/*將buf中nbytes個位元組寫入到filedes關聯的檔案中,返回實際寫入的位元組數*/
對於普通檔案,寫操作從檔案的當前偏移量處開始,成功寫之後,該檔案偏移量增加實際寫的位元組數。
上面教科書式的介紹了這幾個常用的I/O函式。下面通過幾個簡單程式來理解這幾個函式的應用。
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFFSIZE 4096
int main(void)
{
int fd;
int n;
char buf[BUFFSIZE];
char data[] = "sunburn";
off_t offset;
if((fd = open("examplefiles", O_RDWR)) == -1)
perror("open error\n");
printf("fd = %d\n", fd);
offset = lseek(fd, 0, SEEK_CUR);
printf("offset = %d\n", offset);
if((n = read(fd, buf, BUFFSIZE)) == -1)
perror("read error\n");
if(write(STDOUT_FILENO, buf, n) != n)//標準輸出
perror("write error\n");
offset = lseek(fd, -5, SEEK_CUR);//當前偏移量前移
printf("offset = %d\n", offset);
lseek(fd, 100, SEEK_SET);//設定新的偏移量
write(fd, data, sizeof(data)/sizeof(char));//基於新的偏移量寫入資料
close(fd);
return 0;
}
下面是程式驗證結果
最終的examplefiles 檔案存在空洞
揭祕檔案描述符
核心支援的檔案描述符資料結構如下圖
右側的表稱為 i 節點表,整個系統只有一張。該表可以視為結構體陣列,該陣列的一個元素對應於一個物理檔案。
中間的表稱為檔案表,整個系統只有一張,該表可以視為結構體陣列,一個結構體中有很多欄位,其中有3個欄位比較重要:
- file status flags:用於記錄檔案被開啟時採用的選項,其實記錄的就是open 呼叫中使用者指定的第 2個引數
- current file offset:用於記錄檔案的當前讀寫位置(指標)正是由於此欄位的存在,使得一個檔案被開啟並讀取後,下一次讀取將從上一次讀取的字元後開始讀取
- v-node ptr:該欄位是指標,指向右側表的一個元素,從而關聯了物理檔案
右側的表稱為檔案描述符表,每個程序有且只有一張,該表可以視為指標陣列,陣列的元素指向檔案表的一個元素,最重要的是陣列元素的下標就是檔案描述符。
open函式的實際操作就是:新建一個 i 節點表元素,讓其對應開啟的物理檔案,新建一個檔案表的元素,根據open的第二個引數設定檔案狀態標誌,將當前檔案偏移量置0,將v節點指標指向剛建立的i節點表元素,在檔案描述符表中尋找一個上未使用的元素,在該元素中填入一個指標值,讓其指向剛建立的檔案表元素,該元素下標作為open的返回值,也就是檔案描述符。
這樣一來,當呼叫read(write)函式時,根據傳入的檔案描述符,系統可以找到對應的檔案描述符元素,進而找到檔案表的元素,找到i節點表元素,從而完成對物理檔案的讀寫。
參考資料:
《Unix 高階環境程式設計》
《Linux下C語言應用程式設計》