[0]

Superblock 구조체는 리눅스 커널에서 가상 파일 시스템(VFS)의 핵심을 이루는 구조 체중 하나이다. VFS는 리눅스 커널에서 객체 지향 프로그래밍을 C로 구현한 가장 대표적인 예시인데, 때문에 Superblock 구조체를 Superblock 오브젝트라고도 많이 지칭한다. C에서는 구조체에 메소드를 삽입할 수 없기 때문에, Superblock 구조체에는 super_operations라는 함수 포인터를 담은 구조체를 멤버로 소유하고 있다. Superblock은 기본적으로 하나의 파일 시스템을 나타낸다. 즉 마운팅된 파일 시스템 하나당 Superblock 하나가 할당되게 되며, Superblock은 마운팅 된 파일 시스템에 대한 메타데이터를 지니고 있다. 이 포스팅에선 Superblock 구조체의 핵심적인 멤버를 알아본다.

 

[1]

struct super_block {
	struct list_head	s_list;		/* Keep this first */
	dev_t			s_dev;		/* search index; _not_ kdev_t */
	unsigned char		s_blocksize_bits;
	unsigned long		s_blocksize;
	loff_t			s_maxbytes;	/* Max file size */
	struct file_system_type	*s_type;
	const struct super_operations	*s_op;
	const struct dquot_operations	*dq_op;
	const struct quotactl_ops	*s_qcop;
	const struct export_operations *s_export_op;
	unsigned long		s_flags;
	unsigned long		s_iflags;	/* internal SB_I_* flags */
	unsigned long		s_magic;
	struct dentry		*s_root;
	struct rw_semaphore	s_umount;
	int			s_count;
	atomic_t		s_active;
#ifdef CONFIG_SECURITY
	void                    *s_security;
#endif
	const struct xattr_handler **s_xattr;
#ifdef CONFIG_FS_ENCRYPTION
	const struct fscrypt_operations	*s_cop;
	struct key		*s_master_keys; /* master crypto keys in use */
#endif
#ifdef CONFIG_FS_VERITY
	const struct fsverity_operations *s_vop;
#endif
	struct hlist_bl_head	s_roots;	/* alternate root dentries for NFS */
	struct list_head	s_mounts;	/* list of mounts; _not_ for fs use */
	struct block_device	*s_bdev;
	struct backing_dev_info *s_bdi;
	struct mtd_info		*s_mtd;
	struct hlist_node	s_instances;
	unsigned int		s_quota_types;	/* Bitmask of supported quota types */
	struct quota_info	s_dquot;	/* Diskquota specific options */

	struct sb_writers	s_writers;

	/*
	 * Keep s_fs_info, s_time_gran, s_fsnotify_mask, and
	 * s_fsnotify_marks together for cache efficiency. They are frequently
	 * accessed and rarely modified.
	 */
	void			*s_fs_info;	/* Filesystem private info */

	/* Granularity of c/m/atime in ns (cannot be worse than a second) */
	u32			s_time_gran;
	/* Time limits for c/m/atime in seconds */
	time64_t		   s_time_min;
	time64_t		   s_time_max;
#ifdef CONFIG_FSNOTIFY
	__u32			s_fsnotify_mask;
	struct fsnotify_mark_connector __rcu	*s_fsnotify_marks;
#endif

	char			s_id[32];	/* Informational name */
	uuid_t			s_uuid;		/* UUID */

	unsigned int		s_max_links;
	fmode_t			s_mode;

	/*
	 * The next field is for VFS *only*. No filesystems have any business
	 * even looking at it. You had been warned.
	 */
	struct mutex s_vfs_rename_mutex;	/* Kludge */

	/*
	 * Filesystem subtype.  If non-empty the filesystem type field
	 * in /proc/mounts will be "type.subtype"
	 */
	const char *s_subtype;

	const struct dentry_operations *s_d_op; /* default d_op for dentries */

	/*
	 * Saved pool identifier for cleancache (-1 means none)
	 */
	int cleancache_poolid;

	struct shrinker s_shrink;	/* per-sb shrinker handle */

	/* Number of inodes with nlink == 0 but still referenced */
	atomic_long_t s_remove_count;

	/* Pending fsnotify inode refs */
	atomic_long_t s_fsnotify_inode_refs;

	/* Being remounted read-only */
	int s_readonly_remount;

