한스 라이저 아저씨의 일로, 라이저 파일시스템의 발전에 지장이 있지 않을까 싶었었는데,

라이저 파일 시스템을 능가할 새로운 파일 시스템의 개발이 착착 진행되고 있다. 바로 해머(HAMMER) 파일 시스템!!!


아직은 개발중이라 자세한 내막은 모르겠지만, 나름 열심히 진행하고 있는것 같다. 2007년 초에 시작해서 진행되고 있는데, 최근 커널 뉴스 그룹에 다음과 같은 글을 올렸다.

HAMMER update - 15 nov 2007
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]
From: Matthew Dillon 
To: 
Subject: HAMMER update - 15 nov 2007
Date: Thursday, November 15, 2007 - 8:54 pm

HAMMER work is still progressing well, I hope to have most of it
    working in a degenerate single-cluster (64MB filesystem) case by the
    end of next week.  (cluster == 64MB block of the disk, not cluster as
    in clustering).

    Gluing the per-cluster B-Tree's together for the multi-cluster case
    is turning out to be more of a headache and will probably take at
    least 2 weeks to get working.  Some fairly sophisticated heuristics
    will be needed to avoid unnecessary copying between clusters.

    I may decide to move the 2.0 release to mid-January to give myself some
    more time.  This is similar to what we did for 1.8.  Also, I think a
    January release is better then a Christmas release because people get
    busy with christmas-like things.  I want the filesystem to be at least
    beta quality as of the release and I don't think its possible to get it
    there by mid-December.

						-Matt
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]

크리스마스보다는 1월 중순쯤에 2.0을 내놓을 것 같다고 하니 빨리 결과를 볼 수 있는 날이 오길 기대해본다.
참고로, 8월에 DragonFlyBSD 커널 메일링 리스트에 올려 놓은 HAMMER 디자인 문서를 같이 올려놔 본다.

HAMMER filesystem update - design document

From:	 Matthew Dillon 
Date:	 Wed, 10 Oct 2007 12:33:45 -0700 (PDT)
    Ok, here's the final design document that I am now implementing.
    Again, I expect most or all of these features to be ready and the
    filesystem to be beta-quality by the December release.


			       Hammer Filesystem

(I) General Storage Abstraction

    HAMMER uses a basic 16K filesystem buffer for all I/O.  Buffers are
    collected into clusters, cluster are collected into volumes, and a
    single HAMMER filesystem may span multiple volumes.

    HAMMER maintains a small hinted radix tree for block management in
    each layer.  A small radix tree in the volume header manages cluster
    allocations within a volume, one in the cluster header manages buffer
    allocations within a cluster, and most buffers (pure data buffers
    excepted) will embed a small tree to manage item allocations within
    the buffer.

    Volumes are typically specified as disk partitions, with one volume
    designated as the root volume containing the root cluster.  The root
    cluster does not need to be contained in volume 0 nor does it have to
    be located at any particular offset.

    Data can be migrated on a cluster-by-cluster or volume-by-volume basis
    and any given volume may be expanded or contracted while the filesystem
    is live.   Whole volumes can be added and (with appropriate data
    migration) removed.

    HAMMER's storage management limits it to 32768 volumes, 32768 clusters
    per volume, and 32768 16K filesystem buffers per cluster.   A volume
    is thus limited to 16TB and a HAMMER filesystem as a whole is limited
    to 524288TB.  HAMMER's on-disk structures are designed to allow future
    expansion through expansion of these limits.  In particular, the volume
    id is intended to be expanded to a full 32 bits in the future and using
    a larger buffer size will also greatly increase the cluster and volume
    size limitations by increasing the number of elements the buffer-
    restricted radix trees can manage.

    HAMMER breaks all of its information down into objects and records.
    Records have a creation and deletion transaction id which allows HAMMER
    to maintain a historical store.  Information is only physically deleted
    based on the data retention policy.  Those portions of the data retention
    policy affecting near-term modifications may be acted upon by the live
    filesystem but all historical vacuuming is handled by a helper process.

    All information in a HAMMER filesystem is CRCd to detect corruption.

