On-Memory File System

706__·2021년 2월 18일
0

Operating System

목록 보기
2/2
post-thumbnail

Homework

1. Gentoo Linux

  • Gentoo Linux에는 /dev/sda3과 /dev/sda1 두 개의 Disk가 존재
  • root file system을 찾는다.
  • Mount 명령어를 사용해 mounting point를 찾는다.
  • /dev/sda3의 Mounting point가 root directory인 것으로 보아, root file system은 /dev/sda3이다.

2. Add another entry in /boot/grub/grub.conf

  • MyLinux3이 생성된 것을 확인하였다.

3. Find out the call chain that leads to "Mount_root"

  • System이 부팅되면 start_kernel이 가장 먼저 실행된다.
  • 이후 Kernel_init이 Schedule되며 prepare namespace 함수가 실행된다.

  • prepare_namespace 함수의 끝 부분에서 mount_root 함수가 선언되었다,
  • mount_root 함수는 root file system을 caching한다

4. Find the data for Superblock, Inode, Buffer_head and Dentry


5. Change the kernel that Display all Superblocks before / after it calls "mount_root"

  • Caching된 모든 Superblock을 출력하는 함수를 작성한다.
  • Superblock Linked-list를 가리키는 글로벌 변수 super_blocks를 이용한다.
  • mount_root 호출 위 아래에 출력함수를 호출한 뒤, recompile 및 reboot를 수행한다.

  • mount_root 함수 이후에 root file system인 /dev/sda3이 추가로 출력되었다.
  • 이를 통해 mount_root 함수는 root file system을 caching하는 것을 확인할 수 있다.

6. Change the kernel that Displays all cached Inodes before / after it calls "mount_root"

  • 캐시된 모든 Inode를 출력하는 함수를 작성한다.
  • Inode Linked-list를 가리키는 글로벌 변수 Inode_in_use를 이용한다.
  • mount_root 함수 위 아래에 호출하고, recompile 및 reboot 수행한다.


7. Change the kernel that it execs to /bin/sh

  • PID = 1인 프로세스 (Kernel_init)은 run_init_process ("/sbin/init")로 /sbin/init에 실행된다.
  • "init/main.c/init_post 함수에서 kernel_execve("/sbin/init")를 호출한다.
  • Kernel이 실행되도록 /bin/sh로 변경한다.
  • Kernel을 재부팅하면 /boot/grub/grub.conf에 access할 수 없는 이유를 설명한다.

  • born shell이 실행되었고, boot directory는 비어있다.
  • /sbin/init 대신 /bin/sh가 먼저 exec되면서 필요한 file system들이 mount되지 못했기 때문이다.
  • 원래라면, run_init_process("sbin/init", ...)는 kernel_execve("sbin/init", ...)를 처리하며 이 때 old body인 Linux는 /sbin/init으로 교체된다.
  • /sbin/init은 fork를 통해 child process를 다수 생성하며, /sbin/init의 마지막 child process가 execve("/bin/agetty", ...)를 처리하며 /bin/agettry로 process를 전환한다.
  • agetty는 화면에 Login을 찍고, 아이디를 받은 후 passwd를 찍는 프로그램이며, 사용자가 login하면 'login' process로부터 exec한다.
  • 정상적인 login이 이루어지면 fork로 child를 생성한 후, child process는 /bin/bash로 exec한다.
  • 이 때부터 bash는 shell code를 수행하며 사용자가 입력한 명령어를 처리한다.

File System Call

File Table

  • 각 열린 파일들에 대해, 파일 구조 { }를 가진다.
  • f_list → next file { }
  • f_dentry → 이 file의 Inode (실제로 dentry { })에 대한 Link
  • f_op → 이 file에 대한 연산 (open, read, write ...)
  • f_pos → file read / write pointer. 즉, 얼마나 읽혀졌는지를 보여줌
  • f_count → 이 file에 대한 link의 수

Open

$ x = open(“/aa/bb”, O_RDWR, 00777);
  • Inode Table에서 /aa/bb의 Inode를 찾는다.
  • 찾은 Inode 위치를 File Table에 기록하는데, File 구조체를 할당. 주요 정보는 4 가지 존재
  • f_dentry가 pointer로 Inode Table에 찾은 Inode를 가리킨다.
  • File 구조체를 point하는 Pointer를 fd table에 할당해주는데, 이는 배열 형태이다.

