Linux中系统调用sys_symlink和sys_unlink函数的实现

文件系统操作整体架构

系统调用层 → VFS层 → 具体文件系统层

用户空间 系统调用层 VFS虚拟文件系统层 具体文件系统层 sys_symlink vfs_symlink sys_link vfs_link sys_unlink vfs_unlink ext4/fat/ntfs等 ext4/fat/ntfs等 ext4/fat/ntfs等

符号链接创建 (symlink)

功能:创建软链接,包含目标路径字符串

核心流程

复制代码
sys_symlink → path_lookup父目录 → lookup_create目录项 → vfs_symlink

关键特性

  • 可跨文件系统
  • 链接文件有独立的inode
  • 存储目标路径字符串
  • 目标不存在也可创建

硬链接创建 (link)

功能 :创建硬链接,多个目录项指向同一inode

核心流程

复制代码
sys_link → 检查同文件系统 → lookup_create目录项 → vfs_link

关键特性

  • 必须在同一文件系统
  • 共享同一inode,增加链接计数
  • 所有链接地位平等
  • 不能链接目录(防循环)

文件删除 (unlink)

功能 :删除文件链接,减少inode链接计数

核心流程

复制代码
sys_unlink → path_lookup父目录 → lookup_hash查找目标 → vfs_unlink

关键特性

  • 只是减少链接计数,不一定立即释放
  • 当链接计数为0且无进程打开时才真正删除
  • 不能删除目录
  • 不能删除挂载点

关键技术组件详解

1. 路径解析组件

path_lookup:解析文件路径

  • LOOKUP_PARENT:查找父目录
  • LOOKUP_FOLLOW:跟踪符号链接

lookup_create:为创建操作准备目录项

  • 检查路径组件合法性
  • 获取父目录信号量锁
  • 创建或查找目标目录项

2. 权限和安全检查

may_create/may_delete:基础权限检查

  • 文件系统权限位检查
  • 目录写权限验证

安全模块钩子

  • security_inode_symlink/link/unlink:操作前检查
  • security_inode_post_*:操作后处理

3. 通知机制

目录变更通知

  • inode_dir_notify:发送目录事件通知
  • DN_CREATE/DN_DELETE:创建/删除事件
  • 通过SIGIO信号通知监视进程
c 复制代码
asmlinkage long sys_symlink(const char __user * oldname, const char __user * newname)
{
	int error = 0;
	char * from;
	char * to;

	from = getname(oldname);
	if(IS_ERR(from))
		return PTR_ERR(from);
	to = getname(newname);
	error = PTR_ERR(to);
	if (!IS_ERR(to)) {
		struct dentry *dentry;
		struct nameidata nd;

		error = path_lookup(to, LOOKUP_PARENT, &nd);
		if (error)
			goto out;
		dentry = lookup_create(&nd, 0);
		error = PTR_ERR(dentry);
		if (!IS_ERR(dentry)) {
			error = vfs_symlink(nd.dentry->d_inode, dentry, from, S_IALLUGO);
			dput(dentry);
		}
		up(&nd.dentry->d_inode->i_sem);
		path_release(&nd);
out:
		putname(to);
	}
	putname(from);
	return error;
}

函数声明和变量定义

c 复制代码
asmlinkage long sys_symlink(const char __user * oldname, const char __user * newname)
{
    int error = 0;
    char * from;
    char * to;
  • asmlinkage:告诉编译器参数通过堆栈传递,这是系统调用的标准调用约定
  • const char __user *:指针指向用户空间的内存,需要特殊处理
  • oldname:源文件路径(符号链接指向的目标)
  • newname:新创建的符号链接路径
  • error:错误返回值,初始化为0表示成功
  • from, to:内核空间的文件名字符串指针

获取源文件名

c 复制代码
    from = getname(oldname);
    if(IS_ERR(from))
        return PTR_ERR(from);
  • getname(oldname):从用户空间复制文件名字符串到内核空间
  • IS_ERR(from):检查是否复制成功,如果失败返回错误码
  • PTR_ERR(from):将错误指针转换为错误码值

获取目标文件名

c 复制代码
    to = getname(newname);
    error = PTR_ERR(to);
    if (!IS_ERR(to)) {
  • getname(newname):同样复制目标文件名到内核空间
  • 先将返回值预设为可能的错误码
  • !IS_ERR(to):如果成功获取目标文件名,继续执行符号链接创建

路径查找和准备

c 复制代码
        struct dentry *dentry;
        struct nameidata nd;

        error = path_lookup(to, LOOKUP_PARENT, &nd);
        if (error)
            goto out;
  • dentry:目录项结构,表示文件系统中的一个节点
  • nameidata:路径查找结果的数据结构
  • path_lookup(to, LOOKUP_PARENT, &nd):查找目标路径的父目录
  • LOOKUP_PARENT:标志表示查找父目录而不是文件本身
  • 如果查找失败,跳转到清理代码

创建目录项

c 复制代码
        dentry = lookup_create(&nd, 0);
        error = PTR_ERR(dentry);
        if (!IS_ERR(dentry)) {
  • lookup_create(&nd, 0):在父目录中创建新的目录项
  • 第二个参数0表示创建文件
  • 检查目录项创建是否成功

实际创建符号链接

c 复制代码
            error = vfs_symlink(nd.dentry->d_inode, dentry, from, S_IALLUGO);
            dput(dentry);
        }
  • vfs_symlink():虚拟文件系统层的符号链接创建函数
  • 参数:
    • nd.dentry->d_inode:父目录的inode
    • dentry:新创建的目录项
    • from:符号链接指向的目标路径
    • S_IALLUGO:文件权限标志(所有用户权限)
  • dput(dentry):释放目录项的引用计数

清理资源

c 复制代码
        up(&nd.dentry->d_inode->i_sem);
        path_release(&nd);
out:
        putname(to);
    }
    putname(from);
    return error;
}
  • up(&nd.dentry->d_inode->i_sem):释放父目录inode的信号量锁
  • path_release(&nd):释放路径查找相关的资源
  • putname(to)putname(from):释放复制的文件名内存
  • 返回错误码(0表示成功)

