아직 커널에 대해서 깊이 알고 있지 못한 상태에서 바로 커널 레벨의 파일 시스템을 만드는 것은 여러모로 무리가 있다. 따라서, 이번에는 사용자 어플리케이션 레벨에서 파일 시스템을 만들 수 있는 방법을 먼저 알아보도록 하겠다.
FUSE는 Filesystem in USEr space의 약자로서 http://fuse.sourceforge.net/ 에서 프로젝트가 진행중이다. FUSE는 리눅스 커널 2.6.15부터는 기본적으로 탑재되어 있다. 물론, FUSE가 부팅시부터 사용가능하도록 되어 있지는 않을 수 있으며, 커널 설정을 확인할 필요가 있다. 만약, 그 이전 버전에서 사용하고자 한다면 코드를 받아서 직접 컴파일해서 사용하면 된다.
FUSE의 장점은 어플리케이션 레벨에서 작업이 이루어지기 때문에 보안이나 안정성 등의 면에 있어서 좀더 나을 수 있다는 것이며, 리눅스 이외의 운영체제에서도 FUSE가 사용 가능하기 때문에 한 번 작성된 사용자 파일 시스템을 여러 운영체제에서 큰 문제 없이 돌릴 수 있다는 장점이 있다.
현재, FUSE는 리눅스를 비롯해, Mac OS X, 윈도우즈, 솔라리스 등등에서 사용 가능하다. 자세한 것은 프로젝트 사이트에서 확인 가능하다.
하지만, FUSE는 단점도 있는데, 가장 큰 단점으로는 계층이 추가됨으로 인해서 속도저하가 발생할 수 있다는 점이다.
FUSE를 사용하기 위해서는 세 가지 요소가 필요하다. 첫째, 커널 모듈(fuse.ko)가 적재되어 있어야 한다. 현재 커널에 FUSE가 적재되어 있는지 확인하기 위해서는 /proc/filesystems를 살펴보면 된다. 둘째, 유저스페이스 라이브러들이 필요하다. 여기에는 libfuse.so, libfuse.a가 해당된다. 셋째, 사용자가 작성한 파일시스템 코드가 필요하다.
register_filesystem을 통해서 전달되는 구조체의 내용 중 가장 중요한 부분은 바로 super block에 대한 정보를 반환하는 get_sb 함수이다. 이 함수를 통해서 파일시스템의 가장 중요하며 시작점에 해당하는 역할을 하는 슈퍼블럭에 대한 구조체인 struct super_block을 얻을 수 있다.
슈퍼블럭은 파일시스템마다 불리는 이름이 다르며, MS-DOS의 경우에는 FAT(File Allocation Table)이라고 부른다. 여기에는 파일을 찾기 위한 기본적인 정보들이 들어 있다. 물론, 유닉스 계열의 파일 시스템에서는 이에 대한 내용이 i-node 테이블에서 주로 관리된다. 일반적으로 슈퍼블럭은 해당 파일시스템에 대한 기본적인 정보들을 가지고 있다고 보면 된다.
다음 그림은 ext2 파일 시스템의 전체 레이아웃으로서 슈퍼블럭이 차지하는 위치를 알 수 있다.
908 struct super_block { 909 struct list_head s_list; /* Keep this first */ 910 dev_t s_dev; /* search index; _not_ kdev_t */ 911 unsigned long s_blocksize; 912 unsigned char s_blocksize_bits; 913 unsigned char s_dirt; 914 unsigned long long s_maxbytes; /* Max file size */ 915 struct file_system_type *s_type; 916 const struct super_operations *s_op; 917 struct dquot_operations *dq_op; 918 struct quotactl_ops *s_qcop; 919 struct export_operations *s_export_op; 920 unsigned long s_flags; 921 unsigned long s_magic; 922 struct dentry *s_root; 923 struct rw_semaphore s_umount; 924 struct mutex s_lock; 925 int s_count; 926 int s_syncing; 927 int s_need_sync_fs; 928 atomic_t s_active; 929 #ifdef CONFIG_SECURITY 930 void *s_security; 931 #endif 932 struct xattr_handler **s_xattr; 933 934 struct list_head s_inodes; /* all inodes */ 935 struct list_head s_dirty; /* dirty inodes */ 936 struct list_head s_io; /* parked for writeback */ 937 struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */ 938 struct list_head s_files; 939 940 struct block_device *s_bdev; 941 struct mtd_info *s_mtd; 942 struct list_head s_instances; 943 struct quota_info s_dquot; /* Diskquota specific options */ 944 945 int s_frozen; 946 wait_queue_head_t s_wait_unfrozen; 947 948 char s_id[32]; /* Informational name */ 949 950 void *s_fs_info; /* Filesystem private info */ 951 952 /* 953 * The next field is for VFS *only*. No filesystems have any business 954 * even looking at it. You had been warned. 955 */ 956 struct mutex s_vfs_rename_mutex; /* Kludge */ 957 958 /* Granularity of c/m/atime in ns. 959 Cannot be worse than a second */ 960 u32 s_time_gran; 961 962 /* 963 * Filesystem subtype. If non-empty the filesystem type field 964 * in /proc/mounts will be "type.subtype" 965 */ 966 char *s_subtype; 967 };
파일 시스템을 등록할 때 전달되는 struct file_system_type의 get_sb() 함수를 통해 반환되도록 되어 있는 struct super_block 구조체는 슈퍼 블럭에서 관리하는 정보들을 모두 얻을 수 있도록 되어 있다. 물론, 파일 시스템에 다라서는 일부 내용이 무의미할 수 도 있다. 어쨌든, 파티션상에 존재하는 해당 파일 시스템에 대해서 데이타를 다룰 때 사용하게 되는 여러가지 정보들이 여기에 존재하게 된다.
이 중에서 슈퍼블럭을 다루기 위해서 필요한 연산들이 916번째 줄에 있는 struct super_operations *s_op 필드이다. struct super_operations는 다음과 같이 정의되어 있다.
super_operation 구조체에서 하는 일은 super_block을 다루는 코드와 함께 super_block을 기반으로 실제 파일에 해당하는 정보를 찾을 수 있는 inode를 다루는 연산들로 구성되어 있다. 새로운 파일이 생성될 경우에는 alloc_inode를 사용하게 되며, 파일 정보와 관련해서 read_inode(), write_inode() 함수들도 사용하게 된다. mount 관련된 구조체인 vfsmount는 이 전번 글에서 간단히 살펴보았으므로 여기에서는 그냥 넘어간다.
inode는 실제로 파일을 만들기 위한 기본이 되는 정보로서 이를 통해 어떤 파일이 어디에 있으며, 어떠한 속성으로 만들어 졌는지, 크기는 얼마인지, 소유자는 누구인지, 접근 권한은 어떻게 되는지 등의 정보를 볼 수 있다. 즉, 일반적으로 ls -l 명령을 사용했을 때 나오는 결과에 해당하는 정보를 가지고 있다고 보면 된다.
i-node를 통해 파일의 실제 데이타를 제외한 모든 정보를 얻을 수 있다. 일부 파일시스템(FAT, FAT16, FAT32 등등)에 대해서는 struct inode 구조체의 일부 필드가 의미없을 수 도 있다. inode 구조체에서는 실제 inode를 다루는 방법과 파일을 다루는 방법을 위해서 두 가지 구조체를 제공하고 있다.
먼저, inode를 다루는 함수들을 위해서는 struct inode_operations *i_op 필드가 제공된다. 이 구조체는 다음과 같은 형태를 가지고 있다.
/** * Notes: * Implementing a small filesystem having one file * * -> What happens when we mount a file system? * -> What do we need to provide to the kernel so that we are mountable? * -> What inode, dentry and file operations do we have to support? */
static struct super_block * rkfs_get_sb(struct file_system_type *fs_type, int flags, const char *devname, void *data) { /* rkfs_fill_super this will be called to fill the superblock */ return get_sb_single( fs_type, flags, data, &rkfs_fill_super); }
/* * No need to use i_size_read() here, the i_size * cannot change under us because we hold i_sem. */ if (pos > inode->i_size) { i_size_write(inode, pos); mark_inode_dirty(inode); }