강좌

HOME > 강좌 >
강좌| 리눅스 및 오픈소스에 관련된 강좌를 보실 수 있습니다.
 
Step by Step 커널 프로그래밍 강좌⑤
조회 : 9,880  


Step by Step 커널 프로그래밍 강좌

파일시스템마운트

 

 

이번 강좌에서는 파일 시스템의 동작과정에 대하여 알아보기로 한다. 파일 시스템의 read/write 대한 내용은 다른 많은 리눅스 커널 책에도 소개되어 있다. 그러므로 이번 강좌와 다음 강좌에서는 파일 시스템의 마운트 과정에 대하여 자세히 분석하며 파일 시스템의 동작 과정을 이해하도록 예정이다.

 

_ 김민찬 KLDP 멤버, 전문 프로그래머

 

연재 순서

커널 프로그래밍 환경 구축하기와 특징

모듈 구현하기

리눅스 커널의 메모리 관리

커널의 동기화에 관하여

파일 시스템 마운트 1

파일 시스템 마운트 2

 

sys_mount 함수 호출 과정을 항상 참조하라

이번 강좌에서는 파일 시스템의 분석을 위해 간단한 파일 시스템을 사용하며 분석할 것이다. rkfs라는 파일 시스템이며 이 파일 시스템에 관련된 사항은 다음 URL 참조하면 된다.

 

http://www.geocities.com/ravikiran_uvs/articles/rkfs.html

 

먼저 사용자가 파일시스템을 mount 하게 되면 커널의 다른 system call들과 마찬가지로 커널의 sys_mount 함수가 호출된다.

먼저 sys_mount 분석하기 전에 주의 사항이 있다. 함수는 여러 중요한 함수들이 굉장히 깊이 있게 연결되어 있다. 그러므로 소스를 분석하다 보면 자신이 지금 어디에 서있는지 길을 잃어버리는 경우가 많다. 그러므로 항상 [그림 1] 참조하여 자신이 지금 어느 곳에 서있는지 기억하고 있어야 것이다.

 

sys_mount 인자로 넘겨받은 데이터들을 커널 메모리에 복사한 , 실제적인 mount operation 처리하는 do_mount 함수를 호출한다.

do_mount 함수는 기본적인 sys_mount 함수의 C++ overriding 같은 역할을 한다. 넘겨받은 파라미터 flags 따라서 실제 일을 담당하는 함수 하나를 호출해준다. 호출되는 함수들은 다음과 같다.

 

do_remount : flags MS_REMOUNT 옵션이 있을

do_loopback : flags MS_BIND 옵션이 있을

do_move_mount : flags MS_MOVE 있을

do_new_mount: 외의 모든 경우

 

위의 함수를 호출하기 , 먼저 sys_mount로부터 넘겨받은 파라미터들의 간단한 유효성 검사를 수행한다. 다음 mount point dentry, vfsmount 등의 구조체를 얻어오기위하여 path_lookup 함수를 호출한다. vfsmount 다음과 같은 필드를 갖는다.

 

 

 

struct vfsmount

{

struct list_head mnt_hash;

struct vfsmount *mnt_parent; // 우리의 vfsmount가 마운트된 파일이 속해있는 vfsmount

struct dentry *mnt_mountpoint; /* mount point 파일 과 관련된 dentry /*

struct dentry *mnt_root; /* root of the mounted tree */

struct super_block *mnt_sb; /* vfsmount root inode 관련된 superblock */

struct list_head mnt_mounts; /* vfsmountchildren vfsmount들의 리스트 /*

struct list_head mnt_child; /* child간의 연결 리스트 /*

atomic_t mnt_count;

int mnt_flags;

int mnt_expiry_mark; /* true if marked for expiry */

char *mnt_devname; /* Name of device e.g./dev/dsk/hda1 */

struct list_head mnt_list;

struct list_head mnt_fslink; /* link in fs-specific expiry list */

struct namespace *mnt_namespace; /* containing namespace */

};

1. vfsmount 구조체

 

 

do_new_mount 함수에 대한 호출

다음으로 do_new_mount 함수에 대한 호출을 살펴볼 것이다.

do_new_mount 다음과 같이 호출된다.

 

