欢迎光临
我们一直在努力

【Linux 编程】:深入解析 fcntl 函数

在这里插入图片描述

📃个人主页:island1314

🔥个人专栏:Linux—登神长阶

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

  • 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》

🔥 目录

    • 一、序言
    • 二、认识 fcntl 函数
      • 1. 函数介绍
      • 2. 函数用途
      • 3. 错误处理
      • 4. 示例代码
    • 三、复制文件描述符
      • 1. F_DUPFD
      • 2. F_DUPFD_CLOEXEC
      • 3. 二者区别
      • 4. 补充 — FD_CLOEXEC 意义
    • 四、获取/设置文件描述符标志
      • 1. F_GETFD
      • 2. F_SETFD
    • 五、获取/设置文件状态标志
      • 1. 文件状态标志(File Status Flags)
      • 2. fcntl 命令详解
        • 1. F_GETFL: 获取文件状态标志
        • 2. F_SETFL: 设置文件状态标志
      • 3. 文件描述符标志 vs 文件状态标志
    • 六、获取/设置记录锁
      • 1. 记录锁的基本概念
      • 2. struct flock 结构体
      • 2. fcntl 命令详解
        • 2.1 F_GETLK: 检测锁冲突
        • 2.2 F_SETTLK: 尝试加锁/解锁
        • 2.3 F_SETLKW: 阻塞等待加锁

一、序言

🔥fcntl 函数是一个在 UNIX 和类 UNIX 系统(如 Linux)上用来操作文件描述符的系统调用

  • 作用:可以用于改变文件描述符的属性或状态,或者执行基本的控制操作
  • 场景:fcntl 函数非常强大且灵活,常用于实现各种文件和进程间通信的功能

二、认识 fcntl 函数

1. 函数介绍

函数原型

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
// arg表示可变参数,由cmd决定

参数说明

  • fd:文件描述符,指定要操作的文件或套接字的描述符
  • cmd:控制命令,指示要执行的操作类型
  • fcntl()的第三个参数是可选,是否需要此参数由 cmd 决定
    • 所需的参数类型在每个 cmd 名称后面的括号中指示(在大多数情况下,所需的类型是int,我们使用名称arg来标识参数),如果不需要参数,则指定 void
    • 例如:对于 F_SETFL 命令,可以传递新的状态标志

返回值

  • 成功时返回命令的结果,通常是状态标志或锁信息
  • 失败时返回 -1,并设置 errno 以指示错误类型