函数功能

功能:创建一个符号链接文件

作用

  • 在指定路径 newname 创建一个符号链接文件
  • 该符号链接指向 oldname 指定的目标
  • 符号链接是一个特殊的文件,其内容是指向另一个文件或目录的路径

使用场景

  • 在文件系统中创建软链接
  • 类似于 shell 中的 ln -s 命令

返回值

  • 0:成功创建符号链接
  • 负数:错误码,表示创建失败的原因

为文件创建准备或查找目录项lookup_create

c 复制代码
struct dentry *lookup_create(struct nameidata *nd, int is_dir)
{
	struct dentry *dentry;

	down(&nd->dentry->d_inode->i_sem);
	dentry = ERR_PTR(-EEXIST);
	if (nd->last_type != LAST_NORM)
		goto fail;
	nd->flags &= ~LOOKUP_PARENT;
	dentry = lookup_hash(&nd->last, nd->dentry);
	if (IS_ERR(dentry))
		goto fail;
	if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode)
		goto enoent;
	return dentry;
enoent:
	dput(dentry);
	dentry = ERR_PTR(-ENOENT);
fail:
	return dentry;
}

函数声明和变量定义

c 复制代码
struct dentry *lookup_create(struct nameidata *nd, int is_dir)
{
    struct dentry *dentry;
  • struct nameidata *nd:路径查找结果的数据结构,包含查找状态和相关信息
  • int is_dir:标志位,表示要创建的是目录(1)还是文件(0)
  • dentry:返回的目录项指针

获取inode信号量锁

c 复制代码
    down(&nd->dentry->d_inode->i_sem);
  • down():获取信号量(互斥锁)
  • nd->dentry->d_inode->i_sem:父目录inode的信号量
  • 这确保了在创建操作期间对父目录的独占访问,防止竞态条件

检查路径组件类型

c 复制代码
    dentry = ERR_PTR(-EEXIST);
    if (nd->last_type != LAST_NORM)
        goto fail;
  • 预设返回值为 -EEXIST(文件已存在错误)
  • nd->last_type:最后一个路径组件的类型
  • LAST_NORM:表示正常的文件名组件
  • 如果最后一个组件不是普通文件名(可能是...或根目录),直接失败

清除LOOKUP_PARENT标志

c 复制代码
    nd->flags &= ~LOOKUP_PARENT;
  • nd->flags:路径查找的标志位
  • LOOKUP_PARENT:表示查找父目录的标志
  • &= ~LOOKUP_PARENT:清除这个标志,因为现在要查找的是目标文件本身而不是其父目录

执行哈希查找

c 复制代码
    dentry = lookup_hash(&nd->last, nd->dentry);
    if (IS_ERR(dentry))
        goto fail;
  • lookup_hash(&nd->last, nd->dentry):在目录项缓存中查找指定的文件名或者创建一个dentry
  • &nd->last:要查找的最后一个路径组件(文件名)
  • nd->dentry:父目录的目录项
  • 如果查找出错,跳转到fail标签

检查文件不存在的情况

c 复制代码
    if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode)
        goto enoent;
  • !is_dir:要创建的不是目录(即普通文件或符号链接)
  • nd->last.name[nd->last.len]:检查文件名末尾是否有特殊字符(非零值)
    • 表示文件名后面不是"\0",有可能是"/",代表目录
  • !dentry->d_inode:查找的目录项没有对应的inode(文件不存在)
  • 如果这三个条件都满足,说明要创建一个不存在的文件但路径有问题,跳转到enoent

成功返回

c 复制代码
    return dentry;
  • 如果所有检查都通过,返回找到或新创建的目录项

文件不存在错误处理

c 复制代码
enoent:
    dput(dentry);
    dentry = ERR_PTR(-ENOENT);
  • dput(dentry):释放目录项的引用计数
  • 设置返回值为 -ENOENT(文件不存在错误)

通用错误处理

c 复制代码
fail:
    return dentry;
}
  • 返回预设的错误值

函数功能详解

主要功能:为文件创建操作准备或查找目录项

具体作用

  1. 安全性检查:验证路径组件的有效性
  2. 并发控制:通过信号量确保对父目录的独占访问
  3. 目录项查找:在目录项缓存中查找或创建目标文件的目录项
  4. 状态验证:检查文件是否存在以及路径的合法性

