fcntl函数

使用场景和相关用法

fcntl函数

一、fcntl 函数基础

1
2
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* 可选参数 arg */ );
  • 返回值:成功时返回与 cmd 相关的值(如标志、文件描述符),失败返回 -1 并设置 errno

  • 核心作用:通过 cmd 命令对 fd(文件描述符)执行控制操作,涵盖文件状态、I/O 模式、锁机制等。

二、与 O_NONBLOCK 相关的操作(文件状态标志)

O_NONBLOCK非阻塞模式标志,用于设置文件描述符的 I/O 行为:

  • 阻塞模式(默认):当执行 read/write 等操作时,若暂时无法完成(如无数据可读、缓冲区满),进程会进入睡眠等待。

  • 非阻塞模式(O_NONBLOCK:操作无法立即完成时,函数会立即返回 -1 并设置 errnoEAGAINEWOULDBLOCK,不阻塞进程。

相关 cmd 命令:

  1. F_GETFL:获取当前文件状态标志(包含是否设置 O_NONBLOCK

    1
    2
    3
    4
    int flags = fcntl(fd, F_GETFL, 0);  // 0 表示无额外参数
    if (flags == -1) {
    perror("fcntl F_GETFL failed");
    }
  2. F_SETFL:设置文件状态标志(用于开启 / 关闭 O_NONBLOCK

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 开启非阻塞模式(保留原有标志,添加 O_NONBLOCK)
    int new_flags = flags | O_NONBLOCK;
    if (fcntl(fd, F_SETFL, new_flags) == -1) {
    perror("fcntl F_SETFL failed");
    }

    // 关闭非阻塞模式(清除 O_NONBLOCK 标志)
    new_flags = flags & ~O_NONBLOCK;
    fcntl(fd, F_SETFL, new_flags);

适用场景:

  • 网络套接字(socket):避免 recv/send 阻塞导致进程卡死。

  • 管道(pipe)、FIFO:处理读写端不同步的情况。

  • 设备文件(如终端、串口):实时响应 I/O 状态变化。

三、文件锁操作(进程间同步)

fcntl 支持对文件的部分或全部内容加锁,避免多进程竞争。相关 cmd 命令需配合 struct flock 结构体使用:

1
2
3
4
5
6
7
struct flock {
short l_type; // 锁类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁)
short l_whence; // 偏移基准:SEEK_SET(文件头)、SEEK_CUR(当前位置)、SEEK_END(文件尾)
off_t l_start; // 锁的起始偏移量
off_t l_len; // 锁的长度(0 表示至文件末尾)
pid_t l_pid; // 持有锁的进程 ID(仅 F_GETLK 时有效)
};
  1. F_GETLK:检查锁冲突(获取当前阻塞当前锁请求的锁信息)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct flock lock;
    lock.l_type = F_WRLCK; // 尝试获取写锁
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0; // 锁整个文件

    if (fcntl(fd, F_GETLK, &lock) == -1) {
    perror("fcntl F_GETLK failed");
    }
    if (lock.l_type != F_UNLCK) {
    // 存在冲突的锁,lock.l_pid 为持有锁的进程
    }
  2. F_SETLK:设置或释放锁(非阻塞模式,失败立即返回)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 加写锁
    lock.l_type = F_WRLCK;
    if (fcntl(fd, F_SETLK, &lock) == -1) {
    if (errno == EAGAIN || errno == EACCES) {
    // 锁被占用,无法获取
    }
    }

    // 解锁
    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLK, &lock);
  3. F_SETLKW:设置或释放锁(阻塞模式,若无法获取则等待)

    1
    2
    3
    4
    // 等待获取锁,直到成功或被信号中断
    if (fcntl(fd, F_SETLKW, &lock) == -1) {
    perror("fcntl F_SETLKW failed");
    }

四、文件描述符管理

  1. F_DUPFD:复制文件描述符(类似 dup

    1
    2
    // 生成一个 >= min_fd 的新描述符,与原 fd 指向同一文件
    int new_fd = fcntl(old_fd, F_DUPFD, min_fd);
  2. F_DUPFD_CLOEXEC:复制描述符并设置 FD_CLOEXEC 标志

    1
    2
    // 新描述符在进程执行 exec 时自动关闭,避免资源泄漏
    int new_fd = fcntl(old_fd, F_DUPFD_CLOEXEC, min_fd);
  3. F_GETFD/F_SETFD:获取 / 设置文件描述符标志(仅 FD_CLOEXEC 有效)

    1
    2
    3
    4
    // 获取标志
    int fd_flags = fcntl(fd, F_GETFD);
    // 设置 FD_CLOEXEC(exec 时关闭)
    fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);

五、异步 I/O 与信号通知

  1. F_SETOWN:设置接收 SIGIOSIGURG 信号的进程 / 进程组

    1
    2
    // 指定进程 ID 接收信号(负数表示进程组 ID)
    fcntl(fd, F_SETOWN, pid);
  2. F_GETOWN:获取当前接收信号的进程 / 进程组 ID

    1
    pid_t owner = fcntl(fd, F_GETOWN);
  3. F_SETFL 配合 O_ASYNC:开启异步 I/O 通知(数据可读 / 可写时触发 SIGIO

    1
    2
    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK); // 同时开启异步和非阻塞

六、其他系统相关命令

  • F_GETPIPE_SZ/F_SETPIPE_SZ:获取 / 设置管道(pipe)的缓冲区大小

    1
    2
    int pipe_size = fcntl(fd, F_GETPIPE_SZ);  // 获取大小
    fcntl(fd, F_SETPIPE_SZ, new_size); // 设置大小(需权限)
  • F_ADD_SEALS/F_GET_SEALS:为文件添加 / 查询 “密封” 标志(限制修改)

    1
    2
    3
    4
    // 添加写密封(禁止后续写入)
    fcntl(fd, F_ADD_SEALS, F_SEAL_WRITE);
    // 查询当前密封标志
    int seals = fcntl(fd, F_GET_SEALS);
  • F_GETLEASE/F_SETLEASE:获取 / 设置文件租约(协调进程间文件访问)

    1
    2
    // 设置读租约(其他进程写操作会触发通知)
    fcntl(fd, F_SETLEASE, F_RDLEASE);

fcntl 是一个功能强大的多用途函数,核心能力包括:

  1. 控制 I/O 模式(如 O_NONBLOCK 非阻塞);

  2. 进程间文件锁(读锁、写锁);

  3. 文件描述符管理(复制、标志设置);

  4. 异步 I/O 信号通知;

  5. 系统特定扩展功能(管道大小、文件密封等)。

使用时需注意:不同 Unix/Linux 系统对命令的支持可能有差异,建议通过 man 2 fcntl 查看本地系统的详细文档。非阻塞模式(O_NONBLOCK)常与 I/O 多路复用(select/poll/epoll)结合,是高性能网络编程的基础。