Buffered I/O#
Buffer I/O 是一種 abstraction 的概念,主要是為了減少 system call 的次數,本質上是一種 API 而非是 System interface,Buffer I/O 會在 user space allocate 一塊 buffer,然後把 buffer 裡面的東西透過 Unbuffer I/O 送出去
Standard Library of C#
Standard Library of C 就是一種提供 Buffered I/O 的 abstraction
- Call
fopen()會得到一個 file object pointer 會放在 Virtual Memory
File object 會有一個 buffer,裡面還會記錄一些 File 相關的資訊,像是
- File descriptor
- Pointer to buffer
- Buffer size
- # of remaining chars, …etc.
Buffer I/O 最後依然會透過 Unbuffered I/O 出去,只是他可以做更多的控制操作(e.g. 累積後一次呼叫 system call)
- Command line 裡面打
strace可以 trace 所有 Buffer I/O
直接碰到 system call 是 high cost (time) 的,所以 C library 會先 allocate 一塊 buffer 去防止直接碰到 kernel
而這樣做的結果除了可以有效加快 I/O Efficiency,還有 Prefetch 的效果
fopen()#
FILE *fopen(const char *pathname, const char *type);c這個 funciton call 可以支援 atomic 的 open,會同時做 open + read/write 的動作,並且會回傳一個 FILE *,這個 FILE * 會放在 Virtual Memory 裡面,裡面會有 buffer、file descriptor、buffer size、remaining chars 等等資訊
以下是各種 flag 的代表意義
| Restriction | r | w | a | r+ | w+ | a+ |
|---|---|---|---|---|---|---|
| file must already exist | ● | ● | ||||
| previous contents of file discarded | ● | ● | ||||
| stream can be read | ● | ● | ● | ● | ||
| stream can be written | ● | ● | ● | ● | ● | |
| stream can be written only at end | ● | ● |
freopen()#
FILE *freopen(const char *pathname, const char *type, FILE *fp);c這個 function 可以把某個已經 open 的 FILE * 重新指定到另一個 file,並且可以指定新的 type (通常是常見 e.g. stdout),下面這段 code 會把掛在 stdout 底下的 file 重新指定到 /tmp/logfile,並且可以同時做 append 的動作
FILE *fp;
fp = freopen ("/tmp/logfile", "a+", stdout);
printf(“Sent to stdout and redirected to /tmp/logfile”);
fclose(stdout);cfdopen()#
FILE *fdopen(int fildes, const char *type);c做 unbuffer I/O 後想重新指定型別,也可以把開好的 fd 來指定型別
- 開啟的 type 一定要是前一個 open 的 type 的 subset,就是 permission 的 subset,e.g.
r+可以指定成r但是不能指定成w - 如果用這個 function call 用
wtype 開啟檔案的話不會先行 truncate
fileno()#
int fileno(FILE *fp);c可以拿到 FILE object 的 file descriptor (fd)
fclose()#
int fclose(FILE *fp);c要做這個 function call 指定的 fstream 裡面的 buffer 必須有效,不然會失效,這個 function 的流程是
- Discard input buffer
- Flush output buffer
- Close file descriptor
- Deallocate file object
FILE *open_data(void)
{
FILE *fp;
char databuf[BUFSIZ]; /* setvbuf makes this the stdio buffer */
if ((fp = fopen(DATAFILE, "r")) == NULL)
return (NULL);
if (setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0)
return (NULL);
return (fp);
}cdatabuf 是一個 local variable 在這個 function 結束後 call fclose(fp) 會失效
Buffering#
Buffer I/O 最主要目的是要減少 system call 的次數,而 buffer 機制就是可以達到這個目的方法。直接 call standard I/O 會自動 allocate buffer,但是也可以自行用 setbuf(), setvbuf() 設定
雖然說是 buffered I/O 但最終還是會往 Unbuffer I/O 送,由 Unbuffer I/O 做真正的 I/O
以下為常見的三種 buffer type
Fully Buffered#
Fully buffered 會等到 buffer 滿了才會送出去,通常是用在 disk、pipe、socket 等等,這些都是需要等到 buffer 滿了才會送出去的
Line Buffered#
每個 new line 就會送一次像是 terminal 就是一種 line buffered 環境,通常兩種情況要特殊處理
- Full: buffer 滿了就一定得送出去
- Flush: 系統要求一定要馬上 flush
下面這個 code,新一行雖然沒看到 \n 但 terminal 必須先輸出 $ 就是一種典型的 flush
char buf[100];
printf(“$ “);
scanf(“%s”, buf); cUnbuffered#
需要有錯誤就馬上輸出不能拖著 (e.g., stderr)
fflush()#
int fflush(FILE *fp);`c- 把
fp裡面的 buffer 內容透過 Unbuffer I/O 送出去 - 如果設定
fp == NULL就會把所有 output buffer 送出去 - 送是送到 buffer cache 而不是直接進去 DISK 還要呼叫
sysc(),fsysc(),fdatasysc()
setbuf()#
void setbuf(FILE *fp, char *buf);c對一個 file object 指定要使用某段 memory 作為 buffer,但缺點是不能指定 buffer size(固定是 BUFSIZ)跟 buffer type
setbuf(fp, NULL);就是把 buffer 關掉
setvbuf()#
int setvbuf(FILE *fp, char *buf, int mode, size_t size);c可以指定 buffer type, buffer size,也可以選擇是否讓 LibC allocate buffer,有三種 mode 可以選擇 - _IOFBF: Fully buffered
_IOLBF: Line buffered_IONBF: Unbuffered
這類型的
setbuf()操作應該出現在fopen()之後,任何對fp的 I/O 操作之前,否則會失效
ftell()#
long ftell(FILE *fp);c找尋目前 offset
fseek()#
int fseek(FILE *fp, long offset, int whence);c在 file object 裡面指定 offset 的功能,跟 lseek() 很像但是有一個差別就是 fseek() 會先把 buffer flush 掉,然後再去做 offset 的動作
whence可以是SEEK_SET,SEEK_CUR,SEEK_ENDoffset可以是正數或負數- Binary file
SEEK_END無用因為 buffer 性質會 append 一堆奇怪東西 - Text file
SEEK_SET就是 0 或著ftell()的 return value
Unformatted I/O#
Formatted I/O 是指有格式的輸入輸出,像是
printf()、scanf(),裡面的%就是格式化的符號,Unformatted I/O 就是沒有格式化的輸入輸出,像是read()、write()、fread()、fwrite()
- Character-at-a-time I/O, e.g.,
getc()- Buffering handled by standard I/O lib
- Line-at-a-time I/O, e.g.,
fgets()- Buffer limit might need to be specified.
- Direct I/O, e.g.,
fread()- Read/write a number of objects of a specified size
Character-at-a-time I/O#
getchar(), getc(), fgetc()#
int getc(FILE *fp);
int getchar(void);
int fgetc(FILE *fp);c這些都是一次接一個 character 的 I/O,getc() 會有可能是 macro,getchar() 一定是 function,所以 getc() 會比 getchar() 快一點,EOF 或是 error 都會回傳 -1
int main() {
char c;
while ((c = getchar()) != EOF)
putchar(c);
}c因為 char 是 unsigned 所以永遠不會停永遠不會是 -1,而如果用 signed char,就會常常有 -1 出現而不一定是在 EOF,所以 c 必須要是 int
putchar(), putc(), fputc()#
int putc(int c, FILE *fp);
int putchar(int c);
int fputc(int c, FILE *fp);c這些都是一次輸出一個的 I/O,putc() 會有可能是 macro,putchar() 一定是 function
fgets()#
char *fgets(char *buf, int size, FILE *fp);c一次讀一行,會把 \n 也讀進去,直到 buffer 滿了或是遇到 \n 或是 EOF 才會停下來
gets()#
char *gets(char *buf);c一次讀一行,會把 \n 也讀進去,但沒有指定 buffer size,會 overflow.
fputs(), puts()#
int fputs(const char *s, FILE *fp);
int puts(const char *s);cputs() 會自動在字串後面加上 \n,並且是一直寫到 stdout,而 fputs() 不會,而這兩個 function 都不會有 overflow 的問題,因為只要輸出空間夠大就可以了
fread(), fwrite()#
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);c是一種 Binary I/O 可以以 Object 為單位寫入讀取
struct {
short count;
long total;
char name[NAME_SIZE];
} item;
if (fwrite(&item, sizeof(item), fp) != 1)
err_sys("fwrite error");c這段程式就是把 item 這個 struct 寫入到 fp 指定的檔案裡面,並且以 sizeof(item) 為單位寫入,寫入的數量是 1 個 object,如果寫入失敗就會回傳錯誤訊息
I/O Functions in Standard C Library#
以下這些都是 Standard C Library 提供的 Formatted I/O functions
Input Functions:#
int scanf(const char *format, …);int fscanf(FILE *fp, const char *format, …);int sscanf(char *buf, const char *format, …);
Output Functions:#
int printf(const char *format, …);int fprintf(FILE *fp, const char *format, …);int sprintf(char *buf, const char *format, …);Overflow is possible for sprintf()\0appended at the end of the string. A better substitute:snprintf()int vprintf(const char *format, va_list arg);int vfprintf(FILE *fp, const char *format, va_list arg);int vsprintf(char *buf, const char *format, va_list arg);
Standard I/O Performance#
| Function | User CPU (s) | System CPU (s) | Clock Time (s) | Bytes of Program Text |
|---|---|---|---|---|
Best time using read/write | 0.01 | 0.18 | 6.67 | – |
fgets, fputs | 2.59 | 0.19 | 7.15 | 139 |
getc, putc | 10.84 | 0.27 | 12.07 | 120 |
fgetc, fputc | 10.44 | 0.27 | 11.42 | 120 |
Single byte using read/write | 124.89 | 161.65 | 288.64 | – |
真正用 System CPU 的時間,Best user CPU time 跟其他 Standard Library 的其實一樣,因此 Standard library 是在節省 System CPU time
fwide()#
int fwide(FILE *fp, int mode)cStandard I/O in C lib 會有 orientation
- single byte character
- multiple byte character 比如某些 emoji 就是 multi-byte character,這個 function 可以指定 orientation,但是 orientation 一但被 set 就不能 reset
Back to the content
NTU PJ System Programming
2025 Fall
← Back to the content