sys_close() を掘ってみる

掘っていく内にナチュラル判明。
スーパブロックなオブジェクトの s_blocks_count 云々って言ってましたが、これってトータルのブロックじゃねぇか。sys_close() 掘っていく内にスーパーブロックなオブジェクトの s_free_blocks_count は更新してるんだけど、、というあたりまで分かった時点で勘違いを疑えよ、と。

とりあえず掘っていった経緯を

まず、sys_close() から。怪しげなのは filp_close() で以下なあたり (一部のみ)

asmlinkage long sys_close(unsigned int fd)
{
	struct file * filp;
	struct files_struct *files = current->files;
	struct fdtable *fdt;
	int retval;

	spin_lock(&files->file_lock);
	fdt = files_fdtable(files);
	if (fd >= fdt->max_fds)
		goto out_unlock;
	filp = fdt->fd[fd];
	if (!filp)
		goto out_unlock;
	rcu_assign_pointer(fdt->fd[fd], NULL);
	FD_CLR(fd, fdt->close_on_exec);
	__put_unused_fd(files, fd);
	spin_unlock(&files->file_lock);
	retval = filp_close(filp, files);

filp_close() が以下。

int filp_close(struct file *filp, fl_owner_t id)
{
	int retval = 0;

	if (!file_count(filp)) {
		printk(KERN_ERR "VFS: Close: file count is 0\n");
		return 0;
	}

	if (filp->f_op && filp->f_op->flush)
		retval = filp->f_op->flush(filp, id);

	dnotify_flush(filp, id);
	locks_remove_posix(filp, id);
	fput(filp);
	return retval;
}

これも fput() がアヤシげ、と見て掘る。ここは fs/file_table.c

void fastcall fput(struct file *file)
{
	if (atomic_dec_and_test(&file->f_count))
		__fput(file);
}

__fput() は直下で定義されている。色々ヤッてますが vfsmount オブジェクトを渡している一番下の mntput() 呼び出しがアヤシいと見て掘る。一番上と

void fastcall __fput(struct file *file)
{
	struct dentry *dentry = file->f_path.dentry;
	struct vfsmount *mnt = file->f_path.mnt;
	struct inode *inode = dentry->d_inode;

一番下を引用。

	file->f_path.dentry = NULL;
	file->f_path.mnt = NULL;
	file_free(file);
	dput(dentry);
	mntput(mnt);
}

mntput() は include/linux/mount.h にて inline な定義。

static inline void mntput(struct vfsmount *mnt)
{
	if (mnt) {
		mnt->mnt_expiry_mark = 0;
		mntput_no_expire(mnt);
	}
}

mntput_no_expire() は fs/namespace.c にて定義されている。

void mntput_no_expire(struct vfsmount *mnt)
{
repeat:
	if (atomic_dec_and_lock(&mnt->mnt_count, &vfsmount_lock)) {
		if (likely(!mnt->mnt_pinned)) {
			spin_unlock(&vfsmount_lock);
			__mntput(mnt);
			return;
		}
		atomic_add(mnt->mnt_pinned + 1, &mnt->mnt_count);
		mnt->mnt_pinned = 0;
		spin_unlock(&vfsmount_lock);
		acct_auto_close_mnt(mnt);
		security_sb_umount_close(mnt);
		goto repeat;
	}
}

これもいっちゃん最後の __mntput() か。

static inline void __mntput(struct vfsmount *mnt)
{
	struct super_block *sb = mnt->mnt_sb;
	dput(mnt->mnt_root);
	free_vfsmnt(mnt);
	deactivate_super(sb);
}

super_block なオブジェクトを渡している deactivate_super() は fs/super.c にて定義されてて以下。

void deactivate_super(struct super_block *s)
{
	struct file_system_type *fs = s->s_type;
	if (atomic_dec_and_lock(&s->s_active, &sb_lock)) {
		s->s_count -= S_BIAS-1;
		spin_unlock(&sb_lock);
		DQUOT_OFF(s);
		down_write(&s->s_umount);
		fs->kill_sb(s);
		put_filesystem(fs);
		put_super(s);
	}
}

kill_sb はファイルシステム特有のナニと見て ext3 の kill_sb 属性を grep してみたら super.c にて以下な記述。

static struct file_system_type ext3_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext3",
	.get_sb		= ext3_get_sb,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};

kill_block_super() は汎用的な手続きらしく fs/super.c にて定義。

void kill_block_super(struct super_block *sb)
{
	struct block_device *bdev = sb->s_bdev;

	bdev_uevent(bdev, KOBJ_UMOUNT);
	generic_shutdown_super(sb);
	sync_blockdev(bdev);
	close_bdev_excl(bdev);
}

で、sb 渡してる generic_shutdown_super() がナニ。fs/super.c にて以下 (一部のみ)。

void generic_shutdown_super(struct super_block *sb)
{
	struct super_operations *sop = sb->s_op;

	if (sb->s_root) {
		shrink_dcache_for_umount(sb);
		fsync_super(sb);
		lock_super(sb);
		sb->s_flags &= ~MS_ACTIVE;
		/* bad name - it should be evict_inodes() */
		invalidate_inodes(sb);
		lock_kernel();

		if (sop->write_super && sb->s_dirt)
			sop->write_super(sb);
		if (sop->put_super)
			sop->put_super(sb);

色々見てみたんですが、sop->put_super() が ext3_put_super と見て確認 (一部のみ)。

static void ext3_put_super (struct super_block * sb)
{
	struct ext3_sb_info *sbi = EXT3_SB(sb);
	struct ext3_super_block *es = sbi->s_es;
	int i;

	ext3_xattr_put_super(sb);
	journal_destroy(sbi->s_journal);
	if (!(sb->s_flags & MS_RDONLY)) {
		EXT3_CLEAR_INCOMPAT_FEATURE(sb, EXT3_FEATURE_INCOMPAT_RECOVER);
		es->s_state = cpu_to_le16(sbi->s_mount_state);
		BUFFER_TRACE(sbi->s_sbh, "marking dirty");
		mark_buffer_dirty(sbi->s_sbh);
		ext3_commit_super(sb, es, 1);
	}

es 渡してるのがアヤしいという事で ext3_commit_super() 見てみると以下なカンジ。

static void ext3_commit_super (struct super_block * sb,
                               struct ext3_super_block * es,
                               int sync)
{       
        struct buffer_head *sbh = EXT3_SB(sb)->s_sbh;

        if (!sbh)
                return;
        es->s_wtime = cpu_to_le32(get_seconds());
        es->s_free_blocks_count = cpu_to_le32(ext3_count_free_blocks(sb));
        es->s_free_inodes_count = cpu_to_le32(ext3_count_free_inodes(sb));
        BUFFER_TRACE(sbh, "marking dirty");
        mark_buffer_dirty(sbh);
        if (sync)
                sync_dirty_buffer(sbh);
}

ここで s_free_blocks_count 属性とかをナニ。ここまで掘れてたんですが、まだナチュラルに気づかず。おかしいな、と言いつつ 3rd Edition を色々見つつ意識を失なってたりしてた訳ッス。

これは

もっかい statfs() のナニを見てみた方が良いな。