비어 있는 공간을 찾아서 pointer를 전달하는데, 이를 Chaining이라 한다.

  • 즉, fd table에서 할당된 pointer가 /aa/bb 파일이 된다.
  • /aa/bb의 Inode의 위치는 file table에 저장하고, file table의 위치는 fd table에 저장한다.
  • open("/aa/bb", ...)의 반환값 x는 fd table에 저장되어 있는 file table의 위치가 된다.

f_pos는 그 파일을 읽거나 쓸 때, 시작하는 위치로, 처음 시작하면 f_pos는 0이 된다.

  • x 값에 /aa/bb를 가리키는 fd 값이 return된다.
  • 똑같은 파일이어도 다시 한 번 open하면 위의 5 STEP을 거쳐 비어 있는 FD를 찾아서 Chaining 후, FD 위치를 return해준다.

반환된 FD의 return 값은 달라도, 그 값이 가리키는 파일은 /aa/bb로 동일하다.

  • FD의 Default값으로 1023까지 open이 가능하다.

이 때, FD의 0, 1, 2는 이미 사용이 되고 있으므로, 빈 공간을 할당하게 될 경우, 3번이 return이 된다.


Read

$ y = read(x, buf, 10);
  • fd table에 할당된 x에 가서 10 Byte를 읽는다.
  • fd[x]로 가리키는 파일로 가서 f_op->read()를 이용해 10 Byte를 읽어 Buf에 저장한다.
  • 반환값으로 읽어들인 Byte를 가진다.

즉, x를 열어서 10 Byte를 읽고 이를 Buf에 저장하며, 반환값 y로는 읽어들인 Byte 수를 갖는다.

  • x가 4번에 가서 pointer를 따라갔더니 /aa/bb를 가리키고 있으며, 현재 f_pos 위치에서부터 읽어들인 Byte를 읽어들인 후, Buf에 넣어주고, f_pos는 10으로 증가가 된다.

뒤에 다시 read를 열게 되면 f_pos = 10부터 반복
이 된다.


Close

$ close(x);
  • x가 가리키는 4번 pointer를 끊으라는 의미이다.

4번이 가리키는 주소를 reset해서 0으로 만들어 비우게 된다.


lseek

$ lseek(x, 20, 0);

f_pos를 바꿔주는 함수이다.

  • 4번 파일에 가서 f_pos를 20으로 바꿔준다.
  • 그렇게 되면 다음에 read, open을 할 때, 바뀐 f_pos = 20에서부터 동작이 수행된다.

Dup

$ dup(x);

fd의 값을 복사하라는 의미이다.

  • fd 4번의 주소값을 복사해서, fd에서 0번부터 시작해서 첫번째로 비어있는 공간을 만나면, 그 주소값을 비어있는 공간에 넣어주고, 그 위치를 반환한다.

  • dup 후에 read, write을 하면 같은 파일에 같은 f_pos를 쓰기 때문에 서로 영향을 받는다.

f_count는 얼마나 많은 fd가 파일 구조체를 가리키는지 표시해준다.

  • dup(x)를 통해 복사한 주소값을 다른 fd에 넣어주기 때문에, 기본값인 f_count = 1이 2가 된다.

$ link("/aa/bb", "/aa/newbb");

실제 Block에 가서 정보를 변경하는 함수이다.

  • /aa/bb라는 원 파일에 대한 link 파일을 /aa/newbb로 바로가기 파일을 새로 만들어준다는 의미이다.

즉, 같은 Inode number를 가지지만, fname이 bb와 newbb로 추가가 된다.


Fork

$ x2 = fork();

Process 관련 system call이다.

  • fork는 process 자체를 복제하므로 process 내부를 관리하는 fd table 역시 복제가 된다.
  • 즉, child에게 같은 fd table이 복제가 되고, 연결되는 pointer값도 복제가 된다.

다시 말해서, f_count 값이 증가하게 된다.


8. Try following code

  • Make /aa/bb → Mkdir /aa로 aa Directory를 만들어준 후에 vi /aa/bb 로 /aa/bb파일 생성 가능
$ open("/d1/d2/f1", ...)

