Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

137199
1107
135865


[리눅스커널] 가상파일시스템: 아이노드 함수 오퍼레이션 13. 가상 파일 시스템

아이노드와 관련된 함수를 호출하는 동작을 아이노드 함수 오퍼레이션이라고 말합니다. 함수 오퍼레이션은 struct inode_operations 구조체 타입인데 아이노드 객체인 struct inode 구조체 i_op 필드로 아이노드 함수 오퍼레이션 주소에 접근할 수 있습니다.
 
아이노드 struct inode_operations 구조체 분석하기

아이노드 함수 오퍼레이션은 다음 구조체로 선언돼 있습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/include/linux/fs.h]
struct inode_operations {
struct dentry *(*lookup) (struct inode *,struct dentry *, unsigned int);
char *(*get_link)(struct dentry *, struct inode *, struct delayed_call *);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);

int (*readlink) (struct dentry *, char __user *,int);

int (*create) (struct inode *,struct dentry *, umode_t, bool);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
      u64 len);
int (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *,
   struct file *, unsigned open_flag,
   umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;

구조체 필드를 보면 모두 함수 포인터로 구성돼 있습니다. 각 파일시스템별로 아이노드를 세부 함수 포인터를 실행하는 역할을 수행합니다.

이제부터 아이노드 함수 오퍼레이션 자료구조인 struct inode_operations 구조체 필드를 소개하고 리눅스 커널의 어떤 코드에서 실행하는지 살펴보겠습니다.
lookup

덴트리 객체 파일 이름에 해당하는 아이노드를 관리하는 디렉토리를 찾습니다. 

[선언부]
struct dentry *(*lookup) (struct inode *,struct dentry *, unsigned int);

lookup 함수 오퍼레이션은 다양한 조건에서 lookup_open(), lookup_real() 그리고 lookup_slow() 함수에서 수행합니다.

이 중 lookup_hash() 함수를 보면서 lookup 함수 오퍼레이션 동작을 확인해볼까요?
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
01 static struct dentry *__lookup_hash(const struct qstr *name,
02 struct dentry *base, unsigned int flags)
03 {
04 struct dentry *dentry = lookup_dcache(name, base, flags);
05 struct dentry *old;
06 struct inode *dir = base->d_inode;
...
07 if (unlikely(!dentry))
08 return ERR_PTR(-ENOMEM);
09
10 old = dir->i_op->lookup(dir, dentry, flags);

위 함수 10번째 줄에서 lookup 함수 오퍼레이션을 실행합니다.

permission;

아이노드에 해당 하는 파일이 지정된 접근 모드가 허용되는지 체크합니다. 접근 권한이 있는 경우 0을 반환하고, 반대인 경우 음수 오류 매크로를 반환합니다. 

[선언부]
int (*permission) (struct inode *, int);

이 함수 오퍼레이션은 do_inode_permission() 함수에서 실행합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 static inline int do_inode_permission(struct inode *inode, int mask)
2 {
3 if (unlikely(!(inode->i_opflags & IOP_FASTPERM))) {
4 if (likely(inode->i_op->permission))
5 return inode->i_op->permission(inode, mask);

5번째 줄 코드에서 permission 오퍼레이션 함수를 실행합니다.

readlink;

dentry가 가리키는 심볼링 링크의 전체 디렉토리 경로를 지정한 버퍼 크기만큼 복사합니다.

[선언부]
int (*readlink) (struct dentry *, char __user *,int);

관련 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
01 int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen)
02 {
03 struct inode *inode = d_inode(dentry);
...
04 if (unlikely(!(inode->i_opflags & IOP_DEFAULT_READLINK))) {
05 if (unlikely(inode->i_op->readlink))
06 return inode->i_op->readlink(dentry, buffer, buflen);

vfs_readlink() 함수 06번째 줄 코드에서 readlink 함수 오퍼레이션을 수행합니다.  

create;

지정한 덴트리 객체에 해당 일반 파일용 아이노드를 새로 생성합니다. 주로 create() 시스템 콜 호출 시 실행합니다.

[선언부]
int (*create) (struct inode *,struct dentry *, umode_t, bool);

이 함수 오퍼레이션이 어느 함수에서 수행되는지 확인해볼까요? 
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
01 int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
02 bool want_excl)
03 {
...
04 error = security_inode_create(dir, dentry, mode);
05 if (error)
06 return error;
07 error = dir->i_op->create(dir, dentry, mode, want_excl);

vfs_create() 함수 07번째 줄 코드에서 create 함수 오퍼레이션을 수행합니다.

link;

하드 링크를 새로 생성합니다. 실행 결과 dir 디렉터리 안에 old_dentry가 가리키는 파일을 참조합니다. 새로운 하드 링크는 new_dentry가 지정한 이름으로 저장됩니다.

[선언부]
int (*link) (struct dentry *,struct inode *,struct dentry *);

관련 코드를 분석해볼까요?
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, 2 struct inode **delegated_inode)
3 {
...
4 if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
5 error =  -ENOENT;
6 else if (max_links && inode->i_nlink >= max_links)
7 error = -EMLINK;
8 else {
9 error = try_break_deleg(inode, delegated_inode);
10 if (!error)
11 error = dir->i_op->link(old_dentry, dir, new_dentry);
12 }

vfs_link() 함수 11번째 줄 코드에서 link 함수 오퍼레이션을 수행합니다.

unlink;

덴트리 객체와 아이노드가 저장한 하드링크를 제거합니다.

[선언부]
int (*unlink) (struct inode *,struct dentry *);

unlink 함수 오퍼레이션이 어느 함수에서 수행되는지 알아보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
01 int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode)
02 {
...
03 if (!error) {
04 error = try_break_deleg(target, delegated_inode);
05 if (error)
06 goto out;
07 error = dir->i_op->unlink(dir, dentry);

vfs_unlink() 7번째 줄 코드에서 해당 함수 오퍼레이션을 수행합니다.

symlink;

특정 파일을 심볼릭 링크로 지정할 때 실행합니다. 덴트리 객체가 지정한 파일에 대한 심볼링 링크를 위한 아이노드를 생성합니다. 

[선언부]
int (*symlink) (struct inode *,struct dentry *,const char *);

symlink 함수 오퍼레이션을 실행하는 코드를 볼까요?
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
2 {
...
3 error = security_inode_symlink(dir, dentry, oldname);
4 if (error)
5 return error;
6
7 error = dir->i_op->symlink(dir, dentry, oldname);

vfs_symlink() 7번째 줄 코드에서 해당 함수 오퍼레이션을 수행합니다.

mkdir;

터미널에서 새로운 폴더를 생성할 때 mkdir 명령어를 입력할 때 실행합니다. 인자로 전달되는 dentry객체와 연관된 디렉터리용 inode를 생성합니다. 

[선언부]
int (*mkdir) (struct inode *,struct dentry *,umode_t);

관련 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
2 {
...
3 error = dir->i_op->mkdir(dir, dentry, mode);

vfs_mkdir() 3번째 줄 코드에서 mkdir 필드에 저장된 함수를 호출해 함수 오퍼레이션을 수행합니다.

rmdir;

덴트리 객체 하위 디렉토리를 제거할 때 호출합니다. rmdir이란 시스템 콜과 연동해서 실행합니다.

[선언부]
int (*rmdir) (struct inode *,struct dentry *);

rmdir 함수 오퍼레이션이 어느 함수에서 수행되는지 알아보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 int vfs_rmdir(struct inode *dir, struct dentry *dentry)
2 {
...
3 error = security_inode_rmdir(dir, dentry);
4 if (error)
5 goto out;
6
7 shrink_dcache_parent(dentry);
8 error = dir->i_op->rmdir(dir, dentry);

vfs_rmdir() 함수 8번째 줄 코드에서 rmdir 함수 오퍼레이션을 실행합니다.

mknod;

덴트리 객체와 연관된 장치 파일과 같은 특수 파일용 아이노드를 생성합니다. mknodat 시스템 콜과 연동해서 동작합니다.

[선언부]
int (*mknod) (struct inode *,struct dentry *,umode_t mode, dev_t rdev);

vfs_mknod() 함수 7번째 줄 코드에서 해당 동작을 수행합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
2 {
...
3 error = security_inode_mknod(dir, dentry, mode, dev);
4 if (error)
5 return error;
6
7 error = dir->i_op->mknod(dir, dentry, mode, dev);

rename;

다음 선언부와 같이 old_dentry가 가리키는 파일을 old_dir 디렉터리에서 new_dir 디렉터리로 이동합니다. 새로운 파일 이름을 new_dentry가 가리키는 dentry 객체에 저장됩니다. 

[선언부]
int (*rename) (struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry, unsigned int);
관련 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/namei.c]
1 int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
2        struct inode *new_dir, struct dentry *new_dentry,
3        struct inode **delegated_inode, unsigned int flags)
4{
...
5 error = old_dir->i_op->rename(old_dir, old_dentry,
6        new_dir, new_dentry, flags);

이 함수 오퍼레이션은 vfs_rename() 함수 5번째 줄 코드에서 실행합니다.

아이노드 함수 오퍼레이션에서 디렉토리 파일 처리 방식 알아보기
“/home/pi/sample_text.text” 파일이 있다고 가정합시다. 여기서 home과 pi는 디렉토리이고 sample_text.text는 파일입니다.

리눅스 커널에서는 디렉토리와 파일을 어떻게 해석할까요? sample_text.text와 home 그리고 pi를 모두 파일로 간주합니다. 대신 home, pi를 파일 타입 중 디렉토리로 보는 것입니다. 

만약 /home/pi 디렉토리에서 RPi이란 디렉토리를 생성한다고 가정합시다. 리눅스 커널에서는 디렉토리도 파일로 간주하니 디렉토리를 생성하는 규칙도 아이노드 오퍼레이션에 추가한 것입니다.

아이노드 함수 오퍼레이션 동작 중 다음 필드는 디렉토리 파일을 생성하는 함수 주소를 저장합니다. 
int (*mkdir) (struct inode *,struct dentry *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);

다음은 ext4 파일시스템에서 디렉토리 파일을 관리하는 아이노드 함수 오퍼레이션 정보입니다.
(static struct inode_operations) ext4_dir_inode_operations = (
    (struct dentry * (*)()) lookup = 0x80347A70 = ext4_lookup,
    (char * (*)()) get_link = 0x0 = ,
    (int (*)()) permission = 0x0 = ,
    (struct posix_acl * (*)()) get_acl = 0x8036B9E4 = ext4_get_acl,
    (int (*)()) readlink = 0x0 = ,
    (int (*)()) create = 0x803491A4 = ext4_create,
    (int (*)()) link = 0x8034BA18 = ext4_link,
    (int (*)()) unlink = 0x8034B0E4 = ext4_unlink,
    (int (*)()) symlink = 0x8034B670 = ext4_symlink,
    (int (*)()) mkdir = 0x80349700 = ext4_mkdir,
    (int (*)()) rmdir = 0x8034AE44 = ext4_rmdir,
    (int (*)()) mknod = 0x80349020 = ext4_mknod,
    (int (*)()) rename = 0x8034A10C = ext4_rename2,
    (int (*)()) setattr = 0x80334BB4 = ext4_setattr,
    (int (*)()) getattr = 0x8032F904 = ext4_getattr,
    (ssize_t (*)()) listxattr = 0x80368D54 = ext4_listxattr,
    (int (*)()) fiemap = 0x8031A6E0 = ext4_fiemap,
    (int (*)()) update_time = 0x0 = ,
    (int (*)()) atomic_open = 0x0 = ,
    (int (*)()) tmpfile = 0x80349F88 = ext4_tmpfile,
    (int (*)()) set_acl = 0x8036BC44 = ext4_set_acl)

대부분 아이노드 함수 오퍼레이션으로 함수가 지정돼 있습니다. 

다음은 ext4 파일시스템에서 파일 종류 중 파일(우리가 알고 있는 파일)을 관리하는 아이노드 함수 오퍼레이션 정보입니다.
 (static struct inode_operations) ext4_file_inode_operations = (
    (struct dentry * (*)()) lookup = 0x0 = ,
    (char * (*)()) get_link = 0x0 = ,
    (int (*)()) permission = 0x0 = ,
    (struct posix_acl * (*)()) get_acl = 0x8036B9E4 = ext4_get_acl,
    (int (*)()) readlink = 0x0 = ,
    (int (*)()) create = 0x0 = ,
    (int (*)()) link = 0x0 = ,
    (int (*)()) unlink = 0x0 = ,
    (int (*)()) symlink = 0x0 = ,
    (int (*)()) mkdir = 0x0 = ,
    (int (*)()) rmdir = 0x0 = ,
    (int (*)()) mknod = 0x0 = ,
    (int (*)()) rename = 0x0 = ,
    (int (*)()) setattr = 0x80334BB4 = ext4_setattr,
    (int (*)()) getattr = 0x8032F9C8 = ext4_file_getattr,
    (ssize_t (*)()) listxattr = 0x80368D54 = ext4_listxattr,
    (int (*)()) fiemap = 0x8031A6E0 = ext4_fiemap,
    (int (*)()) update_time = 0x0 = ,
    (int (*)()) atomic_open = 0x0 = ,
    (int (*)()) tmpfile = 0x0 = ,
    (int (*)()) set_acl = 0x8036BC44 = ext4_set_acl)

아이노드 오퍼레이션 필드들 대부분이 0x0으로 함수 포인터 지정을 안 합니다. 이렇게 파일 타입에 따라 아이노드 함수 오퍼레이션 동작이 다릅니다.


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


Reference(가상 파일시스템)

가상 파일시스템 소개
파일 객체
파일 객체 함수 오퍼레이션 동작
프로세스는 파일객체 자료구조를 어떻게 관리할까?
슈퍼블록 객체
아이노드 객체
덴트리 객체
가상 파일시스템 디버깅

핑백

덧글

댓글 입력 영역