서울대학교 컴퓨터공학과 김진수 교수님의 "고급 운영체제" 강의를 필기한 내용입니다.

다소 잘못된 내용과 구어적 표현 이 포함되어 있을 수 있습니다.

Filesystem 구성요소

File (*)

  • 누가 길가다가 물어보면
  • (1) Persistent storage 에 저장된 (2) 이름을 가지고 있는 (3) 연관된 byte 들의 모음 라고 말해라.

inode (*)

  • 파일의 metadata 를 inode 에 저장
  • 이런애들이 저장된다고 한다
NAMEDESCRIPTION
파일 사이즈
Block 들의 위치아마 LBA 주소일 듯하다.
소유자user, group
권한rwx
Timestamp갖가지 시간 정보들

Directory (*)

  • Directory[file_name, inode_id] 의 list 를 담은 파일
  • 파일 이름은 directory 를 이용해 inode number 로 변환된다.
    • 몰랐던 사실이죠? inode 가 아니라 dir 에 저장된다.
    • 여기서 path 는 directory 를 재귀적으로 참조하여 file 의 위치를 알아냄
  • 이 inode number 로 inode 를 알아내는 것은 쉬움 - fs 의 inode 영역에서 index 로 접근
  • 여기서 metadata 를 읽어서 권한 등을 판단하는 두둥실을 한다고 한다.

Filesystem (*)

  • Filesystem 는 사용자가 생성한 file 에 대한 metadata 를 관리하는 시스템 이라고 정의할 수 있다.
    • 즉, 각 file 자체에 대해서는 별로 신경쓰지 않는다.

Layer

  • 일반 프로그램은 lib 을 통해 (뒤에서 설명할) POSIX API 를 이용해 Filesystem 에 접근하게 된다.
  • Virtual fs: 모든 종류의 fs 들이 공통적으로 수행하는 기능을 정의한 fs 들의 superset
    • proc: 파일시스템 인터페이스를 이용해 kernel 의 설정값에 접근할 수 있게 해주는 가상의 fs
  • Generic block layer: 몇번 block 을 r/w 해라 등의 명령어 제공
    • 즉, device driver 와 filesystem 간의 interface 임
  • 이 아래에 device specific driver 들이 실제로 device 이랑 소통하며 명령어 처리

POSIX API

POSIX operations

  • 어떻게 정리해야 할 지 모르겠네; 일단 아래 그림 애피타이저로 먹고있어라:

POSIX inode

  • inode number: inode 는 파일마다 하나씩 있고, 각각의 ID 가 있다.
  • File type: 뭐 그냥 파일인지, directory 인지 등등 명시
    • symlink (Soft link): 파일인데, 내용에 다른 파일의 path 가 적혀있는 것 (*)
      • 원본 파일과는 무관해서 원본파일이 지워져도 link 는 남아 있음 (당연히 접근은 안됨)
  • Device ID: 이 파일이 저장되어 있는 디스크 ID
  • Access permission: 누가? (user, group, others - ugo), 뭐를? (read, write, exec - rwx)
  • Number of hard links: (*)
    • Hard link (Link): 얘는 symlink 와는 다르게 파일은 아님; 디렉토리 entry 상에만 있는 가짜 file 로 하나의 inode 에 여러개의 filename 이 link 되어 있는 것
    • POSIX API 에는 delete operation 이 없다; 대신 unlink 라는 것이 있다.
      • 이 Hard link 때문; 파일을 지워버리면 다른 hard link 들에서 접근할 수가 없기 때문이다.
      • 따라서 지우는 것 대신 unlink 를 하는 것이고 아무에게도 link 되지 않으면 그때 지워진다.
      • 이를 위해 inode 안의 이 Number of hard link count 가 있어서 몇개의 link 가 있는지 추적하는 것.
    • 생각해보자: directory 의 경우에는 이 link count 가 어떻게 될까?
      • 디렉토리는 link 가 최소 2개 생성된다.
        • 상위 디렉토리에서 바라보는 파일로서 하나
        • 현재 디렉토리 내에서 . 로 하나
      • 그럼 하위에 2개의 sub-directory 가 있으면 4개의 link 가 됨 (..)
        • A/B/C,D 의 구조에서 directory B 를 예로 들어보자.
        • 일단 A 에 entry 로 B 가 들어있을 것이다 (+1)
        • 그리고 B 의 entry 로 . 가 들어있을 것이고 (+1)
        • C 의 entry 로 .. 가 들어있을 것이며 (+1)
        • D 의 entry 로 .. 가 들어있을 것이다. (+1) -> 총합 4개!
  • Timestamp
    • atime: access time
    • mtime: 파일 데이터 변경 시간 (modified time)
    • ctime: inode (metadata) 변경 시간
    • 주의할 것은 POSIX 에서는 file creation time 를 관리하지는 않는다!

Filesystem 구현상의 어려움…

  • Block allocation 에 대해 고려해야 한다: File 을 구성하는 block 들을 어떻게 배정할 것인가?
  • Block indexing 에 대해서도 고려해야 한다: File 을 구성하는 block 들을 어떻게 빠르게 찾아갈 것인가?
  • Path name 를 빠르게 inode 변환할 수 있게도 해야 하고
    • File path 를 찾아가는 것은 기본적으로 directory 를 반복적으로 참조해야 하기 때문에 이것을 최적화 해야 한다.
  • Crash 에 대한 대비 (journaling) 도 해야 한다.
  • 즉, 목표는
    • Performance (빠르게 작동)
    • Reliability (문제상황시에 복구)
    • Consistency (metadata 들 간의 sync)

