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信号通知监视进程

创建一个符号链接文件sys_symlink

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

函数声明和变量定义

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:内核空间的文件名字符串指针

获取源文件名

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

获取目标文件名

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

路径查找和准备

        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:标志表示查找父目录而不是文件本身
  • 如果查找失败,跳转到清理代码

创建目录项

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

实际创建符号链接

            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):释放目录项的引用计数

清理资源

        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

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

函数声明和变量定义

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

获取inode信号量锁

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

检查路径组件类型

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

清除LOOKUP_PARENT标志

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

执行哈希查找

    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标签

检查文件不存在的情况

    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

成功返回

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

文件不存在错误处理

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

通用错误处理

fail:
    return dentry;
}
  • 返回预设的错误值

函数功能详解

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

具体作用

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

参数说明

  • nd:包含完整路径查找信息的结构体
  • is_dir:区分创建目录(1)还是文件(0),影响验证逻辑

实际创建符号链接vfs_symlink

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

vfs_symlink 函数

权限检查

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):检查是否有在目录中创建文件的权限
  • 如果权限检查失败,立即返回错误

文件系统操作检查

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

安全模块检查

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

磁盘配额初始化

	DQUOT_INIT(dir);
  • 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;
}
  • 如果创建成功:
    • inode_dir_notify(dir, DN_CREATE):发送目录通知
    • security_inode_post_symlink():安全模块的后置处理钩子
  • 返回操作结果(0表示成功)

inode_dir_notify 函数

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 函数

变量定义和锁获取

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的自旋锁,保护并发访问

遍历目录通知链表

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

发送信号通知

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

处理单次/多次通知

		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:如果设置多次通知,保留通知结构继续使用
  • 否则(单次通知):
    • 从链表中移除该节点
    • 标记链表已变化
    • 释放通知结构内存

清理工作

	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

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检查

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

配额启用检查

	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的标志位,如特殊文件可能不受配额限制
  • 两个条件都满足时才执行配额初始化

执行配额初始化

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

宏定义解析

sb_any_quota_enabled 宏

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

sb_has_quota_enabled 宏

#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设置初始配额结构
    • 跟踪用户的磁盘使用情况
    • 为后续的配额检查和强制执行做准备

硬链接创建sys_link

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

变量声明和初始化

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:内核空间的新链接路径字符串

获取新链接路径名

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

查找源文件路径

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

查找新链接的父目录

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

检查文件系统边界

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

创建新链接的目录项

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

执行硬链接创建

		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):释放目录项的引用计数

释放父目录信号量

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

资源清理部分

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,同文件系统限制
  • 符号链接:包含路径字符串,可跨文件系统

实际创建硬链接vfs_link

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

变量声明和初始化

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:错误返回值变量

源文件存在性检查

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

创建权限检查

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

文件系统一致性检查

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

文件属性检查

	/*
	 * 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)

文件系统操作支持检查

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

目录链接限制检查

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

安全模块检查

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

执行硬链接创建

	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的信号量锁

成功处理和后置操作

	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链接计数
  • 删除一个硬链接只是减少链接计数,不影响其他链接

unlink系统调用sys_unlink

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

变量声明和初始化

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

获取路径名

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

查找父目录

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

检查路径组件类型

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

获取父目录信号量

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

查找目标文件目录项

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

检查路径格式

		/* 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引用

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

执行删除操作

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

释放目录项

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

释放父目录信号量

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

释放inode引用

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

资源清理

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

斜杠错误处理

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删除目录
  • 原子性:通过信号量确保操作的原子性
  • 错误处理:提供精确的错误码指示失败原因

vfs_unlink

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

函数声明和权限检查

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 表示不是目录删除
  • 如果权限检查失败,立即返回错误

文件系统操作支持检查

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

磁盘配额初始化

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

获取inode信号量锁

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

检查挂载点

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

安全检查和执行删除

	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信号量锁

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

目录项删除和通知

	/* 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):发送目录删除通知

返回错误码

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

函数功能详解

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

详细执行流程

  1. 权限验证

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

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

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

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

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

    • 从目录项缓存中移除条目
    • 发送文件系统通知
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

---学无止境---

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值