Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

493
557
422263


[라즈베리파이] 가상 파일시스템 동작 분석(유저 프로세스 입장) 13. 가상 파일 시스템

이번에 유저 어플리케이션 입장에서 가상 파일시스템 동작을 살펴보겠습니다.

먼저 유저 공간에서 실행하는 다음 코드를 같이 작성해 봅시다.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <signal.h>
6 #include <string.h>
7 #include <fcntl.h>
8
9 #define FILENAME_NAME "/home/pi/sample_text.text"
10
11 int main() 
12 {
13 int fd = 0;
14 ssize_t read_buf_size;
15 off_t new_file_pos;
16
17    char buf[256];
18    char string[] = "Raspbian Linux!\n";
19    
20    fd = open(FILENAME_NAME, O_RDWR);
21    
22    read_buf_size = read(fd, buf, 256);
23    printf("%s", buf);
24    
25    memset(buf, 0x0, sizeof(buf));
26
27    write(fd, string, strlen(string));
28    
29    new_file_pos = lseek(fd, (off_t)0, SEEK_SET);
30    
31    read_buf_size = read(fd, buf, 256);
32    printf("read again \n");
33    printf("[+]read buffer: %s \n", buf);
34
35 close(fd);
36
37 return 0;
38 }

"/home/pi/sample_text.text" 파일을 열어서 "Raspbian Linux!\n” 텍스트를 써주는 간단한 프로그램입니다.

20번째 줄 코드를 보면 "/home/pi/sample_text.text" 파일을 open() 함수로 접근합니다.
9 #define FILENAME_NAME "/home/pi/sample_text.text"
...
20    fd = open(FILENAME_NAME, O_RDWR);

open() 함수를 쓰면 지정한 파일을 두 번째 아규먼트(O_RDWR)에 맞게 오픈하는 동작을 수행합니다.

여기서 유저 어플리케이션 입장에서 "/home/pi/sample_text.text" 파일을 어떤 파일시스템에서 관리하는지 알 필요가 없습니다. 단지 open() 이란 함수만 쓰면 리눅스 커널에서 지정한 파일을 관리하는 파일시스템을 찾아서 지정된 파일을 열어 주기 때문입니다.

open() 함수를 호출하면 fd란 정수형 값을 반환합니다. fd는 파일 디스크립터라고 하며 파일에 대한 식별자입니다. 파일을 정수로 식별할 수 있으니 유저 어플리케이션은 쉽게 파일을 처리할 수 있습니다.

fd는 누가 반환을 할까요? 주인공은 리눅스 커널 가상 파일 시스템입니다.
 

위 그림에서 vfs_open() 함수에서 호출하는 do_dentry_open() 함수가 보입니다. 가상 파일시스템에서 해당 파일을 관리하는 파일시스템에서 파일 열기 함수를 호출하는 역할을 수행합니다.

가상 파일시스템 계층에서 sample_text.text 이란 파일이 /home/pi 디렉토리에 위치했으니 ext4 파일시스템에서 이 파일을 처리하도록 분기하는 것입니다.

/home/pi 디렉토리에 위치한 데이터는 하드 디스크와 같은 저장매체에 저장됩니다. 이런 유형의 파일시스템을 일반 파일시스템이라고 하며 라즈베리파이에선 ext4 파일시스템을 적용합니다.

다음 22번째와 27번째 줄 코드를 보겠습니다.
22    read_buf_size = read(fd, buf, 256);
23    printf("%s", buf);
24    
25    memset(buf, 0x0, sizeof(buf));
26
27    write(fd, string, strlen(string));

파일을 식별하는 정수값인 fd란 파일 디스크립터로 파일을 읽고 쓰는 동작을 수행합니다
이 함수들을 호출할 때도 마찬가지입니다. 역시 어떤 파일 시스템에 접근해서 지정한 파일을 읽고 쓰는지 알 필요가 없습니다.
 
이와 같이 유저 어플리케이션에서 파일에 대한 핸들을 간단히 관리할 수 있는 이유는 리눅스 커널 가상 파일 시스템에서 해당 파일에 대한 세밀한 처리를 하기 때문입니다.