Persistence

  • Filesystem 은 메모리도 버퍼로 사용하고 (메모리의 page cache 공간) 그 아래에서는 SSD 의 DRAM 도 버퍼가 되기 때문에 write 시에 persistence 를 보장하는 것이 쉬운 문제는 아니다.
  • 일단 Linux 의 경우에는 fs 의 mem (page cache) 상에 최대 30초정도 보관한다.
    • 여기에 있는 것을 storage 로 내려보내는 것은 데몬이 수행한다.
  • 추가적으로, 다음의 syscall 을 이용해서 명시적으로 디스크로 내려보내기도 한다.
    • sync(): 모든 파일의 안내려간 부분을 전부 내림
    • fsync(): 특정 파일의 안내려간 부분을 전부 내림
    • fdatasync(): 특정 파일의 data 중 안내려간 부분을 전부 내리고, metadata 는 선택적으로
      • Metadata 가 깨져도 큰 문제가 되지 않는 상황에 사용한다고 한다.
  • Reliability 를 위해서 로그를 적고 sync 를 해 로그 내용을 디스크까지 저장하고 crash 가 나면 이 로그의 내용을 디스크에서 불러와 복구작업을 진행하고
  • Consistency fs 의 syscall 은 atomic 해야 한다 - 즉, 완벽하게 수행되거나, 전부 취소되거나 - 하다 만 중간상태로 남는 것은 없다

여기부터는 2024-04-02 강의

Journaling (Write-ahead log, WAL)

  • 하나의 file 관련 syscall 을 처리하기 위해서는 여러번의 disk write 가 필요하기도 하고, 이들이 모두 정상적으로 끝나야지만 정상 종료로 판단하는 원자성을 fs 에서 제공해 줘야 한다.. 이를 위해 journaling 이 필요하다.
    • 물론 Disk 의 1 sector 에대한 작업의 원자성은 디스크차원에서 보존해준다고 한다.
  • Filesystem 에는 journaling 공간 (혹은 log 공간) 이 있어서 그곳에다가 적어놓고 작업을 시작하게 된다.
    • 기존 데이터 AA' 로 바꾸고자 할 때
    • Undo logging: journaling 에 이전 버전인 A 을 적어 놓고 작업을 하다가 문제가 생기면 적어놓은 이전 버전으로 되돌리는 것
    • Redo logging: A’ 를 journal 에 적어놓고 문제가 생기면 이후 버전으로 복구하는 것
  • fs 에서는 metadata 는 중요하기 때문에 이놈들에 대해 redo logging 을 한다고 한다.
    • 물론, data 도 할 수는 있지만 너무 오버헤드가 크기에 안한다네
  • 아마 journaling 은 한번의 disk write 만 필요하기에 journaling 을 journaling 하지는 않을듯?
  • 그리고 복구시에는 full disk scan 이 아니라 이 journal area 만 scan 하면 되기 때문에 복구가 더 빠르다는 장점도 있다.

Semantics

  • 다음의 상황을 고려해 보자:
    • 한 파일에 두 프로세스가 write: 어차피 메모리에 버퍼해놓기 때문에 마지막으로 write 한 놈의 버전이 적용됨
    • 한 파일에 한 프로세스는 write, 한 프로세스 read: 하나의 copy 만을 open file page cache 에 두고 각자의 virtual memory address space 에 매핑하여 접근하기 때문에 한쪽의 변경사항이 다른 한쪽에서도 보인다.
      • 프로세스에서 파일에 접근할 때는 메모리에 올려진 inode 를 보고 원하는 offset 에 대한 LBA 를 page cache 에서 찾아서 접근한다
      • 즉, 누가 올렸는지는 고려 안하고, 하나의 copy 만을 사용한다는 것.
    • 파일 사용중에 누군가가 지웠음: 한놈이 파일을 directory 에서 unlink 하면 사용하던 놈은 이 파일을 open 하고 있어 계속 사용할 수 있다.
      • 하지만 이때 directory 에서는 unlink 되어 있기 때문에 새로운 프로세스가 이 파일을 open 하는 것은 불가능하다.
      • 또한 사용하던 프로세스가 종료되어 이 파일이 어디에서도 open 되어 있지 않게 되면 그때 실제로 삭제된다.
    • 파일 사용중에 다른 프로세스가 권한 바꿈: 현재의 프로세스가 open 할 때에만 permission check 를 하기 때문에 예를 들어 ro 로 바꾼다 하더라도 나는 계속 write 할 수 있다.
    • 파일 사이즈를 벗어난 공간에 write 를 하려고 함: 가능하다. 이 공간까지 합쳐져서 파일 사이즈가 커짐.
      • lseek syscall 로 r/w pointer 를 옮길 수 있는데, 이것으로 기존의 파일 사이즈 4Ki 를 벗어난 위치 5Ki 에 2Ki data 를 write 를 하려고 한다고 해보자.
      • 그럼 파일 사이즈가 4Ki 에서 7Ki 로 바뀌고, 그 사이 공간 1Ki (주소로는 4Ki ~ 5Ki) 은 Hole 이 된다.
      • 그리고 만약 이 Hole 에 접근하게 되면, 0 으로 초기화된 공간을 보게 됨.
    • 파일 이름 변경 중에 머신의 전력이 차단되어 꺼짐: Filesystem 은 consistency 를 제공하기 위해서는, 기존의 이름 혹은 바뀐 이름 둘 중에 하나로만 보여야 한다.
      • 즉, 둘 다 보이거나 둘 다 안보이면 안된다는 것.
  • 물론 위와 같은 정책들은 정하기 나름이다; 일반적으로는 위와 같이 하더라.