Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[리눅스커널][가상파일시스템] open(): 파일 객체를 오픈할 때 세부 동작 13. Virtual Filesystem

open(): 파일 객체를 오픈할 때 세부 동작

유저 공간에서 open() 함수를 실행하면 커널 공간에서 다음 그림과 같은 함수 흐름을 확인할 수 있습니다.
 

위 그림은 유저 공간에서 함수 오픈 오퍼레이션을 실행할 때 함수 실행 흐름입니다. 유저 공간에서 open() 함수를 호출하면 시스템 콜을 발생시켜 실행 흐름이 커널 공간으로 바뀝니다. 이후 open() 함수에 해당하는 시스템 콜 핸들러 함수인 sys_open() 함수가 실행 한 후 ext4 파일시스템에서 관리하는 파일 오픈 함수인 ext4_file_open() 함수를 호출합니다.

이전 절에서 유저 공간에서 read() 혹은 write() 함수를 실행할 때도 위와 비슷한 함수 실행 흐름을 확인했습니다. 파일을 오픈할 때는 read()나 write() 시스템콜 핸들러 함수를 호출할 때에 비해 더 많은 동작을 수행합니다. 파일 객체를 생성하고 생성된 파일 객체를 프로세스 태스크 디스크립터에 저장하는 것입니다.
 

파일을 열 때 실행 흐름은 3단계로 분류할 수 있습니다.
 
 
1단계: 파일 객체 생성
파일 오픈을 할 때 가장 먼저 파일 객체를 생성합니다. 이 때 __alloc_fd() 함수를 호출합니다.

2단계: 파일 오퍼레이션 실행
 각 파일시스템에서 관리하는 파일 종류별로 등록된 open() 함수를 호출합니다. 만약 “/home/pi/sample_text.text” 이란 파일을 오픈할 경우 ext4_file_open() 함수를 실행합니다. 위 그림에서 본 함수 호출 흐름은 이 단계에 해당 합니다.

3 단계: 파일객체 태스크 디스크립터 등록
파일 오픈에 대한 함수 오퍼레이션을 마무리하면 프로세스에서 관리하는 파일 디스크립터 테이블에 파일 객체를 등록합니다.

파일을 오픈해서 읽고 쓸 때 프로세스가 파일 객체와 파일 디스크립터를 관리합니다. 이 세부 동작은 다음 소절에서 살펴볼 예정입니다.

sys_open() 함수 코드를 분석하기 전에 함수 선언부를 보겠습니다.
asmlinkage long sys_open(const char __user *filename,
int flags, umode_t mode);

sys_open() 함수에 전달하는 인자와 속성은 다음과 같습니다.
const char __user *filename: 파일 이름
int flags: 플래그
umode_t mode: 모드 

sys_open() 함수는 정수형인 파일 디스크립터를 반환합니다.

다음은 라즈베리파이에서 추출한 ftrace 로그로 시스템 콜 핸들러 sys_open() 함수를 호출할 때 메시지입니다.
1 lxterminal-794 [000] .... 172.191637: sys_enter: NR 5 (11749f8, 200c2, 180, 0, c2, 11749f8)
2 lxterminal-794 [000] .... 172.191847: ext4_file_open+0x14/0x21c <-do_dentry_open+0x218/0x324
3 lxterminal-794 [000] .... 172.191885: <stack trace>
4 => path_openat+0x40c/0xf48
5 => do_filp_open+0x74/0xd8
6 => do_sys_open+0x13c/0x22c
7 => SyS_open+0x28/0x2c
8 => __sys_trace_return+0x0/0x10
9 lxterminal-794   [000] ....   172.191900: sys_exit: NR 5 = 15

위 메시지는 1번째와 9번째 메시지는 시스템콜 동작을 출력하고, 2~8번째 메시지는 ext4_file_open() 함수를 호출할 때 함수 호출 흐름을 표현합니다.

먼저 시스템 콜 동작을 먼저 보겠습니다. 1번째 줄 메시지를 보겠습니다.
1 lxterminal-794 [000] .... 172.191637: sys_enter: NR 5 (11749f8, 200c2, 180, 0, c2, 11749f8)

"NR 5" 이란 정보로 open이 시스템 콜 번호가 5번이란 사실을 알 수 있습니다.

9번째 줄 메시지에서 눈여겨볼 로그는 "NR 5 = 15"입니다.  
9 lxterminal-794   [000] ....   172.191900: sys_exit: NR 5 = 15