参数说明

  • nd:包含完整路径查找信息的结构体
  • is_dir:区分创建目录(1)还是文件(0),影响验证逻辑
c 复制代码
int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, int mode)
{
	int error = may_create(dir, dentry, NULL);

	if (error)
		return error;

	if (!dir->i_op || !dir->i_op->symlink)
		return -EPERM;

	error = security_inode_symlink(dir, dentry, oldname);
	if (error)
		return error;

	DQUOT_INIT(dir);
	error = dir->i_op->symlink(dir, dentry, oldname);
	if (!error) {
		inode_dir_notify(dir, DN_CREATE);
		security_inode_post_symlink(dir, dentry, oldname);
	}
	return error;
}
static inline void inode_dir_notify(struct inode *inode, unsigned long event)
{
	if (inode->i_dnotify_mask & (event))
		__inode_dir_notify(inode, event);
}
void __inode_dir_notify(struct inode *inode, unsigned long event)
{
	struct dnotify_struct *	dn;
	struct dnotify_struct **prev;
	struct fown_struct *	fown;
	int			changed = 0;

	spin_lock(&inode->i_lock);
	prev = &inode->i_dnotify;
	while ((dn = *prev) != NULL) {
		if ((dn->dn_mask & event) == 0) {
			prev = &dn->dn_next;
			continue;
		}
		fown = &dn->dn_filp->f_owner;
		send_sigio(fown, dn->dn_fd, POLL_MSG);
		if (dn->dn_mask & DN_MULTISHOT)
			prev = &dn->dn_next;
		else {
			*prev = dn->dn_next;
			changed = 1;
			kmem_cache_free(dn_cache, dn);
		}
	}
	if (changed)
		redo_inode_mask(inode);
	spin_unlock(&inode->i_lock);
}

权限检查

c 复制代码
int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, int mode)
{
	int error = may_create(dir, dentry, NULL);

	if (error)
		return error;
  • dir:父目录的inode
  • dentry:要创建的符号链接的目录项
  • oldname:符号链接指向的目标路径
  • mode:文件权限模式
  • may_create(dir, dentry, NULL):检查是否有在目录中创建文件的权限
  • 如果权限检查失败,立即返回错误

文件系统操作检查

c 复制代码
	if (!dir->i_op || !dir->i_op->symlink)
		return -EPERM;
  • dir->i_opinode操作函数表指针
  • dir->i_op->symlink:具体的符号链接创建函数指针
  • 如果文件系统不支持符号链接操作,返回 -EPERM(操作不允许)

安全模块检查

c 复制代码
	error = security_inode_symlink(dir, dentry, oldname);
	if (error)
		return error;
  • security_inode_symlink():Linux安全模块(LSM)的钩子函数
  • 允许安全模块(如SELinux)检查符号链接创建权限
  • 如果安全模块拒绝操作,返回错误

磁盘配额初始化

c 复制代码
	DQUOT_INIT(dir);
  • DQUOT_INIT(dir):初始化磁盘配额检查
  • 确保用户没有超出磁盘空间配额限制

执行符号链接创建

c 复制代码
	error = dir->i_op->symlink(dir, dentry, oldname);
  • 调用具体文件系统实现的符号链接创建函数
  • 这是实际创建符号链接的核心操作

成功处理和后置操作

c 复制代码
	if (!error) {
		inode_dir_notify(dir, DN_CREATE);
		security_inode_post_symlink(dir, dentry, oldname);
	}
	return error;
}
  • 如果创建成功:
    • inode_dir_notify(dir, DN_CREATE):发送目录通知
    • security_inode_post_symlink():安全模块的后置处理钩子
  • 返回操作结果(0表示成功)

inode_dir_notify 函数

c 复制代码
static inline void inode_dir_notify(struct inode *inode, unsigned long event)
{
	if (inode->i_dnotify_mask & (event))
		__inode_dir_notify(inode, event);
}
  • 内联函数,检查是否需要发送目录通知
  • inode->i_dnotify_mask:目录通知的事件掩码
  • 如果事件在掩码中,调用实际的通知函数

__inode_dir_notify 函数

变量定义和锁获取

