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_count
:struct 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
:要查找的文件描述符
逻辑:
- 检查
fd
是否在有效范围内(fd < files->max_fds
) - 如果有效,从文件描述符数组
files->fd[fd]
中获取对应的struct file
指针 - 返回文件指针(如果
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
无效或对应文件不存在,file
为NULL
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_RDONLY
、O_NONBLOCK
、O_APPEND
等
3.2. F_SETFL - 设置文件状态标志
case F_SETFL:err = setfl(fd, filp, arg);break;
作用:修改文件状态标志
setfl()
函数实际执行设置操作- 只能修改部分标志(如
O_APPEND
、O_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
,返回"无效参数"错误