(II) Filesystem Object Topology

    The objects and records making up a HAMMER filesystem is organized into
    a single, unified B-Tree.  Each cluster maintains a B-Tree of the
    records contained in that cluster and a unified B-Tree is constructed by
    linking clusters together.  HAMMER issues PUSH and PULL operations
    internally to open up space for new records and to balance the global
    B-Tree.  These operations may have the side effect of allocating
    new clusters or freeing clusters which become unused.

    B-Tree operations tend to be limited to a single cluster.  That is,
    the B-Tree insertion and deletion algorithm is not extended to the
    whole unified tree.  If insufficient space exists in a cluster HAMMER
    will allocate a new cluster, PUSH a portion of the existing
    cluster's record store to the new cluster, and link the existing
    cluster's B-Tree to the new one.

    Because B-Tree operations tend to be restricted and because HAMMER tries
    to avoid balancing clusters in the critical path, HAMMER employs a
    background process to keep the topology as a whole in balance.  One
    side effect of this is that HAMMER is fairly loose when it comes to
    inserting new clusters into the topology.

    HAMMER objects revolve around the concept of an object identifier.
    The obj_id is a 64 bit quantity which uniquely identifies a filesystem
    object for the entire life of the filesystem.  This uniqueness allows
    backups and mirrors to retain varying amounts of filesystem history by
    removing any possibility of conflict through identifier reuse.  HAMMER
    typically iterates object identifiers sequentially and expects to never
    run out.  At a creation rate of 100,000 objects per second it would
    take HAMMER around 6 million years to run out of identifier space.
    The characteristics of the HAMMER obj_id also allow HAMMER to operate
    in a multi-master clustered environment.

    A filesystem object is made up of records.  Each record references a
    variable-length store of related data, a 64 bit key, and a creation
    and deletion transaction id which is indexed along with the key.

    HAMMER utilizes a 64 bit key to index all records.  Regular files use
    the base data offset of the record as the key while directories use a
    namekey hash as the key and store one directory entry per record.  For
    all intents and purposes a directory can store an unlimited number of
    files. 

    HAMMER is also capable of associating any number of out-of-band
    attributes with a filesystem object using a separate key space.  This
    key space may be used for extended attributes, ACLs, and anything else
    the user desires.

(III) Access to historical information

    A HAMMER filesystem can be mounted with an as-of date to access a
    snapshot of the system.  Snapshots do not have to be explicitly taken
    but are instead based on the retention policy you specify for any
    given HAMMER filesystem.  It is also possible to access individual files
    or directories (and their contents) using an as-of extension on the
    file name.

    HAMMER uses the transaction ids stored in records to present a snapshot
    view of the filesystem as-of any time in the past, with a granularity
    based on the retention policy chosen by the system administrator. 
    feature also effectively implements file versioning.

(IV) Mirrors and Backups

    HAMMER is organized in a way that allows an information stream to be
    generated for mirroring and backup purposes.  This stream includes all
    historical information available in the source.  No queueing is required
    so there is no limit to the number of mirrors or backups you can have
    and no limit to how long any given mirror or backup can be taken offline.
    Resynchronization of the stream is not considered to be an expensive
    operation.

    Mirrors and backups are maintained logically, not physically, and may
    have their own, independant retention polcies.  For example, your live
    filesystem could have a fairly rough retention policy, even none at all,
    then be streamed to an on-site backup and from there to an off-site
    backup, each with different retention policies.

(V) Transactions and Recovery

    HAMMER implement an instant-mount capability and will recover information
    on a cluster-by-cluster basis as it is being accessed.

    HAMMER numbers each record it lays down and stores a synchronization
    point in the cluster header.  Clusters are synchronously marked 'open'
    when undergoing modification.  If HAMMER encounters a cluster which is
    unexpectedly marked open it will perform a recovery operation on the
    cluster and throw away any records beyond the synchronization point.

    HAMMER supports a userland transactional facility.  Userland can query
    the current (filesystem wide) transaction id, issue numerous operations
    and on recovery can tell HAMMER to revert all records with a greater
    transaction id for any particular set of files.  Multiple userland
    applications can use this feature simultaniously as long as the files
    they are accessing do not overlap.  It is also possible for userland
    to set up an ordering dependancy and maintain completely asynchronous
    operation while still being able to guarentee recovery to a fairly
    recent transaction id.

