首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 系統程式設計:檔案 I/O 基礎 —— 檔案描述符、open/close/creat 詳解

     編輯:關於C語言

前言

在 Linux 世界裡,「一切皆檔案」。無論是普通檔案、目錄、裝置、Socket,還是管道,核心都統一用檔案描述符 來管理。掌握低層 I/O (Low-level I/O) 是進行系統程式開發、理解高階庫 (如 `stdio.h`) 底層運作的基石。本文詳細講解檔案描述符概念、核心系統呼叫 `open`、`close`、`creat` 及其關鍵細節。

一、I/O 分類與檔案描述符

1. 高階 I/O vs 低階 I/O

特性高階 I/O (Standard I/O)低階 I/O (System I/O)
代表函數庫`stdio.h` (`fopen`, `fread`, `fprintf`...)`unistd.h`, `fcntl.h` (`open`, `read`, `write`...)
緩衝機制帶緩衝 (全緩衝/行緩衝/無緩衝)無緩衝 (直接呼叫核心)
資料類型`FILE *` (檔案指標)`int` (檔案描述符 fd)
適用場景一般應用開發、格式化輸出系統程式、高效能需求、精確控制

2. 檔案描述符 核心概念

  • 定義:非負整數,核心為每個進程維護的「打開檔案表」索引。
  • 核心資料結構:進程打開檔案表 → 系統打開檔案表 → i-node 表 (檔案實體資訊)。
  • 三個預設開啟的 fd (定義於 `unistd.h`):
    • `STDIN_FILENO` (0) — 標準輸入 (`stdin`)
    • `STDOUT_FILENO` (1) — 標準輸出 (`stdout`)
    • `STDERR_FILENO` (2) — 標準錯誤 (`stderr`)
  • fd 與 FILE* 互轉:
    • `int fileno(FILE *stream)` — 取得底層 fd
    • `FILE *fdopen(int fd, const char *mode)` — 將 fd 包裝為 `FILE*` (需包含 `stdio.h`)

二、核心系統呼叫詳解

1. `open` — 打開或建立檔案

#include 
#include 
#include 

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode); // 若 flags 含 O_CREAT

關鍵 flags (可按位或 `|` 組合):

  • 存取模式 (互斥,必選其一):`O_RDONLY`、O_WRONLY、O_RDWR
  • 建立/控制模式:
    • `O_CREAT`:檔案不存在則建立,需配合 `mode` 參數 (權限位,會受 `umask` 影響)
    • `O_EXCL`:配合 `O_CREAT`,若檔案已存在則報錯 (原子性檢查建立)
    • `O_TRUNC`:若檔案存在且為寫入打開,截斷長度為 0
    • `O_APPEND`:每次寫入前自動定位到檔案末尾 (原子性追加)
    • `O_NONBLOCK` / `O_SYNC` / `O_DIRECTORY` ... 等進階選項

返回值:成功返回新 fd (最小未用非負整數);失敗返回 -1,設定 `errno`。

2. `close` — 釋放檔案描述符

#include 
int close(int fd);
  • 成功返回 0,失敗返回 -1。
  • 釋放 fd 使其可被後續 `open` 重用。
  • 進程終止時核心會自動關閉所有 fd,但良好習慣是顯式 `close` 並檢查返回值 (如 NFS 網路檔案系統寫入錯誤可能只在 close 時回報)。

3. `creat` — 歷史遺留的建立檔案呼叫

#include 
int creat(const char *pathname, mode_t mode);

等價於:`open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)`

  • 早期 UNIX `open` 無 `O_CREAT`,故需獨立 `creat`。
  • 現代 Linux 核心內部 `creat` 直接轉呼叫 `open`,不建議新程式碼使用,統一用 `open` 即可。

三、權限位 mode 與 umask 機制

  • `mode` 典型值:`0644` (rw-r--r--)、`0755` (rwxr-xr-x)。
  • 實際權限 = `mode & ~umask`。進程預設 `umask` 常為 `0022`,故 `0666` → `0644`,`0777` → `0755`。
  • 可用 `umask(0)` 暫時關閉遮罩,但需注意安全性。

四、實戰範例:安全建立並寫入檔案

#include 
#include 
#include 
#include 
#include 

int main() {
    const char *path = "demo.txt";
    // 原子性建立:不存在才建立,避免競態條件
    int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
    if (fd == -1) {
        if (errno == EEXIST) {
            fprintf(stderr, "檔案已存在,拒絕覆蓋\n");
        } else {
            perror("open 失敗");
        }
        return 1;
    }

    const char *msg = "Hello, Low-level I/O!\n";
    ssize_t written = write(fd, msg, strlen(msg));
    if (written == -1) {
        perror("write 失敗");
    }

    if (close(fd) == -1) {
        perror("close 失敗"); // 關鍵:檢查 close 返回值
    }
    return 0;
}

五、總結與避坑指南

  1. **優先使用 `open` + 旗標組合**,棄用 `creat`。
  2. **建立檔案必加 `O_EXCL`** 防止 TOCTOU 競態 (Time-of-check to time-of-use)。
  3. **寫入關鍵資料** 考慮 `O_SYNC` 或呼叫 `fsync(fd)` 強制落盤。
  4. **善用 `fcntl`** 進階控制:非阻塞 (`O_NONBLOCK`)、檔案鎖 (`F_SETLK`)、修改 fd 旗標 (`F_SETFL`)。
  5. **永遠檢查系統呼叫返回值**,包含 `close`,並處理 `errno` (特別是 `EINTR` 中斷重試)。

掌握低階 I/O,不僅能寫出高效、健壯的系統程式,更能深入理解 C 標準庫、C++ iostream 乃至各類高階框架的底層運作原理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved