目錄表
File I/O and Standard I/O
0x00 Outline
- Introduction
- File I/O Function
- File I/O Issues
- Standard I/O Function
0x01 Introduction
Standard Input、Output、and Error
- 當 shell 執行一個 program 時,會產生 standard input、standard output 和 standard error
- standard input、standard output 和 standard error可以被piped或redirected
File I/O vs Standard I/O
- File I/O(Unbuffered I/O)
- System call,直接和 kernel 溝通
- 通過 file descriptors 來識別 file
- No buffering,直接存取檔案
- Standard I/O(Buffered I/O)
- User-level library
- Uses streams
- 通過 FILE 這個資料結構的 pointer 來識別檔案
- Buffered
- 預設有standard input、standard output 和 standard error
- C語言中為stdin、stdout、stderr
- 涉及fileno function
int fileno(FILE *stream); //此函式會檢測參數的串流,若發生 error 回傳 -1,成功則回傳一個 file descriptor
Unbuffered I/O
- 每個 read 和 write 都會在 kernel 中涉及 system call
- 在 user space 的 program 或 libraries 沒有 buffered
- 通常只有 open、read、write、lseek 和 close 五個 function
File Descriptors
- 在 kernel 中,所有被開啟的檔案都會被 file descriptors 參照
- file descriptors 是非負整數
- 按照 shell 和 多數程式的慣例
- file descriptors 的 0、1、2 分別對應到 standard input、standard output 和 standard error
- 在 unistd.h 中定義了 STDIN_FILENO (0),STDOUT_FILENO (1),STDERR_FILENO (2)
- 一個 process 可以使用的 file descriptors 範圍是 0 ~ OPEN_MAX-1
- OPEN_MAX 可以用 setrlimit(2) function 改變,但需要 root 權限
0x02 File I/O Function
The open(2) Function
int open(const char *pathname, int flags, mode_t mode);
- 開啟一個檔案
- 給一個檔案路徑為參數,回傳目前沒被其他process使用的最小 file descriptor, 有error則回傳 -1
- 第二參數至少要包含以下其中一個
- O_RDONLY : read-only
- O_WRONLY : write-only
- O_RDWR : read/write
- 其他第二參數可選,使用 | 結合,簡單列舉
- O_APPEND : 檔案以 append mode 開啟,每次 write 前會先透過 lseek function 將指標設置到檔案結尾
- O_CREAT : 檔案不存在時建立,檔案的 uid 為該 process 的 euid,gid 為該 process 的 egid 或 parent directory 的 gid (depending on file system type and mount options)
- O_TRUNC : 如果檔案已經存在,是 regular file 且可寫入,則檔案會被 truncated 為長度 0
- flags 使用 O_CREAT | O_EXCL,可以達到 automic creating of a non-exist file 的效果
在 NFS 中當有兩個以上 process 同時 append data 可能會損壞檔案,因為 NFS 本身沒有支援 append 方式,必須靠 client kernel 模擬,而無法應付 race condition
The creat(2) Function
int creat(const char *pathname, mode_t mode);
- 等於是 open(const char *pathname, O_CREAT|O_WRONLY|O_TRUNC, mode_t mode)
The close(2) Function
int close(int fd);
- 關閉 file descriptor
- 回傳 0 表示成功, -1 表示 error
- 當一個 process 終止後,所有它開啟的檔案會自動被關閉
The lseek(2) Function
off_t lseek(int fd, off_t offset, int whence);
- 重新定位 file descriptior offset
- 回傳新的 file offset,error 則回傳 -1
- 通常 offset 為 32-bit long,另外有 lseek64() using off64_t
- 第二參數 offset 單位為 character,若給 0 可以用來檢測檔案是否 seekable
- 第三參數 whence包含
- SEEK_SET : 指標設在檔案開頭為基準的 offset char
- SEEK_CUR : 指標設在目前位置為基準的 offset char
- SEEK_END : 指標設在檔案結尾為基準的 offset char,append mode 開啟 file 即為此方式
- File hole(待補)
The read(2) Function
ssize_t read(int fd, void *buf, size_t count);
- 從 file descriptor 讀取 count bytes 進 buffer
- 回傳讀取的 byte 數,如果 EOF(End of File,已讀完) 則是 0,error 為 1
- 每次讀取後 file offset 會往後移
The write(2) Function
ssize_t write(int fd, const void *buf, size_t count);
- 從 buffer 讀取 count bytes 寫進 file descriptor 指向的檔案
- 回傳寫入的 byte 數,error 為 1
- 每次寫入後 file offset 會往後移
0x03 File I/O Issues
I/O Efficiency
- 基本上 buffer 越大,I/O 所花時間越短
File Sharing
- 同個檔案可以被多個 process 同時開啟
- kernal 會為開啟的檔案維護數份 data structure
- 每個 process 會在 process table 有一項條目
- 每項 process table 的條目又包含一張 table ,記錄已開啟的 file descriptors
- file descriptors table 中的每筆紀錄會有 file pointer 指向一張 file table,代表著一個被開啟的檔案
- 每個 file table 會關聯到一個 v-node 的資料結構
Atomic Operation
- 如果有兩個執行緒都 lseek 到檔案尾端並執行 write 則會發生 error,任何需要兩次以上 system call 的操作都不能稱作 Atomic Operation
pread(2) And pwrite(2)
ssize_t pread(int fd, void *buf, size_t count, off_t offset); ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
- 將 seek 和 read、write 結合成 atomic operation,兩件事無法被打斷分離
- 有自己的 offset,在 read、write 時 File offset 不會被改變
dup(2) And dup2(2)
int dup(int oldfd); int dup2(int oldfd, int newfd);
- 複製出一個新的 file descriptor
- 回傳複製出的 fd,有 error 則回傳 -1
- dup 使用未被使用的最小號碼為新的 file descriptor
- dup2 把舊的 fd 複製到新的 fd
- 如果有需要會先關閉新的 fd
- 如果 oldfd 是無效的,這個 system call 會 fail,且 newfd 不會被關閉
- 如果 oldfd 是有效的,且 oldfd 值和 newfd 相同,則 dup2 會直接回傳 newfd
- dup(fd); 等價於 fnctl(fd, F_DUPED, 0);
- dup2(fd, fd2); 等價於 fnctl(fd, F_DUPED, fd2);
- dup2 是 atomic operation
sync(2), fsync(2) And fdatasync(2)
void sync(void); int fsync(int fd); int fdatasync(int fd);
- return 0, error 則為 -1
- 一般 write 時,更新的只是記憶體和快取,並不會立即寫到硬碟中
- 因為 write 不會等到資料寫入硬碟後才 return,所以若在 write 後,寫入硬碟之前停電或壞軌,可能導致資料遺失,儘管時間差很短
- 使用 sync 等方法將資料寫入硬碟中,這些方法都會等到資料確實寫入後才 return
- 要寫入的部分除了 filedata 外,還包含 metadata (檔案大小、最後存取時間等屬性)
- fsync 會將 filedata 和 metadata 一起寫入硬碟,要兩次 I/O
- fdatasync 一般只會將 filedata 寫入,僅僅在有必要時才會寫入 metadata,以減少 I/O 時間成本
- 有必要的定義為會影響檔案資料正常運作的,例如寫入資料後檔案大小(st_size)改變,若 metadata 未同步則可能錯估邊界而讀不到內容,而像最後存取時間(atime)/修改時間(mtime)
- sync for all files, fsync and fdatasync for specific file
fnctl(2)
int fcntl(int fd, int cmd, ... /* arg */ );
- 操作 file descriptor,改變開啟檔案的屬性
- 常見 command 包含
- F_DUPFD : duplicate the file descriptor
- F_GETFD/F_SETFD : get or set the file descriptor flag
- F_GETFL/F_SETFL : get or set the file status flags,像是 O_RDONLY、O_WRONLY、O_RDWR、O_APPEND
- 用
|=
Enable flag - 用
&=
Disable flag
#include "apue.h" #include <fcntl.h> void set_fl(int fd, int flags) /* flags are file status flags to turn on */ { int val; if ((val = fcntl(fd, F_GETFL, 0)) < 0) err_sys("fcntl F_GETFL error"); val |= flags; /* turn on flags */ if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error"); }
ioctl(2)
int ioctl(int d, int request, ...);
/dev/fd
- 較現代的系統提供了 /dev/fd 資料夾
- 這是一個 virtual file system
- 假設 fd n 已經被開啟,則開啟 /dev/fd/n 等同於複製 fd n
- fd = open(“/dev/fd/0”, mode);
- fd = dup(0);
- 主要用途在 shell,使程式可以像處理 regular file 一樣處理 standard in/out
0x04 Standard I/O Function
Standard I/O
- 處理 buffer 分配
- Perform I/O in optimal-sized chunks
- 較容易使用
- FILE Structure
- 將開啟的檔案都視為串流
- 串流和 file descriptors 相關
- 維護 buffer states
Buffering
- Fully buffered
- Characters written to or read from a fully buffered stream are transmitted to or from the file in blocks of arbitrary size.
- 一般 disk 中的檔案都是藉由 standard I/O library 做 fully buffered
- 開始串流時通常使用 malloc function 來得到可使用的 buffer
- 真正 I/O 的時機
- Reading : buffer 是空的,需要填入時
- Writing : buffer 是滿的,需要被 flushing,當 buffer 滿時會自動 flush,或是手動呼叫 fflush() 進行
- Line buffered
- Characters written to a line buffered stream are transmitted to the file in blocks when a newline character is encountered.
- 一般使用在 terminal device
- 真正 I/O 的時機在 standard I/O library 遭遇換行字元時
- 因為 buffer 大小固定,有可能在還沒遭遇換行字元時就已經滿了,此時也會進行 I/O
- 當有 input 來自 line buffered 或 unbuffered 時,line buffered output stream 都會先被 flush
- Unbuffered
- Characters written to or read from an unbuffered stream are transmitted individually to or from the file as soon as possible.
- stdand error stream 使用
Newly opened streams are normally fully buffered, with one exception: a stream connected to an interactive device such as a terminal is initially line buffered.
int fflush(FILE *stream);
Function for Setting Buffer (3)
void setbuf(FILE *stream, char *buf); int setvbuf(FILE *stream, char *buf, int mode, size_t size);
- return 0 if OK,nonzero on error
- Buffering mode
- _IOFBF: fully buffered
- _IOLBF: line buffered
- _IONBF: unbuffered
Function for Open and Close Files (3)
FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *stream); int fclose(FILE *fp);
- return file pointer if OK,NULL on error
- mode
- r or rb: open for reading
- w or wb: truncate to 0 length or create for writing
- a or ab: append, open for writing at EOF or create for writing
- r+, r+b, or rb+: open for reading and writing
- w+, w+b, or wb+: equivalent to w or wb plus reading
- a+, a+b, or ab+: equivalent to a or ab plus reading
Function for Read and Write By Character (3)
//read character int getc(FILE *stream); int getchar(void);
- return next char if OK, EOF on EOF or error
//Test EOF or Error int feof(FILE *stream); int ferror(FILE *stream);
- return nonzero(true) if condition is true ,or zero(false)
//write character int putc(int c, FILE *stream); int putchar(int c);
- return c if OK,EOF on error
Function for Read and Write By Line
//read line char *fgets(char *s, int size, FILE *stream); char *gets(char *s);
- reutrn buf if OK,NULL on EOF or error
//write line int fputs(const char *s, FILE *stream); int puts(const char *s);
- return non-negative value if OK,EOF on error
Binary I/O
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);
- return number of object read or write
Positioning a Stream
int fseek(FILE *stream, long offset, int whence); long ftell(FILE *stream); void rewind(FILE *stream);
- fseek 用法類似 lseek
- ftell 會得到目前 file position indicator 指向 stream 中位置的值
- rewind 會將 stream file position indicator 設到檔案開頭,效用等同於 (void) fseek(stream, 0L, SEEK_SET)
Temporary Files
char *tmpnam(char *str); FILE *tmpfile(void);
- 用於建立臨時文件
- tmpnam 回傳一個指標指向獨特的路徑名稱,NULL on error
- tmpfile 回傳 file pointer if OK,NULL on error
- 不建議使用 tmpnam
- 若參數 str 為 NULL,回傳的 char 會保留在內部 buffer,下次再呼叫 tmpnam 時會被覆蓋
- tmpnam 回傳的文件路徑名稱一般為 /tmp/fileXXXXXX,不是 FILE pointer,在開檔讀寫還需另外處理