(VI) Database files

    HAMMER uses 64 bit keys internally and makes key-based files directly
    available to userland.  Key-based files are not regular files and do not
    operate using a normal data offset space.

    You cannot copy a database file using a regular file copier.  The
    file type will not be S_IFREG but instead will be S_IFDB.   The file
    must be opened with O_DATABASE.  Reads which normally seek the file
    forward will instead iterate through the records and lseek/qseek can
    be used to acquire or set the key prior to the read/write operation.

사용자 삽입 이미지
크리에이티브 커먼즈 라이선스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by Daniel Kwon
사용자 삽입 이미지

이번에는 FUSE 파일 시스템에 대해서 살펴 보려고 한다.

아직 커널에 대해서 깊이 알고 있지 못한 상태에서 바로 커널 레벨의 파일 시스템을 만드는 것은 여러모로 무리가 있다. 따라서, 이번에는 사용자 어플리케이션 레벨에서 파일 시스템을 만들 수 있는 방법을 먼저 알아보도록 하겠다.

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가 해당된다.
셋째, 사용자가 작성한 파일시스템 코드가 필요하다.

만약, 사용하는 시스템에 FUSE가 설치되어 있지 않다면, http://fuse.sourceforge.net/ 에서 소스를 받아서 다음 과정을 거쳐 컴파일을 먼저 해야 한다.

설치 과정도 해당 사이트에 잘 나와 있지만, 간단히 보자면 다음과 같은 단계를 거치면 /usr/local/ 밑에 설치가 된다.

[work] $ ./configure
[work] $ make
[work] $ make install

만약 설치되는 경로를 바꾸고 싶다면  ./configure --prefix=/usr 등과 같이 원하는 경로를 prefix 다음에 지정하면 된다.

다음에는 FUSE를 기반으로 한 사용자 파일 시스템을 작성한 후 다음과 같은 형태로 fuse 라이브러리를 사용하도록 컴파일해야 한다.

[work] $ gcc -o my_filesystem my_fs.c -lfuse


다음에는 해당 프로그램을 컴파일하고 사용하면된다.

./my_filesystem   /마운트포인트
 
만약 컴파일이나 실행이 안된다면 라이브러리 경로가 제대로 되어 있는지 확인을 해야 한다. 만약, FUSE 라이브러리가 있는 /usr/local/lib 경로가 빠져 있다면 다음과 같이 경로를 먼저 잡아 준 후 수행해야 한다.

LD_LIBRARY_PATH=/usr/local/lib   ./my_filesystem   /마운트포인트

파일 시스템을 마운트 하는 것은 mount 명령을 사용하지 않으며, 작성한 사용자 프로그램을 올리면서 뒤에 인자로 마운트할 경로를 적어 주면 된다.

반면, 마운트를 해제할 경우에는 기존의 umount 명령을 사용해서 처리하면 된다.

umount /마운트포인트

다음 그림은 FUSE 파일 시스템의 전체적인 그림을 나타내는 것으로 fuse 프로젝트 사이트에서 가져온 것이다.
사용자 삽입 이미지
맥 버전의 FUSE 경우에는  http://code.google.com/p/macfuse/ 에서 코드를 구할 수 있다. 맥의 경우에는 다운로드후 더블클릭만으로 쉽게 설치가 되므로 더 편하게 사용할 수 있다.
MacFUSE 로고


