rename代码阅读(linux 3.10.104)

时间:2024-03-28 08:54:04

前言

本文通过分析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的原理,让读者有个概念上的认识。本节后面的章节是源码级的细节描述。

用户视图,文件目录结构如下:

rename代码阅读(linux 3.10.104)

linux系统dentry视图:

rename代码阅读(linux 3.10.104)

ext3磁盘视图如下:

rename代码阅读(linux 3.10.104)

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步执行完成后,磁盘视图如下:

rename代码阅读(linux 3.10.104)

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。代码调用流程如下:

rename代码阅读(linux 3.10.104)

从上面调用关系可以看出,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分量信息。最后一个分量信息的解析见第六节。

rename代码阅读(linux 3.10.104)

                                                             图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中的红色代码路径解析到了需要的分量信息:

rename代码阅读(linux 3.10.104)

                                                                           图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代码阅读(linux 3.10.104)

当然有人会说,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链表中,见下图。

rename代码阅读(linux 3.10.104)

 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机制回收文件数据块。

主要的工作结束。