<fcntl.h>是Linux/Unix系统下核心的文件控制头文件,主要提供文件描述符操作、文件状态控制、记录锁等核心接口,是系统编程中操作文件的基础。
一、核心概念:文件描述符(File Descriptor)
所有<fcntl.h>的操作都围绕文件描述符(fd) 展开,它是一个非负整数,用于标识进程打开的文件(包括普通文件、设备、管道、socket等)。
1. 系统默认文件描述符
每个进程启动时会默认打开3个文件描述符:
0:标准输入(STDIN)1:标准输出(STDOUT)2:标准错误(STDERR)
2. 文件描述符的取值范围
- 最小为
0,最大默认受RLIMIT_NOFILE限制(通常为1024或更高,可通过ulimit -n查看/修改)。 - 新打开的文件会分配最小可用的未使用文件描述符。
二、核心函数:fcntl()
fcntl()是<fcntl.h>中最核心的函数,用于对已打开的文件描述符执行多种控制操作,函数原型如下:
c
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
1. 参数说明
fd:目标文件描述符(必须是已打开的有效描述符)。cmd:操作命令(决定fcntl()的功能,是核心参数)。...:可选参数(根据cmd的不同,可能是int类型或struct flock结构体指针)。
2. 返回值
- 成功:根据
cmd返回不同值(如文件状态标志、复制后的新文件描述符等)。 - 失败:返回
-1,并设置errno(如EBADF表示fd无效,EINVAL表示cmd非法)。
3. 常用cmd分类及功能
cmd决定了fcntl()的作用,可分为4大类:
| 类别 | 常用cmd | 功能描述 |
|---|---|---|
| 文件状态标志操作 | F_GETFL | File GET File status FLags , 获取fd的文件状态标志(如O_RDONLY、O_NONBLOCK等) |
| F_SETFL | File SET File status FLags,设置fd的文件状态标志(仅能修改部分标志,如O_NONBLOCK、O_APPEND) | |
| 文件描述符操作 | F_DUPFD | File Descriptor DUPlicate,复制fd,返回大于等于arg的最小可用fd(不设置FD_CLOEXEC) |
| F_DUPFD_CLOEXEC | File Descriptor DUPlicate with CLOEXEC (Close On EXEC) Flag,复制fd并设置FD_CLOEXEC标志(exec时自动关闭新fd,避免泄漏) | |
| F_GETFD | File Descriptor GET Flags,获取fd的文件描述符标志(目前仅支持FD_CLOEXEC) | |
| F_SETFD | File Descriptor SET Flags,设置fd的文件描述符标志(仅支持FD_CLOEXEC) | |
| 记录锁操作 | F_SETLK | File Record Lock SET (Non-blocking Mode),设置/释放记录锁(非阻塞模式,获取不到锁时返回-1) |
| F_SETLKW | File Record Lock SET Wait (Blocking Mode),设置/释放记录锁(阻塞模式,获取不到锁时阻塞等待) | |
| F_GETLK | File Record Lock GET (Holder Process Info),检查指定区域是否有锁,若有则返回持有锁的进程ID(l_pid) | |
| 其他操作 | F_GETOWN | SIGIO/SIGURG Signal Owner GET,获取接收SIGIO/SIGURG信号的进程ID或线程ID(用于异步I/O) |
| F_SETOWN | SIGIO/SIGURG Signal Owner SET,设置接收SIGIO/SIGURG信号的进程ID或线程ID |
示例:设置文件为非阻塞模式
c
int flags = fcntl(fd, F_GETFL, 0); // 1. 获取当前状态标志
if (flags == -1) { perror("fcntl F_GETFL"); return -1; }
flags |= O_NONBLOCK; // 2. 追加非阻塞标志
if (fcntl(fd, F_SETFL, flags) == -1) { perror("fcntl F_SETFL"); return -1; }
三、关键常量:文件状态标志
文件状态标志通过open()的第二个参数(如O_RDONLY)或fcntl(F_SETFL)设置,分为访问模式标志 和操作模式标志两类。
1. 访问模式标志(不可通过fcntl修改)
O_RDONLY:只读模式(互斥,与O_WRONLY/O_RDWR二选一)O_WRONLY:只写模式O_RDWR:读写模式
2. 操作模式标志(可通过fcntl修改)
O_NONBLOCK:非阻塞模式(I/O操作不阻塞,无数据时返回-1并设errno=EAGAIN)O_APPEND:追加模式(每次write前自动将文件指针移到末尾,原子操作,避免多进程写冲突)O_ASYNC:异步I/O模式(文件状态变化时,向进程发送SIGIO信号)O_DIRECT:直接I/O模式(跳过内核缓冲区,数据直接在用户空间与硬件间传输,需对齐)O_NOATIME:访问文件时不更新atime(文件最后访问时间,用于减少磁盘I/O)
3. 其他常用标志(仅open()时有效)
O_CREAT:文件不存在时创建(需配合第三个参数mode指定文件权限,如0644)O_EXCL:与O_CREAT配合使用,若文件已存在则open()失败(用于确保文件唯一性,如锁文件)O_TRUNC:打开文件时清空文件内容(仅对可写模式有效)O_CLOEXEC:exec()时自动关闭文件描述符(避免fd泄漏到子进程)
四、文件描述符标志:FD_CLOEXEC
文件描述符标志仅有一种:FD_CLOEXEC ,用于控制exec()系统调用时的fd行为。
1. 作用
- 当fd设置
FD_CLOEXEC后,调用exec()启动新程序时,该fd会自动关闭。 - 未设置时,fd会被新程序继承(可能导致资源泄漏,如socket未关闭)。
2. 设置/获取方式
c
// 1. 设置FD_CLOEXEC
int fd_flags = fcntl(fd, F_GETFD, 0);
if (fd_flags == -1) { perror("fcntl F_GETFD"); return -1; }
fd_flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, fd_flags) == -1) { perror("fcntl F_SETFD"); return -1; }
// 2. 推荐方式:open时直接设置O_CLOEXEC(原子操作,避免竞态)
int fd = open("file.txt", O_RDONLY | O_CLOEXEC);
五、记录锁(Record Locking)
记录锁是fcntl()提供的细粒度文件锁 ,支持对文件的部分区域(而非整个文件)加锁,用于多进程/线程同步。
1. 核心结构体:struct flock
记录锁的参数通过struct flock传递,定义如下:
c
struct 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表示从l_start到文件末尾)
pid_t l_pid; // 持有锁的进程ID(F_GETLK时返回,设置锁时忽略)
};
2. 锁的特性
- 读锁(共享锁):多个进程可同时对同一区域加读锁(读-读兼容)。
- 写锁(排他锁):同一区域只能有一个写锁,且与读锁互斥(写-读、写-写冲突)。
- 继承性 :子进程(fork后)会继承父进程的锁,但
exec()后锁会自动释放。 - 自动释放:进程退出或关闭fd时,锁会自动释放。
3. 使用示例:对文件前100字节加写锁
c
struct flock lock;
lock.l_type = F_WRLCK; // 写锁
lock.l_whence = SEEK_SET; // 从文件开头算起
lock.l_start = 0; // 起始偏移0
lock.l_len = 100; // 锁100字节
lock.l_pid = 0; // 忽略
// 阻塞加锁(F_SETLKW)
if (fcntl(fd, F_SETLKW, &lock) == -1) {
perror("fcntl F_SETLKW");
close(fd);
return -1;
}
// 操作文件...
// 解锁
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl F_SETLK unlock");
}
六、常见错误码及原因
调用fcntl()失败时,errno会指示具体原因,需重点关注以下几种:
EBADF:fd不是有效文件描述符,或已被关闭。EINVAL:cmd参数非法,或struct flock成员取值无效(如l_whence不是SEEK_*)。EAGAIN/EWOULDBLOCK:F_SETLK非阻塞模式下,无法获取锁(资源被占用)。EACCES:open()时权限不足(如文件为只读,但用O_WRONLY打开)。ENOLCK:系统锁资源耗尽(记录锁数量达到上限)。
注意事项
- 原子操作优先 :设置
O_APPEND或O_CLOEXEC时,优先用open()直接指定(原子操作),避免用fcntl()后续修改(可能存在竞态)。 - 非阻塞I/O的错误处理 :使用
O_NONBLOCK时,需区分"真错误"(如EBADF)和"暂时无数据"(EAGAIN/EWOULDBLOCK),后者可重试。 - 记录锁的局限性 :记录锁不支持线程间同步(同一进程内线程共享锁),线程同步需用
pthread_mutex_t。 - 权限检查 :
fcntl()操作需确保进程对文件有对应权限(如加写锁需文件可写)。 - 跨系统兼容性 :部分标志(如
O_DIRECT)的行为在不同Unix系统(如Linux、FreeBSD)中可能有差异,需针对性测试。