다음 코드는 FUSE  프로젝트 사이트에서 제공하는 코드로 간단히 파일시스템을 구현하는 방법을 보여주고 있다.
/*
    FUSE: Filesystem in Userspace
    Copyright (C) 2001-2005  Miklos Szeredi <miklos@szeredi.hu>
    This program can be distributed under the terms of the GNU GPL.
    See the file COPYING.
*/
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
static const char *hello_str = "Hello World!\n";
static const char *hello_path = "/hello";
static int hello_getattr(const char *path, struct stat *stbuf)
{
    int res = 0;
    memset(stbuf, 0, sizeof(struct stat));
    if(strcmp(path, "/") == 0) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
    }
    else if(strcmp(path, hello_path) == 0) {
        stbuf->st_mode = S_IFREG | 0444;
        stbuf->st_nlink = 1;
        stbuf->st_size = strlen(hello_str);
    }
    else
        res = -ENOENT;
    return res;
}
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                         off_t offset, struct fuse_file_info *fi)
{
    (void) offset;
    (void) fi;
    if(strcmp(path, "/") != 0)
        return -ENOENT;
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, hello_path + 1, NULL, 0);
    return 0;
}
static int hello_open(const char *path, struct fuse_file_info *fi)
{
    if(strcmp(path, hello_path) != 0)
        return -ENOENT;
    if((fi->flags & 3) != O_RDONLY)
        return -EACCES;
    return 0;
}
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
                      struct fuse_file_info *fi)
{
    size_t len;
    (void) fi;
    if(strcmp(path, hello_path) != 0)
        return -ENOENT;
    len = strlen(hello_str);
    if (offset < len) {
        if (offset + size > len)
            size = len - offset;
        memcpy(buf, hello_str + offset, size);
    } else
        size = 0;
    return size;
}
static struct fuse_operations hello_oper = {
    .getattr = hello_getattr,
    .readdir = hello_readdir,
    .open = hello_open,
    .read = hello_read,
};
int main(int argc, char *argv[])
{
    return fuse_main(argc, argv, &hello_oper);
}


크리에이티브 커먼즈 라이선스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by Daniel Kwon
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 };
<위치 : http://lxr.linux.no/source/include/linux/fs.h#L908>


파일 시스템을 등록할 때 전달되는 struct file_system_type의 get_sb() 함수를 통해 반환되도록 되어 있는 struct super_block 구조체는 슈퍼 블럭에서 관리하는 정보들을 모두 얻을 수 있도록 되어 있다. 물론, 파일 시스템에 다라서는 일부 내용이 무의미할 수 도 있다. 어쨌든, 파티션상에 존재하는 해당 파일 시스템에 대해서 데이타를 다룰 때 사용하게 되는 여러가지 정보들이 여기에 존재하게 된다.

이 중에서 슈퍼블럭을 다루기 위해서 필요한 연산들이 916번째 줄에 있는 struct super_operations *s_op 필드이다. struct super_operations는 다음과 같이 정의되어 있다.

1161 struct super_operations {
1162         struct inode *(*alloc_inode)(struct super_block *sb);
1163         void (*destroy_inode)(struct inode *);
1164
1165         void (*read_inode) (struct inode *);
1166  
1167         void (*dirty_inode) (struct inode *);
1168         int (*write_inode) (struct inode *, int);
1169         void (*put_inode) (struct inode *);
1170         void (*drop_inode) (struct inode *);
1171 void (*delete_inode) (struct inode *);
1172         void (*put_super) (struct super_block *);
1173         void (*write_super) (struct super_block *);
1174         int (*sync_fs)(struct super_block *sb, int wait);
1175         void (*write_super_lockfs) (struct super_block *);
1176         void (*unlockfs) (struct super_block *);
1177         int (*statfs) (struct dentry *, struct kstatfs *);
1178         int (*remount_fs) (struct super_block *, int *, char *);
1179         void (*clear_inode) (struct inode *);
1180         void (*umount_begin) (struct vfsmount *, int);
1181
1182         int (*show_options)(struct seq_file *, struct vfsmount *);
1183         int (*show_stats)(struct seq_file *, struct vfsmount *);
1184 #ifdef CONFIG_QUOTA
1185         ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
1186         ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
1187 #endif
1188 };
<위치 : http://lxr.linux.no/source/include/linux/fs.h#L1161>

super_operation 구조체에서 하는 일은 super_block을 다루는 코드와 함께 super_block을 기반으로 실제 파일에 해당하는 정보를 찾을 수 있는 inode를 다루는 연산들로 구성되어 있다. 새로운 파일이 생성될 경우에는 alloc_inode를 사용하게 되며, 파일 정보와 관련해서 read_inode(), write_inode() 함수들도 사용하게 된다. mount 관련된 구조체인 vfsmount는 이 전번 글에서 간단히 살펴보았으므로 여기에서는 그냥 넘어간다.

