inode的i_nlink(linux4.0.4)
一、背景
1)通过命令ln a b创建硬链接b->a后(a是一个普通文件),stat a命令看到Links值是多少?
gsf@ubuntu:~$ stat a File: 'a' Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 801h/2049d Inode: 1442959 Links: 2 |
2)一个空目录dirA,stat命令看到的Links值是多少?
gsf@ubuntu:~$ stat dirA File: 'dirA' Size: 4096 Blocks: 8 IO Block: 4096 directory Device: 801h/2049d Inode: 1442960 Links: 2 |
在dirA中创建一个文件后,dirA的Links值是多少?
gsf@ubuntu:~$ stat dirA File: 'dirA' Size: 4096 Blocks: 8 IO Block: 4096 directory Device: 801h/2049d Inode: 1442960 Links: 2 |
在dirA中创建一个目录后,dirA的Links值是多少?
gsf@ubuntu:~$ stat dirA File: 'dirA' Size: 4096 Blocks: 8 IO Block: 4096 directory Device: 801h/2049d Inode: 1442960 Links: 3 |
以上问题涉及到文件的“链接计数”概念,即inode的i_nlink字段。
创建一个普通文件a,它的链接计数”是1,创建该文件的硬链接b,a与b的数据块在磁盘上是相同的(或者说inode是相同的),因两个文件引用同一个数据块,所以创建硬链接时将“链接计数”加1。
创建一个目录dirA,dirA本身一个硬链接计数,还有一个表示当前目录的“.”代表dirA,所以目录dirA的硬链接计数为2。在dirA中创建一个子目录,子目录的”..”代表dirA,所以dirA的硬链接计数变为3。
二、理论知识
linux中文件是以inode来标识的,不同的inode代表不同的文件。磁盘上存储着inode的信息,以ext3为例,磁盘上的inode结构为struct ext3_inode。为避免每次直接读写磁盘inode信息(性能低),内核会把磁盘inode读入内存,并填充一些额外信息构成vfs的inode,vfs inode定义为struct inode。内核写内存inode后将其标记为dirty状态,在适当的时候写入磁盘。inode有一个“链接计数“概念,这就是本文要讨论的主题。
struct ext3_inode-->i_links_count、struct inode-->i_nlink字段分别是磁盘inode、内存inode中的“链接计数”字段。unlink删除文件时先将磁盘上对应的ext3_dir_entry_2“无效”掉(这个“无效”操作并不是指直接把磁盘上的ext3_dir_entry_2删除,而是把前一个ext3_dir_entry_2的rec_len字段适当地增加长度就可以了,这与ext3的磁盘layout有关,不属于本文要描述的问题),然后将该链接计数”减1,如果此时“链接计数”为0,则说明该文件真的没有人引用了,可以释放磁盘inode了。当文件的ext3_dir_entry_2被“无效”掉后,文件系统已经无法通过该ext3_dir_entry_2找到这个文件了,但是依然可以通过其他ext3_dir_entry_2找到该文件。具体来说硬链接b-->a(硬链接文件的inode是一样的),删除a后,文件系统把a对应的ext3_dir_entry_2“无效”掉了,也就是不存在a这个文件了,但是依然可以通过b对应的ext3_dir_entry_2找到文件。
三、代码分析
1)文件的删除
SYSCALL_DEFINE1(unlink, const char __user *, pathname)--> do_unlinkat-->vfs_unlink:
int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode) { struct inode *target = dentry->d_inode;
mutex_lock(&target->i_mutex); if (is_local_mountpoint(dentry)) error = -EBUSY; else { error = security_inode_unlink(dir, dentry); if (!error) { error = try_break_deleg(target, delegated_inode); if (error) goto out; error = dir->i_op->unlink(dir, dentry); } } |
其中的dir->i_op->unlink即ext3_unlink,关键代码如下:
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;
bh = ext3_find_entry(dir, &dentry->d_name, &de); inode = dentry->d_inode; retval = ext3_delete_entry(handle, dir, de, bh); ext3_mark_inode_dirty(handle, dir); drop_nlink(inode); if (!inode->i_nlink) ext3_orphan_add(handle, inode);
|
ext3_find_entry从磁盘上找到待删除文件的ext3_dir_entry_2结构,然后通过ext3_delete_entry将找到的ext3_dir_entry_2结构从磁盘layout上“无效”掉,然后将待删除文件的目录的inode标记为dirty状态,在适当的时候会同步到磁盘上,接着通过drop_nlink将待删除文件的“链接计数”减1。ext3_orphan_add也是删除文件中一个重要的操作,避免系统突然断电导致inode对应的数据块无法回收的问题,这在其他文章中描述。
2)目录的删除
在linux中,任何东西都是文件,目录也是文件,但删除目录与删除文件却是有一点区别的,正如第一部分中第2个问题描述的,任何目录都有“.”及“..”影响着“链接计数”,代码如下:
SYSCALL_DEFINE1(rmdir, const char __user *, pathname)--> do_rmdir--> vfs_rmdir:
int vfs_rmdir(struct inode *dir, struct dentry *dentry) { error = dir->i_op->rmdir(dir, dentry); |
dir->i_op->rmdir即是ext3_rmdir,关键代码如下:
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;
bh = ext3_find_entry(dir, &dentry->d_name, &de);
inode = dentry->d_inode;
retval = ext3_delete_entry(handle, dir, de, bh); if (inode->i_nlink != 2) ext3_warning (inode->i_sb, "ext3_rmdir", "empty directory has nlink!=2 (%d)", inode->i_nlink);
clear_nlink(inode);
ext3_orphan_add(handle, inode); ext3_mark_inode_dirty(handle, inode); drop_nlink(dir); |
大致操作与删除文件一样。ext3_find_entry从磁盘上找到待删除目录的ext3_dir_entry_2结构,然后通过ext3_delete_entry将找到的ext3_dir_entry_2结构从磁盘layout上“无效”掉,接着通过clear_nlink将待删除目录的“链接计数”设为0(因为目录不可能是硬链接,也不可能删除一个非空目录,所以直接设为0)。ext3_orphan_add作用同删除文件中描述的一样。最后的drop_nlink是将父目录的硬链接计数减1,因为被删掉的目录的”..”是父目录硬链接。
四、i_nlink的演变
在vfs inode的定义中,有i_nlink及__i_nlink字段,两者其实是一个东西,这样做的目录是:
Prevent direct modification of i_nlink by making it const and adding a non-const __i_nlink alias.(引用自 http://lkml.iu.edu/hypermail/linux/kernel/1110.1/01493.html)
struct inode { unsigned long i_ino; /* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */ union { const unsigned int i_nlink; unsigned int __i_nlink; }; }
|