常见的命令包括:

  • 复制一个现有的描述符

    • F_DUPFD:复制文件描述符
    • F_DUPFD_CLOEXEC:复制文件描述符,设置FD_CLOEXEC标志
  • 获得/设置文件状态标记

    • F_GETFL:获取文件描述符的当前状态标志
    • F_SETFL:设置文件描述符的状态标志
  • 获得/设置文件描述符标记

    • F_GETFD:获取文件描述符的内部标志 F_SETFD:设置文件描述符的内部标志
  • 获得/设置异步I/O所有权

    • F_GETOWN:获得异步I/O所有权
    • F_SETOWN:设置异步I/O所有权
  • 获得/设置记录锁 F_GETLK:获取文件锁定信息 F_SETLK:设置文件锁定信息 F_SETLKW:以阻塞方式设置文件锁定信息。

  • 注意:以下某些操作仅在特定的 Linux 内核版本之后才受支持。检查主机内核是否支持特定操作的首选方法是使用所需的 cmd 值调用 fcntl() ,然后使用EINVAL测试调用是否失败,这表明内核是否能够识别该值

    2. 函数用途

    1️⃣获取和设置文件状态标志

    作用:设置文件为非阻塞模式

    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK); // 将这个描述符设置为非阻塞模式

    2️⃣文件锁定

    作用:防止多个进程同时写入同一文件

    struct flock lock;
    lock.l_type = F_WRLCK; // 请求写锁
    lock.l_whence = SEEK_SET; // 从文件开始处锁定
    lock.l_start = 0; // 从文件开始位置
    lock.l_len = 0; // 锁定整个文件

    fcntl(fd, F_SETLK, &lock); // 设置锁定

    3️⃣更改文件描述符的属性

    作用:动态更改文件描述符的功能,例如 将其设置为 异步I/O工作模式 或 实时信号

    3. 错误处理

    使用 fcntl 函数时,如果返回值为 -1,可以通过 errno 获取具体错误信息。常见的错误如下:

    • EBADF:提供的文件描述符无效
    • EINTR:操作被信号中断
    • EINVAL:指定的命令无效或者参数不符合规范

    4. 示例代码

    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main() {
    int fd = open("example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == 1) {
    perror("open");
    return 1;
    }

    // 获取当前的文件状态标志
    int flags = fcntl(fd, F_GETFL);
    if (flags == 1) {
    perror("fcntl get");
    return 1;
    }

    // 设置为非阻塞模式
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 1) {
    perror("fcntl set");
    return 1;
    }

    // 关闭文件描述符
    close(fd);
    return 0;
    }

    三、复制文件描述符

    1. F_DUPFD

    int fcntl(int fd, F_DUPFD, int n);

    /* 参数说明 */
    fd: 要复制的原始文件描述符。
    n: 分配的新文件描述符的起始值。如果可用,新的文件描述符将返回一个大于或等于 n 的最小的文件描述符。

    功能:

    • 创建一个新的文件描述符,该描述符是现有文件描述符的副本
    • 新的文件描述符将从指定的最小文件描述符值开始分配,使用大于或等于arg参数的编号最低的可用文件描述符
    • 新描述符与旧 fd 共享同一文件表项
    • 但是:新描述符有它自己的一套文件描述符标志,其 FD_CLOEXEC 文件描述符标志被清除〈这表示该描述符在 exec 时仍保持打开状态)

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>

    int main()
    {
    int fd = open("./fcntl_F_DUPFD.txt", O_RDWR | O_CREAT | O_TRUNC, 0775);
    int fcntlFd = fcntl(fd, F_DUPFD, 0); // 指定从 0 开始分配最小的可用描述符作为新描述符
    int dupFd = dup(fd); // 等效于 fcntl(fd, F_DUPFD, 0);

    close(fd);
    close(fcntlFd);
    close(dupFd);
    return 0;
    }

    2. F_DUPFD_CLOEXEC

    int fcntl(int fd, F_DUPFD_CLOEXEC, int n);

    功能:

    • 类似于 F_DUPFD,区别在于F_DUPFD_CLOEXEC在复制的同时会设置文件描述符标志 FD_CLOEXEC
    • 这意味着如果当前进程调用 exec 系列函数时,将自动关闭这个文件描述符

    3. 二者区别

    • dup 和 F_DUPFD :新描述符的 FD_CLOEXEC 默认为 0,无论原描述符是否设置了该标志(都不会保留 FD_CLOEXEC 标志)

    • F_DUPFD_CLOEXEC :新描述符的 FD_CLOEXEC 强制为 1,覆盖原描述符的设置

    • F_GETFD:仅读取文件描述符标志(如 FD_CLOEXEC),不会修改标志状态

      int flags = fcntl(fd, F_GETFD); // 获取标志
      if (flags & FD_CLOEXEC) { /* 检查 FD_CLOEXEC 是否设置 */ }

    示例代码

    样例一

    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>

    int main() {
    int fd = open("testfile", O_RDWR | O_CREAT, 0644);
    if (fd == 1) { perror("open"); return 1; }

    // 设置原描述符的 FD_CLOEXEC
    fcntl(fd, F_SETFD, FD_CLOEXEC);

    // 测试 dup
    int dup_fd = dup(fd);
    int dup_flags = fcntl(dup_fd, F_GETFD);
    printf("dup_fd FD_CLOEXEC: %d\\n", (dup_flags & FD_CLOEXEC) ? 1 : 0); // 输出 0

    // 测试 F_DUPFD_CLOEXEC
    int cloexec_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
    int cloexec_flags = fcntl(cloexec_fd, F_GETFD);
    printf("cloexec_fd FD_CLOEXEC: %d\\n", (cloexec_flags & FD_CLOEXEC) ? 1 : 0); // 输出 1

    close(fd);
    close(dup_fd);
    close(cloexec_fd);
    return 0;
    }

    样例二

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    int main()
    {
    //fd 设置标记 fcntlFd 不设置 fcntlCloFd 设置 dupFd 不设置
    int fd = open("./fcntl_F_DUPFD_CLOEXEC", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0775);
    int fcntlFd = fcntl(fd, F_DUPFD, 0);
    int fcntlCloFd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
    int dupFd = dup(fd);

    //通过fcntl(fd, F_GETFD, 0);获取状态标记
    int fdFlag = fcntl(fd, F_GETFD, 0);
    int fcntlFdFlag = fcntl(fcntlFd, F_GETFD, 0);
    int fcntlCloFdFlag = fcntl(fcntlCloFd, F_GETFD, 0);
    int dupFdFlag = fcntl(dupFd, F_GETFD, 0);

    // 结果是:fdFlag=1, fcntlFdFlag=0, fcntlCloFdFlag=1, dupFdFlag=0
    printf("fdFlag=%d, fcntlFdFlag=%d, fcntlCloFdFlag=%d, dupFdFlag=%d\\n",
    fdFlag,fcntlFdFlag,fcntlCloFdFlag,dupFdFlag);

    close(fd);
    close(fcntlFd);
    close(fcntlCloFd);
    close(dupFd);
    return 0;
    }

    4. 补充 – FD_CLOEXEC 意义

    ♐️ 文件描述符标志FD_CLOEXEC,用来表示该描述符在执行完 fork+exec 系列函数创建子进程时会自动关闭,以防止它们被传递给子进程。那么为什么要这样做呢?

    原因:因为当一个进程调用 exec 系列函数(比如 execve )来创建子进程时,所有打开的文件描述符都会被传递给子进程

    • 如果文件描述符没有设置 FD_CLOEXEC 标志,这些文件将保持打开状态并继续对子进程可见
    • 这样就可能导致潜在的安全风险或者意外行为

    四、获取/设置文件描述符标志

    🏖 fcntl 函数中的 F_GETFD 和 F_SETFD 命令用于获取和设置文件描述符的标志,这在操作文件描述符的行为时非常重要

    1. F_GETFD

    int fcntl(int fd, F_GETFD);

    // 参数说明:
    fd: 要查询状态的文件描述符

    功能:获取指定文件描述符的标志

    返回值:返回文件描述符的标志,如果失败则返回 -1,并设置 errno。主要的标志如下:

    • FD_CLOEXEC:文件描述符在 exec 系列调用时会被关闭。如果该标志被设置,返回值包含该标志。
    • 如果返回值与 FD_CLOEXEC 持平,表示标志已设置;如果返回值包含 0,表示没有设置

    2. F_SETFD

    int fcntl(int fd, F_SETFD, int flags);

    // 参数说明:
    fd: 要设置状态的文件描述符。
    flags: 新的状态标志,可以是以下值之一,或者是它们的组合:
    FD_CLOEXEC: 设置该标志,表示在调用 exec 系列函数时关闭文件描述符

    功能:设置指定文件描述符的标志

    上面我们讲了 FD_CLOEXEC,下面来演示一下如何获取

    文件描述符的 FD_CLOEXEC 标志可以通过三个方法得到:

  • 调用 open 函数是,指定 O_CLOEXEC
  • 通过 fcntl 函数使用 F_DUPFD_CLOEXEC 复制文件描述符,新的描述符就是 FD_CLOEXEC
  • 通过 fcntl 函数使用 F_SETFD 直接设置 FD_CLOEXEC
  • 代码示例

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    int main()
    {
    int fd = open("./fcntl_F_GETFD", O_RDWR | O_CREAT | O_TRUNC, 0775);
    int fdCloExec = open("./fcntl_F_GETFD2", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0775);
    int fdCloExecDup = fcntl(fd, F_DUPFD_CLOEXEC, 0);
    int fdSetFd = dup(fd);
    fcntl(fdSetFd, F_SETFD, FD_CLOEXEC);

    int flagFd = fcntl(fd, F_GETFD);
    int flagFdCloExec = fcntl(fdCloExec, F_GETFD);
    int flagFdCloExecDup = fcntl(fdCloExecDup, F_GETFD);
    int flagFdSetFd = fcntl(fdSetFd, F_GETFD);
    // 打印结果:flagFd=0, flagFdCloExec=1, flagFdCloExecDup=1 flagFdSetFd=1
    printf("flagFd=%d, flagFdCloExec=%d, flagFdCloExecDup=%d flagFdSetFd=%d\\n",
    flagFd,flagFdCloExec,flagFdCloExecDup,flagFdSetFd);

    close(fd);
    close(fdCloExec);
    close(fdCloExecDup);
    close(fdSetFd);
    return 0;
    }

    分析

    • flagFd=0 :fd 通过 open(…) 创建时未设置 O_CLOEXEC,标志未启用
    • flagFdCloExec=1 :fdCloExec 在 open 时指定了 O_CLOEXEC,标志已设置
    • flagFdCloExecDup=1 :F_DUPFD_CLOEXEC 自动设置 FD_CLOEXEC
    • flagFdSetFd=1 :dup 后通过 F_SETFD 显式设置标志

    五、获取/设置文件状态标志

    🛠 fcntl 函数中的 F_GETFL 和 F_SETFL 命令用于获取和设置文件的状态标志,这些标志控制文件的行为和特性。

    1. 文件状态标志(File Status Flags)

    • 作用 :控制文件的行为,如读写模式、追加、非阻塞、同步等。
    • 常用标志 :
      • 访问模式 :O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)。
      • 操作模式 :O_APPEND(追加)、O_NONBLOCK(非阻塞)、O_SYNC(同步写入)、O_ASYNC(异步 I/O)。
      • 兼容性标志 :O_DIRECT(直接 I/O)、O_NOATIME(不更新访问时间)。

    标志值与系统差异

    • 访问模式 :
      • O_RDONLY:0(二进制 0b00)。
      • O_WRONLY:1(0b01)。
      • O_RDWR:2(0b10)。
      • 这些值属于位掩码的一部分,需通过 O_ACCMODE 提取。
    • 其他标志 :
      • O_APPEND:0x400(十六进制)。
      • O_NONBLOCK:0x800(Linux 中对应八进制 04000)。
      • O_ASYNC:0x2000(十六进制)
      • 一些其他标志具体值因系统而异

    2. fcntl 命令详解

    1. F_GETFL: 获取文件状态标志

    #include <fcntl.h>

    int fcntl(int fd, F_GETFL);

    • 返回值 :
      • 成功:返回标志值(如 O_RDWR | O_APPEND)
      • 失败:返回 -1,并设置 errno

    访问方式标志:O_RDONLY 、O_WRONLY、O_RDWR。这3个值是互斥的,因此首先必须用屏蔽 O_ACCMODE 取得访问方式位,然后将结果与这3个值中的每一个相比较。

    示例:访问模式处理

    int flags = fcntl(fd, F_GETFL);
    int access_mode = flags & O_ACCMODE; // 提取访问模式
    if (access_mode == O_RDONLY) { /* 只读 */ }

    2. F_SETFL: 设置文件状态标志

    #include <fcntl.h>

    int fcntl(int fd, F_SETFL, int flags);

    返回值:

    • 成功返回 0
    • 失败返回 -1

    注意:fcntl(fd, F_SETFL, flags) 并非所有标志都可通过此命令修改 。根据 Linux 手册页和 POSIX 标准,F_SETFL 可以设置的标志包括以下常见选项:

    标志名用途说明应用场景
    O_APPEND 强制每次写操作前将文件偏移量设置到文件末尾(追加模式)。 日志文件(确保多进程写入时自动追加到文件末尾)
    O_NONBLOCK 设置非阻塞模式(对设备、管道、套接字等有意义) 网络编程( I/O 多路复用)
    O_ASYNC 异步 I/O 通知(当数据可读/写时发送信号,需特定系统支持) 异步信号驱动 I/O(如通过SIGIO信号触发处理逻辑)
    O_DIRECT 绕过内核缓冲区(直接 I/O,减少内存拷贝,需硬件/文件系统支持) 高性能数据库(绕过内核缓冲区,减少内存拷贝)
    O_NOATIME 读取文件时不更新文件的访问时间(atime),用于优化性能 文件系统优化(避免频繁更新atime,减少磁盘 I/O)

    注意 :

    • O_TRUNC (截断文件)和 O_CREAT (创建文件)等标志只能通过 open() 设置 ,不能通过 fcntl(F_SETFL) 修改
    • O_SYNC (同步写入,确保数据落盘)等标志在某些系统(如 Linux)中也属于 F_SETFL 的可修改范围,但需注意其行为可能与 O_DSYNC 等标志有差异

    最常用标志:O_NONBLOCK

    • 非阻塞 I/O :当读写套接字、管道或设备时,若没有数据可读或缓冲区满,操作会立即返回而非阻塞等待。
    • 多路复用结合使用 :与 select()、poll()、epoll() 等配合,实现高性能 I/O 多路复用。
    • 避免阻塞线程 :在事件驱动模型(如 Reactor 模式)中,非阻塞模式是实现高并发的基础

    int flags = fcntl(sockfd, F_GETFL); // 获取当前标志
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); // 设置非阻塞模式

    示例代码:设置非阻塞

    #include <iostream>
    #include <unistd.h>
    #include <fcntl.h>
    #include <cerrno>
    #include <string>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/select.h>

    const int MAX = 1024;

    void SetNonBlock(int fd)
    {
    int flags = fcntl(fd, F_GETFL, 0);
    if(flags < 0){
    std::cout << "Get flags error" << std::endl;
    return;
    }
    flags |= O_NONBLOCK; // O_NONBLOCK = 04000 :让 fd 以非阻塞的方式工作
    if(fcntl(fd, F_SETFL, flags) < 0){
    std::cout << "Set flags error" << std::endl;
    }
    }

    int main()
    {
    std::string tips = "Please Enter# ";
    char buffer[MAX];
    SetNonBlock(0);
    while(true)
    {
    write(0, tips.c_str(), tips.size());
    // 非阻塞,如果我们不输入,数据就不会读取(就绪),所以会一直循环,以出错形式返回
    // read 不是有读取失败(-1),失败 vs 底层数据没就绪 -> 底层数据没就绪其实不算失败
    // 如果是 -1,失败 vs 底层数据没就绪 后续的做法不同的,需要区分的必要性
    // errno:更详细的出错原因,最近一次调用出错的错误码

    int n = read(0, buffer, sizeof(buffer));
    if(n > 0){
    buffer[n] = 0;
    std::cout << "Read " << n << " echo# " << buffer << std::endl;
    }
    else if(n == 0){ // 在 标准输入中,Ctrl + d 退出
    std::cout << "Read over" << std::endl;
    break;
    }
    else{
    if(errno == EAGAIN || errno == EWOULDBLOCK){ // 11(try again) || (Operation would block)
    std::cout << "Data not ready" << std::endl; // 底层数据没就绪
    }
    else if(errno == EINTR)
    {
    std::cout << "Interrupted system call" << std::endl; // 被中断,重新来过
    sleep(1);
    continue;
    }
    else std::cout << "Read error: " << n << ", errno " << errno << std::endl;
    }
    sleep(1);
    }
    return 0;
    }

    3. 文件描述符标志 vs 文件状态标志

    文件描述符标志(File Descriptor Flags)

    • 通过 fcntl(F_SETFD) 修改。
    • 主要控制描述符的生命周期和继承性,例如:
      • FD_CLOEXEC:在 execve() 时自动关闭描述符

    文件状态标志(File Status Flags)

    • 通过 fcntl(F_SETFL) 修改。
    • 影响文件描述符(File Descriptor)的行为,例如:
      • O_APPEND:写入时自动定位到文件末尾
      • O_NONBLOCK:非阻塞模式

    六、获取/设置记录锁

    ⛵️ 在 Unix 和类 Unix 系统中,fcntl 函数提供了用于记录锁(record locks)的功能,通过命令 F_GETLK、F_SETLK 和 F_SETLKW 来实现。这些命令用于获取、设置和管理文件的记录锁,帮助实现进程间的同步。

    1. 记录锁的基本概念

    Linux实现了 POSIX 标准化的传统(“进程相关”)UNIX记录锁

    • 记录锁 (record locking)的功能是:当一个进程正在读或修改文件的某个部分时,它可以阻止其他进程修改同一文件区。
    • 对于UNIX系统而言,“记录”这个词是一种误用,因为 UNIX 系统内核根本 没有使用 文件记录 这种概念
    • 更适合的术语可能是字节范围锁 (byte-rangelocking),因为它锁定的只是文件中的一个区域(也可能是整个文件)
  • 字节范围锁(Byte-range Locking) :
    • UNIX/Linux 的记录锁本质是对文件的某个字节范围加锁 ,而非传统意义上的“记录”。
    • 例如:锁定文件从偏移 100 到 200 字节的区域,其他进程不能修改该区域。
  • 锁的类型 :
    • 读锁(F_RDLCK) :共享锁,允许多个进程同时读取锁定区域,但阻止写操作。
    • 写锁(F_WRLCK) :独占锁,阻止其他进程读写锁定区域。
    • 解锁(F_UNLCK) :释放已持有的锁。
  • 锁的继承与释放 :
    • 进程终止时,所有锁自动释放。
    • fork() 子进程不会继承父进程的锁。
    • execve() 后,若文件描述符设置了 FD_CLOEXEC,锁会被释放。
  • F_SETLK、F_SETLKW 和 F_GETLK 用于获取、释放和测试记录锁(也称为字节范围、文件段或文件区域锁)的存在。使用记录锁时,第三个参数是指向 struct flock 结构的指针

    2. struct flock 结构体

    strucy flock 结构体定义如下:

    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)
    };

    • l_whence、l_start、l_len这三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence= SEEK_SET,l_start= 0,l_len= 0
    • l_type 有三种状态 :
      • F_RDLCK 建立一个供读取用的锁定,允许其他进程读该文件,但不允许其他进程写该文件
      • F_WRLCK 建立一个供写入用的锁定,不允许其他进程读、写该文件
      • F_UNLCK 删除之前建立的锁定
    • l_whence 也有三种方式:
      • SEEK_SET 以文件开头为锁定的起始位置
      • SEEK_CUR 以目前文件读写位置为锁定的起始位置
      • SEEK_END 以文件结尾为锁定的起始位置。

    2. fcntl 命令详解

    2.1 F_GETLK: 检测锁冲突
    • 功能 :测试是否可以对指定区域加锁,不会实际加锁

    • 行为 :

      • 如果存在冲突锁,fcntl 返回 0,并将冲突锁的信息填充到 flock 结构体中(如 l_type 为冲突锁类型,l_pid 为持有锁的进程 ID)。
      • 如果没有冲突锁,l_type 会被设置为 F_UNLCK。
    • 错误处理 :此命令不会设置 errno,返回值始终为 0

    • 示例:

      struct flock lock = {0};
      lock.l_type = F_WRLCK; // 测试写锁
      lock.l_whence = SEEK_SET;
      lock.l_start = 0; // 从文件开头
      lock.l_len = 1024; // 锁定前 1KB

      if (fcntl(fd, F_GETLK, &lock) == 0) {
      if (lock.l_type == F_UNLCK) {
      printf("No conflicting lock.\\n");
      } else {
      printf("Conflicting lock by PID %d\\n", lock.l_pid);
      }
      }

    2.2 F_SETTLK: 尝试加锁/解锁
    • 功能 :尝试对指定区域加锁或解锁。

    • 行为 :

      • 加锁 :若无冲突,立即加锁;若有冲突,返回 -1,errno 设置为 EACCES 或 EAGAIN
      • 解锁 :直接释放指定区域的锁
    • 错误处理 :

      • 冲突锁存在时返回 -1,errno = EACCES/EAGAIN
      • 其他错误(如无效参数)返回 -1,errno 设置为具体错误码
    • 示例:

      struct flock lock = {0};
      lock.l_type = F_WRLCK; // 加写锁
      lock.l_whence = SEEK_SET;
      lock.l_start = 0;
      lock.l_len = 0; // 锁定整个文件

      if (fcntl(fd, F_SETLK, &lock) == 1) {
      perror("fcntl F_SETLK failed");
      }

    2.3 F_SETLKW: 阻塞等待加锁
    • 功能 :类似于 F_SETLK,但若存在冲突锁,会阻塞等待 直到锁被释放或被信号中断。

    • 行为 :

      • 成功加锁后返回 0。
      • 被信号中断时返回 -1,errno = EINTR
    • 示例 :

      struct flock lock = {0};
      lock.l_type = F_RDLCK; // 加读锁
      lock.l_whence = SEEK_SET;
      lock.l_start = 0;
      lock.l_len = 0; // 锁定整个文件

      if (fcntl(fd, F_SETLKW, &lock) == 1) {
      perror("fcntl F_SETLKW failed");
      }

    注意:F_SETLKW 不会填充 flock 结构体 ,它只是阻塞等待锁可用。填充结构体是 F_GETLK 的功能

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 【Linux 编程】:深入解析 fcntl 函数
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址