c 复制代码
void __inode_dir_notify(struct inode *inode, unsigned long event)
{
	struct dnotify_struct *	dn;
	struct dnotify_struct **prev;
	struct fown_struct *	fown;
	int			changed = 0;

	spin_lock(&inode->i_lock);
  • dn:目录通知结构指针
  • prev:用于链表遍历的二级指针
  • fown:文件所有者信息
  • changed:标记链表是否发生变化
  • spin_lock(&inode->i_lock):获取inode的自旋锁,保护并发访问

遍历目录通知链表

c 复制代码
	prev = &inode->i_dnotify;
	while ((dn = *prev) != NULL) {
		if ((dn->dn_mask & event) == 0) {
			prev = &dn->dn_next;
			continue;
		}
  • inode的目录通知链表头开始遍历
  • 检查每个通知结构的掩码是否包含当前事件
  • 如果不包含,继续遍历下一个节点

发送信号通知

c 复制代码
		fown = &dn->dn_filp->f_owner;
		send_sigio(fown, dn->dn_fd, POLL_MSG);
  • 获取文件所有者信息
  • send_sigio():向监视进程发送SIGIO信号,通知目录变化

处理单次/多次通知

c 复制代码
		if (dn->dn_mask & DN_MULTISHOT)
			prev = &dn->dn_next;
		else {
			*prev = dn->dn_next;
			changed = 1;
			kmem_cache_free(dn_cache, dn);
		}
  • DN_MULTISHOT:如果设置多次通知,保留通知结构继续使用
  • 否则(单次通知):
    • 从链表中移除该节点
    • 标记链表已变化
    • 释放通知结构内存

清理工作

c 复制代码
	if (changed)
		redo_inode_mask(inode);
	spin_unlock(&inode->i_lock);
}
  • 如果链表发生变化,重新计算inode的通知掩码
  • 释放inode自旋锁

函数功能详解

vfs_symlink 主要功能

  1. 权限验证:检查创建文件的权限
  2. 能力检查:验证文件系统支持符号链接操作
  3. 安全审查:通过Linux安全模块进行访问控制
  4. 配额管理:检查磁盘空间配额
  5. 实际操作:调用具体文件系统的符号链接创建
  6. 事件通知:向监视进程发送目录变化通知

目录通知系统功能

  • 实现dnotify机制,允许进程监视目录变化
  • 通过信号(SIGIO)异步通知监视进程
  • 支持单次和多次通知模式
  • 维护进程级的目录监视配置

磁盘配额初始化DQUOT_INIT

c 复制代码
static __inline__ void DQUOT_INIT(struct inode *inode)
{
	BUG_ON(!inode->i_sb);
	if (sb_any_quota_enabled(inode->i_sb) && !IS_NOQUOTA(inode))
		inode->i_sb->dq_op->initialize(inode, -1);
}
#define sb_any_quota_enabled(sb) (sb_has_quota_enabled(sb, USRQUOTA) | \
				  sb_has_quota_enabled(sb, GRPQUOTA))
#define sb_has_quota_enabled(sb, type) ((type)==USRQUOTA ? \
	(sb_dqopt(sb)->flags & DQUOT_USR_ENABLED) : (sb_dqopt(sb)->flags & DQUOT_GRP_ENABLED))

DQUOT_INIT 函数

函数声明和BUG检查

c 复制代码
static __inline__ void DQUOT_INIT(struct inode *inode)
{
	BUG_ON(!inode->i_sb);
  • static __inline__:静态内联函数,编译时直接展开,减少函数调用开销
  • struct inode *inode:参数为要初始化配额的inode
  • BUG_ON(!inode->i_sb):内核调试宏,检查inode是否有有效的超级块
    • !inode->i_sb:如果inode的超级块指针为NULL
    • 如果条件为真,触发内核BUG,导致系统panic

配额启用检查

c 复制代码
	if (sb_any_quota_enabled(inode->i_sb) && !IS_NOQUOTA(inode))
  • sb_any_quota_enabled(inode->i_sb):检查文件系统是否启用了任何配额
  • !IS_NOQUOTA(inode):检查inode是否不受配额限制
    • IS_NOQUOTA(inode):检查inode的标志位,如特殊文件可能不受配额限制
  • 两个条件都满足时才执行配额初始化

执行配额初始化

c 复制代码
		inode->i_sb->dq_op->initialize(inode, -1);
}
  • inode->i_sb->dq_op:超级块的磁盘配额操作函数表
  • ->initialize(inode, -1):调用配额初始化函数
    • inode:要初始化配额的inode
    • -1:表示未知的配额类型,通常用于初始化默认配额结构

宏定义解析

sb_any_quota_enabled 宏

c 复制代码
#define sb_any_quota_enabled(sb) (sb_has_quota_enabled(sb, USRQUOTA) | \
				  sb_has_quota_enabled(sb, GRPQUOTA))
  • 检查超级块是否启用了用户配额或组配额
  • 使用位或操作 |:如果任一配额启用就返回真
  • USRQUOTA:用户配额类型常量
  • GRPQUOTA:组配额类型常量

sb_has_quota_enabled 宏

c 复制代码
#define sb_has_quota_enabled(sb, type) ((type)==USRQUOTA ? \
	(sb_dqopt(sb)->flags & DQUOT_USR_ENABLED) : (sb_dqopt(sb)->flags & DQUOT_GRP_ENABLED))
  • 三元条件运算符检查特定类型的配额是否启用
  • sb_dqopt(sb):获取超级块的配额选项结构
  • sb_dqopt(sb)->flags:配额标志位
  • DQUOT_USR_ENABLED:用户配额启用标志位
  • DQUOT_GRP_ENABLED:组配额启用标志位
  • 位与操作 &:检查特定标志位是否设置

函数功能详解

主要功能 :初始化inode的磁盘配额信息

具体作用

  1. 安全性验证

    • 确保inode有有效的超级块引用
    • 防止对无效inode进行操作
  2. 配额系统检查

    • 检查文件系统是否启用了配额系统
    • 区分用户配额和组配额
    • 检查特定inode是否豁免配额限制
  3. 配额初始化

    • inode设置初始配额结构
    • 跟踪用户的磁盘使用情况
    • 为后续的配额检查和强制执行做准备