inode는 실제로 파일을 만들기 위한 기본이 되는 정보로서 이를 통해 어떤 파일이 어디에 있으며, 어떠한 속성으로 만들어 졌는지, 크기는 얼마인지, 소유자는 누구인지, 접근 권한은 어떻게 되는지 등의 정보를 볼 수 있다. 즉, 일반적으로 ls -l 명령을 사용했을 때 나오는 결과에 해당하는 정보를 가지고 있다고 보면 된다.

530 struct inode {
531         struct hlist_node       i_hash;
532         struct list_head        i_list;
533         struct list_head        i_sb_list;
534         struct list_head        i_dentry;
535         unsigned long           i_ino;
536         atomic_t                i_count;
537         unsigned int            i_nlink;
538         uid_t                   i_uid;
539         gid_t                   i_gid;
540         dev_t                   i_rdev;
541         unsigned long           i_version;
542         loff_t                  i_size;
543 #ifdef __NEED_I_SIZE_ORDERED
544         seqcount_t              i_size_seqcount;
545 #endif
546         struct timespec         i_atime;
547         struct timespec         i_mtime;
548         struct timespec         i_ctime;
549         unsigned int            i_blkbits;
550         blkcnt_t                i_blocks;
551         unsigned short          i_bytes;
552         umode_t                 i_mode;
553         spinlock_t              i_lock; /* i_blocks, i_bytes, maybe i_size */
554         struct mutex            i_mutex;
555         struct rw_semaphore     i_alloc_sem;
556         const struct inode_operations   *i_op;
557         const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
558         struct super_block      *i_sb;
559         struct file_lock        *i_flock;
560         struct address_space    *i_mapping;
561         struct address_space    i_data;
562 #ifdef CONFIG_QUOTA
563         struct dquot            *i_dquot[MAXQUOTAS];
564 #endif
565         struct list_head        i_devices;
566         union {
567                 struct pipe_inode_info  *i_pipe;
568                 struct block_device     *i_bdev;
569                 struct cdev             *i_cdev;
570         };
571         int                     i_cindex;
572
573         __u32                   i_generation;
574
575 #ifdef CONFIG_DNOTIFY
576         unsigned long           i_dnotify_mask; /* Directory notify events */
577         struct dnotify_struct   *i_dnotify; /* for directory notifications */
578 #endif
579
580 #ifdef CONFIG_INOTIFY
581         struct list_head        inotify_watches; /* watches on this inode */
582         struct mutex            inotify_mutex;  /* protects the watches list */
583 #endif
584
585         unsigned long           i_state;
586         unsigned long           dirtied_when;   /* jiffies of first dirtying */
587
588         unsigned int            i_flags;
589
590         atomic_t                i_writecount;
591 #ifdef CONFIG_SECURITY
592         void                    *i_security;
593 #endif
594         void                    *i_private; /* fs or device private pointer */
595 };
<위치 : http://lxr.linux.no/source/include/linux/fs.h#L530>

i-node를 통해 파일의 실제 데이타를 제외한 모든 정보를 얻을 수 있다. 일부 파일시스템(FAT, FAT16, FAT32 등등)에 대해서는 struct inode 구조체의 일부 필드가 의미없을 수 도 있다. inode 구조체에서는 실제 inode를 다루는 방법과 파일을 다루는 방법을 위해서 두 가지 구조체를 제공하고 있다.

먼저, inode를 다루는 함수들을 위해서는 struct inode_operations *i_op 필드가 제공된다. 이 구조체는 다음과 같은 형태를 가지고 있다.

