在linux下,常常可以执行rm -rf /home/tmp/test这样的命令删除一个目录,或是使用其他的参数删除一个文件或目录,纠结在系统内部,这些删除命令是如何处理的呢?
这些命令其实是由系统提供的可执行程序实现的,而这些程序调用了库函数或是直接调用了系统调用函数,主要的相关的系统调用有两个,下面先介绍这些相关的系统调用函数:
- asmlinkage long sys_unlink(const char __user *pathname);
< do_unlinkat
< vfs_unlink:检查删除的条目是否是一个挂载点,如果是则返回EBUSY的错误;否则删除该inode,如果这个条目是一个私有条目,则调用安全删除机制将其删除,否则直接调用文件系统的inode删除接口删除;
< security_inode_unlink:安全删除,如果是一个非私有数据,则返回0;
dir->i_op->unlink:调用具体文件系统的删除接口;
- asmlinkage long sys_rmdir(const char __user *pathname);
< do_rmdir:查找路径,然后判断删除目录的类型,对目录上锁,找到相应的dentry,调用vfs层的目录删除接口
< vfs_rmdir:对目录项上锁,判断删除的目录是否为挂载点,如果是挂载点则返回EBUSY错误;否则,判断删除的inode是否为私有节点,如果是的话则使用安全删除机制删除,返回直接调用文件系统的删除命令;
< security_inode_rmdir:
dir->i_op->rmdir:
现在重点介绍块inode中的元数据操作
:
const struct inode_operations ext3_dir_inode_operations = {
.create = ext3_create,
.lookup = ext3_lookup,
.link = ext3_link,
.unlink = ext3_unlink,
.symlink = ext3_symlink,
.mkdir = ext3_mkdir,
.rmdir = ext3_rmdir,
.mknod = ext3_mknod,
.rename = ext3_rename,
.setattr = ext3_setattr,
#ifdef CONFIG_EXT3_FS_XATTR
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.listxattr = ext3_listxattr,
.removexattr = generic_removexattr,
#endif
.permission = ext3_permission,
};
static int ext3_unlink(struct inode * dir, struct dentry *dentry)
{
int retval;
struct inode * inode;
struct buffer_head * bh;
struct ext3_dir_entry_2 * de;
handle_t *handle;
/* Initialize quotas before so that eventual writes go
* in separate transaction */
DQUOT_INIT(dentry->d_inode);
//申请一个原子操作
handle = ext3_journal_start(dir, EXT3_DELETE_TRANS_BLOCKS(dir->i_sb));
if (IS_ERR(handle))
return PTR_ERR(handle);
if (IS_DIRSYNC(dir))
handle->h_sync = 1;
retval = -ENOENT;
//找到inode对应的buffer head
bh = ext3_find_entry (dentry, &de);
if (!bh)
goto end_unlink;
inode = dentry->d_inode;
retval = -EIO;
if (le32_to_cpu(de->inode) != inode->i_ino)
goto end_unlink;
if (!inode->i_nlink) {
//inode中的i_nlink字段为0,表示删除的目标文件不存在
ext3_warning (inode->i_sb, "ext3_unlink",
"Deleting nonexistent file (%lu), %d",
inode->i_ino, inode->i_nlink);
inode->i_nlink = 1;
}
//通过将目标目录下与前一个目录下合并,删除目标目录项
retval = ext3_delete_entry(handle, dir, de, bh);
if (retval)
goto end_unlink;
dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC;
ext3_update_dx_flag(dir);
ext3_mark_inode_dirty(handle, dir);
drop_nlink(inode);
if (!inode->i_nlink)
//如果inode的i_nlink为0,则认为当前节点为孤儿节点,加入到super block中的孤儿节点链表中
ext3_orphan_add(handle, inode);
inode->i_ctime = dir->i_ctime;
ext3_mark_inode_dirty(handle, inode);
retval = 0;
end_unlink:
ext3_journal_stop(handle);
brelse (bh);
return retval;
}
static int ext3_rmdir (struct inode * dir, struct dentry *dentry)
{
int retval;
struct inode * inode;
struct buffer_head * bh;
struct ext3_dir_entry_2 * de;
handle_t *handle;
/* Initialize quotas before so that eventual writes go in
* separate transaction */
DQUOT_INIT(dentry->d_inode);
handle = ext3_journal_start(dir, EXT3_DELETE_TRANS_BLOCKS(dir->i_sb));
if (IS_ERR(handle))
return PTR_ERR(handle);
retval = -ENOENT;
bh = ext3_find_entry (dentry, &de);
if (!bh)
goto end_rmdir;
if (IS_DIRSYNC(dir))
handle->h_sync = 1;
inode = dentry->d_inode;
retval = -EIO;
if (le32_to_cpu(de->inode) != inode->i_ino)
goto end_rmdir;
retval = -ENOTEMPTY;
//判断删除的目录是否是空目录,如果不为空目录,则
//返回ENOTEMPTY的错误
if (!empty_dir (inode))
goto end_rmdir;
//将删除的目标dentry与磁盘上的前一个dentry合并,
//从而删除目标dentry
retval = ext3_delete_entry(handle, dir, de, bh);
if (retval)
goto end_rmdir;
if (inode->i_nlink != 2)
ext3_warning (inode->i_sb, "ext3_rmdir",
"empty directory has nlink!=2 (%d)",
inode->i_nlink);
inode->i_version++;
//将i_nlink设置为0
clear_nlink(inode);
/* There's no need to set i_disksize: the fact that i_nlink is
* zero will ensure that the right thing happens during any
* recovery. */
inode->i_size = 0;
ext3_orphan_add(handle, inode);
inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME_SEC;
ext3_mark_inode_dirty(handle, inode);
drop_nlink(dir);
ext3_update_dx_flag(dir);
ext3_mark_inode_dirty(handle, dir);
end_rmdir:
ext3_journal_stop(handle);
brelse (bh);
return retval;
}
接下来看看coreutils中具体的程序流程:与删除相关的源文件主要有
- remove.h
- remove.c
- rm.c
- rmdir.c
1.首先看remove.h文件,该文件定义了一些与删除操作相关的数据结构:
struct rm_options:定义了rm操作的参数,即上层应用指定的rm -r -f -i等之类的选项
enum RM_status:定义了删除操作的返回状态,即删除成功、出错或是目标目录非空等
2.接着重点看看remove.c文件,与rm相关的操作核心函数就在这个文件中
- 数据结构
enum Prompt_action { PA_DESCEND_INTO_DIR = 2, //进入到目录内部 PA_REMOVE_DIR //删除目录 };
struct AD_ent //在active directory堆栈中的一个条目,每个条目对应一个active directory { Hash_table *unremovable; //相应目录中,不能删除的条目的文件名(如.和..,以及调用删除操作失败的条目) enum RM_status status; union { struct dev_ino a; //指示上级目录,当调用chdir进入子目录后,再调用chdir ..时通过该参数回到上次访问的目录下 struct saved_cwd saved_cwd; //重构初始工作目录必须的信息,只有最底层条目才有该结构 } u; };
struct dirstack_state { struct obstack dir_stack; //正在处理的目录的文件名,当用户进入子目录后,将在堆栈中压入一个新的条目,使用chdir时将堆栈栈顶的元素出栈 struct obstack len_stack; //目录名长度堆栈 struct obstack Active_dir; //active目录的栈,第一个条目是初始工作目录,当用户调用chdir时,将相应的条目压栈 struct cycle_check_state cycle_check_state; jmp_buf current_arg_jumpbuf; }; typedef struct dirstack_state Dirstack_state;
- 处理函数
注意:ext3_rmdir只能删除一个空目录,如果目录非空,则函数将返回ENOTEMPTY的错误。无论是ext3_unlink还是ext3_rmdir函数,当目录为非空时,都不会变量该目录的子目录递归删除非空的目录,因此推测,rm -rf /hone/test这样的shell命令删除一个非空的目录,应该是由shell对命令进行解析和递归调用,传给系统的调用应该只能删除一个目录或是文件,而不能批量删除目录和文件。
参考资料:
http://www.ibm.com/developerworks/cn/linux/l-cn-usagecounter/index.html