本文共 5243 字,大约阅读时间需要 17 分钟。
在linux下,常常可以执行rm -rf /home/tmp/test这样的命令删除一个目录,或是使用其他的参数删除一个文件或目录,纠结在系统内部,这些删除命令是如何处理的呢?
这些命令其实是由系统提供的可执行程序实现的,而这些程序调用了库函数或是直接调用了系统调用函数,主要的相关的系统调用有两个,下面先介绍这些相关的系统调用函数:
< do_unlinkat
< vfs_unlink:检查删除的条目是否是一个挂载点,如果是则返回EBUSY的错误;否则删除该inode,如果这个条目是一个私有条目,则调用安全删除机制将其删除,否则直接调用文件系统的inode删除接口删除;
< security_inode_unlink:安全删除,如果是一个非私有数据,则返回0;
dir->i_op->unlink:调用具体文件系统的删除接口;
< 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中具体的程序流程:与删除相关的源文件主要有
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://jsztb.baihongyu.com/