/d1/d2/f1의 Inode를 찾는 함수
Superblock을 읽고, GroupDescriptor의 위치를 찾는다.
GroupDescriptor를 읽고, Inode Table의 위치를 찾는다.
Inode Table을 일고, Inode 2를 찾아 "/"의 Block 위치를 찾는다.
"/"의 Block을 읽고, "d1"의 Inode Number를 찾는다.
"/d1"의 Inode를 찾고, "/d1"의 Block 위치를 찾는다.
"/d1"의 Block을 읽고, "d2"의 Inode Number를 찾는다.
"/d1/d2"의 Inode를 찾고, "/d1/d2"의 Block 위치를 찾는다.
"/d1/d1"의 Block을 찾고, Inode Number f1을 찾는다.
"/d1/d2/f1"의 Inode를 찾는다

  • file system call이 호출될 때 변화과정을 보기 위한 코드를 작성하였다.
  • 먼저 x에 open을 사용하여 /aa/bb를 open해준 후에 이를 읽고 buf에 저장한다.
  • /aa/bb의 Inode를 찾고, file table의 f_count, f_pos, f_op, f_dentry 등을 알맞게 할당한다.
  • 그리고 fd table에서 빈 자리를 찾는다.
  • 보통 fd = 0, 1, 2에는 시스템의 기본값이 저장되어 있기 때문에 3에 할당될 것이다.
  • 빈 자리를 찾으면 Chaining시킨다.
  • 따라서 최종적으로 x에는 3이 저장된다.
  • 그 다음 y는 read 함수를 사용하여 읽는다.
  • 즉 x=3이 가리키는 파일에 가서 f_pos에서부터 10 Byte만큼 buf에 저장한 뒤 출력하도록 하였다.
  • 이 때 0이었던 f_pos는 읽은 Byte 수만큼 업데이트된다.
  • 두 번째로, lseek 함수를 사용하여 f_pos를 10에서 20으로 변경한다.
  • f_pos 값을 20으로 변경했기 때문에 20에서부터 10 Byte를 buf에 저장하고 출력할 것이다.
  • 또한, 다시 10 Byte를 읽었기 때문에 f_pos는 30으로 변경된다.
  • 세 번째로, dup 함수를 사용해서 fd table에서 빈 자리를 찾아 fd[x]를 복사한다.
  • x1에는 4가 할당될 것이다.
  • x1과 x는 같은 파일을 가리키고 있으므로 현재 /aa/bb의 f_pos 값인 30에서부터 10 Byte를 저장하고, f_pos는 40으로 변경된다.
  • 또한 f_count는 2가 된다.
  • 마지막으로 link 함수를 사용하여 기존의 /aa/bb에 /aa/newbb의 새로운 바로가기 파일을 만든다.
  • 이름은 다르지만 동일한 Inode Number로 새로운 파일을 생성하기 때문에 시스템에는 같은 파일로 인식된다.
  • 그 다음 마찬가지로, read 함수를 통해 읽고, open 함수를 통해 f_pos에서부터 10 Byte만큼 buf에 저장한다.
  • 출력된 결과에서 마지막에 다시 "111111111"이 출력된 이유는 link 함수를 통해 새로운 파일이 생성되면서 f_pos가 다시 0으로 초기화되었기 때문이다.

9. Check the Inode Number of /aa/bb abd /aa/newbb

  • 8번 문제를 통해 /aa/bb에서 dup 함수로 /aa/newbb가 생성되었다.
  • 이 때 생성된 /aa/newbb는 /aa/bb와 동일한 fd table을 가리키고 있으므로, 같은 Inode Number를 갖는다.

10. Try Fork()

  • /aa/bb를 읽고, fork를 사용해서 복제해주었다.
  • y=0일때, 즉 child일 때와 parent일 때 각각 10 Bytes 씩 읽고 출력하였다.

  • fork를 통해 parent의 root와 pwd, fd table을 복사한다.
  • 부모와 자식은 동일하게 /aa/bb를 가리키고 있으며, 이 때 f_count는 2가 된다.
  • 동일한 f_ps를 공유하고 있으므로, 자식이 먼저 10 Bytes를 읽은 뒤, 부모가 이어서 10 Bytes를 읽고 출력한다.

11. Using "chroot" and "chdir"

a) Make f1 in several places with different content (in "/", in "/root", in "/root/d1") as follows.