1118 struct inode_operations {
1119         int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
1120         struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
1121         int (*link) (struct dentry *,struct inode *,struct dentry *);
1122         int (*unlink) (struct inode *,struct dentry *);
1123         int (*symlink) (struct inode *,struct dentry *,const char *);
1124         int (*mkdir) (struct inode *,struct dentry *,int);
1125         int (*rmdir) (struct inode *,struct dentry *);
1126         int (*mknod) (struct inode *,struct dentry *,int,dev_t);
1127 int (*rename) (struct inode *, struct dentry *,
1128 struct inode *, struct dentry *);
1129 int (*readlink) (struct dentry *, char __user *,int);
1130         void * (*follow_link) (struct dentry *, struct nameidata *);
1131 void (*put_link) (struct dentry *, struct nameidata *, void *);
1132         void (*truncate) (struct inode *);
1133         int (*permission) (struct inode *, int, struct nameidata *);
1134         int (*setattr) (struct dentry *, struct iattr *);
1135         int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
1136         int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
1137         ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
1138         ssize_t (*listxattr) (struct dentry *, char *, size_t);
1139         int (*removexattr) (struct dentry *, const char *);
1140         void (*truncate_range)(struct inode *, loff_t, loff_t);
1141 };
<위치 : http://lxr.linux.no/source/include/linux/fs.h#L1118>


inode_operations에서는 실제 파일 정보를 만들고 제거하는 다양한 방법을 제공한다.

파일 내용을 다루는 연산을 위해서는 struct file_operations *i_fop 필드가 제공된다. 이 구조체는 다음과 같은 형태를 가지고 있다.

1088 struct file_operations {
1089 struct module *owner;
1090         loff_t (*llseek) (struct file *, loff_t, int);
1091         ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1092         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1093         ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1094         ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1095         int (*readdir) (struct file *, void *, filldir_t);
1096         unsigned int (*poll) (struct file *, struct poll_table_struct *);
1097         int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
1098         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1099         long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1100         int (*mmap) (struct file *, struct vm_area_struct *);
1101         int (*open) (struct inode *, struct file *);
1102         int (*flush) (struct file *, fl_owner_t id);
1103         int (*release) (struct inode *, struct file *);
1104         int (*fsync) (struct file *, struct dentry *, int datasync);
1105         int (*aio_fsync) (struct kiocb *, int datasync);
1106         int (*fasync) (int, struct file *, int);
1107         int (*lock) (struct file *, int, struct file_lock *);
1108         ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
1109         ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1110         unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1111         int (*check_flags)(int);
1112         int (*dir_notify)(struct file *filp, unsigned long arg);
1113         int (*flock) (struct file *, int, struct file_lock *);
1114         ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1115         ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1116 };
<위치 : http://lxr.linux.no/source/include/linux/fs.h#L1088>

파일 시스템을 만드는 것은 결국 이러한 연산자들을 잘 구성하는 것으로 시작한다고 볼 수 있다. 물론, 각각은 실제 물리적인 장치에 어떻게 배치되느냐도 중요하지만, 일단은 이러한 함수들에 내용을 넣는 것부터 시작해 보는 것도 괜찮을 것이다.

아래 코드는 이러한 내용들을 간단히 채워 넣은 파일 시스템 예제 코드로서 아래 주소에서 코드 및 설명을 볼 수 있다.
<참조 위치 : http://www.geocities.com/ravikiran_uvs/articles/rkfs.html>

/**
 * 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?
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/statfs.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
#include <linux/buffer_head.h>
#include <linux/pagemap.h> /* unlock_page */
#define RKFS_MAGIC 0xabcd
#define FILE_INODE_NUMBER 2
/* file_system_type */
/* get_sb */
static struct super_block *
rkfs_get_sb(struct file_system_type *, int, const char *, void *);
/* kill_sb */
static void
rkfs_kill_sb(struct super_block *);
/* super_operations */
/* read_inode */
static void
rkfs_super_read_inode(struct inode *inode);
/* write_inode */
static int
rkfs_super_write_inode(struct inode *inode, int sync);
/* inode_operations */
/* lookup */
static struct dentry *
rkfs_inode_lookup(struct inode *parent_inode, struct dentry *dentry, struct nameidata *);
/* file_operations */
static int
rkfs_file_open (struct inode *, struct file *);
/* readdir */
static int
rkfs_file_readdir(struct file *file, void *dirent, filldir_t filldir);
/* release */
static int
rkfs_file_release (struct inode *, struct file *);
/* Address Space Operations */
/* writepage */
static int
rkfs_writepage(struct page *page, struct writeback_control *wbc);
/* readpage */
static int
rkfs_readpage(struct file *file, struct page *page);
/* prepare_write */
static int
rkfs_prepare_write(struct file *file, struct page *page,
     unsigned from, unsigned to);
