fcntl 是 Unix/Linux 系統編程中「瑞士軍刀」般的系統調用,通過 cmd 參數控制文件描述符屬性、文件狀態標志、異步 I/O 所有權、記錄鎖等五大類功能。本文系統梳理 F_DUPFD、F_GETFL/F_SETFL、F_GETOWN/F_SETOWN、F_GETLK/F_SETLK/F_SETLKW 等核心命令,附帶代碼實例與避坑指南。
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);
| 類別 | 命令 | 用途 | 第三參數 |
|---|---|---|---|
| 描述符複製 | F_DUPFD | 複製 fd,返回 ≥ arg 的最小可用描述符 | long arg (起始值) |
F_DUPFD_CLOEXEC | 同上,並設置 FD_CLOEXEC | long arg | |
| 描述符標志 | F_GETFD | 獲取 FD_CLOEXEC 標志 | 無 |
F_SETFD | 設置 FD_CLOEXEC | long arg (0/1) | |
| 文件狀態標志 | F_GETFL | 獲取 O_RDONLY、O_APPEND、O_NONBLOCK 等 | 無 |
F_SETFL | 設置 O_APPEND、O_NONBLOCK、O_ASYNC 等 | long arg (標志位或) | |
| — | 不可改 O_RDONLY/W/RDWR(open 時確定) | — | |
| 異步 I/O 所有權 | F_GETOWN | 獲取接收 SIGIO/SIGURG 的進程/組 ID | 無 |
F_SETOWN | 設置接收進程/組 ID(負值=組) | long arg (pid 或 -pgid) | |
| 記錄鎖 | F_GETLK | 測試是否有鎖阻塞,結果覆蓋 flock 結構 | struct flock * |
F_SETLK | 設置/釋放鎖(非阻塞),失敗返回 EAGAIN | struct flock * | |
F_SETLKW | 設置/釋放鎖(阻塞等待) | struct flock * |
struct flockstruct flock {
short l_type; // F_RDLCK(讀鎖), F_WRLCK(寫鎖), F_UNLCK(解鎖)
short l_whence; // SEEK_SET/SEEK_CUR/SEEK_END
off_t l_start; // 相對 l_whence 的起始偏移
off_t l_len; // 鎖定字節數,0=到文件末尾
pid_t l_pid; // 持有鎖的進程 PID (F_GETLK 返回)
};
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int set_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int lock_region(int fd, short type, off_t start, off_t len, bool wait) {
struct flock fl = {
.l_type = type,
.l_whence = SEEK_SET,
.l_start = start,
.l_len = len
};
int cmd = wait ? F_SETLKW : F_SETLK;
return fcntl(fd, cmd, &fl);
}
int main(void) {
int fd = open("data.db", O_RDWR | O_CREAT, 0644);
if (fd < 0) { perror("open"); return 1; }
// 1. 設置非阻塞
if (set_nonblock(fd) < 0) { perror("nonblock"); return 1; }
// 2. 獲取寫鎖保護 0-100 字節
if (lock_region(fd, F_WRLCK, 0, 100, true) < 0) {
perror("lock");
return 1;
}
printf("獲得寫鎖,進行關鍵寫入...\n");
write(fd, "hello", 5);
// 3. 釋放鎖
lock_region(fd, F_UNLCK, 0, 100, false);
close(fd);
return 0;
}
fcntl、flock、lockf 均屬此類。mand 選項,且文件屬組執行位關閉、Set-GID 位開啟。內核**強制**檢查每次讀寫,違規返回 EAGAIN。非 POSIX 標準,移植性差。flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_NONBLOCK); —— 直接 F_SETFL 會清除原有標志。F_SETLKW 等待期間若捕獲信號且未設置 SA_RESTART,會返回 EINTR 需重試。