前言
本文通过分析rename的代码,让读者对rename流程有清晰的认识。对于文中涉及的dentry、inode、ext3 disk layout、rcu锁、dcache等基础知识,请参考其他博文。
为避免用大篇篇幅介绍dentry lookup过程,我们假设路径名中各级分量的dentry存在dcache中,这样dentry lookup流程能够以无锁RCU方式快速地从dcache中找到对应的dentry。
本文结合linux 3.10.104源码(请从www.kernel.org下载,源码行号与本文档中代码行号一致),分析mv /opt/testdir/geshifei /opt/testdir/aa场景。假设文件系统为ext3。
通过本文章你将掌握rename的实现原理、dentry的lookup过程,并且还能弄清以下问题:
1) 如果a、b文件都存在的话,执行rename a-->b后,新b文件的inode number与a、旧b哪个一样,为什么?
2) rename为什么不可以跨文件系统?mv命令是如何支持跨文件系统的?
3) 内核在同一个时刻可以执行多个rename吗?
4) 为避免死锁,rename加锁原则是什么?
一、原理描述
本节以rename a-->b为例,从文件系统层描述rename的原理,让读者有个概念上的认识。本节后面的章节是源码级的细节描述。
用户视图,文件目录结构如下:
linux系统dentry视图:
ext3磁盘视图如下:
dirA的数据块存放的是ext3_dir_entry_2结构,每个ext3_dir_entry_2描述dirA的一个子文件(目录也是文件),ext3_dir_entry_2结构体的成员inoe存放inode number,根据inode number能够找到磁盘上该文件的数据块。
rename执行的操作如下:
1) 删除dirA磁盘上数据块中a文件的ext3_dir_entry_2结构
2) 修改dirB磁盘上数据块中b文件的ext3_dir_entry_2->inode为a文件的inode
3) b文件inode添加到orphan inode链表中。b文件占用的数据块由orphan inode机制回收。
上述3步执行完成后,磁盘视图如下:
4) 修改dentry(a)的信息,用dentry(b)的信息来填充dentry(a)的相关字段,比如修改parent、name等字段。因dentry(a)的parent变化,所以还重新计算dentry(a)的hash值。
5) 执行__d_drop(dentry),unhash掉b的dentry。当执行dput(dentry)时,因该dentry已经unhash,所以会free掉该dentry内存。(参考源码中3941行dput(new_dentry))。
二、背景知识
dentry lookup借助nameidata寻找dentry,有必要对这个核心数据结构做个说明。
3.10.104/include/linux/namei.h
13 struct nameidata { 14 struct path path; 15 struct qstr last; 16 struct path root; 17 struct inode *inode; /* path.dentry.d_inode */ 18 unsigned int flags; 19 unsigned seq; 20 int last_type; 21 unsigned depth; 22 char *saved_names[MAX_NESTED_LINKS + 1]; 23 }; |
dentry lookup通过path与last成员逐级解析路径分量,path存放路径名中已经解析出的分量信息,last存放将要解析的分量信息,这两个变量随着路径的解析过程在变化。比如/opt/testdir/geshifei:
执行dentry lookup前,path存放根目录“/”分量信息,last->name存放字符串“opt/testdir/geshifei”,因las->hash_len存放了字符串“opt”长度,所以实际上last->nam中只有“opt”是有效的字符串,也就是说last实际上代表的是“opt”分量信息。
dentry lookup在path代表的分量(“/”根目录)中查找last(“opt”分量)的dentry,找到opt的dentry后,path存放opt的分量信息,last->name存放testdir分量信息。
接着dentry lookup继续在在path代表的父目录“opt”中查找last(“testdir”分量)的dentry,找到后,path存放testdir的分量信息。找到testdir的dentry后,path存放testdir的分量信息,last->name存放geshifei分量信息。
接着dentry lookup继续在在path代表的父目录“testdir”中查找last(“geshifei”分量)的dentry,找到后,path存放geshifei的分量信息。此时/opt/testdir/geshifei 整个路径已经解析完,因last->name为空,dentry lookup结束。至此,路径中各级分量已经被解析出来。
3.10.102/include/linux/path.h
7 struct path { 8 struct vfsmount *mnt; 9 struct dentry *dentry; 10 }; |
linux3.10.102/include/linux/dcache.h
42 struct qstr { 43 union { 44 struct { 45 HASH_LEN_DECLARE; 46 }; 47 u64 hash_len; 48 }; 49 const unsigned char *name; 50 }; |
nameidata->root用来记录当前进程的根目录。一般情况下,进程的根目录current->fs-> root就是进程可执行文件的mount point,但是用户也可以通过系统调用chroot修改进程的根目录。本文侧重点在于rename流程,在我们场景中用不到nameidata->root字段,所以本文不分析该字段,在分析dentry lookup流程时再做说明。
三、rename系统调用
3.10.104/fs/namei.c
3964 SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newname) 3965 { 3966 return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname); 3967 } |
注意,oldname与newname必须属于同一个文件系统,否则会返回-EXDEV错误。原因如下。
linux目录文件的数据是由ext3_dir_entry_2组成的,每个ext3_dir_entry_2代表一个文件,该结构体定义如下:
3.10.104/fs/ext3/ext3.h
833 struct ext3_dir_entry_2 { 834 __le32 inode; /* Inode number */ 835 __le16 rec_len; /* Directory entry length */ 836 __u8 name_len; /* Name length */ 837 __u8 file_type; 838 char name[EXT3_NAME_LEN]; /* File name */ 839 }; |
rename时,删除newname paraent中的newname对应的ext3_dir_entry_2,然后通过ext3_add_entry把oldname的ext3_dir_entry_2添加到newname paraent中,注意到ext3_dir_entry_2中有inode number成员,inode number只在一个文件系统内唯一,跨文件系统没有意义,所以oldname与newname必须在同一个文件系统中。
但我们可能发现,平时使用的mv命令是可以跨文件系统重命名文件的,为什么呢?
mv命令把一个文件重命名成本文件系统内的另一个文件,调用rename函数,但如果把A文件系统中的文件重命名成B文件系统中的文件,调用rename函数返回-EXDEV后,mv会执行read-write操作,而不是简单的rename了。比如下面:
$ df Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 476536856 124820156 327486968 28% / /dev/sdc1 14740624 37120 13948060 1% /media/shifei.ge/usbdir $ touch /media/shifei.ge/usbdir/geshifei $ strace mv /media/shifei.ge/usbdir/geshifei /aa rename("/media/shifei.ge/usbdir/geshifei", "/aa") = -1 EXDEV (Invalid cross-device link) unlink("/aa") = 0 open("/media/shifei.ge/usbdir/geshifei", O_RDONLY|O_NOFOLLOW) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=45, ...}) = 0 open("/aa", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4 fstat(4, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0 fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0 read(3, "11111111111111111111111111111111"..., 65536) = 45 write(4, "11111111111111111111111111111111"..., 45) = 45 close(4) = 0 close(3) = 0 |
四、查找oldname的父目录dentry
4.1 起始dentry,从这里开始查找
dentry lookup过程需要一级一级解析路径,路径中的每一个分量对应一个dentry。在解析路径前,需要设置起始dentry,即以这个dentry为父目录,开始查找路径中的各级分量。对于/opt/testdir/geshifei,起始路径为根目录“/”dentry。对于“.”开头的路径,起始路径为pwd的dentry。代码调用流程如下:
从上面调用关系可以看出,flags有LOOKUP_RCU和LOOKUP_PARAENT两个属性,nd指向一个类型为struct nameidata的临时变量,该变量用来实现dentry lookup过程。
3.10.104/fs/namei.c
1859 static int path_init(int dfd, const char *name, unsigned int flags, 1860 struct nameidata *nd, struct file **fp) 1861 { 1862 int retval = 0; 1863 1864 nd->last_type = LAST_ROOT; /* if there are only slashes... */ 1865 nd->flags = flags | LOOKUP_JUMPED; 1866 nd->depth = 0; 1867 if (flags & LOOKUP_ROOT) { 1868 struct inode *inode = nd->root.dentry->d_inode; 1869 if (*name) { 1870 if (!can_lookup(inode)) 1871 return -ENOTDIR; 1872 retval = inode_permission(inode, MAY_EXEC); 1873 if (retval) 1874 return retval; 1875 } 1876 nd->path = nd->root; 1877 nd->inode = inode; 1878 if (flags & LOOKUP_RCU) { 1879 lock_rcu_walk(); 1880 nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq); 1881 } else { 1882 path_get(&nd->path); 1883 } 1884 return 0; 1885 } 1886 1887 nd->root.mnt = NULL; 1888 1889 if (*name=='/') { 1890 if (flags & LOOKUP_RCU) { 1891 lock_rcu_walk(); 1892 set_root_rcu(nd); 1893 } else { 1894 set_root(nd); 1895 path_get(&nd->root); 1896 } 1897 nd->path = nd->root; 1898 } else if (dfd == AT_FDCWD) { 1899 if (flags & LOOKUP_RCU) { 1900 struct fs_struct *fs = current->fs; 1901 unsigned seq; 1902 1903 lock_rcu_walk(); 1904 1904 1905 do { 1906 seq = read_seqcount_begin(&fs->seq); 1907 nd->path = fs->pwd; 1908 nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq); 1909 } while (read_seqcount_retry(&fs->seq, seq)); 1910 } else { 1911 get_fs_pwd(current->fs, &nd->path); 1912 } 1913 } else { 1914 /* Caller must check execute permissions on the starting path component */ 1915 struct fd f = fdget_raw(dfd); 1916 struct dentry *dentry; 1917 1918 if (!f.file) 1919 return -EBADF; 1920 1921 dentry = f.file->f_path.dentry; 1922 1923 if (*name) { 1924 if (!can_lookup(dentry->d_inode)) { 1925 fdput(f); 1926 return -ENOTDIR; 1927 } 1928 } 1929 1930 nd->path = f.file->f_path; 1931 if (flags & LOOKUP_RCU) { 1932 if (f.need_put) 1933 *fp = f.file; 1934 nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq); 1935 lock_rcu_walk(); 1936 } else { 1937 path_get(&nd->path); 1938 fdput(f); 1939 } 1940 } 1941 1942 nd->inode = nd->path.dentry->d_inode; 1943 return 0; 1944 } |
函数入参flags只有LOOKUP_RCU和LOOKUP_PARAENT两个属性,所以1867~1885行代码不会执行。
我们要rename的文件/opt/testdir/geshifei是一个绝对路径,执行1889~1898行代码。flags有LOOKUP_RCU属性,执行1892行的set_root_rcu(nd)函数:
3.10.104/fs/namei.c
645 static __always_inline void set_root_rcu(struct nameidata *nd) 646 { 647 if (!nd->root.mnt) { 648 struct fs_struct *fs = current->fs; 649 unsigned seq; 650 651 do { 652 seq = read_seqcount_begin(&fs->seq); 653 nd->root = fs->root; 654 nd->seq = __read_seqcount_begin(&nd->root.dentry->d_seq); 655 } while (read_seqcount_retry(&fs->seq, seq)); 656 } 657 } |
该函数获取当前进程的根目录信息赋值给nd->root。回到path_init函数1897行,执行nd->path = nd->root,后继让我们忘记nd->root(在dentry lookup会分析到这个字段)。如前背景知识部分描述的,nd->path用来存放已经解析出的路径分量信息,此时存放的根目录“/”分量信息就是我们dentry lookup的起始dentry。对于图1而言,设置了出参nd->path,但nd->last没有设置。
4.2 查找oldname文件的父目录dentry
现在我们要从起始dentry开始,一级一级解析路径分量,直至解析出需rename文件的父目录分量信息,这个过程由link_path_walk函数实现,解析路径所需的所有信息存放在入参nameidata中,nd->path存放已解析出的分量信息,nd->last存放待解析的下一级分量信息。
图2-1蓝色框显示了本节内容对应代码调用流程,需结合代码理解。有一个细节需要注意,link_path_walk只会解析到路径最后一个分量的上级,比如/opt/testdir/geshifei,该函数只会解析opt、testdir分量对应的path信息,而不会解析geshifei分量信息。这个细节由下面代码实现,完整代码见后面的link_path_walk,在读该函数时注意一下:
link_path_walk代码片段:
1824 nd->last = this; 1825 nd->last_type = type; 1826 1827 if (!name[len]) 1828 return 0; |
本节解析完路径后,nd->path代表的是testdir分量信息,nd->last代表的是geshifei分量信息。最后一个分量信息的解析见第六节。
图2-1 解析父目录分量信息
路径分量解析核心源码如下:
3.10.104/fs/namei.c
1778 static int link_path_walk(const char *name, struct nameidata *nd) 1779 { 1780 struct path next; 1781 int err; 1782 1783 while (*name=='/') 1784 name++; 1785 if (!*name) 1786 return 0; 1787 1788 /* At this point we know we have a real path component. */ 1789 for(;;) { 1790 struct qstr this; 1791 long len; 1792 int type; 1793 1794 err = may_lookup(nd); 1795 if (err) 1796 break; 1797 1798 len = hash_name(name, &this.hash); 1799 this.name = name; 1800 this.len = len; 1801 1802 type = LAST_NORM; 1803 if (name[0] == '.') switch (len) { 1804 case 2: 1805 if (name[1] == '.') { 1806 type = LAST_DOTDOT; 1807 nd->flags |= LOOKUP_JUMPED; 1808 } 1809 break; 1810 case 1: 1811 type = LAST_DOT; 1812 } 1813 if (likely(type == LAST_NORM)) { 1814 struct dentry *parent = nd->path.dentry; 1815 nd->flags &= ~LOOKUP_JUMPED; 1816 if (unlikely(parent->d_flags & DCACHE_OP_HASH)) { 1817 err = parent->d_op->d_hash(parent, nd->inode, 1818 &this); 1819 if (err < 0) 1820 break; 1821 } 1822 } 1823 1824 nd->last = this; 1825 nd->last_type = type; 1826 1827 if (!name[len]) 1828 return 0; 1829 /* 1830 * If it wasn't NUL, we know it was '/'. Skip that 1831 * slash, and continue until no more slashes. 1832 */ 1833 do { 1834 len++; 1835 } while (unlikely(name[len] == '/')); 1836 if (!name[len]) 1837 return 0; 1838 1839 name += len; 1840 1841 err = walk_component(nd, &next, LOOKUP_FOLLOW); 1842 if (err < 0) 1843 return err; 1844 1845 if (err) { 1846 err = nested_symlink(&next, nd); 1847 if (err) 1848 return err; 1849 } 1850 if (!can_lookup(nd->inode)) { 1851 err = -ENOTDIR; 1852 break; 1853 } 1854 } 1855 terminate_walk(nd); 1856 return err; 1857 } |
函数的核心是一个for循环,执行for循环前,nd->path存放根目录“/”分量信息,nd->last存放待解析的下级分量信息,不过此时nd->last为空。
1798~1800及1824~1825行设置待解析的下级分量信息,nd->last存放需解析的下级分量信息,对于/opt/testdir/geshifei,nd->last.name存放字符串“opt/testdir/geshifei”,nd->last.hash_len存放字符串“opt”长度。
1803~1812行处理分量名是“.”或“..”的情况,我们分析的路径名中没有这种情况,忽略。
1813~1822行,清除LOOKUP_JUMPED标记,且我们parent->d_flags 没有DCACHE_OP_HASH,所以也不会执行parent->d_op->d_hash重新计算hash值。
至此,已解析的分量nd->path、待解析的分量nd->last都已经设置好,1841行调用walk_component开始解析nd->last的分量对应的path信息。
walk_component的流程图2-2,dentry lookup过程很复杂,为了性能会先在dcache hash表中查找,见lookup_fast函数。如果dcache hash表中找不到,就会通过lookup_slow进行慢速查找。lookup_slow会加互斥锁,然后再dcache中再查一遍,如果还没有,就新分配一个dentry结构,初始化后加入dcache中。lookup_fast查找dcache过程又分RCU模式__d_lookup_rcu和非RCU模式__d_lookup两种场景,RCU模式为无锁操作,性能比非RCU模式高,但是RCU模式不能保证读到的数据是最新的(可以参考RCU知识),所以存在lookup失败的可能,如果失败的话,会通过unlazy_wlak清除flags中的LOOKUP_RCU属性,通过非RCU模式__d_lookup再执行一次查找。__d_lookup依然是在dcache中查找,不过与__d_lookup_rcu相比,__d_lookup会给parent dentry加spin lock。如果__d_lookup也失败了,就需要执行lookup_slow进行查找了。我们这篇文章的主要任务是分析rename流程,故不对lookup源码展开叙述。
假设一切顺利,lookup过程通过图2-2中的红色代码路径解析到了需要的分量信息:
图2-2 解析父目录分量信息
__d_lookup_rcu会查找dcache hash表,然后遍历对应的hash链表,通过dentry_cmp把遍历出的dentry与待解析的分量名比如“opt”进行字符串比较,如果相等则说明找到了待解析的分量dentry。
上面的lookup_fast解析出分量后,会把nd->path赋值为已解析的分量信息,回到link_path_walk函数for循环中,以nd->path为起始dentry,继续解析下一级分量,直至解析出最后一个分量geshifei的上级分量testdir。1856行退出函数时,nd->path存放testdir分量信息,nd->last存放geshifei分量信息(这个分量还没有解析,存放的是“geshifei”字符串及hashlen值)。
五、查找newname的父目录dentry
第四节执行完user_path_parent(olddfd, oldname, &oldnd, lookup_flags),代码回到SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,int, newdfd, const char __user *, newname)函数中,见图1。
在renameat函数中,3876~3880行开始解析newname的父目录dentry,详细过程与4.2节一模一样。
六、rename加锁
renameat函数3883行检查oldname、newname是否属于同一个文件系统,如果不属于同一个文件系统,那么返回-EXDEV错误。原因前面已经说明过。
renameat函数3895行检查文件系统是否只读。
renameat函数3903行lock_rename(new_dir, old_dir)函数先获取s_vfs_rename_mutex,然后对oldname paraent、newname paraent加锁,这么做的目的是为了避免死锁:
3.10.104/fs/namei.c
2304 struct dentry *lock_rename(struct dentry *p1, struct dentry *p2) 2305 { 2306 struct dentry *p; 2307 2308 if (p1 == p2) { 2309 mutex_lock_nested(&p1->d_inode->i_mutex, I_MUTEX_PARENT); 2310 return NULL; 2311 } 2312 2313 mutex_lock(&p1->d_inode->i_sb->s_vfs_rename_mutex); 2314 2315 p = d_ancestor(p2, p1); 2316 if (p) { 2317 mutex_lock_nested(&p2->d_inode->i_mutex, I_MUTEX_PARENT); 2318 mutex_lock_nested(&p1->d_inode->i_mutex, I_MUTEX_CHILD); 2319 return p; 2320 } 2321 2322 p = d_ancestor(p1, p2); 2323 if (p) { 2324 mutex_lock_nested(&p1->d_inode->i_mutex, I_MUTEX_PARENT); 2325 mutex_lock_nested(&p2->d_inode->i_mutex, I_MUTEX_CHILD); 2326 return p; 2327 } 2328 2329 mutex_lock_nested(&p1->d_inode->i_mutex, I_MUTEX_PARENT); 2330 mutex_lock_nested(&p2->d_inode->i_mutex, I_MUTEX_CHILD); 2331 return NULL; 2332 } |
2313行确保整个本文件系统内只有一个rename操作,避免死锁。比如有如下场景:
$ ls dirA dirB $ ls dirA a $ ls dirB b |
一个rename执行mv dirA/a dirB/tmp,另一个rename执行mv dirB/b dirA/a,如果先加源文件父目录锁,再加目的文件父目录锁,则可能死锁。linux的处理方式是用s_vfs_rename_mutex确保一个文件系统内一个时刻只有一个rename在执行。
d_ancestor(struct dentry *p1, struct dentry *p2)如果p2的某个paraent的paraent为p1,那么返回p2的paraent。
2316~2190说明p2是1的祖先目录,2323~2326说明p1是p2的祖先目录,先给祖先目录加锁,再给子目录加锁。这样“从上往下”加锁的目录是为了与文件系统的其他操作执行的加锁顺序一致,避免死锁。比如unlink也会按照“从上往下”先对父目录加锁,再对待删除的文件加锁。
七、查找oldname的dentry
3.10.104/fs/namei.c文件3905行执行renameat函数中的old_dentry = lookup_hash(&oldnd)查找oldname的dentry,调用lookup_hash前,oldnd->path为oldname的父目录分量信息,oldnd->last为oldname分量信息,并且清除了oldnd.flags、newnd.flags的LOOKUP_PARENT属性,增加LOOKUP_RENAME_TARGET属性。
3.10.104/fs/namei.c
2106 static struct dentry *lookup_hash(struct nameidata *nd) 2107 { 2108 return __lookup_hash(&nd->last, nd->path.dentry, nd->flags); 2109 } |
3.10.104/fs/namei.c
1348 static struct dentry *__lookup_hash(struct qstr *name, 1349 struct dentry *base, unsigned int flags) 1350 { 1351 bool need_lookup; 1352 struct dentry *dentry; 1353 1354 dentry = lookup_dcache(name, base, flags, &need_lookup); 1355 if (!need_lookup) 1356 return dentry; 1357 1358 return lookup_real(base->d_inode, dentry, flags); 1359 } |
lookup_hash-->__lookup_hash--> lookup_dcache先在dcache hash表中找,如果没有找到,就分配一个dentry结构,并且初始化dentry->d_d_name(定义为struct qstr d_name),然后设置need_lookup为1,在1358行读取父目录的数据(由ext3_dir_entry_2组成,定义见第五节),通过比较ext3_dir_entry_2->name、dentry->d_iname,找到oldname的ext3_dir_entry_2结构体,然后根据ext3_dir_entry_2->inode记录的inode number构造一个struct inode结构体, dentry->inode就指向新构造的struct inode。具体的细节比较多,会在其他博文专门描述,这里只需要知道oldname的dentry已经找到或者初始化设置好了。
八、查找newname的dentry
查找过程同第七节。
九、rename的核心操作
在SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,int, newdfd, const char __user *, newname)中,经过前面几节的处理,已经准备好oldname newname及其paraent dir的dentry信息,现在可以开始真正的rename操作了。简化的renameat代码如下:
3.10.104/fs/namei.c
3857 SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname, 3858 int, newdfd, const char __user *, newname) 3859 { 3860 struct dentry *old_dir, *new_dir; 3861 struct dentry *old_dentry, *new_dentry; 第三到第七节内容,省略…… 3938 error = vfs_rename(old_dir->d_inode, old_dentry, 3939 new_dir->d_inode, new_dentry); |
3.10.104/fs/namei.c
3819 int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, 3820 struct inode *new_dir, struct dentry *new_dentry) 3821 { 3822 int error; 3823 int is_dir = S_ISDIR(old_dentry->d_inode->i_mode); 3824 const unsigned char *old_name; 3825 省略部分代码…… 3844 3845 if (is_dir) 3846 error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry); 3847 else 3848 error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry); 省略部分代码…… 3854 return error; 3855 } |
rename分为file rename、dir rename,两者大部分操作相同,都会执行ext3_rename,9.1节、9.2节列出不同的部分,9.3节介绍ext3_rename。
9.1 文件重命名
vfs_rename_othe执行文件重命名操作,比目录重命名要简单一些。
3.10.104/fs/namei.c
3786 static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, 3787 struct inode *new_dir, struct dentry *new_dentry) 3788 { 3789 struct inode *target = new_dentry->d_inode; 3790 int error; 3791 3792 error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry); 3793 if (error) 3794 return error; 3795 3796 dget(new_dentry); 3797 if (target) 3798 mutex_lock(&target->i_mutex); 3799 3800 error = -EBUSY; 3801 if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) 3802 goto out; 3803 3804 error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); 3805 if (error) 3806 goto out; 3807 3808 if (target) 3809 dont_mount(new_dentry); 3810 if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) 3811 d_move(old_dentry, new_dentry); 3812 out: 3813 if (target) 3814 mutex_unlock(&target->i_mutex); 3815 dput(new_dentry); 3816 return error; 3817 } |
3792~3803行做代码比较简单,自己看一下就可以了。
3804行old_dir->i_op->rename调用ext3_rename函数执行真正的rename操作。不同的文件系统由自己的inode_operations,对于ext3,见ext3_dir_inode_operations:
3.10.104/fs/ext3/namei.c
2510 const struct inode_operations ext3_dir_inode_operations = { 2511 .create = ext3_create, 2512 .lookup = ext3_lookup, 2513 .link = ext3_link, 2514 .unlink = ext3_unlink, 2515 .symlink = ext3_symlink, 2516 .mkdir = ext3_mkdir, 2517 .rmdir = ext3_rmdir, 2518 .mknod = ext3_mknod, 2519 .rename = ext3_rename, |
对于文件rename,我们现在只要知道,将调用ext3_rename,没有什么特殊的地方。
9.2 目录重命名
vfs_rename_dir执行目录重命名a-->b操作,要考虑回收目录b的所有直接间接的子文件(linux中目录也是文件)在dcache中未使用dentry,也就是回收那些dentyr->d_count为0的dentry。其他的操作与文件重命名一样,都是调用底层ext3_rename函数。
什么要回收呢?我们以下图目录结构为例。linux为了性能,经过dentry lookup的dentry会缓存在dcache hash表中,如果这些dentry项没有内核路径引用,那么dentry->d_count=0。假设下图中所有文件的dentry已经缓存在dcache hash表中,若执行rename dira ---> dirA,那么所有文件的路径生变化,这些dcache中无内核引用的dentry项存在dcache hash表中已经没有意义,如果不回收反而占用内存。
以下图为例,说明回收dentry的过程,图中红色部分denty->d_count=0。
当然有人会说,rename dira -->dirA会失败,因为dirA非空,确实是这样,但是我们在rename之前,可以在dirA目录中执行rm -rf *删除所有东西啊,然后再执行rename操作,此时就符合我们要描述的需回收dentry场景了。
回收dentry的代码如下:
3.10.104/fs/dcache.c
1222 void shrink_dcache_parent(struct dentry * parent) 1223 { 1224 LIST_HEAD(dispose); 1225 int found; 1226 1227 while ((found = select_parent(parent, &dispose)) != 0) { 1228 shrink_dentry_list(&dispose); 1229 cond_resched(); 1230 } 1231 } |
在分析上面代码前,需要理解下面的内容。
父子文件通过dentry->d_subdirs组织,dentry->d_subdirs是一个链表头,该目录的子文件通过自己的dentry->d_child链接到dentry->d_subdirs链表中,见下图。
1227行select_parent是回收dentry的核心函数,在select_parent内部如果找到了需要回收的dentry且系统需要调度,将对应的dentry加入到回收链表中后,select_parent返回到1227行,一切重头开始,以dirA再次调用select_parent进行回收。
如果系统很闲,select_parent内部判断不需要调度,select_parent的流程如下:
1)dirA dentry->d_subdirs找到dirA的子目录B,B的detry->d_count不为0,不需要加入回收链表,接着判断B的denry->d_subdirs为空(函数list_empty(&dentry->d_subdirs)),找到B dentry->parent即dirA,以dirA dentry->d_subdirs再次搜索。
2)dirA dentry->d_subdirs找到dirA的子目录C,B的detry->d_count为0,将B的dentry加入回收链表。如果系统需要调度,那么select_parent返回到shrink_dcache_parent,以dirA为起始点重新搜索。如果系统不需要调度,判断C目录dentry->d_subdirs非空,1163行代码设置this_parent = dentry,1165行执行goto repeat查找C的子目录dentry,当C所有子目录查找完毕后,执行1174行的代码返回到上级目录dirA继续查找,1176行struct dentry *child = this_parent的this_parent为B,1178行this_parent = child->d_parent的this_parent为dirA。返回到dirA后,通过dirA dentry->d_subdirs就又可以找出D目录,然后继续上述过程直至查找完成。
9.3 ext3_rename
ext3_rename函数比较长,下面代码省略了部分不重要的代码,原理请参考第一节。
fs/ext3/namei.c
2335 static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry, 2336 struct inode * new_dir,struct dentry *new_dentry) 2337 { 2347 old_bh = new_bh = dir_bh = NULL; ……省略部分代码 2362 old_bh = ext3_find_entry(old_dir, &old_dentry->d_name, &old_de); 2369 old_inode = old_dentry->d_inode; 2374 new_inode = new_dentry->d_inode; 2375 new_bh = ext3_find_entry(new_dir, &new_dentry->d_name, &new_de); ……省略old_dentry是目录的场景 2399 if (!new_bh) { 2400 retval = ext3_add_entry (handle, new_dentry, old_inode); 2403 } else { 2408 new_de->inode = cpu_to_le32(old_inode->i_ino); 2411 new_de->file_type = old_de->file_type; 2412 new_dir->i_version++; 2414 ext3_mark_inode_dirty(handle, new_dir); 2419 brelse(new_bh); 2420 new_bh = NULL; 2421 } 2422 2427 old_inode->i_ctime = CURRENT_TIME_SEC; 2428 ext3_mark_inode_dirty(handle, old_inode); 2433 if (le32_to_cpu(old_de->inode) != old_inode->i_ino || 2434 old_de->name_len != old_dentry->d_name.len || 2435 strncmp(old_de->name, old_dentry->d_name.name, old_de->name_len) || 2436 (retval = ext3_delete_entry(handle, old_dir, 2437 old_de, old_bh)) == -ENOENT) { ……省略 2452 } 2458 2459 if (new_inode) { 2460 drop_nlink(new_inode); 2461 new_inode->i_ctime = CURRENT_TIME_SEC; 2462 } 2463 old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC; 2487 ext3_mark_inode_dirty(handle, old_dir); 2488 if (new_inode) { 2489 ext3_mark_inode_dirty(handle, new_inode); 2490 if (!new_inode->i_nlink) 2491 ext3_orphan_add(handle, new_inode); 2492 if (ext3_should_writeback_data(new_inode)) 2493 flush_file = 1; 2494 } 2495 retval = 0; 2496 2505 } |
第一种场景:假设rename a->b重命名文件,且b文件不存在。
2362行读取old_dir数据块,找到数据块中old_dentry->d_name的ext3_dir_entry_2,该数据块信息存放在old_bh中,ext3_dir_entry_2信息存放在old_de中。
2375行同上。但是因为newfile不存在,所以new_bh为null。
2400行ext3_add_entry读取new dir的数据块,找到空闲的目录项,根据new dentry信息,填写新一个新的目录项进去。
2436行删除old_dir数据块中old文件对应的ext3_dir_entry_2。
主要的工作结束。
第二种场景:假设rename a->b重命名文件,且b文件存在。
2362行读取old_dir数据块,找到数据块中old_dentry->d_name的ext3_dir_entry_2,该数据块信息存放在old_bh中,ext3_dir_entry_2信息存放在old_de中。
2375行同上。但与第一种场景相比,因为b文件存在,所以new_bh不为null。
2436行删除old_dir数据块中old文件对应的ext3_dir_entry_2。
2459行new文件的nlink减1,如果没有人使用该文件,后面该文件将会被删除。
2491行new文件的inode添加到orphan inode链表,有orphan inode机制回收文件数据块。
主要的工作结束。