Unix環境高階程式設計 讀書筆記 第三章 檔案IO
概述
對於open、read、write、lseek、close等函式,被稱為不帶緩衝的I/O。即unbuffered i/O,術語“不帶緩衝”指的是每個read或者write都呼叫核心中的一個系統呼叫。
檔案描述符
- 檔案描述符是一個非負的整數;
- 檔案描述符0與程序的標準輸入關聯;
- 檔案描述符1與程序的標準輸出關聯;
- 檔案描述符2與程序的標準錯誤關聯;
- 在unistd.h中定義了檔案描述符0、1、2的符號常量:
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
函式open與openat
函式的原型為:
#include <fcntl.h>
int open(const char *path, int oflag, .../*mode_t mode*/);
int openat(int fd, const char *path, int oflag, .../*mode_t mode*/);
/*兩個函式若成功,返回檔案描述符fd,若出錯,返回-1*/
函式的引數中,path是要開啟或者建立的檔案的名字。oflag說明此函式的多個選項。可以是多個常量的“或”運算構成,這些常量在fcntl.h中定義。
/* File access modes for `open' and `fcntl'. */
#define O_RDONLY 0 /* Open read-only. */
#define O_WRONLY 1 /* Open write-only. */
#define O_RDWR 2 /* Open read/write. */
/* Bits OR'd into the second argument to open. */
#define O_CREAT 0x0200 /* Create file if it doesn't exist. */
#define O_EXCL 0x0800 /* Fail if file already exists. */
#define O_TRUNC 0x0400 /* Truncate file to zero length. */
#define O_NOCTTY 0x8000 /* Don't assign a controlling terminal. */
#define O_ASYNC 0x0040 /* Send SIGIO to owner when data is ready. */
#define O_FSYNC 0x0080 /* Synchronous writes. */
#define O_SYNC O_FSYNC
#ifdef __USE_MISC
#define O_SHLOCK 0x0010 /* Open with shared file lock. */
#define O_EXLOCK 0x0020 /* Open with shared exclusive lock. */
#endif
#ifdef __USE_XOPEN2K8
# define O_DIRECTORY 0x00200000 /* Must be a directory. */
# define O_NOFOLLOW 0x00000100 /* Do not follow links. */
# define O_CLOEXEC 0x00400000 /* Set close_on_exec. */
#endif
#if defined __USE_POSIX199309 || defined __USE_UNIX98
# define O_DSYNC 0x00010000 /* Synchronize data. */
# define O_RSYNC 0x00020000 /* Synchronize read operations. */
#endif
/* All opens support large file sizes, so there is no flag bit for this. */
#ifdef __USE_LARGEFILE64
# define O_LARGEFILE 0
#endif
/* File status flags for `open' and `fcntl'. */
#define O_APPEND 0x0008 /* Writes append to the file. */
#define O_NONBLOCK 0x0004 /* Non-blocking I/O. */
#ifdef __USE_MISC
# define O_NDELAY O_NONBLOCK
#endif
通過fd區分了open函式與openat函式:
- path引數是絕對路徑,則fd引數會被忽略,openat函式等同於open函式;
- path引數是相對路徑,則fd引數指出了相對路徑名在檔案系統中的起始地址。fd引數是通過開啟相對路徑名所在的目錄來獲取的;
- path引數是相對路徑,fd引數是AT_FDCWD,則路徑名是在當前工作目錄中獲取。
函式creat
函式原型為:
#include <fcntl.h>
int creat(const char *path, mode_t mode);
/*函式呼叫成功,則返回開啟的檔案描述符,若出錯,則返回-1*/
此函式的功能也可通過open函式實現,等效於:
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
函式close
函式原型:
#include <unistd.h>
int close(int fd);
/*函式呼叫成功,則返回0,出錯返回-1*/
函式lseek
函式原型:
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*函式呼叫成功,則返回新的檔案偏移量,若出錯則返回-1*/
- 當前檔案偏移量:一個非負整數,用以度量從檔案開始處計算的位元組數;
- 讀寫操作都是從當前檔案偏移量處開始,並使得偏移量增加所讀寫的位元組數;
- 當whence引數是SEEK_SET,則將當前檔案偏移量設定為距檔案開始處offset個位元組;
- 當whence引數是SEEK_CUR,則將當前檔案偏移量設定為當前值加offset,其中offset可正可負;
- 當whence引數是SEEK_END,則將當前檔案偏移量設定為檔案長度加offset,其中offset可正可負;
- 可通過以下方式確定開啟的檔案的當前偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
- 檔案的偏移量可以大於檔案的長度,對檔案的下一次寫,將在檔案中產生一個空洞。檔案中的空洞並不要求在磁碟上佔有儲存空間。
- 創造空洞檔案的範例如下:
#include "apue.h"
#include <fcntl.h>
char buf1[] = "abcdefg";
char buf2[] = "ABCDEFG";
int main(void)
{
int fd;
if((fd = creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error");
if(write(fd, buf1, 7) != 7)
err_sys("buf1 write error");
if(lseek(fd, 100, SEEK_SET) == -1)
err_sys("lseek error");
if(write(fd, buf2, 7) != 7)
err_sys("buf2 write error");
exit(0);
}
函式read
函式原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
/*返回讀到的位元組數,若到檔案尾端,返回0,若出錯,返回-1*/
存在多種情況導致實際讀取到的位元組數少於要求讀取的位元組數。
ssize_t表示帶符號的整型,而size_t表示不帶符號的整型。
函式write
函式原型:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
/*若成功,返回已寫的位元組數,若出錯,返回-1*/
函式的返回值一般與nbytes的值相同,否則表示出錯。
檔案共享
在書中,開啟檔案的核心資料結構如下:
兩個獨立的程序各自開啟同一檔案,其核心資料結構的表關係如下:
- 在每完成write操作後,在檔案表項中的當前檔案偏移量即增加所寫入的位元組數;
- 如果用O_APPEND標誌開啟一個檔案,則相應標誌也被設定到檔案表項的檔案狀態標誌中。每次對這種具有追加寫標誌的檔案執行寫操作時,檔案表項中的當前檔案偏移量首先會被設定為i節點表項中的檔案長度;
- 若一個檔案用lseek定位到檔案當前的尾端,則檔案表項中當前檔案偏移量被設定為i節點表項中的當前檔案長度;
- lseek函式只修改檔案表項中的當前檔案偏移量,不進行任何實際的IO操作;
- 可能有多個檔案描述符指向同一個檔案表項;
- 每個程序都有其自己的檔案表項,因而也有其自己的檔案偏移量。
檔案描述符與檔案狀態標誌的區別:
檔案描述符只用於一個檔案的一個描述符,而檔案狀態標誌應用於指向該給定檔案表項的任何程序中的所有描述符。
函式pread與pwrite
函式原型:
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
/*返回讀到的位元組數,若到檔案尾端,返回0,若出錯,返回-1*/
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
/*若成功,返回已寫的位元組數,若出錯,返回-1*/
函式pread與pwrite相當於呼叫lseek後再呼叫read或者write,但是pread與pwrite函式具有原子操作的特性。
函式dup和dup2
函式原型:
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
/*若成功,返回新的檔案描述符,若出錯,返回-1*/
這兩個函式的作用是複製一個現有的檔案描述符。
由dup返回的新檔案描述符一定是當前可用的檔案描述符中的最小數值。
使用dup2可以用fd2引數指定新的描述符值。
這些函式返回的新的檔案描述符與引數fd共享同一個檔案表項,如下圖:
函式sync、fsync和fdatasync
函式由來背景:
為了提高系統的執行效率,在向檔案寫入資料時,核心通常先將資料複製到緩衝區,即設定的緩衝區快取記憶體或者頁快取記憶體,晚些時候才真正的寫入磁碟中。這種方式被稱為延遲寫。
當核心需要重用緩衝區來存放其他磁碟塊資料時,核心會把所有延遲寫資料塊寫入磁碟,為了保證磁碟上實際檔案系統與緩衝區中的內容一致,Unix系統提供了sync、fsync和fdatasync函式。
函式的原型為:
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
/*若成功,返回0,若出錯,返回-1*/
void sync(void);
sync函式只是將所有修改過的塊緩衝區排入寫佇列,然後立即返回,並不等待實際寫磁碟操作結束。
fsync函式對檔案描述符fd指定的檔案起作用,函式在同步更新檔案的屬性,等待寫磁碟操作結束後才返回。
fdatasync函式類似fsync函式,但是隻影響檔案的資料部分。
函式fcntl
函式fcntl可以改變已經開啟檔案的屬性。函式原型為:
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /*int arg*/);
/*若成功,返回值依賴於cmd,若出錯,返回-1*/
函式的功能:
- 複製一個已有的描述符,cmd=F_DUPFD或者F_DUPFD_CLOEXEC
- 獲取或設定檔案描述符標誌,cmd=F_GETFD或者F_SETFD
- 獲取或設定檔案狀態標誌,cmd=F_GETFL或者F_SETFL
- 獲取或設定非同步IO所有權,cmd=F_GETOWN或者F_SETOWN
- 獲取或設定記錄鎖,cmd=F_GETLK或者F_SETLK或者F_SETLKW