目錄
一、重定向的原理與實踐
1. 輸出重定向:讓數據流向新目的地
2. 追加重定向:在文件末尾追加數據
3. 輸入重定向:從指定文件讀取數據
4. 標準輸出流與標準錯誤流的區別
5. 使用?dup2?實現重定向
二、FILE 結構體的奧秘
1. FILE 中的文件描述符
2. FILE 中的緩沖區
3. 緩沖區的提供者與位置
4. 操作系統的緩沖區
5.?緩沖體系的層級架構
?編輯
一、重定向的原理與實踐
1. 輸出重定向:讓數據流向新目的地
原理 :
????????輸出重定向的本質是修改文件描述符下標對應的
struct file*
內容,將原本應該輸出到一個文件(通常是顯示器)的數據,改為輸出到另一個文件。
代碼示例 :
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {// 關閉標準輸出流(文件描述符 1)close(1);// 打開 log.txt 文件,獲取文件描述符int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0) {perror("open");return 1;}// 使用 printf 輸出數據printf("hello world\n");printf("hello world\n");printf("hello world\n");printf("hello world\n");printf("hello world\n");// 刷新緩沖區,確保數據寫入文件fflush(stdout);// 關閉文件描述符close(fd);return 0;
}
運行結果 :運行程序后,顯示器上沒有輸出數據,而 log.txt
文件中寫入了多行 "hello world"。
說明 :printf
函數默認向標準輸出流(stdout
)輸出數據,而 stdout
指向的 FILE
結構體中存儲的文件描述符是 1。通過關閉文件描述符 1 并重新打開文件,我們實現了輸出重定向。
2. 追加重定向:在文件末尾追加數據
原理 :
????????追加重定向與輸出重定向類似,區別在于數據是追加到目標文件末尾,而不是覆蓋原有內容。通過
O_APPEND
標志,每次寫入都會自動定位到文件末尾,保留原有內容。
代碼示例 :
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {// 關閉標準輸出流(文件描述符 1)close(1);// 以追加方式打開 log.txt 文件int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT, 0666);if (fd < 0) {perror("open");return 1;}// 使用 printf 輸出數據printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");printf("hello Linux\n");// 刷新緩沖區,確保數據寫入文件fflush(stdout);// 關閉文件描述符close(fd);return 0;
}
運行結果 :運行程序后,log.txt
文件中新增了多行 "hello Linux",且這些內容追加在原有內容之后。
3. 輸入重定向:從指定文件讀取數據
原理 :
????????輸入重定向是將原本從標準輸入流(通常是鍵盤)讀取數據的操作,改為從指定文件讀取數據。
代碼示例 :
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {// 關閉標準輸入流(文件描述符 0)close(0);// 打開 log.txt 文件,獲取文件描述符int fd = open("log.txt", O_RDONLY | O_CREAT, 0666);if (fd < 0) {perror("open");return 1;}// 定義字符數組,用于存儲讀取的數據char str[40];// 使用 scanf 從文件讀取數據while (scanf("%s", str) != EOF) {printf("%s\n", str);}// 關閉文件描述符close(fd);return 0;
}
運行結果 :運行程序后,scanf
函數從 log.txt
文件中讀取數據,并通過 printf
輸出到顯示器。
說明 :scanf
函數默認從標準輸入流(stdin
)讀取數據,而 stdin
指向的 FILE
結構體中存儲的文件描述符是 0。通過關閉文件描述符 0 并重新打開文件,我們實現了輸入重定向。
4. 標準輸出流與標準錯誤流的區別
原理 :
????????標準輸出流(
stdout
,文件描述符 1)和標準錯誤流(stderr
,文件描述符 2)都默認輸出到顯示器,但它們在重定向時的行為不同。重定向操作只影響標準輸出流,而標準錯誤流不受影響。
操作 | stdout表現 | stderr表現 |
---|---|---|
直接運行 | 屏幕輸出 | 屏幕輸出 |
重定向到文件 | 寫入文件 | 仍顯示屏幕 |
代碼示例 :
#include <stdio.h>int main() {// 向標準輸出流輸出數據printf("hello printf\n");// 向標準錯誤流輸出數據perror("perror");// 使用 fprintf 向標準輸出流和標準錯誤流輸出數據fprintf(stdout, "stdout:hello fprintf\n");fprintf(stderr, "stderr:hello fprintf\n");return 0;
}
運行結果 :直接運行程序時,顯示器上輸出四行字符串。若將程序運行結果重定向到文件 log.txt
,則 log.txt
中只有標準輸出流的兩行字符串,而標準錯誤流的兩行數據仍輸出到顯示器。
5. 使用?dup2
?實現重定向
原理 :
dup2
函數用于將一個文件描述符的內容拷貝到另一個文件描述符,從而實現重定向。
函數原型 :
int dup2(int oldfd, int newfd);
代碼示例 :
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>int main() {// 打開 log.txt 文件,獲取文件描述符int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0) {perror("open");return 1;}// 關閉標準輸出流(文件描述符 1)close(1);// 使用 dup2 將 fd 拷貝到文件描述符 1// 將log.txt克隆到標準輸出dup2(fd, 1);// 使用 printf 輸出數據// 自動寫入log.txtprintf("hello printf\n");fprintf(stdout, "hello fprintf\n");// 關閉文件描述符close(fd);return 0;
}
運行結果 :運行程序后,log.txt
文件中寫入了 "hello printf" 和 "hello fprintf"。
二、FILE 結構體的奧秘
1. FILE 中的文件描述符
原理 :
????????C 語言的庫函數是對系統調用接口的封裝,文件操作本質上是通過文件描述符進行的。
FILE
結構體內部封裝了文件描述符。
在 /usr/include/stdio.h
頭文件中,FILE
是 struct _IO_FILE
的別名。
typedef struct _IO_FILE FILE;
在 /usr/include/libio.h
頭文件中,struct _IO_FILE
定義了 _fileno
成員,用于存儲文件描述符。
struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags//緩沖區相關/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base; /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno; //封裝的文件描述符
#if 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
示例代碼 :
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "w");if (fp == NULL) {perror("fopen");return 1;}// 獲取 FILE 結構體中的文件描述符int fd = fileno(fp);// 使用文件描述符進行操作// ...// 關閉文件fclose(fp);return 0;
}
說明 :fopen
函數在上層為用戶申請 FILE
結構體變量,并返回該結構體的地址。在底層通過系統接口 open
打開文件,得到文件描述符,并將其存儲在 FILE
結構體的 _fileno
成員中。
2. FILE 中的緩沖區
原理 :
????????C 語言中的文件操作函數(如
printf
、fputs
等)使用緩沖區來提高效率。緩沖區的類型有無緩沖、行緩沖和全緩沖。
緩沖類型對比表:
緩沖類型 | 刷新條件 | 典型應用場景 |
---|---|---|
無緩沖 | 立即寫入 | 標準錯誤 |
行緩沖 | 遇換行符或緩沖區滿 | 終端輸出 |
全緩沖 | 緩沖區滿或強制刷新 | 文件操作 |
代碼示例 :
#include <stdio.h>
#include <unistd.h>int main() {// 使用 printf 輸出數據printf("hello printf\n");// 使用 fputs 輸出數據fputs("hello fputs\n", stdout);// 使用 write 系統接口輸出數據write(1, "hello write\n", 12);// 創建子進程fork();return 0;
}
運行結果 :直接運行程序時,數據輸出到顯示器。若將程序運行結果重定向到文件 log.txt
,則 log.txt
中 printf
和 fputs
的數據出現兩次,而 write
的數據只出現一次。
說明 :printf
和 fputs
使用緩沖區,當程序運行結果重定向到文件時,緩沖區的數據會被復制到父進程和子進程中,導致數據重復。而 write
是系統接口,沒有緩沖區,數據直接輸出到目標文件。
3. 緩沖區的提供者與位置
原理 :
????????緩沖區由 C 語言提供,在
FILE
結構體中維護。FILE
結構體中包含多個成員,用于記錄緩沖區的相關信息。
在 /usr/include/libio.h
頭文件中,struct _IO_FILE
定義了多個與緩沖區相關的成員,如 _IO_read_ptr
、_IO_read_end
等。
//緩沖區相關
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
示例代碼 :
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "w");if (fp == NULL) {perror("fopen");return 1;}// 使用 fputs 寫入數據fputs("hello fputs", fp);// 刷新緩沖區fflush(fp);// 關閉文件fclose(fp);return 0;
}
說明 :fputs
函數將數據寫入 FILE
結構體中的緩沖區,fflush
函數用于刷新緩沖區,將數據寫入文件。
4. 操作系統的緩沖區
原理 :
????????操作系統也有自己的緩沖區。當用戶緩沖區的數據被刷新到操作系統緩沖區后,操作系統會根據自己的刷新機制,將數據寫入磁盤或顯示器。
示例代碼 :
#include <stdio.h>
#include <unistd.h>int main() {// 使用 printf 輸出數據printf("hello printf\n");// 刷新用戶緩沖區fflush(stdout);// 暫停 2 秒sleep(2);return 0;
}
說明 :printf
輸出的數據先存儲在用戶緩沖區,fflush
刷新用戶緩沖區后,數據進入操作系統緩沖區。操作系統會根據自己的刷新機制,將數據寫入顯示器。
5.?緩沖體系的層級架構
數據流動路徑:
用戶程序 → C庫緩沖區(用戶空間)→ 內核緩沖區(內核空間)→ 物理存儲設備
關鍵特點:
用戶緩沖區由C庫維護
內核緩沖區由OS管理
fflush
只刷新用戶→內核階段最終落盤由內核決定