Linux 檔案 I/O 是系統程式設計基石,open/read/write/lseek/close 五大核心系統呼叫構成無緩衝 I/O (Unbuffered I/O) 基礎,直接與核心互動,效能高、可控性強。本文深度解析各 API 簽名、旗標、返回值、錯誤處理、檔案描述符管理、原子操作與實戰程式碼範例。
0: STDIN_FILENO (標準輸入)、1: STDOUT_FILENO (標準輸出)、2: STDERR_FILENO (標準錯誤)。open 返回最小未使用描述符,特性常用於重導向實現。#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode); // 需 O_CREAT 時提供 mode
// 等效於 open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)
int creat(const char *pathname, mode_t mode);
O_RDONLY:唯讀O_WRONLY:唯寫O_RDWR:讀寫| 旗標 | 作用 | 注意事項 |
|---|---|---|
O_CREAT | 檔案不存在則建立,需第三參數 mode | mode 受 umask 影響,實際權限 = mode & ~umask |
O_EXCL | 配合 O_CREAT,檔案存在則報錯 (EEXIST) | 原子性檢查-建立,防競爭條件 |
O_TRUNC | 檔案存在且為寫入模式開啟時,截斷長度為 0 | 危險:資料清空,不可恢復 |
O_APPEND | 每次寫入追加到檔案末尾 | 核心保證原子追加,多進程安全 |
O_SYNC | 同步 I/O,寫入等待資料寫入磁碟 | 效能低,保證資料持久化 |
O_DSYNC | 資料同步,不含元資料 | 比 O_SYNC 輕量 |
O_NONBLOCK | 非阻塞模式 | FIFO/管道/裝置檔案常用 |
O_CLOEXEC | 執行 exec 時自動關閉 | 防檔案描述符洩漏到子進程 |
O_DIRECTORY | 路徑必須為目錄 | 防符號連結攻擊 |
O_NOFOLLOW | 不跟隨符號連結 | 安全開啟 |
| 返回值 | 含義 |
|---|---|
> 0 | 成功讀取位元組數 (可能小於 count,如管道/終端/檔案末尾) |
0 | EOF (檔案末尾) |
-1 | 錯誤,errno 指示原因 (EINTR, EIO, EBADF...) |
ssize_t n;
while ((n = read(fd, buf, sizeof(buf))) > 0) {
// 處理 n 位元組
}
if (n < 0) perror("read");
#include
ssize_t write(int fd, const void *buf, size_t count);
buf 前 count 位元組寫入 fd。count;-1 錯誤 (如磁碟滿 ENOSPC、超過檔案大小限制 EFBIG)。#include
#include
#include
#include
#include
int main() {
const char *path = "demo.io";
const char *msg = "Hello, Linux File I/O!\n";
char buf[128];
int fd;
// 1. 開啟/建立:讀寫、建立、截斷
fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open"); return 1; }
// 2. 寫入
size_t len = strlen(msg);
ssize_t written = write(fd, msg, len);
if (written != len) { perror("write"); close(fd); return 1; }
printf("Written %zd bytes\n", written);
// 3. 定位到開頭
off_t pos = lseek(fd, 0, SEEK_SET);
if (pos < 0) { perror("lseek"); close(fd); return 1; }
printf("Seeked to offset %ld\n", pos);
// 4. 讀取
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n < 0) { perror("read"); close(fd); return 1; }
buf[n] = '\0';
printf("Read %zd bytes: %s", n, buf);
// 5. 關閉
close(fd);
return 0;
}
編譯執行:gcc demo.c -o demo && ./demo
O_CREAT | O_EXCL:原子性「檢查存在並建立」,用於鎖檔案實現。O_APPEND:核心保證多進程並發寫入時,每次寫入都原子追加到當前末尾,無需額外鎖。rename():同檔案系統內原子替換,常用於配置檔案熱更新 (寫臨時檔 → rename)。// Unix Domain Socket 透過 sendmsg/recvmsg 傳遞 fd
// 實現進程間共享開啟檔案描述符,高效零拷貝
O_NONBLOCK + select/poll/epoll 實現高併發網路伺服器。io_uring (Linux 5.1+) 或線程池。// 統一錯誤處理巨集
#define CHECK(call) \
do { if ((call) < 0) { perror(#call); exit(EXIT_FAILURE); } } while(0)
// 使用
CHECK(fd = open("file", O_RDONLY));
CHECK(write(fd, buf, len));
CHECK(close(fd));
| 特性 | 無緩衝 I/O (syscall) | 標準 I/O (FILE*) |
|---|---|---|
| 緩衝 | 無 (直接核心) | 用戶空間緩衝 (全緩衝/行緩衝/無緩衝) |
| API | open/read/write/lseek/close | fopen/fread/fwrite/fseek/fclose |
| 效能 | 系統呼叫開銷大,適合大塊/精確控制 | 緩衝減少系統呼叫,適合小量頻繁 I/O |
| 可移植性 | POSIX 標準,類 Unix 通用 | C 標準庫,跨平台 (含 Windows) |
| 格式化 | 無 | fprintf/fscanf 格式化強大 |
掌握無緩衝 I/O 核心 API,是深入理解 Linux 核心、開發高效能儲存引擎、除錯複雜 I/O 問題的必經之路。
Linux 核心 Panic 排查實戰:從無法重現到 Red Hat 官方 Bug 確認 ftrace 時間戳溢位
HTTP 斷點續傳核心原理:Range 標頭與 206 Partial Content 回應
Ubuntu 系統安裝 Guake 終端機:輕量級下拉式終端神器
yum.conf 設定檔詳細範本:快取目錄、日誌、鎖定最新版、多倉庫 baseurl 配置
Linux 桌面環境七大發展趨勢:選擇多元、設計優先、回歸經典
Ubuntu 解決「can't find libjava.so」錯誤:使用 PPA 安裝 Oracle Java 8 避免手動配置坑