[Operating System] File System API

2020. 6. 2. 17:12Operating System

  • 파일이란 읽고 쓸 수 있는 영구적인 byte array이다.
  • 각각의 파일은 inode number라는 low-level 이름을 가지고 있다. (inode 하나가 file 하나를 가리킨다.)

 

File Name

  1. inode number
    • 하나의 FS에서 각각의 inode number는 유일하다. (다른 FS끼리는 같은 넘버가 사용될 수 있음)
    • inode number는 삭제된 뒤에는 재사용 가능하다.
  2. path
    • inode #는 유저에게 친숙하지 않기 때문에 string file name을 따로 정의하고 inode와 매핑시킨다.
    • path-inode 간의 매핑에 대한 정보는 root 파일(inode 2번)에 저장된다.
      • 따라서 특정 파일이나 directory를 read할 때 root inode부터 읽어야한다.
      • 만약 /etc/.bashrc 를 읽는다고 하면, 총 6번의 read가 발생한다. (각각 inode와 data block)
  3. file descriptor
    • 0: stdin, 2: stdout, 3:stderr

 

Directory

  • 디렉토리 역시 low-level name을 가지는 파일이다.
  • 디렉토리의 엔트리는 파일이나 다른 디렉토리를 가리킨다.

 

API

  • mkdir, readdir, rmdir
  • file create는 open() syscall을 통해서 이루어진다.
    • open()fd를 리턴함.
    • open 된 파일은 current offset 정보를 가진다.(파일 내에서 어디서부터 읽고 쓸지를 정하기 위하여)

 

File R/W API Strategy

1. inode를 통한 R/W

read(int inode, void *buf, size_t nbyte)

  • 단점
    • 이름이 기억하기 어렵다.
    • inode #를 통해 파일에 대한 정보를 파악하기가 어렵다.

2. path를 통한 R/W

pread(char *path, void *buf, off_t offset, size_t nbyte)
  • 단점
    • Root 디렉토리부터 타겟 파일까지 순회하면서 inode와 data 블록을 읽어야하기 때문에 오버헤드가 크다.
      • fd를 사용하면 오버헤드를 줄일 수 있다.

3. File Descriptor를 통한 R/W (Best)

 

  • 처음 딱 한번만 fd를 얻기 위해서 path를 통해 open()
int fd = open(char* path, int flag, mode_t mode)
  • 그 다음부터는 fd를 통해 R/W
read(int fd, void *buf, size_t nbytes)
  • close(fd)를 통해서 fd를 지울 수 있다.
  • path의 파일을 찾아 순회하는 과정을 딱 한번만 수행한다. (파일을 open 할 때)
  • 파일을 open한 뒤에는 inode를 fd 구조체에 넣고(메모리에 캐싱) 이 fd를 통해서 파일에 R/W를 수행한다. (offset 역시 트래킹된다.)
  • 각각의 프로세스마다 fd table이 존재하며, open files을 가리키는 포인터를 담고 있다.
struct file {
  struct inode *ip;
  uint off;
}

 

기타 API

파일 삭제

  • 명시적으로 파일을 삭제하는 syscall은 존재하지 않으며, 파일의 inode에 대한 참조가 없을 때 Garbage Collect(GC) 된다.
    • inode에 대한 참조는 path나 fd를 통해서 이루어진다.
      • path는 unlink()가 호출되었을 때 지워짐
        • unlink()는 디렉토리 내부에 file이 존재하지 않을 때만 가능하다.
      • fd는 close()가 호출되었을 때 지워짐
  • rm -rf를 했을 때는 파일의 가장 밑부터 파일을 하나하나 지우면서 올라간다.

 

Hard Link

  • link(old_pathname, new_pathname)
  • ln file file2: file, file2가 같은 데이터를 참조하게 됨
  • 기존 파일에 대한 새로운 참조가 하나 더 생긴다. 따라서 inode와 data block은 모두 그대로이고, 그 데이터를 가리키는 이름이 두 개가 될 뿐이다.

 

  • rm file을 하면 해당 파일에 대한 ref cnt가 1 감소하며, ref cnt가 0이 되면 GC된다.
    • link() 시에 ref cnt 1 증가
    • unlink() 시에 ref cnt 1 감소
    • stat()을 통해 ref cnt를 확인할 수 있다.
  • directory나 다른 FS의 파일에 대해서는 link가 불가능하다.

 

Soft Link (Symbolic Link)

  • ln -s file file2: -s 옵션을 주면된다.
  • hard link는 inode를 통해서 데이터에 접근하는 반면, soft link는 절대 경로를 통해 순회를 하며 데이터에 접근하기 때문에 path가 깊어질 수록 link file의 크기도 증가한다.
  • ref cnt가 없다.
  • 디렉토리에도 link가 가능하다.

 

FS 관련 API

Linux Many File Systems

 

  • FS 생성: mkfs
    • bitmap, inode table과 같은 기본적인 metadata를 initialize하고, 비어있는 root directory를 생성한다.
  • mount: 특정 디렉토리를 mounting point로 삼아서 새로운 파일 시스템을 붙여넣는다. mounting point는 새로운 FS의 root 역할을 하게된다.

 

Memory - Disk Sync

  • fsync(): write한 데이터는 곧장 disk에 write 되지 않고 일단 메모리에 버퍼링된다. 만약 disk에 바로 write를 해야하는 상황이라면 fsync()를 통해 buffer flush를 할 수 있다.
    • 참고) Disk driver에서 자체적으로 optimize를 하는 경우엔 OS에서 fsync를 했더라도 disk write가 실제로는 이루어지지 않을 수 있다.

 

Rename

  • mv foo bar
  • rename(char *old, char *new): atomicity가 보장되는 함수
  • Atomicity를 위해서, new file link 생성 $\to$ link 교체 $\to$ old link 삭제

 

Old link 삭제를 먼저 하면 안 되는 이유

다음과 같은 순서로 파일 renaming이 이루어진다고 해보자.

1. Old link 삭제
2. New link를 생성하여 연결

만약 1과 2 사이에서 컴퓨터가 꺼져버린다면 해당 파일 데이터에 대한 참조가 완전히 날아가버리는 문제가 생긴다. 따라서 renaming을 비롯하여 파일의 데이터를 업데이트할 때는 먼저 새로운 파일 링크를 만들어서 연결을 하고 그 뒤에 old link를 제거해야 crash가 발생하더라도 업데이트 이전의 온전한 데이터 또는 업데이트 이후의 온전한 데이터 둘 중 하나의 상태를 보존할 수 있다.

 

멀티 프로세스에서의 파일 concurrency

  • flock(): 여러개의 프로세스에서 같은 파일을 동시에 수정하는 것을 막기 위한 lock