b) Make ex1.c that will display "/f1" before and after "chroot", and "f1" before and after "chdir" as follows.

  • ex11.c에 절대 경로 /f1을 open하는 함수 display_root_f1과 상대 경로 f1을 open하는 함수 display_f1을 정의하였다.
  • 절대 경로는 시스템 파일 트리의 루트 (/)부터의 경로이고 상대경로는 현재 위치를 기준으로 한다.
  • chroot 함수를 사용하여 root 디렉토리를 .로 변경하기 전과 후의 display_root_f1을 호출하여 경로가 변화하였는지 확인한다.

  • 함수 chdir은 현재 작업 디렉토리를 변경하는 기능을 한다.
  • 즉 디렉토리가 d1으로 변경되기 전과 후에 display_f1을 호출하여 변화를 확인한다.

  • 첫 display_root_f1은 cd /로 이동해서 만든 f1의 내용을 보여준다.
  • chroot(".")를 통해 현재 디렉토리로 root가 변경이 되는데, 현재 디렉토리는 홈 디렉토리이다.
  • root가 변경된 이후로 다시 display_root_f1을 실행하면 현재 디렉토리가 root이므로 현재 디렉토리에 있는 f1의 내용이 출력되므로 hello2가 출력이 된다.
  • 첫 display_f1은 현재 디렉토리의 f1의 내용이 출력되므로 똑같이 hello2가 출력된다.
  • chdir("d1")으로 현재 디렉토리를 d1을 바꾼 뒤 실행하면, d1 안쪽에 만든 f1이 출력되므로 hello3이 출력된다.

12. Make a new System Call

  • 새로운 System Call "show_fpos()"를 만든다.
  • 이 때, show_fpos는 현재 process의 fd=3, 4에 대한 f_pos를 출력하고자 한다.
  • f_pos는 긴 정수이므로 %lld를 사용하여 출력한다.

  • show_fpos를 syscall 31번에 지정해주었다.

  • fd가 각각 3, 4일 때, f_pos를 출력하는 system call을 fs/read_write.c에 만들어주었다.

  • f1와 f2를 open으로 열게 되면 각각 fd=3, fd=4로 열리게 된다.
  • 처음 open을 하게 되면 f_pos는 0이 되는데, read로 각각 10, 20 Bytes씩 읽고 f_pos를 출력해보았더니 각각 7, 20 Byte만큼 이동한 것을 확인할 수 있었다.
  • 이 때, f1을 7 Byte만큼 읽어들인 이유는 f1 파일에 7 Byte의 내용만 담겨있었기 때문이다.

13. Modify your show_fpos

  • 각각 fd가 0, 1, 2, 3, 4에 대한 f_op→read 및 f_op→write 함수도 출력되도록 show_fpos 함수를 수정한다.
  • system.map에서 해당 함수의 이름을 찾는다.
  • 시스템이 fd가 0, 1, 2, 3, 4에 대해 서로 다른 기능을 한다.

  • 다음과 같이 출력되었다.

  • System.map 파일에서 각각의 주소에 해당하는 함수이다.

14. Use show_fpos to explain the result of the following code

  • file f1는 "ab"를 가진다.
  • file f2는 "q"를 가진다.
  • 프로그램을 돌리면, f2는 "ba"가 된다.

  • show_fpos에서 주소를 출력하는 부분을 주석처리하였다.
  • open을 통해 파일 ./f1과 ./f2를 연 다음, 그에 대한 f1, f2 값을 출력하였다.
  • fd의 빈 자리에 할당될 것이므로 f1에는 3, f2에는 4가 할당될 것이다.
  • fork를 통해 복제한 process에서 sleep의 효과를 주고 syscall을 이용해 show_fpos를 호출하였다.

  • 예상한대로, f1과 f2에는 각각 3과 4가 할당되었다.
  • 먼저 실행되는 child를 보면 read하기 전에는 f_pos가 0으로 초기화되므로 첫번째의 모든 f_pos는 0이 된다.
  • read를 통해 1 Byte를 읽고 나서 sleep을 해주었기 때문에 parent가 실행이 된다.
  • 이 때, parent가 1 Byte를 읽기 때문에 f_pos의 값은 1로 바뀐다.
  • 다음 parent가 sleep에서 깨어나면서 f1의 내용을 f_pos에서부터 1 Byte를 읽는다.
  • 그러므로 f_pos는 2로 바뀌므로 두번째 글자인 b가 Buf에 저장이 되고 write하면서 f2에 저장된다.
  • 마지막으로 child가 sleep에서 깨어나면서 나머지 내용을 write하기 때문에 첫번째 글자 a가 f2에 저장이 된다.
  • 즉, f2는 'ba'로 저장될 것이다.