c 复制代码
asmlinkage long sys_link(const char __user * oldname, const char __user * newname)
{
	struct dentry *new_dentry;
	struct nameidata nd, old_nd;
	int error;
	char * to;

	to = getname(newname);
	if (IS_ERR(to))
		return PTR_ERR(to);

	error = __user_walk(oldname, 0, &old_nd);
	if (error)
		goto exit;
	error = path_lookup(to, LOOKUP_PARENT, &nd);
	if (error)
		goto out;
	error = -EXDEV;
	if (old_nd.mnt != nd.mnt)
		goto out_release;
	new_dentry = lookup_create(&nd, 0);
	error = PTR_ERR(new_dentry);
	if (!IS_ERR(new_dentry)) {
		error = vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry);
		dput(new_dentry);
	}
	up(&nd.dentry->d_inode->i_sem);
out_release:
	path_release(&nd);
out:
	path_release(&old_nd);
exit:
	putname(to);

	return error;
}

变量声明和初始化

c 复制代码
asmlinkage long sys_link(const char __user * oldname, const char __user * newname)
{
	struct dentry *new_dentry;
	struct nameidata nd, old_nd;
	int error;
	char * to;
  • asmlinkage:系统调用标准调用约定,参数通过堆栈传递
  • oldname:源文件路径(已存在的文件)
  • newname:新创建的硬链接路径
  • new_dentry:新链接的目录项结构
  • nd:新链接路径的查找数据
  • old_nd:源文件路径的查找数据
  • error:错误返回值
  • to:内核空间的新链接路径字符串

获取新链接路径名

c 复制代码
	to = getname(newname);
	if (IS_ERR(to))
		return PTR_ERR(to);
  • getname(newname):从用户空间复制新链接路径到内核空间
  • IS_ERR(to):检查复制是否成功
  • PTR_ERR(to):如果失败,将错误指针转换为错误码并返回

查找源文件路径

c 复制代码
	error = __user_walk(oldname, 0, &old_nd);
	if (error)
		goto exit;
  • __user_walk(oldname, 0, &old_nd):解析源文件路径
  • 第二个参数 0:查找标志,表示普通查找
  • &old_nd:存储源文件的路径查找结果
  • 如果查找失败,设置错误码并跳转到清理代码

查找新链接的父目录

c 复制代码
	error = path_lookup(to, LOOKUP_PARENT, &nd);
	if (error)
		goto out;
  • path_lookup(to, LOOKUP_PARENT, &nd):查找新链接路径的父目录
  • LOOKUP_PARENT:标志表示查找父目录而不是文件本身
  • &nd:存储父目录的路径查找结果
  • 如果查找失败,跳转到清理代码

检查文件系统边界

c 复制代码
	error = -EXDEV;
	if (old_nd.mnt != nd.mnt)
		goto out_release;
  • -EXDEV:跨文件系统错误码
  • old_nd.mnt != nd.mnt:比较源文件和新链接的挂载点
  • 如果不在同一个文件系统,硬链接不能创建,跳转到释放资源

创建新链接的目录项

c 复制代码
	new_dentry = lookup_create(&nd, 0);
	error = PTR_ERR(new_dentry);
	if (!IS_ERR(new_dentry)) {
  • lookup_create(&nd, 0):在父目录中创建新的目录项
  • 第二个参数 0:表示创建的是文件而不是目录
  • 预设错误值为可能的错误码
  • 如果目录项创建成功,继续执行链接操作

执行硬链接创建

c 复制代码
		error = vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry);
		dput(new_dentry);
	}
  • vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry):虚拟文件系统层的硬链接创建
    • old_nd.dentry:源文件的目录项
    • nd.dentry->d_inode:父目录的inode
    • new_dentry:新链接的目录项
  • dput(new_dentry):释放目录项的引用计数

释放父目录信号量

c 复制代码
	up(&nd.dentry->d_inode->i_sem);
  • up(&nd.dentry->d_inode->i_sem):释放父目录inode的信号量锁
  • 这个锁是在 lookup_create 中获取的,现在需要释放

资源清理部分

c 复制代码
out_release:
	path_release(&nd);
out:
	path_release(&old_nd);
exit:
	putname(to);

	return error;
}
  • out_release:释放新链接路径资源
  • path_release(&nd):释放新链接的路径查找资源
  • out:释放源文件路径资源
  • path_release(&old_nd):释放源文件的路径查找资源
  • exit:释放路径名字符串
  • putname(to):释放复制的路径名内存
  • 返回最终的错误码

函数功能详解

主要功能:创建硬链接(hard link)

硬链接特性

  • 多个目录项指向同一个inode
  • 所有硬链接地位平等,删除一个不影响其他
  • 必须在同一个文件系统中
  • 不能为目录创建硬链接(防止循环)

与符号链接的区别

  • 硬链接:直接指向inode,同文件系统限制
  • 符号链接:包含路径字符串,可跨文件系统