static int do_new_mount(struct nameidata *nd, char *type, int flags,

int mnt_flags, char *name, void *data)

{

struct vfsmount *mnt;

if (!type || !memchr(type, 0, PAGE_SIZE))

return -EINVAL;

/* we need capabilities... */

if (!capable(CAP_SYS_ADMIN))

return -EPERM;

mnt = do_kern_mount(type, flags, name, data);

if (IS_ERR(mnt))

return PTR_ERR(mnt);

return do_add_mount(mnt, nd, mnt_flags, NULL);

}

코드 1. do_new_mount

 

함수는 파라미터로 nameidata 구조체와 type, flags,mnt_flags, mount되는 device name, data_page 받는다. 함수는 함수는 do_kern_mount 함수를 호출하여 superblock, dentry, address_space 관련된 구조체들을 생성하고 연결한다. 그런 do_add_mount 함수를 호출하여

namespace tree 연결시킨다. 함수를 자세히 살펴보기로 하자.

 

 

do_kern_mount() 함수는 다음과 같다.

 

struct vfsmount *

do_kern_mount(const char *fstype, int flags, const

char *name, void *data)

{

struct file_system_type *type = get_fs_type(fstype);

struct super_block *sb = ERR_PTR(-ENOMEM);

struct vfsmount *mnt;

int error;

char *secdata = NULL;

if (!type)

return ERR_PTR(-ENODEV);

mnt = alloc_vfsmnt(name);

if (!mnt)

goto out;

...

sb = type get_sb(type, flags, name, data);

if (IS_ERR(sb))

goto out_free_secdata;

error = security_sb_kern_mount(sb, secdata);

if (error)

goto out_sb;

mnt mnt_sb = sb;

mnt mnt_root = dget(sb s_root);

mnt mnt_mountpoint = sb s_root;

mnt mnt_parent = mnt;

mnt mnt_namespace = current namespace;

up_write(&sb s_umount);

put_filesystem(type);

return mnt;

코드 2. do_kern_mount

 

 

함수는 먼저 get_fs_type 아규먼트로 패스된 fstype 패스 mount file_system_type 구조체를 찾는다

 

struct file_system_type {

const char *name;

int fs_flags;

struct super_block *(*get_sb)

(struct file_system_type *, int,

const char *, void *);

void (*kill_sb) (struct super_block *);

struct module *owner;

struct file_system_type * next;

struct list_head fs_supers;

};

코드 3. file_system_type 구조체

 

구조체는 register_filesystem 함수로 파일시스템을 등록할 파라미터로 사용된다. 구조체의 필드 가장 중요한 필드는 get_sb 함수 포인터이다. 함수는 파일시스템 개발

자가 커널에 있는 VFS(Virtual Filesystem Layer) hook(갈고리) 걸어 놓는 것이다. 이렇게 callback 함수를 두는 이유는 VFS general 인터페이스를 이용하되 자신에 입맛에 맞게 부분을 수정하려는 것이다. 위의 함수 get_sbsuperblock 대한 초기화를 담당하게 되는데 파일시스템별로 superblock 많은 필드들이 서로 다르다. 그러므로 커널은 위와 같은 hook 있는 인터페이스를 제공하여 파일시스템 개발자들에게 꿈과 희망을 주는 것이다.

다음으로는 alloc_vfsmnt 호출하여 아규먼트로 넘어온 name 해당하는 vfsmount 구조체를 만든다. 이때 vfsmount mnt_cache 통하여 만들어지며 여러 필드들이 초기화된다. 아규먼트로 패스된 name vfsmount 구조체필드의 mnt_devname 저장된다.

 

파일 시스템의 Specific Layer 넘어가자

다음은 get_sb 호출한다. 부분에서 우리는 최초로 VFS generic layer에서 파일 시스템의 Specific Layer로 넘어온 것이다. 필자는 filesystem specific 부분에 대한 이해를 돕기 위하여 간단한 ram file system rfks 예를 들어 설명한다. (rfks 소스 - 부록 참고). get_sb 함수는 rfks file_system_type 다음과 같이 정의되어 있다

 

static struct file_system_type rkfs = {

name: "rkfs",

get_sb: rkfs_get_sb,

kill_sb: rkfs_kill_sb,

owner: THIS_MODULE

};

코드 4. rkfs file_system_type

 

 

rkfs filesystem 이름은rkfs이며 나머지는 필드에서 보는 바와 같이 초기화되어 있다. 그러므로 get_sb 함수는 rfks_get_sb 함수를 호출한다. 함수는 미리 커널에 정의되어 있는 get_sb_single 함수를 호출하는 단순한 wrapper 함수이다. 하지만 함수를 호출할 때 파라미터로 rfks_fill_super 함수의 포인터를 패스한다.

 

static struct super_block *

rkfs_get_sb(struct file_system_type *fs_type,int flags, const char *devname, void *data, struct vfsmount *mnt)

{

/* rkfs_fill_super this will be called to fill the superblock */

return get_sb_single( fs_type, flags, data, &rkfs_fill_super, mnt);

}

코드 5. rkfs_get_sb

 

이것도 위와 같은 hook 거는 함수이다. hook 새로이 할당받은 superblock rkfs 알맞은 데이터로 채워 넣기 위해서이다. 아래의 superblock 구조체는 file system에서

inode 만큼이나 중요한 역할을 하는 구조체이다. 대부분의 필드들은 이름이 직관적이어서 굳이 설명이 필요 없을 같다. s_list, s_files, s_instances 필드들은 용도를 보게

이다.

 

struct super_block {

struct list_head s_list; /* Keep this first */

dev_t s_dev; /* search index; _not_kdev_t */

unsigned long s_blocksize;

unsigned long s_old_blocksize;

unsigned char s_blocksize_bits;

unsigned char s_dirt;

unsigned long long s_maxbytes; /* Max file size*/

struct file_system_type *s_type;

struct super_operations *s_op;

...

unsigned long s_flags;

unsigned long s_magic;

struct dentry *s_root;

struct list_head s_inodes; /* all inodes */

struct list_head s_dirty; /* dirty inodes */

struct list_head s_io; /* parked for writeback */

struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */

struct list_head s_files;

struct block_device *s_bdev;

struct list_head s_instances;

...

}

코드 6. superblock 구조체

get_sb_single 함수는 fs/super.c 정의되어 있다. 함수는 sget이라는 커널함수를 호출하여 superblock 할당받고 superblock s_root 설정되어 있지 않다면 사용자의 hook

, 여기서는 rkfs_fill_super 함수를 호출하여 할당 받은 superblock 사용자의 입맛에 맞게 초기화하게 되는 것이다. rkfs_fill_super 함수는 다음과 같다. 함수는 파일시스템 개발자가 자신의 구미에 맞게 superblock 초기화 하기 위해 등록해 놓은 callback함수이다. 함수는 get_sb 함수가 호출될 rkfs_get_sb 함수에 의해서 패스된다.

 

static int

rkfs_fill_super(struct super_block *sb, void *data,

int silent)

{

printk("RKFS: rkfs_fill_supern" );

...

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 theinode 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;

코드 7. rkfs_fill_super

 

코드는 superblock operation table 지정하고 file_system_type rkfs 주소로 지정한다. 그런 iget을 호출해서 rkfs root inode 위한 inode 할당받고 inode

i_op, i_fop operation table 지정한다. 그리고 마지막으로 d_alloc_root 함수를 방금 할당 받은 root inode 파라미터로 패스하여 호출한다. 이것은 inode 관련된 dentry

환하게 된다.

여기까지의 과정을 살펴보면 다음과 같다.

 

 

Superblock 중요한 필드들

그럼 지금부터 위의 함수들을 따라서 더욱 깊은 곳으로 들어 보도록 하자.

현재 kernel control path rkfs rkfs_fill_super 함수가 가지고 있다고 가정하자. 함수는 sget 통해서 할당받은 superblock 기본적인 필드들을 채운다. rkfs_fill_super

수는 먼저 기본적인 필드들을 지정한다. superblock 필드는 매우 많지만 중요한 가지만 살펴보기로 한다.

 

s_op : superblock operation 함수 포인터 테이블

s_type : rkfs file_system_type

s_root : superblock root inode 대한 dentry

 

 

먼저 s_op 필드를 살펴보면 다음과 같다.

 

static struct super_operations rkfs_sops = {

read_inode: rkfs_super_read_inode,

statfs: simple_statfs, /* handler from libfs */

write_inode: &rkfs_super_write_inode

};

코드 8. rkfs superblock operations

 

 

나머지 필드들은 0으로 채워진다. 필드의 함수 포인터들의 사용은 사용이 일어날 자세히 알아보기로 하고 지금은 다음으로 일단 넘어가자. s_type 필드는 이미 전에 살펴보았다.

마지막으로 s_root 대한 것을 알아보자. s_root dentry 만들기 위해서는 dentry 관련될 inode가 필요하다. inode 생성하기 위해 iget 함수를 superblock 할당될 inode number 파라미터로 패스하여 호출하여 호출한다.

 

static inline struct inode *iget(struct super_block

*sb, unsigned long ino)

{

struct inode *inode = iget_locked(sb, ino);

if (inode && (inode i_state & I_NEW)) {

sb s_op read_inode(inode);

unlock_new_inode(inode);

}

return inode;

}

코드 9. iget

 

 

iget 함수는 iget_locked 함수를 호출해서 inode hash table에서 파라미터로 패스된 ino 번호를 가진 inode 찾는다. 해당 inode 발견되지 않으면 새로운 inode 할당해서 반환하게 된다. 할당받은 inode 기존에 inode cache에 들어있던 inode 아니고 새로운 inode라면 sb->s_op->read_inode, rkfs에서는 rkfs_super_read_inode 호출하게 된다. 지금 필자가 설명하고 있는 내용은 새로운 파일시스템의 mount 과정이므로 당연히 inode cache 들어있지 않을 것이다. 그러므로 rkfs_super_read_inode 함수가 호출된다. 함수는 address_space operation table setup 하는 중요한 역할을 한다 함수에 대해서는 iget_locked가 호출하는 ifind_fast get_new_inode_fast 설명한 뒤에 설명하기로 한다.

 

iget_locked 함수는 ifind_fast 함수를 호출해서 inode cache에서 해당 ino inode 찾게되고 발견하면 inode 반환하고 그렇지 않다면 get_new_inode_fast 함수를 호출하여 새로

inode 할당한다. 이때 inode 찾는 방식은 hash 사용하게 되며 hash table 주소는 inode_hashtable 변수에 있다. 다음 hash_table에서 slot 얻기 위해서 hash 함수를 다음과 같이 호출하여 첫번째 slot 찾아낸다.

 

struct hlist_head *head = inode_hashtable + hash(sb, ino);

코드 10. inode cache에서 hash 함수

지금, ifind_fast 지금 inode 찾을 없다. 왜냐하면 우리는 filesystem mount 하는 중이기 때문이다. 아직 어떤inode 할당되어 있지 않은 상황이다. 그러므로 get_new_inode_fast 함수를 호출해서 새로운 inode 할당받는다. 함수는 제일 먼저 alloc_inode(sb) 호출한다. 새로운 inode 할당받은 후에 inode i_no 파라미터로 패스받은 ino로지정하고 inode 자료구조를 inode_in_use sb->s_inodes 연결한다.

 

 

inode_in_use 시스템에 현재 사용중인 inode 관리하는 리스트이고 sb s_inodes sb 할당된 inode 관리하는 리스트이다. 그리고 마지막으로 inode_hashtable inode 를 연결하여 inode cache 넣는다

 

이번 강좌에서는 파일시스템의 마운트 과정에 대한 일부를 살펴보았다. 다음 강좌에서는 이번 강좌에 이어 파일시스템마운트의 나머지 과정에 대해 살펴볼 예정이다.

 

 

출처 : 공개 SW 리포트 11호 페이지 56 ~ 61 발췌(2008 3) - 한국소프트웨어 진흥원 공개SW사업팀 발간

 


[원글링크] : https://www.linux.co.kr/home2/board/subbs/board.php?bo_table=lecture&wr_id=1643


이 글을 트위터로 보내기 이 글을 페이스북으로 보내기 이 글을 미투데이로 보내기

 
한국소프트웨어진흥원 공개SW사업팀