文件系统操作整体架构
系统调用层 → 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
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:父目录的inodedentry:新创建的目录项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;
}
- 返回预设的错误值
函数功能详解
主要功能:为文件创建操作准备或查找目录项
具体作用:
- 安全性检查:验证路径组件的有效性
- 并发控制:通过信号量确保对父目录的独占访问
- 目录项查找:在目录项缓存中查找或创建目标文件的目录项
- 状态验证:检查文件是否存在以及路径的合法性
参数说明:
nd:包含完整路径查找信息的结构体is_dir:区分创建目录(1)还是文件(0),影响验证逻辑
实际创建符号链接vfs_symlink
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);
}
vfs_symlink 函数
权限检查
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:父目录的inodedentry:要创建的符号链接的目录项oldname:符号链接指向的目标路径mode:文件权限模式may_create(dir, dentry, NULL):检查是否有在目录中创建文件的权限- 如果权限检查失败,立即返回错误
文件系统操作检查
c
if (!dir->i_op || !dir->i_op->symlink)
return -EPERM;
dir->i_op:inode操作函数表指针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 主要功能:
- 权限验证:检查创建文件的权限
- 能力检查:验证文件系统支持符号链接操作
- 安全审查:通过Linux安全模块进行访问控制
- 配额管理:检查磁盘空间配额
- 实际操作:调用具体文件系统的符号链接创建
- 事件通知:向监视进程发送目录变化通知
目录通知系统功能:
- 实现
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:参数为要初始化配额的inodeBUG_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的磁盘配额信息
具体作用:
-
安全性验证:
- 确保
inode有有效的超级块引用 - 防止对无效
inode进行操作
- 确保
-
配额系统检查:
- 检查文件系统是否启用了配额系统
- 区分用户配额和组配额
- 检查特定
inode是否豁免配额限制
-
配额初始化:
- 为
inode设置初始配额结构 - 跟踪用户的磁盘使用情况
- 为后续的配额检查和强制执行做准备
- 为
硬链接创建sys_link
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:父目录的inodenew_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,同文件系统限制 - 符号链接:包含路径字符串,可跨文件系统
实际创建硬链接vfs_link
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:获取源文件对应的inodeerror:错误返回值变量
源文件存在性检查
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链接计数 - 删除一个硬链接只是减少链接计数,不影响其他链接
unlink系统调用sys_unlink
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系统调用)
详细执行流程:
-
参数验证:
- 验证用户空间路径指针有效性
- 复制路径名到内核空间
-
路径解析:
- 查找要删除文件的父目录
- 验证路径组件的合法性
-
并发控制:
- 获取父目录的信号量锁,防止竞态条件
- 确保删除操作的原子性
-
目标文件查找:
- 在父目录中查找要删除的文件
- 验证路径格式的正确性
-
实际删除:
- 增加
inode引用计数防止意外释放 - 调用
vfs_unlink执行实际删除操作 - 减少
inode的链接计数,如果为0则真正删除
- 增加
-
资源清理:
- 按正确顺序释放所有临时资源
- 返回操作结果
关键特性:
- 不是立即删除:unlink只是减少链接计数,当链接计数为0且没有进程打开文件时才会真正删除
- 目录限制:不能使用unlink删除目录
- 原子性:通过信号量确保操作的原子性
- 错误处理:提供精确的错误码指示失败原因
vfs_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:父目录的inodedentry:要删除文件的目录项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表示成功)
函数功能详解
主要功能:在虚拟文件系统层删除文件
详细执行流程:
-
权限验证:
- 检查在父目录中删除文件的权限
- 验证调用者是否有足够的权限
-
能力检查:
- 确认文件系统支持unlink操作
- 检查底层文件系统的能力
-
配额管理:
- 初始化磁盘配额系统
- 为可能的磁盘空间更新做准备
-
并发控制:
- 获取文件
inode锁,防止竞态条件 - 确保删除操作的原子性
- 获取文件
-
特殊状况检查:
- 检查是否为挂载点(不能删除)
- 检查NFS特殊重命名文件
-
缓存清理:
- 从目录项缓存中移除条目
- 发送文件系统通知