c 复制代码
int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
{
	struct inode *inode = old_dentry->d_inode;
	int error;

	if (!inode)
		return -ENOENT;

	error = may_create(dir, new_dentry, NULL);
	if (error)
		return error;

	if (dir->i_sb != inode->i_sb)
		return -EXDEV;

	/*
	 * A link to an append-only or immutable file cannot be created.
	 */
	if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
		return -EPERM;
	if (!dir->i_op || !dir->i_op->link)
		return -EPERM;
	if (S_ISDIR(old_dentry->d_inode->i_mode))
		return -EPERM;

	error = security_inode_link(old_dentry, dir, new_dentry);
	if (error)
		return error;

	down(&old_dentry->d_inode->i_sem);
	DQUOT_INIT(dir);
	error = dir->i_op->link(old_dentry, dir, new_dentry);
	up(&old_dentry->d_inode->i_sem);
	if (!error) {
		inode_dir_notify(dir, DN_CREATE);
		security_inode_post_link(old_dentry, dir, new_dentry);
	}
	return error;
}

变量声明和初始化

c 复制代码
int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
{
	struct inode *inode = old_dentry->d_inode;
	int error;
  • old_dentry:源文件的目录项(已存在的文件)
  • dir:目标目录的inode(新链接所在的目录)
  • new_dentry:新链接的目录项
  • inode:获取源文件对应的inode
  • error:错误返回值变量

源文件存在性检查

c 复制代码
	if (!inode)
		return -ENOENT;
  • 检查源文件的inode是否存在
  • 如果inode为NULL,说明源文件不存在,返回 -ENOENT

创建权限检查

c 复制代码
	error = may_create(dir, new_dentry, NULL);
	if (error)
		return error;
  • may_create(dir, new_dentry, NULL):检查在目标目录中创建新文件的权限
  • 第三个参数NULL表示不检查特定的访问模式
  • 如果权限检查失败,立即返回错误

文件系统一致性检查

c 复制代码
	if (dir->i_sb != inode->i_sb)
		return -EXDEV;
  • 比较目标目录和源文件的超级块
  • dir->i_sb:目标目录的超级块
  • inode->i_sb:源文件的超级块
  • 如果不相同,返回 -EXDEV,硬链接不能跨文件系统

文件属性检查

c 复制代码
	/*
	 * A link to an append-only or immutable file cannot be created.
	 */
	if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
		return -EPERM;
  • 检查源文件是否具有特殊属性:
    • IS_APPEND(inode):只追加文件,内容只能追加不能修改
    • IS_IMMUTABLE(inode):不可变文件,完全不能修改
  • 如果具有这些属性,返回 -EPERM(Operation not permitted)

文件系统操作支持检查

c 复制代码
	if (!dir->i_op || !dir->i_op->link)
		return -EPERM;
  • dir->i_op:目标目录的inode操作函数表
  • dir->i_op->link:具体的硬链接创建函数
  • 如果文件系统不支持硬链接操作,返回 -EPERM

目录链接限制检查

c 复制代码
	if (S_ISDIR(old_dentry->d_inode->i_mode))
		return -EPERM;
  • S_ISDIR(old_dentry->d_inode->i_mode):检查源文件是否为目录
  • 如果是目录,返回 -EPERM,防止创建目录的硬链接(避免目录循环)

安全模块检查

c 复制代码
	error = security_inode_link(old_dentry, dir, new_dentry);
	if (error)
		return error;
  • security_inode_link():Linux安全模块(LSM)的钩子函数
  • 允许SELinux等安全模块检查硬链接创建权限
  • 如果安全模块拒绝操作,返回错误

执行硬链接创建

c 复制代码
	down(&old_dentry->d_inode->i_sem);
	DQUOT_INIT(dir);
	error = dir->i_op->link(old_dentry, dir, new_dentry);
	up(&old_dentry->d_inode->i_sem);
  • down(&old_dentry->d_inode->i_sem):获取源文件inode的信号量锁,防止并发修改
  • DQUOT_INIT(dir):初始化目标目录的磁盘配额
  • dir->i_op->link(old_dentry, dir, new_dentry):调用具体文件系统的硬链接创建函数
  • up(&old_dentry->d_inode->i_sem):释放源文件inode的信号量锁

成功处理和后置操作

c 复制代码
	if (!error) {
		inode_dir_notify(dir, DN_CREATE);
		security_inode_post_link(old_dentry, dir, new_dentry);
	}
	return error;
}
  • 如果硬链接创建成功:
    • inode_dir_notify(dir, DN_CREATE):发送目录创建通知
    • security_inode_post_link():安全模块的后置处理钩子
  • 返回最终的错误码(0表示成功)

函数功能详解

主要功能:在虚拟文件系统层创建硬链接

硬链接的核心特性

  • 多个目录项指向同一个inode
  • 所有硬链接地位平等
  • 增加inode链接计数
  • 删除一个硬链接只是减少链接计数,不影响其他链接
