2023-06-16
운영체제 #10 — xv6 파일 시스템 확장하기
마지막 프로젝트는 파일 시스템이었다. 세 가지를 한다. multi indirect로 파일 크기 한계를 늘리고, symbolic link를 만들고, buffered I/O로 커밋 정책을 바꾼다.
설계
Multi Indirect
double indirect와 triple indirect를 추가하기 위해 매크로에 NDINDIRECT,
NTINDIRECT를 추가한다. bmap은 inode 안의 addr을 읽어오므로 수정해야 하고,
itrunc는 inode 안의 addr을 제거하는 것이므로 마찬가지로 수정해야 한다.
Symbolic Link
symlink 시스템 콜을 구현한다. 기존 link는 hard link고, symlink는 inode를 새로
만들고 원본 파일의 path를 inode에 넣는 방식으로 만든다.
Buffered I/O
기존은 group commit으로 하나의 transaction마다 commit을 한다. 이걸 버퍼가 꽉 차면 commit하도록 바꾼다.
구현
1. Multi Indirect
매크로. NDINDIRECT는 indirect 안 주소 수의 제곱, NTINDIRECT는 세제곱이다.
NDINDIRECT, NTINDIRECT와 늘어난 MAXFILE.
dinode와 inode. addr을 2개 더 늘렸다(double indirect, triple indirect).
그런데 그러면 block 사이즈가 dinode 사이즈로 나누어떨어지지 않아서 mkfs를 할 때
assert에 걸린다. direct의 주소 수를 줄이는 방법과 padding을 추가하는 방법이
있는데, padding 쪽을 골랐다. inode에서도 addrs 수를 수정하고 padding을 추가했다.
dinode — addr 2개를 늘리고 padding을 붙였다.
padding을 맞추지 않으면 mkfs의 assert에 걸린다.
inode 쪽도 같은 방식으로 수정했다.
bmap. 기존 direct, indirect 부분 뒤에 double indirect와 triple indirect 코드를
추가했다. 전 단계에서 읽어온 block 번호에 해당하는 block을 bread로 읽어오고,
bn과 적절한 연산을 해서 마지막 단계까지 이동한다. 마지막 단계에서는 block에
address들이 들어 있으므로 읽어야 하는 address를 읽어 return한다.
bmap — 기존 direct와 indirect 부분.
bmap — 뒤에 붙인 double indirect와 triple indirect.
itrunc. bmap과 비슷한 방식으로 했다.
itrunc — bmap과 같은 순서로 내려가며 블록을 해제한다.
iappend. 마찬가지로 double indirect, triple indirect를 추가했다.
iappend에 추가한 double/triple indirect.
balloc(mkfs.c). nbitmap 제한을 bitmap 수를 담을 수 있도록 수정했다. block
단위로 세팅해준다.
mkfs.c의 balloc — 초기 bitmap 수를 늘렸다.
2. Symbolic Link
ln.c. -h(hard link), -s(symbolic link) 옵션을 추가했다.
ln.c — -h와 -s 옵션.
sys_symlink. new에 해당하는 path에 inode를 추가하고 writei로 new에 old의
path를 저장한다. 나중에 open할 때 readi로 읽어와서 old path로 이동한다.
sys_symlink — new의 inode에 old의 path를 써 넣는다.
sys_open. O_LS라는 open option을 추가했다. ls를 할 때 old의 정보가 new
파일 옆에 뜨는 것을 방지하기 위해서다. symlink 파일을 열면 while문으로 original
파일의 path를 찾고 그곳의 데이터를 읽는다.
sys_open — O_LS 옵션과 원본 path를 따라가는 루프.
3. Buffered I/O
sync() 시스템 콜. log.lh.n이 0 이상이면 commit()을 하고 log.lh.n을
return한다. 0보다 작으면 -1을 return한다.
sync() 시스템 콜.
end_op. commit 조건을 수정했다. log.outstanding 조건은 transaction을 위한
조건이므로 그대로 두고, log.lh.n + MAXOPBLOCKS > LOGSIZE를 확인한다. 다음
operation에서 block을 최대로 쓴다면 LOGSIZE를 넘는지 확인하는 것이고, 이는
log(또는 버퍼)가 부족하다는 뜻이다. 그리고 commit 자리에 sync()를 넣었다.
end_op — 버퍼가 부족한지 확인하는 조건으로 바꿨다.
결과
Multi indirect
built-in(iappend)으로 만든 5MB.txt가 5MB로 인식되었다. 한 줄에 1이 1024씩 채워져
있다. 약 15분 정도 걸렸다.
ls에서 5MB.txt가 5242885바이트로 잡힌다.
mkfile로는 8MB 파일이 10분, 16MB 파일이 18분 정도 걸렸다.
mkfile로 만든 8MB, 16MB 파일.
Symlink
ln -s README hi로 README의 symlink 파일을 만들었다. ls를 하면 README의 정보가
아니라 hi 자신의 정보가 옆에 나오고, cat hi로 출력하면 README의 내용이 나온다.
ls에는 hi 자신의 정보가, cat hi에는 README의 내용이 나온다.
hi를 지워도 README 파일은 그대로 존재하고, hi를 열려고 하면 original file이
없다고 출력한다.
hi를 지워도 README는 남는다.
Buffered I/O
테스트에는 위의 mkfile을 그대로 쓰고 시간만 비교했다. 8MB 파일은 1분 안에,
16MB 파일은 4분이 걸렸다. 각각 10분과 18분이었던 것과 비교하면 확실한 차이다.
transaction 하나마다 디스크에 커밋하던 것을 버퍼가 찰 때까지 모았다가 한 번에
쓰는 것만으로 이 정도가 나온다.
Trouble Shooting
iappend와 indirect. 새로 만든 파일을 읽을 때는 70KB 이상도 문제가 없었는데,
iappend로 built-in 추가한 파일을 읽을 때 70KB에서 panic이 났다. iappend를 할 때
indirect에서 읽고 indirect에 넣고 있었는데, indirect2, indirect3를 추가해서
읽어오도록 하니 해결됐다.
bitmap 부족. iappend로 추가할 때 일정 이상 큰 파일을 읽으면 컴파일이 되지
않았다. bitmap이 부족해서 생긴 오류였고, mkfs.c의 balloc을 위 구현대로 수정해
초기 bitmap 수를 늘려서 해결했다.
ls에 원본 정보가 뜬다. ls를 할 때 original file의 stat이 symlink 파일 옆에
나타났다. ls에서 읽을 때는 open에 새 option을 줘서 original file까지 이동하지
않도록 했다.
여기서 하나 알게 된 게 있다. rm은 open을 거치지 않는다. open을 할 때 link
원본 파일을 읽도록 만들었으므로, rm을 고치지 않아도 link 파일을 지울 때 원본이
지워지는 일은 일어나지 않는 듯하다.
log_write 수정. 처음에는 log_write를 수정해서 진행했는데 sync를 하지 못하고
멈췄다. log_write는 기존 틀을 유지하고 end_op에서 sync를 하도록 바꿨다.
시리즈를 마치며
한 학기 분량을 열한 편으로 나눴다. 이론 다섯 편에서 이름을 배우고, 실습에서 그 이름을 xv6 코드에서 찾고, 프로젝트 세 편에서 직접 바꾼 순서다.
돌아보면 프로젝트 세 개가 각각 스케줄러, 프로세스/스레드, 파일 시스템을 하나씩
맡고 있었다. #7에서 scheduler()를 고치고,
#9에서 struct proc에 필드를 붙이고,
이번에 inode에 addr을 붙였다. 교과서에서 그림으로 보던 자료구조에 필드를
하나 더 붙이는 일이 실제로 어떤 파장을 일으키는지 — mkfs의 assert에 걸리고,
copyuvm이 깨지고, 커널 패닉이 나고 — 그걸 손으로 겪은 게 이 수업에서 남은
것이다.