Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[리눅스커널][가상파일시스템] 수퍼블록: 슈퍼블록 정보를 statfs 시스템 콜로 읽는 과정 살펴보기 13. Virtual Filesystem

슈퍼블록 각 멤버들은 파일시스템에 대한 메타 정보를 저장합니다.
유저 공간에서 파일시스템 정보를 알려면 어떤 함수를 호출해야 할까요?

유저 공간에서 statfs() 함수를 호출하면 커널 공간에서 해당 시스템 콜 핸들러 함수인 sys_statfs() 함수를 실행합니다.

예제 코드는 다음과 같습니다.
1 #define FILENAME_NAME "/home/pi"
2 #define BUFF_SIZE 256
3 int main() 
4 {
5 struct statfs file_sys_info;
6 char fname[BUFF_SIZE] = {0,};
7
8 strcpy(fname, FILENAME_NAME);
9
12 if(statfs(fname, &file_sys_info)) {
13 printf("Unable to statfs %s \n", fname);
14 exit(1);
15 }
16
17 return 0;
18}

12번째 줄과 같이 statfs() 함수 첫 번째 인자로 디렉토리 경로와 struct statfs 구조체인 file_sys_info 변수를 지정하면 파일시스템 속성 정보를 읽을 수 있는 것입니다.
12 if(statfs(fname, &file_sys_info)) {

유저 공간에서 statfs() 함수를 호출하면 커널 공간에서 해당 시스템 콜 핸들러 함수인 sys_statfs() 함수를 실행합니다.

이후 커널 공간에서 실행하는 함수 흐름은 다음 ftrace로 확인할 수 있습니다.
vfs_stat_fs-1990 [003] 12724.178684: sys_enter: NR 99 (7ecad4e8, 7ecad5e8, 7ecad5e8, 7ecad4e8, 10620, 0)
 vfs_stat_fs-1990 [003] 12724.178698: ext4_statfs+0x14/0x390 <-statfs_by_dentry+0x54/0x78
 vfs_stat_fs-1990  [003] 12724.178733: <stack trace>
 => user_statfs+0x54/0x90
 => SyS_statfs+0x24/0x40
 => __sys_trace_return+0x0/0x10
 vfs_stat_fs-1990  [003] 12724.178744: sys_exit: NR 99 = 0

99번째 시스템 콜 핸들러 함수는 다음 해더 파일에서 확인할 수 있습니다.
[/usr/include/arm-linux-gnueabihf/asm/unistd.h]     
#define __NR_statfs (__NR_SYSCALL_BASE+ 99)


이번에는 sys_statfs() 함수 선언부를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/include/linux/syscalls.h]
asmlinkage long sys_statfs(const char __user * path,
struct statfs __user *buf);

다음 각각 함수에 전달하는 인자를 확인합시다.
const char __user * path;

디렉토리나 파일 경로를 의미합니다. 
*path는 "/home/pi" 혹은 "/home/pi/sample_text.txt" 가 될 수 있는 것입니다.

struct statfs __user *buf;

유저 공간에 저장할 파일시스템 속성 정보 구조체가 있는 메모리 버퍼 주소입니다.

이번에는 sys_statfs() 함수 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/statfs.c]
1 SYSCALL_DEFINE2(statfs, const char __user *, pathname, struct statfs __user *, buf)
2 {
3 struct kstatfs st;
4 int error = user_statfs(pathname, &st);
5 if (!error)
6 error = do_statfs_native(&st, buf);
7 return error;
8 }

sys_statfs() 함수 동작은 크게 2단계로 나눌 수 있습니다.

1단계: statfs 슈퍼블록 오퍼레이션 함수 호출
파일시스템에서 슈퍼블록 함수 오퍼레이션으로 statfs 멤버로 지정한 함수를 호출합니다.

2단계: 유저 공간에 파일시스템 정보 저장
do_statfs_native() 함수를 호출해서 슈퍼블록 객체에서 관리하는 속성 정보를 저장합니다.

여기서 기억할 포인트는 struct kstatfs 구조체는 커널 공간에서 파일시스템 메타 정보를 관리하는 구조체이고, struct statfs는 유저 공간에서 파일시스템 정보를 읽는 구조체이란 점입니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/statfs.c]
1 int user_statfs(const char __user *pathname, struct kstatfs *st)
2 {
3 struct path path;
4 int error;
5 unsigned int lookup_flags = LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT;
6 retry:
7 error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path);
8 if (!error) {
9 error = vfs_statfs(&path, st);

6번째 줄 코드를 보겠습니다.
7 error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path);

파일 경로에 대한 예외 처리를 수행한 후 파일 경로에 맞는 덴트리 정보가 포함된 정보를 path(struct path) 지역변수로 읽습니다. 이후 8번째 줄 코드와 같이 vfs_statfs() 함수를 호출합니다.

유저 공간에서 지정한 파일 경로가 유효한지 점검한 후, vfs_statfs() 함수를 호출하는 것입니다.