c 复制代码
asmlinkage long sys_unlink(const char __user * pathname)
{
	int error = 0;
	char * name;
	struct dentry *dentry;
	struct nameidata nd;
	struct inode *inode = NULL;

	name = getname(pathname);
	if(IS_ERR(name))
		return PTR_ERR(name);

	error = path_lookup(name, LOOKUP_PARENT, &nd);
	if (error)
		goto exit;
	error = -EISDIR;
	if (nd.last_type != LAST_NORM)
		goto exit1;
	down(&nd.dentry->d_inode->i_sem);
	dentry = lookup_hash(&nd.last, nd.dentry);
	error = PTR_ERR(dentry);
	if (!IS_ERR(dentry)) {
		/* Why not before? Because we want correct error value */
		if (nd.last.name[nd.last.len])
			goto slashes;
		inode = dentry->d_inode;
		if (inode)
			atomic_inc(&inode->i_count);
		error = vfs_unlink(nd.dentry->d_inode, dentry);
	exit2:
		dput(dentry);
	}
	up(&nd.dentry->d_inode->i_sem);
	if (inode)
		iput(inode);	/* truncate the inode here */
exit1:
	path_release(&nd);
exit:
	putname(name);
	return error;

slashes:
	error = !dentry->d_inode ? -ENOENT :
		S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR;
	goto exit2;
}

变量声明和初始化

c 复制代码
asmlinkage long sys_unlink(const char __user * pathname)
{
	int error = 0;
	char * name;
	struct dentry *dentry;
	struct nameidata nd;
	struct inode *inode = NULL;
  • pathname:要删除的文件路径
  • error:错误返回值,初始化为0(成功)
  • name:内核空间的文件路径字符串
  • dentry:要删除文件的目录项
  • nd:路径查找数据
  • inode:文件的inode指针,初始为NULL

获取路径名

c 复制代码
	name = getname(pathname);
	if(IS_ERR(name))
		return PTR_ERR(name);
  • getname(pathname):从用户空间复制路径名到内核空间
  • IS_ERR(name):检查复制是否成功
  • 如果失败,立即返回错误码

查找父目录

c 复制代码
	error = path_lookup(name, LOOKUP_PARENT, &nd);
	if (error)
		goto exit;
  • path_lookup(name, LOOKUP_PARENT, &nd):查找路径的父目录
  • LOOKUP_PARENT:标志表示查找父目录而不是文件本身
  • 如果查找失败,跳转到清理代码

检查路径组件类型

c 复制代码
	error = -EISDIR;
	if (nd.last_type != LAST_NORM)
		goto exit1;
  • 预设错误为 -EISDIR(是一个目录)
  • nd.last_type:最后一个路径组件的类型
  • LAST_NORM:表示正常的文件名
  • 如果不是普通文件名(如...或根目录),跳转到清理

获取父目录信号量

c 复制代码
	down(&nd.dentry->d_inode->i_sem);
  • down(&nd.dentry->d_inode->i_sem):获取父目录inode的信号量锁
  • 确保在删除操作期间对父目录的独占访问

查找目标文件目录项

c 复制代码
	dentry = lookup_hash(&nd.last, nd.dentry);
	error = PTR_ERR(dentry);
	if (!IS_ERR(dentry)) {
  • lookup_hash(&nd.last, nd.dentry):在父目录中查找目标文件的目录项
  • 预设错误值为可能的错误码
  • 如果查找成功,继续执行删除操作

检查路径格式

c 复制代码
		/* Why not before? Because we want correct error value */
		if (nd.last.name[nd.last.len])
			goto slashes;
  • nd.last.name[nd.last.len]:检查文件名后是否有额外字符(如斜杠)
  • 如果有额外字符,说明路径格式有问题,跳转到slashes处理

获取inode引用

c 复制代码
		inode = dentry->d_inode;
		if (inode)
			atomic_inc(&inode->i_count);
  • 获取目录项对应的inode
  • atomic_inc(&inode->i_count):增加inode的引用计数
  • 防止在删除过程中inode被释放

执行删除操作

c 复制代码
		error = vfs_unlink(nd.dentry->d_inode, dentry);
  • vfs_unlink(nd.dentry->d_inode, dentry):调用虚拟文件系统层的删除函数
  • 参数:父目录的inode和要删除文件的目录项

释放目录项

c 复制代码
	exit2:
		dput(dentry);
	}
  • dput(dentry):释放目录项的引用计数
  • 如果引用计数降为0,会真正释放目录项

释放父目录信号量

c 复制代码
	up(&nd.dentry->d_inode->i_sem);
  • up(&nd.dentry->d_inode->i_sem):释放父目录inode的信号量锁

释放inode引用

c 复制代码
	if (inode)
		iput(inode);	/* truncate the inode here */
  • iput(inode):释放inode的引用计数

资源清理

c 复制代码
exit1:
	path_release(&nd);
exit:
	putname(name);
	return error;
  • path_release(&nd):释放路径查找资源
  • putname(name):释放复制的路径名字符串
  • 返回最终的错误码

斜杠错误处理

c 复制代码
slashes:
	error = !dentry->d_inode ? -ENOENT :
		S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR;
	goto exit2;
  • 处理路径中有多余斜杠的情况
  • 三元条件运算符确定具体错误:
    • 如果inode不存在:-ENOENT
    • 如果是目录:-EISDIR
    • 否则:-ENOTDIR
  • 跳转到目录项释放代码

函数功能详解

主要功能:删除文件(unlink系统调用)

详细执行流程

  1. 参数验证

    • 验证用户空间路径指针有效性
    • 复制路径名到内核空间
  2. 路径解析

    • 查找要删除文件的父目录
    • 验证路径组件的合法性
  3. 并发控制

    • 获取父目录的信号量锁,防止竞态条件
    • 确保删除操作的原子性
  4. 目标文件查找

    • 在父目录中查找要删除的文件
    • 验证路径格式的正确性
  5. 实际删除

    • 增加inode引用计数防止意外释放
    • 调用vfs_unlink执行实际删除操作
    • 减少inode的链接计数,如果为0则真正删除
  6. 资源清理

    • 按正确顺序释放所有临时资源
    • 返回操作结果

关键特性

  • 不是立即删除:unlink只是减少链接计数,当链接计数为0且没有进程打开文件时才会真正删除
  • 目录限制:不能使用unlink删除目录
  • 原子性:通过信号量确保操作的原子性
  • 错误处理:提供精确的错误码指示失败原因
c 复制代码
int vfs_unlink(struct inode *dir, struct dentry *dentry)
{
	int error = may_delete(dir, dentry, 0);

	if (error)
		return error;

	if (!dir->i_op || !dir->i_op->unlink)
		return -EPERM;

	DQUOT_INIT(dir);

	down(&dentry->d_inode->i_sem);
	if (d_mountpoint(dentry))
		error = -EBUSY;
	else {
		error = security_inode_unlink(dir, dentry);
		if (!error)
			error = dir->i_op->unlink(dir, dentry);
	}
	up(&dentry->d_inode->i_sem);

	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
		d_delete(dentry);
		inode_dir_notify(dir, DN_DELETE);
	}
	return error;
}