15. Find corresponding kernel code

  • fs/open.c를 열어서 sys_open 코드를 살펴보았더니, do_sys_open이 호출되었다.

  • do_sys_open을 살펴보면 다음과 같다.
  • char *tmp = getname(filename); -> 프로세스 주소공간에서 파일 경로명을 읽음
  • fd = get_unused_fd_flags(flags); -> 비어있는 fd를 찾는다. 대응하는 인덱스저장
  • struct file *f = do_filp_open(dfd, tmp, flags, mode); -> do_filp_open() 함수 호출
  • fd_install(fd, f); -> fsnotify_open()이 반환한 파일 객체의 주소로 설정
  • return fd; -> fd 반환

1. Find Empty Fd

  • get_unused_fd_flags를 호출하여 current→files→fd에서 빈 공간을 찾고, 그 인덱스, 즉 새로운 파일 디스크립터를 fd에 저장한다.

2. Search the Inode for "fpath"

  • do_flip_open을 살펴보면 open_namei를 호출하는 것을 확인할 수 있다.
  • 파일 객체 구조를 반환한다.

  • open_namei를 살펴보면 flag에 따라 권한을 확인하고, path_lookup(_open, _create) 함수를 호출하는 것을 볼 수 있다.
  • 이 두 함수 호출을 통해 fpath를 찾는다.
  • path_lookup_open과 path_lookup_create는 모두 핵심적으로 do_path_lookup을 이용한다.

2 - 1. If "fpath" starts wutg "/", starts from "fs→root" of the current process

  • do_path_lookup 함수를 살펴보니 fpath가 /로 시작하면 fs→root부터 탐색을 시작하는 것을 확인할 수 있었다.

2 - 2. Otherwise, starts from "fs→pwd"

  • 그렇지 않은 경우는, fs→pwd부터 읽어오기 시작하는 것을 확인할 수 있었다.

2 - 3. visit each directory in "fpath" to find the Inode of the "fpath"

2- 4. while following mounted file path if it is a mounting point

  • vfs_path_lookup 함수를 보니, mounting point라면 mounted file path를 따라간다.

  • dentry가 비어있으면 vfs_create 함수를 호출한다.

3. Find empty file { } entry and fill-in relevant Information

  • do_flip_open에서는 nameidata_to_flip 함수를 호출하는 것처럼, 이곳에서는 Inode와 관련된 dentry_open을 호출한다.

  • fs/open.c를 열어 dentry_open을 살펴보니 반환 받은 nameidata 구조체를 바탕으로 가상 파일 시스템을 이용하기 위한 자료구조를 초기화한다는 것을 알 수 있었다.

4. Chaining

  • fd_install을 통해 file table에서 찾은 내용들을 연결하여 current→files→fd[fd] 값을 파일 객체 주소로 설정한다.

5. Return fd

  • do_sys_open 함수의 끝 부분에서 return fd를 통해 파일 객체 주소를 반환한다.

6. read(x, buf, n);

6 - 1. go to the Inode for x

6 - 2. read n bytes starting from the current file position

  • fs/read_write.c를 열어 syscall read를 사용할 때 호출되는 과정을 살펴보았다.
  • file_pos_read, vfs_read, file_pos_write가 차례로 호출되는 것을 볼 수 있었다.

  • 먼저, file_pos_Read를 살펴보니 현재 file position인 f_pos가 반환되는 것을 확인하였다.

6 - 3. Save the data in buf

  • vfs_read에서 file→f_op→read 형태로 pos가 가리키는 위치부터 n만큼 데이터를 읽고 buf에 저장한다.
  • 또한, inc_syscr를 호출하여 file position을 증가시킨다.

6 - 4. Increase the file position by n

  • file_pos_write에서 증가한 pos값을 f_pos에 저장한다.

0개의 댓글