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

多進程存取 INI 設定檔導致損毀的根因與解決方案

     編輯:關於C

問題現象

多進程同時讀寫 .ini 格式設定檔時,檔案經常發生損毀。初期嘗試使用檔案鎖(BSD flock 與 POSIX fcntl)均無法解決。

根因分析

核心問題:使用 `stat(filename, &sb)` 根據檔名取得檔案大小,再依此大小讀取檔案內容到記憶體。但在多進程環境下,另一進程可能正在寫入該檔案,導致 `stat` 取得的 size 不正確(過大或過小),進而讀取到不完整或錯誤的資料,寫回時造成檔案損毀。

錯誤流程 vs 正確流程

階段錯誤做法正確做法
1. 開啟檔案fopen / openfopen / open
2. 取得大小stat(filename, &sb) ← 竄態條件fstat(fd, &sb) ← 基於已開啟的 fd
3. 加鎖flock/fcntlflock/fcntl (讀鎖/寫鎖)
4. 讀取內容依 stat 的 size 讀取依 fstat 的 size 讀取
5. 寫入/釋放寫回 → 解鎖寫回 → 解鎖

關鍵修正:stat → fstat

// 錯誤:根據檔名 stat,檔案可能已被其他進程修改
// stat(pconfig->fileName, &sb);

// 正確:根據已開啟的檔案描述符 fstat,原子性較高
ret = fstat(fd, &sb);

為什麼 fstat 較安全?

  • `fstat` 作用於已開啟的檔案描述符(fd),反映該 fd 當前指向的檔案狀態
  • 配合檔案鎖使用時,`open → lock → fstat → read` 形成原子操作序列
  • 避免了「檔名查找 → inode 定位 → 取得大小」過程中的時間窗口

完整正確操作序列

int fd = open(filename, O_RDWR);
// 1. 申請讀鎖(共享鎖)或寫鎖(獨佔鎖)
flock(fd, LOCK_SH);  // 或 fcntl(fd, F_SETLK, &read_lock)

// 2. 獲取鎖成功後,立即 fstat 取得準確大小
struct stat sb;
fstat(fd, &sb);
size_t size = sb.st_size;

// 3. 根據 size 分配記憶體並讀取
char *buf = malloc(size + 1);
read(fd, buf, size);
buf[size] = '\0';

// 4. 處理資料...

// 5. 寫入時申請寫鎖
flock(fd, LOCK_EX);
ftruncate(fd, 0);  // 清空檔案
lseek(fd, 0, SEEK_SET);
write(fd, new_data, new_size);
flock(fd, LOCK_UN);

close(fd);

INI 操作 API 的額外缺陷

原文提到:ini 庫在 `ret` 返回失敗時,未正確設定輸出參數。建議修改:

#define NULL_STR '\0'
#define NULL_VAL 0xFFFF

// 失敗時將輸出參數設為非法值,避免呼叫端使用髒資料
if (ret != SUCCESS) {
    *out_string = NULL_STR;
    *out_value = NULL_VAL;
}

經驗總結

  1. 檔案操作原子性:多進程環境下,所有檔案元資料查詢必須基於已鎖定的 fd(fstat),而非檔名
  2. 鎖的粒度:讀取用共享鎖(LOCK_SH/F_RDLCK),寫入用獨佔鎖(LOCK_EX/F_WRLCK)
  3. 錯誤處理:API 失敗時必須重置輸出參數,防止上層邏輯誤判
  4. ANSI C 可移植性:純 C 實作的 ini 庫便於跨平台,但需自行處理並發控制

這個案例深刻說明了:檔案鎖只能保護「已開啟檔案描述符」的操作序列,無法保護「檔名路徑查找」過程。正確的同步邊界必須包含 `open → lock → fstat → read/write` 完整鏈路。

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