函数声明和权限检查

c 复制代码
int vfs_unlink(struct inode *dir, struct dentry *dentry)
{
	int error = may_delete(dir, dentry, 0);

	if (error)
		return error;
  • dir:父目录的inode
  • dentry:要删除文件的目录项
  • may_delete(dir, dentry, 0):检查是否有删除权限
    • 第三个参数 0 表示不是目录删除
  • 如果权限检查失败,立即返回错误

文件系统操作支持检查

c 复制代码
	if (!dir->i_op || !dir->i_op->unlink)
		return -EPERM;
  • dir->i_op:父目录的inode操作函数表
  • dir->i_op->unlink:具体的unlink操作函数指针
  • 如果文件系统不支持unlink操作,返回 -EPERM(Operation not permitted)

磁盘配额初始化

c 复制代码
	DQUOT_INIT(dir);
  • DQUOT_INIT(dir):初始化父目录的磁盘配额
  • 确保在删除操作前配额系统处于正确状态

获取inode信号量锁

c 复制代码
	down(&dentry->d_inode->i_sem);
  • down(&dentry->d_inode->i_sem):获取要删除文件的inode信号量锁
  • 防止在删除过程中文件被并发修改

检查挂载点

c 复制代码
	if (d_mountpoint(dentry))
		error = -EBUSY;
  • d_mountpoint(dentry):检查目录项是否是挂载点
  • 如果是挂载点,返回 -EBUSY
  • 不能删除被挂载的文件或目录

安全检查和执行删除

c 复制代码
	else {
		error = security_inode_unlink(dir, dentry);
		if (!error)
			error = dir->i_op->unlink(dir, dentry);
	}
  • security_inode_unlink(dir, dentry):Linux安全模块(LSM)检查
  • 如果安全检查通过,调用具体文件系统的unlink操作
  • dir->i_op->unlink(dir, dentry):实际执行删除操作

释放inode信号量锁

c 复制代码
	up(&dentry->d_inode->i_sem);
  • up(&dentry->d_inode->i_sem):释放inode的信号量锁
  • 无论删除成功与否,都必须释放锁

目录项删除和通知

c 复制代码
	/* We don't d_delete() NFS sillyrenamed files--they still exist. */
	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
		d_delete(dentry);
		inode_dir_notify(dir, DN_DELETE);
	}
  • !(dentry->d_flags & DCACHE_NFSFS_RENAMED):检查不是NFS特殊重命名文件
  • d_delete(dentry):从目录项缓存中删除该目录项
  • inode_dir_notify(dir, DN_DELETE):发送目录删除通知

返回错误码

c 复制代码
	return error;
}
  • 返回最终的操作结果(0表示成功)

函数功能详解

主要功能:在虚拟文件系统层删除文件

详细执行流程

  1. 权限验证

    • 检查在父目录中删除文件的权限
    • 验证调用者是否有足够的权限
  2. 能力检查

    • 确认文件系统支持unlink操作
    • 检查底层文件系统的能力
  3. 配额管理

    • 初始化磁盘配额系统
    • 为可能的磁盘空间更新做准备
  4. 并发控制

    • 获取文件inode锁,防止竞态条件
    • 确保删除操作的原子性
  5. 特殊状况检查

    • 检查是否为挂载点(不能删除)
    • 检查NFS特殊重命名文件
  6. 缓存清理

    • 从目录项缓存中移除条目
    • 发送文件系统通知
相关推荐
FoldWinCard7 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计8 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1238 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质8 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX9 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.9 小时前
Linux系统管理
linux·运维
阿龙的工作记录9 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手9 小时前
Linux的基本指令
linux·运维·服务器
hkj880810 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动12 小时前
Linux编程笔记3【个人用】
linux·笔记