이번에는 다른 파일시스템에 있는 파일을 오픈할 때 어떤 함수를 실행하는지 알아보겠습니다. 이를 위해 다음 파일을 열겠습니다.
root@raspberrypi:/proc # cat kmsg 

위 파일을 읽을 때 가상 파일시스템에서 어떤 함수 흐름일까요?
 
do_dentry_open() 함수에서 이번에는 proc_reg_open() 함수를 호출합니다.

ext4 파일시스템에서 관리하는 sample_text.text 파일을 오픈할 때는 do_dentry_open() 함수에서 ext4_file_open() 함수를 호출했습니다. do_dentry_open() 함수 코드를 보면서 이 과정에 대해 조금 더 생각해 봅시다.
1 static int do_dentry_open(struct file *f,
2   struct inode *inode,
3   int (*open)(struct inode *, struct file *),
4   const struct cred *cred)
5 {
...
6 if (!open)
7 open = f->f_op->open;
8 if (open) {
9 error = open(inode, f);

9번째 줄 코드에서는 proc_reg_open() 함수를 호출합니다. 이 이유는 무엇일까요?

가상 파일시스템에서 파일 위치에 따라 파일 시스템 종류를 식별해서 파일 시스템 별 함수 테이블을 로딩하기 때문입니다. proc 파일 시스템의 경우 파일을 열 때 proc_reg_open() 함수를 실행해서 파일 오픈을 실행합니다.

이번에는 /proc/kmsg 파일을 열고 읽을 때 동작을 확인하겠습니다. 
 
이렇게 /proc/kmsg 파일은 proc 파일시스템에서 관리하는 특수 파일입니다. 가상 파일시스템에서 /proc/kmsg 파일이 proc 파일시스템에서 관리한다는 사실을 파악한 후 proc 파일시스템에서 등록한 파일 오퍼레이션 함수 테이블에 따라 함수를 분기합니다.

다음은 리눅스 커널에서 sysfs 파일시스템에서 관리하는 파일을 하나 열어보겠습니다.
root@raspberrypi:/sys/devices/system/cpu/cpu0/cpufreq# cat cpuinfo_cur_freq 
1200000

위 파일은 CPU 주파수 정보를 출력합니다.


sysfs는 리눅스 커널이 지원하는 장치 드라이버에 대한 정보를 출력하는 가상 파일시스템 중 하나입니다. 디바이스와 하드웨어 장치를 디렉토리 계층 구조로 알려줍니다.


다음 그림을 보면서 sysfs 파일시스템에서 관리하는 파일을 오픈할 때 어떤 함수 흐름인지 살펴봅시다.
 

sysfs 파일시스템에서 관리하는 파일을 열 때는 kernfs_fop_open() 함수를 호출해서 파일 오픈을 수행합니다.  

이번에는 sysfs 파일시스템에서 관리하는 파일을 열 때 kernfs_fop_open() 함수를 호출합니다.

다음 그림은 sysfs 파일시스템에서 파일을 읽고 쓸 때 어떤 함수 흐름인지 표현합니다.
 
 
파일을 읽을 때는 kernfs_fop_read() 함수, 파일을 쓸 때는 kernfs_fop_write() 함수를 호출합니다.

여기까지 각 파일시스템별로 파일을 오픈하고 읽고 쓸 때 흐름을 살펴봤습니다. 이제 가상 파일시스템 관점으로 위에서 다룬 내용을 정리합시다.

파일을 오픈해서 열고 쓰고 닫을 때 가상 파일시스템 계층에서 각 파일 시스템 별 함수 테이블로 분기를 시킵니다. 이런 동작이 가능한 이유는 가상 파일시스템에서 파일 객체란 struct file 자료 구조가 파일 시스템 별 파일 오픈 및 쓰고 읽기 동작에 대한 관리를 해주기 때문입니다.

다음 그림은 파일시스템 별로 파일을 오픈할 때 함수 흐름을 볼 수 있습니다.
 

어떤 파일을 읽을 때 가상 파일시스템 계층에서는 다음과 같은 동작을 수행합니다.
1. 덴트리 객체로 현재 실행 중인 디렉토리를 관리하는 파일 시스템 점검
2. 파일 종류별 파일 오퍼레이션 로딩
3. 파일을 읽고 쓰는 동작을 수행할 때 파일 시스템 함수 테이블로 분기

소프트웨어 계층 구조 관점으로 가상 파일시스템은 파일시스템 상위에서 파일시스템을 분기하고 인터페이싱 하는 역할을 수행합니다. 이 구조로 소프트웨어 구조를 설계하면 다양한 파일 시스템을 공존해서 실행시킬 수 있습니다.

라즈베리파이에서는 위에서 언급한 ext4, proc, sysfs 파일 시스템 이외에 다른 파일 시스템도 지원합니다. 이 파일시스템에 접근하기 위해서 가상 파일시스템 계층을 거쳐야 하는 것입니다.


라즈베리파이에서 지원하는 파일시스템 종류는 어떻게 파악할 수 있을까요? 다음 경로에 있는 파일을 열면 확인할 수 있습니다.
root@raspberrypi:/proc # cat filesystems 
nodev  sysfs
nodev  rootfs
nodev  ramfs
nodev  bdev
nodev  proc
nodev  cpuset
nodev  cgroup
nodev  cgroup2
nodev  tmpfs
nodev  devtmpfs
nodev  configfs
nodev  debugfs
nodev  tracefs
nodev  sockfs
nodev  pipefs
nodev  rpc_pipefs
nodev  devpts
ext3
ext2
ext4
vfat
msdos
nodev  nfs

nodev  nfs4
nodev  autofs
f2fs
nodev  mqueue
fuseblk
nodev  fuse
nodev  fusectl


VFS는 파일시스템별 함수 테이블을 로딩해서 동적으로 파일시스템이 관리하는 함수를 호출하는 역할을 수행합니다. 그래서 가상 파일시스템에서는 함수 포인터로 각 파일시스템별로 등록된 함수를 호출하는 패턴이 많습니다. 위에서 알아봤던 open(), write() 그리고 read() 함수는 함수 포인터로 각 파일 열고 닫기 동작을 수행합니다. 

함수 포인터로 함수를 호출하는 동작은 슈퍼블락, 아이노드 그리고 덴트리 객체에서도 확인할 수 있습니다.

ext4_alloc_inode() 함수가 어느 코드에서 실행하는지 확인합시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/inode.c]
1 static struct inode *alloc_inode(struct super_block *sb)
2 {
3 struct inode *inode;
4
5 if (sb->s_op->alloc_inode)
6 inode = sb->s_op->alloc_inode(sb);

파일시스템 별로 파일시스템별 전체 메타 데이터와 세부 동작을 관리하는 주인공은 슈퍼 블록 객체입니다. 슈퍼블록 객체 내 각 파일시스템별 함수 테이블 정보인 s_op 멤버가 있습니다.

6번째 줄 코드에서 파일시스템별 슈퍼블록 함수 테이블 정보가 포함된 sb->s_op 멤버에 있는 alloc_inode 함수 포인터를 실행하는 것 입니다.

이번에는 proc 파일 시스템에서 아이노드를 생성하는 함수 흐름을 보겠습니다.
 

alloc_inode() 함수 6번째 줄 코드에서 proc_alloc_inode() 함수를 호출해서 아이노드를 각자 파일 시스템에서 생성하게 합니다.

VFS 객체는 대부분 슈퍼블락, 아이노드, 덴트리 그리고 파일 객체 순서로 소개하는 경우가 많습니다. 저자는 VFS을 가장 이해하기 쉽고 디바이스 드라이버에서도 활용하기 위해 객체 순서(파일객체, 아이노드 객체, 덴트리 객체, 슈퍼블록 객체)로 소개합니다.

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

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


Reference(가상 파일시스템)

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


# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2


핑백

덧글

댓글 입력 영역