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
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
$ 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에 저장한다.