/* commit_write */
static int
rkfs_commit_write(struct file *file, struct page *page,
    unsigned from, unsigned to);

/* readpage */

/*
 * Data declarations
 */
static struct super_operations rkfs_sops = {
  read_inode: rkfs_super_read_inode,
  statfs: simple_statfs, /* handler from libfs */
  write_inode: &rkfs_super_write_inode
};
static struct inode_operations rkfs_iops = {
  lookup: rkfs_inode_lookup
};
static struct file_operations rkfs_fops = {
  open: rkfs_file_open,
  read: &generic_file_read,
  readdir: &rkfs_file_readdir,
  write: &generic_file_write,
  release: &rkfs_file_release,
  fsync: simple_sync_file
};
static struct file_system_type rkfs = {
  name:        "rkfs",
  get_sb:    rkfs_get_sb,
  kill_sb: rkfs_kill_sb,
  owner:        THIS_MODULE
};
static struct address_space_operations rkfs_aops = {
  .readpage = rkfs_readpage,
  .writepage = rkfs_writepage,
  .prepare_write = rkfs_prepare_write,
  .commit_write = rkfs_commit_write
};
static struct inode *rkfs_root_inode;
static char file_buf[PAGE_SIZE] = "Hello World\n";
static int file_size = 12;
/*
 * File-System Operations
 */
static int
rkfs_fill_super(struct super_block *sb, void *data, int silent)
{
  printk("RKFS: rkfs_fill_super\n" );
  sb->s_blocksize = 1024;
  sb->s_blocksize_bits = 10;
  sb->s_magic = RKFS_MAGIC;
  sb->s_op = &rkfs_sops; // super block operations
  sb->s_type = &rkfs; // file_system_type
  rkfs_root_inode = iget(sb, 1); // allocate an inode
  rkfs_root_inode->i_op = &rkfs_iops; // set the inode ops
  rkfs_root_inode->i_mode = S_IFDIR|S_IRWXU;
  rkfs_root_inode->i_fop = &rkfs_fops;
  if(!(sb->s_root = d_alloc_root(rkfs_root_inode))) {
    iput(rkfs_root_inode);
    return -ENOMEM;
  }
  return 0;
}
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);
}
static void
rkfs_kill_sb(struct super_block *super) {
  kill_anon_super(super);
}
/*
 * Super-Block Operations
 */
static void
rkfs_super_read_inode(struct inode *inode) {
  printk("RKFS: rkfs_super_read_inode\n");
  inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
  inode->i_mapping->a_ops = &rkfs_aops;
}
static int
rkfs_super_write_inode(struct inode *inode, int wait) {
  printk("RKFS: rkfs_super_write_inode (i_ino = %d) = %d\n",
  (int)inode->i_ino,
  (int)i_size_read(inode));
  if(inode->i_ino == FILE_INODE_NUMBER) {
    file_size = i_size_read(inode);
  }
  return 0;
}
/*
 * Inode Operations
 */
static char filename[] = "hello.txt";
static int filename_len = sizeof(filename) - 1;
static struct dentry *
rkfs_inode_lookup(struct inode *parent_inode, struct dentry *dentry, struct nameidata *nameidata) {
  struct inode *file_inode;
  printk("RKFS: rkfs_inode_lookup\n");
  if(parent_inode->i_ino != rkfs_root_inode->i_ino ||
     dentry->d_name.len != filename_len ||
     strncmp(dentry->d_name.name, filename, dentry->d_name.len)) {
    d_add(dentry, NULL);
    goto out;
  }
  file_inode = iget(parent_inode->i_sb, FILE_INODE_NUMBER);
  if(!file_inode)
    return ERR_PTR(-EACCES);
  file_inode->i_size = file_size;
  file_inode->i_mode = S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
  file_inode->i_fop = &rkfs_fops;
  //  file_inode->i_fop
  d_add(dentry, file_inode);
 out:
  return NULL;
}
/*
 * File Operations
 */