다음으로 vfs_statfs() 함수 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/statfs.c]
1 int vfs_statfs(const struct path *path, struct kstatfs *buf)
2 {
3 int error;
4
5 error = statfs_by_dentry(path->dentry, buf);

vfs_statfs() 함수 5번째 줄에서 statfs_by_dentry() 함수를 호출합니다.

statfs_by_dentry() 함수 코드를 분석하겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/statfs.c]
1 static int statfs_by_dentry(struct dentry *dentry, struct kstatfs *buf)
2 {
3 int retval;
4
5 if (!dentry->d_sb->s_op->statfs)
6 return -ENOSYS;
7
8 memset(buf, 0, sizeof(*buf));
9 retval = security_sb_statfs(dentry);
10 if (retval)
11 return retval;
12 retval = dentry->d_sb->s_op->statfs(dentry, buf);

5~6번째 줄 코드를 봅시다.
5 if (!dentry->d_sb->s_op->statfs)
6 return -ENOSYS;

덴트리 객체로 얻어온 슈퍼블락 객체 함수 오퍼레이션으로 statfs 멤버가 지정돼있지 않으면 -ENOSYS를 반환하고 함수 실행을 종료합니다.

다음 12번째 줄 코드를 분석하겠습니다. 
12 retval = dentry->d_sb->s_op->statfs(dentry, buf);

슈퍼 블락 함수 오퍼레이션으로 지정한 statfs 함수를 호출합니다.

ext4 파일시스템에 대한 정보를 채워주는 함수를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/ext4/super.c]
static int ext4_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct super_block *sb = dentry->d_sb;
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_super_block *es = sbi->s_es;
ext4_fsblk_t overhead = 0, resv_blocks;
...
buf->f_bsize = sb->s_blocksize;
buf->f_blocks = ext4_blocks_count(es) - EXT4_C2B(sbi, overhead);
...
buf->f_bfree = EXT4_C2B(sbi, max_t(s64, bfree, 0));
buf->f_bavail = buf->f_bfree -
(ext4_r_blocks_count(es) + resv_blocks);

struct kstatfs 구조체인 buf이란 포인터에 파일시스템 메타 정보를 저장하는 것입니다.

ext4 파일시스템에 대한 세부 동작은 이 책의 범위를 넘어섭니다.
각 파일시스템별로 슈퍼블락 함수 오퍼레이션으로 statfs 멤버를 지정했으면 해당 함수가 호출된다는 사실을 기억합시다.

이번에는 sys_statfs() 함수 분석으로 되돌아가서 2단계 코드를 보겠습니다. 
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/statfs.c]
1 SYSCALL_DEFINE2(statfs, const char __user *, pathname, struct statfs __user *, buf)
2 {
3 struct kstatfs st;
4 int error = user_statfs(pathname, &st);
5 if (!error)
6 error = do_statfs_native(&st, buf);
7 return error;
8 }

user_statfs() 함수를 호출하면 서부 루틴으로 statfs_by_dentry() 함수를 호출해서 각 파일시스템별로 지정한 슈퍼블락 함수 멤버로 statfs를 실행했습니다. 
     vfs_stat_fs-10633 [001] ...1  2950.414366: ext4_statfs+0x28/0x240 <-statfs_by_dentry+0x78/0x9c
     vfs_stat_fs-10633 [001] ...1  2950.414379: <stack trace>
 => vfs_statfs+0x28/0xbc
 => user_statfs+0x60/0xb0
 => SyS_statfs+0x38/0x70

do_statfs_native() 함수 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/statfs.c]
1 static int do_statfs_native(struct kstatfs *st, struct statfs __user *p)
2 {
3 struct statfs buf;
...
4 buf.f_type = st->f_type;
5 buf.f_bsize = st->f_bsize;
6 buf.f_blocks = st->f_blocks;
7 buf.f_bfree = st->f_bfree;
8 buf.f_bavail = st->f_bavail;
9 buf.f_files = st->f_files;
10 buf.f_ffree = st->f_ffree;
11 buf.f_fsid = st->f_fsid;
12 buf.f_namelen = st->f_namelen;
13 buf.f_frsize = st->f_frsize;
14 buf.f_flags = st->f_flags;
15 memset(buf.f_spare, 0, sizeof(buf.f_spare));
16 }
17 if (copy_to_user(p, &buf, sizeof(buf)))
18 return -EFAULT;
19 return 0;
20 }

4~14번째 줄 코드를 보겠습니다.
struct kstatfs 구조체인 *st 포인터에 저장된 각각 멤버를 struct statfs 구조체인 *buf 인자 멤버에 복사합니다.

다음 17번째 줄 코드를 분석하겠습니다.
17 if (copy_to_user(p, &buf, sizeof(buf)))
18 return -EFAULT;

유저 공간에 struct statfs 구조체가 있는 메모리 버퍼 주소인 *p에 *buf 인자에 복사 합니다.


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

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


Reference(가상 파일시스템)

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

핑백

덧글

댓글 입력 영역