	/* AIO completions deferred from interrupt context */
	struct workqueue_struct *s_dio_done_wq;
	struct hlist_head s_pins;

	/*
	 * Owning user namespace and default context in which to
	 * interpret filesystem uids, gids, quotas, device nodes,
	 * xattrs and security labels.
	 */
	struct user_namespace *s_user_ns;

	/*
	 * The list_lru structure is essentially just a pointer to a table
	 * of per-node lru lists, each of which has its own spinlock.
	 * There is no need to put them into separate cachelines.
	 */
	struct list_lru		s_dentry_lru;
	struct list_lru		s_inode_lru;
	struct rcu_head		rcu;
	struct work_struct	destroy_work;

	struct mutex		s_sync_lock;	/* sync serialisation lock */

	/*
	 * Indicates how deep in a filesystem stack this SB is
	 */
	int s_stack_depth;

	/* s_inode_list_lock protects s_inodes */
	spinlock_t		s_inode_list_lock ____cacheline_aligned_in_smp;
	struct list_head	s_inodes;	/* all inodes */

	spinlock_t		s_inode_wblist_lock;
	struct list_head	s_inodes_wb;	/* writeback inodes */
} __randomize_layout;

대부분의 멤버들은 이름과 주석으로 사용을 충분히 유추할 수 있다. 여기서는 몇가지 멤버들만 살펴보자. 가장 중요한 s_op는 [2]에서 다룬다.

 

1) s_dev

파일 시스템 identifier

 

2) s_blocksize, s_blocksize_bits

s_blocksize는 이 파일 시스템에서 사용하는 블록의 크기를 바이트단위로 나타낸다. s_blocksize_bits는 블록 사이즈가 몇 비트로 표기될 수 있는지 나타낸다. 즉 s_blocksize = 1024라면 s_blocksize_bits는 10이다. 물론 어느 한쪽 멤버만 있으면 간단한 계산을 통해 다른 한쪽을 알 수 있다.(1<<s_blocksize_bits = s_blocksize) 하지만 커널에서는 자주 사용하는 값에 대해서 메모리를 약간 손해 보더라도 퍼포먼스를 향상하는 선택을 한다. 이 변순들은 커널에서 time과 space의 trade-off에 대한 좋은 예시이다.

 

3) s_flag

슈퍼블록에 대한 flag. 즉 마운팅된 파일 시스템에 대한 flag이다.

 

4) s_root

마운팅된 root directory

 

[2]

super_block에 멤버로 포함되어 있는 super_operations 구조체의 함수 포인터들은 super_block 오브젝트의 메소드들이라고 보면 된다. super_operations에서는 Java 인터페이스처럼 타입만 정의해 놓고, 실질적인 구현은 실제 파일 시스템 구현 단계에서 이루어지게 된다.

struct super_operations {
   	struct inode *(*alloc_inode)(struct super_block *sb);
	void (*destroy_inode)(struct inode *);
	void (*free_inode)(struct inode *);

   	void (*dirty_inode) (struct inode *, int flags);
	int (*write_inode) (struct inode *, struct writeback_control *wbc);
	int (*drop_inode) (struct inode *);
	void (*evict_inode) (struct inode *);
	void (*put_super) (struct super_block *);
	int (*sync_fs)(struct super_block *sb, int wait);
	int (*freeze_super) (struct super_block *);
	int (*freeze_fs) (struct super_block *);
	int (*thaw_super) (struct super_block *);
	int (*unfreeze_fs) (struct super_block *);
	int (*statfs) (struct dentry *, struct kstatfs *);
	int (*remount_fs) (struct super_block *, int *, char *);
	void (*umount_begin) (struct super_block *);

	int (*show_options)(struct seq_file *, struct dentry *);
	int (*show_devname)(struct seq_file *, struct dentry *);
	int (*show_path)(struct seq_file *, struct dentry *);
	int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
	ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
	ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
	struct dquot **(*get_dquots)(struct inode *);
#endif
	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
	long (*nr_cached_objects)(struct super_block *,
				  struct shrink_control *);
	long (*free_cached_objects)(struct super_block *,
				    struct shrink_control *);
};

마찬가지로 대부분의 메소드들은 이름을 통해 기능을 유추할 수 있다. 몇 가지 메소드들을 살펴보자.

 

1) inode 관련 메소드들

