当前位置: 首页 > news >正文

Linux中fcntl系统调用的实现

一、前言

在Linux系统中,fcntl 是一个非常重要且强大的系统调用,它提供了对文件描述符的各种控制操作。无论是文件描述符的复制、文件标志的修改,还是文件锁机制,都离不开它

文件标志汇总

1.文件打开标志 (O_* flags)

这些标志用于 open() 系统调用,定义文件的打开方式

1.1. 文件访问模式标志

#define O_ACCMODE          0003  /* 访问模式掩码 */
#define O_RDONLY             00  /* 只读打开 */
#define O_WRONLY             01  /* 只写打开 */ 
#define O_RDWR               02  /* 读写打开 */

说明:这三个标志互斥,使用 O_ACCMODE 掩码可以提取访问模式。

1.2. 文件创建和控制标志

#define O_CREAT            0100  /* 文件不存在则创建 */
#define O_EXCL             0200  /* 与 O_CREAT 同用,文件存在则失败 */
#define O_NOCTTY           0400  /* 不分配控制终端 */
#define O_TRUNC           01000  /* 打开时截断文件长度为0 */

1.3. 文件状态标志

#define O_APPEND          02000  /* 追加模式,每次写都到文件尾 */
#define O_NONBLOCK        04000  /* 非阻塞I/O */
#define O_NDELAY        O_NONBLOCK  /* O_NONBLOCK 的别名 */
#define O_SYNC           010000  /* 同步写,等待物理I/O完成 */
#define FASYNC           020000  /* 信号驱动I/O */

1.4. 扩展文件标志

#define O_DIRECT         040000  /* 直接I/O,绕过页面缓存 */
#define O_LARGEFILE     0100000  /* 支持大文件 (>2GB) */
#define O_DIRECTORY     0200000  /* 必须是个目录 */
#define O_NOFOLLOW      0400000  /* 不追踪符号链接 */
#define O_NOATIME       01000000 /* 不更新文件访问时间 */

2. fcntl 命令标志 (F_* commands)

这些是 fcntl() 系统调用的命令参数

2.1. 文件描述符操作命令

#define F_DUPFD         0       /* 复制文件描述符 */
#define F_GETFD         1       /* 获取文件描述符标志 */
#define F_SETFD         2       /* 设置文件描述符标志 */
#define F_GETFL         3       /* 获取文件状态标志 */
#define F_SETFL         4       /* 设置文件状态标志 */

2.2. 文件锁命令

#define F_GETLK         5       /* 测试锁,获取锁信息 */
#define F_SETLK         6       /* 设置/释放锁,非阻塞 */
#define F_SETLKW        7       /* 设置锁,阻塞等待 */
#define F_GETLK64       12      /* 64位版本,用于大文件 */
#define F_SETLK64       13
#define F_SETLKW64      14

2.3. 信号和所有权命令

#define F_SETOWN        8       /* 设置接收SIGIO/SIGURG信号的进程 */
#define F_GETOWN        9       /* 获取接收信号的进程 */
#define F_SETSIG        10      /* 设置替代SIGIO的信号 */
#define F_GETSIG        11      /* 获取设置的信号 */

3.锁相关标志

3.1. 文件描述符标志

#define FD_CLOEXEC      1       /* 执行exec时关闭文件描述符,避免文件描述符泄漏 */

3.2. 记录锁类型

#define F_RDLCK         0       /* 读锁(共享锁) */
#define F_WRLCK         1       /* 写锁(排他锁) */
#define F_UNLCK         2       /* 解锁 */

3.3. BSD风格文件锁

#define F_EXLCK         4       /* 排他锁 */
#define F_SHLCK         8       /* 共享锁 */
#define F_INPROGRESS    16      /* 锁操作进行中 */

3.4. flock() 系统调用标志

#define LOCK_SH         1       /* 共享锁 */
#define LOCK_EX         2       /* 排他锁 */
#define LOCK_NB         4       /* 非阻塞 */
#define LOCK_UN         8       /* 释放锁 */
#define LOCK_MAND       32      /* 强制锁 */
#define LOCK_READ       64      /* 允许并发读操作 */

4.使用示例

4.1.设置非阻塞模式

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

4.2.设置close-on-exec

fcntl(fd, F_SETFD, FD_CLOEXEC);

4.3. 复制文件描述符

int new_fd = fcntl(old_fd, F_DUPFD, 0);  /* 复制,使用最小可用fd */

sys_fcntl系统调用

asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
{struct file *filp;long err = -EBADF;filp = fget(fd);if (!filp)goto out;err = security_file_fcntl(filp, cmd, arg);if (err) {fput(filp);return err;}err = do_fcntl(fd, cmd, arg, filp);fput(filp);
out:return err;
}

1.函数原型和参数

asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)

参数说明

  • fd:文件描述符
  • cmd:控制命令(如 F_GETFL, F_SETFL, F_GETFD 等)
  • arg:命令参数(具体含义取决于 cmd

2.第1部分:变量声明和文件获取

struct file *filp;
long err = -EBADF;filp = fget(fd);
if (!filp)goto out;

2.1.fget(fd) - 获取文件结构

  • 根据文件描述符 fd 从当前进程的文件描述符表中查找对应的 struct file 结构
  • 如果找到,增加文件的引用计数(防止在操作过程中文件被关闭)
  • 如果文件描述符无效或不存在,返回 NULL

错误处理

  • 如果 fget() 返回 NULL,说明文件描述符无效,直接跳转到 out 标签返回 -EBADF(Bad file descriptor)

3.第2部分:安全模块检查

err = security_file_fcntl(filp, cmd, arg);
if (err) {fput(filp);return err;
}

security_file_fcntl() - LSM 安全钩子

  • 调用 Linux 安全模块(LSM)框架进行权限检查
  • 允许 SELinux 等安全模块对 fcntl 操作进行访问控制
  • 如果安全模块拒绝操作,返回错误码

错误处理

  • 如果安全检查失败,调用 fput(filp) 释放文件引用并直接返回错误

4.第3部分:执行实际的 fcntl 操作

err = do_fcntl(fd, cmd, arg, filp);

5.第4部分:资源清理和返回

fput(filp);
out:return err;

fput(filp) - 释放文件引用

  • 减少文件的引用计数
  • 如果引用计数降为0,说明没有其他使用者,调用 __fput() 真正释放文件资源

6.完整的执行流程

开始 sys_fcntl(fd, cmd, arg)↓
filp = fget(fd)           // 根据fd获取文件结构if (!filp)                // 检查文件是否存在goto out → 返回 -EBADF↓
security_file_fcntl()     // LSM安全检查if (安全检查失败)          // 安全检查失败fput(filp) → 返回错误↓  
do_fcntl()               // 执行具体的fcntl操作fput(filp)               // 释放文件引用↓
返回操作结果

fget通过文件描述符获取文件结构

#define get_file(x)	atomic_inc(&(x)->f_count)
static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
{struct file * file = NULL;if (fd < files->max_fds)file = files->fd[fd];return file;
}
struct file fastcall *fget(unsigned int fd)
{struct file *file;struct files_struct *files = current->files;spin_lock(&files->file_lock);file = fcheck_files(files, fd);if (file)get_file(file);spin_unlock(&files->file_lock);return file;
}

1.宏定义和辅助函数

1.1. get_file(x)

#define get_file(x)	atomic_inc(&(x)->f_count)

作用:增加文件的引用计数

  • atomic_inc():原子性地增加计数器,防止竞态条件
  • f_countstruct file 中的引用计数字段
  • 用途:标记文件正在被使用,防止在操作过程中文件被意外关闭或释放

1.2. fcheck_files() 函数

static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
{struct file * file = NULL;if (fd < files->max_fds)file = files->fd[fd];return file;
}

参数

  • files:进程的文件描述符表结构
  • fd:要查找的文件描述符

逻辑

  1. 检查 fd 是否在有效范围内(fd < files->max_fds
  2. 如果有效,从文件描述符数组 files->fd[fd] 中获取对应的 struct file 指针
  3. 返回文件指针(如果 fd 无效或对应槽位为空则返回 NULL

2.核心函数:fget()

2.1.函数原型

struct file fastcall *fget(unsigned int fd)
  • fastcall:一种调用约定,表示参数通过寄存器传递(在x86架构上)
  • 返回:指向 struct file 的指针,失败返回 NULL

2.2.函数实现分析

struct file fastcall *fget(unsigned int fd)
{struct file *file;struct files_struct *files = current->files;spin_lock(&files->file_lock);file = fcheck_files(files, fd);if (file)get_file(file);spin_unlock(&files->file_lock);return file;
}
2.2.1.第1步:获取当前进程的文件表
struct files_struct *files = current->files;
  • current:指向当前进程的 task_struct 的宏
  • current->files:进程的文件描述符表,包含所有打开的文件信息
2.2.2.第2步:加锁保护
spin_lock(&files->file_lock);

作用

  • 获取文件表的自旋锁,防止并发访问
  • 保护文件描述符表的完整性,避免在查找过程中表被修改
2.2.3.第3步:查找文件描述符
file = fcheck_files(files, fd);
  • 调用辅助函数在文件描述符表中查找对应的文件结构
  • 如果 fd 无效或对应文件不存在,fileNULL
2.2.4.第4步:增加引用计数
if (file)get_file(file);

关键操作

  • 只有在成功找到文件时才增加引用计数
  • 引用计数确保文件在使用期间不会被释放
2.2.5.第5步:释放锁并返回
spin_unlock(&files->file_lock);
return file;
  • 释放文件表锁,允许其他操作继续
  • 返回找到的文件结构指针(或 NULL

do_fcntl处理所有 fcntl 系统调用的具体命令

static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,struct file *filp)
{long err = -EINVAL;switch (cmd) {case F_DUPFD:get_file(filp);err = dupfd(filp, arg);break;case F_GETFD:err = get_close_on_exec(fd) ? FD_CLOEXEC : 0;break;case F_SETFD:err = 0;set_close_on_exec(fd, arg & FD_CLOEXEC);break;case F_GETFL:err = filp->f_flags;break;case F_SETFL:err = setfl(fd, filp, arg);break;case F_GETLK:err = fcntl_getlk(filp, (struct flock __user *) arg);break;case F_SETLK:case F_SETLKW:err = fcntl_setlk(filp, cmd, (struct flock __user *) arg);break;case F_GETOWN:/** XXX If f_owner is a process group, the* negative return value will get converted* into an error.  Oops.  If we keep the* current syscall conventions, the only way* to fix this will be in libc.*/err = filp->f_owner.pid;force_successful_syscall_return();break;case F_SETOWN:err = f_setown(filp, arg, 1);break;case F_GETSIG:err = filp->f_owner.signum;break;case F_SETSIG:/* arg == 0 restores default behaviour. */if (arg < 0 || arg > _NSIG) {break;}err = 0;filp->f_owner.signum = arg;break;case F_GETLEASE:err = fcntl_getlease(filp);break;case F_SETLEASE:err = fcntl_setlease(fd, filp, arg);break;case F_NOTIFY:err = fcntl_dirnotify(fd, filp, arg);break;default:break;}return err;
}

1. 函数原型和初始化

static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,struct file *filp)
{long err = -EINVAL;  // 默认返回无效参数错误

参数

  • fd:文件描述符
  • cmd:fcntl 命令
  • arg:命令参数
  • filp:已获取的文件结构指针

2. 文件描述符操作命令

2.1. F_DUPFD - 复制文件描述符

case F_DUPFD:get_file(filp);err = dupfd(filp, arg);break;

作用:复制文件描述符

  • get_file(filp):增加原文件的引用计数
  • dupfd(filp, arg):实际执行复制操作,返回新文件描述符

2.2. F_GETFD - 获取文件描述符标志

case F_GETFD:err = get_close_on_exec(fd) ? FD_CLOEXEC : 0;break;

作用:获取 close-on-exec 标志状态

  • 返回 FD_CLOEXEC 1或 0
  • get_close_on_exec(fd):检查文件描述符的 close-on-exec 位

2.3. F_SETFD - 设置文件描述符标志

case F_SETFD:err = 0;set_close_on_exec(fd, arg & FD_CLOEXEC);break;

作用:设置 close-on-exec 标志

  • arg & FD_CLOEXEC:提取标志位
  • set_close_on_exec(fd, 1/0):设置或清除标志

3. 文件状态标志操作

3.1. F_GETFL - 获取文件状态标志

case F_GETFL:err = filp->f_flags;break;

作用:返回文件的打开标志

  • 直接返回 filp->f_flags,包含 O_RDONLYO_NONBLOCKO_APPEND

3.2. F_SETFL - 设置文件状态标志

case F_SETFL:err = setfl(fd, filp, arg);break;

作用:修改文件状态标志

  • setfl() 函数实际执行设置操作
  • 只能修改部分标志(如 O_APPENDO_NONBLOCK),不能修改访问模式

4. 文件锁操作

4.1. F_GETLK - 测试锁

case F_GETLK:err = fcntl_getlk(filp, (struct flock __user *) arg);break;

作用:测试文件锁,检查是否可以加锁

  • arg 指向用户空间的 struct flock 结构
  • 锁冲突时返回锁信息或表示可以加锁

4.2. F_SETLK / F_SETLKW - 设置/释放锁

case F_SETLK:
case F_SETLKW:err = fcntl_setlk(filp, cmd, (struct flock __user *) arg);break;

作用:设置或释放文件锁

  • F_SETLK:非阻塞方式
  • F_SETLKW:阻塞方式(Wait)
  • fcntl_setlk() 处理具体的锁操作

5. 文件所有权和信号

5.1. F_GETOWN - 获取文件所有者

case F_GETOWN:err = filp->f_owner.pid;force_successful_syscall_return();break;

作用:获取接收 SIGIO 信号的进程或进程组ID

  • filp->f_owner.pid:存储所有者信息
  • force_successful_syscall_return():确保系统调用成功返回,即使返回负值(进程组ID为负),将eax寄存器最高位清零

5.2. F_SETOWN - 设置文件所有者

case F_SETOWN:err = f_setown(filp, arg, 1);break;

作用:设置接收 SIGIO 信号的进程或进程组

  • f_setown():实际设置所有者
  • arg > 0:进程ID;arg < 0:进程组ID(取绝对值)

5.3. F_GETSIG - 获取信号

case F_GETSIG:err = filp->f_owner.signum;break;

作用:获取异步I/O通知时发送的信号

  • 默认是 SIGIO,可以修改为其他信号

5.4. F_SETSIG - 设置信号

case F_SETSIG:/* arg == 0 restores default behaviour. */if (arg < 0 || arg > _NSIG) {break;}err = 0;filp->f_owner.signum = arg;break;

作用:设置异步I/O通知时发送的信号

  • arg == 0:恢复默认的 SIGIO
  • arg 必须在有效信号范围内(1 到 _NSIG

6. 高级文件操作

6.1. F_GETLEASE - 获取租约状态

case F_GETLEASE:err = fcntl_getlease(filp);break;

作用:获取文件的租约类型

  • 租约机制用于检测其他进程对文件的访问

6.2. F_SETLEASE - 设置租约

case F_SETLEASE:err = fcntl_setlease(fd, filp, arg);break;

作用:设置文件租约

  • arg 可以是 F_RDLCK(读租约)、F_WRLCK(写租约)、F_UNLCK(释放租约)

6.3. F_NOTIFY - 目录通知

case F_NOTIFY:err = fcntl_dirnotify(fd, filp, arg);break;

作用:设置目录更改通知

  • 用于监控目录中的文件创建、删除、修改等事件

7. 默认情况

default:break;

作用:处理未知命令

  • 保持初始的 err = -EINVAL,返回"无效参数"错误
http://www.dtcms.com/a/449679.html

相关文章:

  • 网站搭建大型公司框架网站模板
  • RAG开发
  • 季度优选策略:年化472%,回撤 8%,夏普5.48,卡玛比率61.55
  • 直播网站建设费用做音乐的网站设计
  • 基于springboot的蜗牛兼职网的设计与实现
  • 网站管理系统是什么wordpress 分类浏览
  • Docker(五)—— Docker Compose 一键搭建 LNMP 架构并部署 WordPress
  • uniapp开发的后台系统
  • 深圳网站营销公司网站做实名验证
  • python编写AI生常用匡架及使用指令集
  • GridView 中使用重绘边界的实用建议
  • FPGA学习篇——Verilog学习之计数器的实现
  • 网站建设万网浙江建设网站公司
  • 刷粉网站推广快点商标设计注册
  • MySQL 配置管理与日志系统完全指南:从基础到高级优化
  • Leecode hot100 - 279. 完全平方数
  • 珠宝营销型网站设计珠海做网站及推广
  • 视频原创度检测算法对比
  • Spring MVC 九大组件源码深度剖析(九):FlashMapManager - 重定向数据的守护者
  • 网站设计上市公司继续浏览此网站(不推荐)
  • The “Launch” - 价值交付与灰度发布
  • 做网站公司(信科网络)网站开发外包报价
  • libopenssl1_0_0-1.0.2p-3.49.1.x86_64安装教程(RPM包手动安装步骤+依赖解决附安装包下载)
  • 有些人做网站不用钱的 对吗网站建设经典范例
  • C52-二级指针
  • 【微科普】PID 多久计算一次?(第四弹):嵌入式系统中 PID 控制周期的科学选择与实践
  • 目前流行的网站开发设计廊坊商昊网站建设
  • 《WSGI 到 ASGI:Python Web 架构的演进与桥梁之道》
  • 数据库完整指南:从基础到 Django 集成
  • 福建设计招聘网站seo sem什么意思