static int
rkfs_file_open(struct inode *inode, struct file *file) {
  printk("RKFS: @rkfs_file_open max_readahead = %d (size = %d)\n", (int)file->f_ra.ra_pages, file_size);
  file->f_ra.ra_pages = 0; /* No read-ahead */
  return generic_file_open(inode, file);
}
static int
rkfs_file_release (struct inode *ino, struct file *file) {
  struct dentry *dentry;
  dentry = file->f_dentry;
  return 0;
}
static int
rkfs_file_readdir(struct file *file, void *dirent, filldir_t filldir) {
  struct dentry *de = file->f_dentry;
  if(file->f_pos > 2)
    return 1;
  if(filldir(dirent, ".", 1, file->f_pos++, de->d_inode->i_ino, DT_DIR))
    return 0;
  if(filldir(dirent, "..", 2, file->f_pos++, de->d_parent->d_inode->i_ino, DT_DIR))
    return 0;
  if(filldir(dirent, filename, filename_len, file->f_pos++, FILE_INODE_NUMBER, DT_REG))
    return 0;
  return 1;
}
/* address_space_operations */
static int
rkfs_writepage(struct page *page, struct writeback_control *wbc) {
  void *page_addr = kmap(page);
  printk("[RKFS] offset = %d\n", (int)page->index);
  printk("RKFS: WritePage: [%s] [%s] [%s] [%s]\n",
  PageUptodate(page) ? "Uptodate" : "Not Uptodate",
  PageDirty(page) ? "Dirty" : "Not Dirty",
  PageWriteback(page) ? "PageWriteback Set" : "PageWriteback Cleared",
  PageLocked(page) ? "Locked" : "Unlocked");
  memcpy(file_buf, page_addr, PAGE_SIZE);
  ClearPageDirty(page);
  if(PageLocked(page))
    unlock_page(page);
  kunmap(page);
  return 0;
}
static int
rkfs_readpage(struct file *file, struct page *page) {
  void *page_addr;
  printk("RKFS: readpage called for page index=[%d]\n", (int)page->index);
  if(page->index > 0) {
    return -ENOSPC;
  }
  printk("RKFS: Page: [%s] [%s] [%s] [%s]\n",
  PageUptodate(page) ? "Uptodate" : "Not Uptodate",
  PageDirty(page) ? "Dirty" : "Not Dirty",
  PageWriteback(page) ? "PageWriteback Set" : "PageWriteback Cleared",
  PageLocked(page) ? "Locked" : "Unlocked");
  SetPageUptodate(page);
  page_addr = kmap(page);
  if(page_addr)
   memcpy(page_addr, file_buf, PAGE_SIZE);
  if(PageLocked(page))
    unlock_page(page);
  kunmap(page);
  return 0;
}
static int
rkfs_prepare_write(struct file *file, struct page *page,
     unsigned from, unsigned to) {
  return 0;
}
/* modified from generic_commit_write. generic_commit_write calls the
 * block device layer to write set up buffer heads for I/O.
 */
static int
rkfs_commit_write(struct file *file, struct page *page,
    unsigned from, unsigned to) {
  struct inode *inode = page->mapping->host;
  void *page_addr = kmap(page);
  loff_t pos = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to;
  printk("RKFS: commit_write: [%s] [%s] [%s] \n",
  PageUptodate(page) ? "Uptodate" : "Not Uptodate",
  PageDirty(page) ? "Dirty" : "Not Dirty",
  PageLocked(page) ? "Locked" : "Unlocked");
  if(page->index == 0) {
    memcpy(file_buf, page_addr, PAGE_SIZE);
    ClearPageDirty(page);
  }
  SetPageUptodate(page);
  kunmap(page);
  
  /*
   * 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);
  }
  
  return 0;
}

static int
rkfs_init_module(void) {
  int err;
  err = register_filesystem(&rkfs);
  return err;
}
static void
rkfs_cleanup_module(void) {
  unregister_filesystem(&rkfs);
}
module_init(rkfs_init_module);
module_exit(rkfs_cleanup_module);
MODULE_LICENSE("GPL");

다음번부터는 실제 파일 시스템들을 하나씩 들쳐볼까 한다. 여러모로 어려운 부분이 있겠지만, 실제 파일 시스템을 살펴보는 것이 제일 좋은 방법이라 생각된다.
크리에이티브 커먼즈 라이선스
Creative Commons License