alloc_inode(), destroy_inode(), write_inode(), delete_inode() 등이 해당한다. VFS에서 일반적으로 추상화 계층은 file-> dentry -> inode -> superblock이다.(dentry와 superblock 간의 인터페이스도 존재하긴 한다.) 따라서 superblock에 inode를 관리하는 메소드들이 포함되어 있다.

 

2) dirty_inode()

물론 이름대로 inode 관련 메소드이지만 약간 헷갈릴 수 있기 때문에 따로 기술한다. 이 메소드는 inode에 수정이 발생하였을 때 VFS에 의해 불리게 되며, 저널링 파일 시스템에서 저널 업데이트 목적으로 이 함수를 이용한다. 이 함수에 대해서는 [3]에서 코드 레벨로 따라가 보도록 하겠다.

 

3) put_super()

VFS가 파일 시스템을 언마운트하는 과정에서 호출된다. 인자로 주어진 superblock을 해제한다.

 

4) remount_fs()

새로운 옵션으로 리마운트될 때 호출된다.

 

5) sync_fs()

변경된 파일 시스템 메타데이터(in-memory)와 디스크 상의 메타데이터를 동기화한다.

 

[3]

메소드를 간략하게 분석만 하고 넘어가기는 아쉬우니, 코드 레벨에서 함수를 한 번 파헤쳐보자. 단, 위의 메소드는 인터페이스에 불과할 뿐이므로, 실제 구현은 파일 시스템마다 상이하다. 여기에서는 리눅스에서 가장 널리 사용되는 ext4의 구현을 따라가 보도록 하겠다.

@ fs/ext4/super.c
static const struct super_operations ext4_sops = {
...
    .dirty_inode	= ext4_dirty_inode,
...
}

 

우선 ext4에서는 dirt_inode()를 위와 같이 ext4_dirty_inode로 매핑해놓았다. 

@ fs/ext4/inode.c

/*
 * ext4_dirty_inode() is called from __mark_inode_dirty()
 *
 * We're really interested in the case where a file is being extended.
 * i_size has been changed by generic_commit_write() and we thus need
 * to include the updated inode in the current transaction.
 *
 * Also, dquot_alloc_block() will always dirty the inode when blocks
 * are allocated to the file.
 *
 * If the inode is marked synchronous, we don't honour that here - doing
 * so would cause a commit on atime updates, which we don't bother doing.
 * We handle synchronous inodes at the highest possible level.
 *
 * If only the I_DIRTY_TIME flag is set, we can skip everything.  If
 * I_DIRTY_TIME and I_DIRTY_SYNC is set, the only inode fields we need
 * to copy into the on-disk inode structure are the timestamp files.
 */
void ext4_dirty_inode(struct inode *inode, int flags)
{
	handle_t *handle;

	if (flags == I_DIRTY_TIME)
		return;
	handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
	if (IS_ERR(handle))
		goto out;

	ext4_mark_inode_dirty(handle, inode);

	ext4_journal_stop(handle);
out:
	return;
}

ordered 저널링을 기본 옵션으로 사용하는 ext4의 특성을 여기에서도 확인할 수 있다. 우선 ext4_journal_start()를 통해 저널링 시작을 알리고 ext4_mark_inode_dirty()로 실제 inode를 dirty로 마킹한다. 이것이 종료되면 ext4_journal_stop()을 통해 저널링이 완료된다.

 

 

 

[참고]

https://unix.stackexchange.com/questions/4402/what-is-a-superblock-inode-dentry-and-a-file

 

What is a Superblock, Inode, Dentry and a File?

From the article Anatomy of the Linux file system by M. Tim Jones, I read that Linux views all the file systems from the perspective of a common set of objects and these objects are superblock, ino...

unix.stackexchange.com

http://egloos.zum.com/rousalome/v/9995793

 

[리눅스커널][가상파일시스템] 수퍼블록: 슈퍼블록 함수 연산과 시스템 콜 연동 동작 알아보기

이번 시간에는 슈퍼블록 함수 오퍼레이션이 시스템 콜과 어떻게 연동해서 실행하는지 살펴봅니다. alloc_inode 파일시스템별로 inode 객체를 생성할 때 호출합니다. 파일시스템별 기능별로 파일을 �

egloos.zum.com

Robert Love, Linux Kernel Development 3rd Edition.

+ Recent posts