파일을 오픈하고 나서 정수형 파일 디스크립터를 15로 등록했다는 의미입니다.

유저 공간에서 다음 코드를 실행하면 fd란 지역변수로 파일 디스크립터를 얻습니다. 
fd = open(FILENAME_NAME, O_RDWR);

위 코드 기준으로 fd란 지역변수는 15으로 변경됩니다. 

2~8번째 줄은 ext4_file_open() 함수가 실행할 때 함수 호출 흐름(콜스택)입니다.
함수 실행 흐름을 눈으로 따라가 보시기 바랍니다. do_dentry_open() 함수에서 ext4_file_open() 함수를 호출하는 정보가 중요합니다.

sys_open() 함수 코드 구현부를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/open.c]
1 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
2 {
3 if (force_o_largefile())
4 flags |= O_LARGEFILE;
5
6 return do_sys_open(AT_FDCWD, filename, flags, mode);
7 }

함수 코드를 보면 특별한 동작을 수행하지 않고 do_sys_open() 함수를 호출할 뿐입니다.
6번째 줄 코드와 같이 do_sys_open() 함수를 호출합니다.

다음 do_sys_open() 함수를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/open.c]
1 long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
2 {
3 struct open_flags op;
4 int fd = build_open_flags(flags, mode, &op);
5 struct filename *tmp;
...
6
7 fd = get_unused_fd_flags(flags);
8 if (fd >= 0) {
9 struct file *f = do_filp_open(dfd, tmp, &op);
10 if (IS_ERR(f)) {
11 put_unused_fd(fd);
12 fd = PTR_ERR(f);
13 } else {
14 fsnotify_open(f);
15 fd_install(fd, f);
16 }
17 }
18 putname(tmp);
19 return fd;
20 }

do_sys_open() 함수에서 많은 동작을 수행하므로 면밀히 살펴볼 필요가 있어 함수 동작을 3단계로 나눠 봅시다.
1. 파일 디스크립터를 커널에 요청해서 할당 받음
2. 가상 파일 시스템에 접근해서 파일을 오픈함
3. 파일 디스크립터를 프로세스 태스크 디스크립터에 저장함

1단계 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/fs/file.c]
int get_unused_fd_flags(unsigned flags)
{
return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}

프로세스 태스크 디스크립터의 files이란 멤버(구조체: struct files_struct)를 첫 번째 인자로 _alloc_fd() 함수를 호출합니다. current는 현재 코드를 실행 중인 프로세스 태스크 디스크립터에 접근하는 매크로입니다.

2단계 입니다. 파일 오퍼레이션에 지정된 정보로 파일 오픈 함수를 호출합니다.
8 if (fd >= 0) {
9 struct file *f = do_filp_open(dfd, tmp, &op);

3단계입니다. fd_install() 함수를 호출해서 파일 디스크립터를 프로세스 태스크 디스크립터에 저장합니다.
10 if (IS_ERR(f)) {
11 put_unused_fd(fd);
12 fd = PTR_ERR(f);
13 } else {
14 fsnotify_open(f);
15 fd_install(fd, f);
16 }

파일 디스크립터 테이블을 로딩한 다음에 파일 디스크립터를 저장합니다. 위에 fd란 정수가 보이는데 유저 어플리케이션에서 로딩한 fd를 의미합니다.

해당 프로세스 태스크 디스크립터에 파일 디스크립터를 저장하는 공간이 있습니다. 파일을 오픈하면 파일 디스크립터 테이블에 파일 디스크립터를 저장합니다.

그래서 파일을 오픈하고 나면 프로세스는 기존에 저장된 파일 디스크립터 테이블에 접근해서 파일 디스크립터를 로딩합니다. 만약 유저 어플리케이션에서 파일 디스크립터를 닫으면 이에 맞게 프로세스 태스크 디스크립터의 파일 디스크립터 테이블에서 해당 파일 디스크립터를 지웁니다.

파일 디스크립터는 가상 파일 시스템에서 가장 중요한 자료구조 중 하나입니다.
파일 디스크립터로 아이노드, 슈퍼 블락 및 파일 오퍼레이션 등등 모든 파일을 관리할 수 있는 객체에 접근할 수 있기 때문입니다.

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

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


Reference(가상 파일시스템)

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

핑백

덧글

댓글 입력 영역