CS

OS 파일 시스템: 파일 테이블과 I/O 성능 개선 기법

컴공오지마라 2024. 12. 30. 10:00

1. 서론

시스템 프로그래밍 시간에 학습한 내용을 바탕으로 OS가 파일을 관리하는 방법에 대해서 정리하고자 한다. 응용프로그래밍에서 파일을 열면 운영체제가 이를 어떻게 관리하는지 간단하게 살펴보도록 하자. 또한, 운영체제가 어떻게 디스크와 같은 저장장치를 추상화 하는지, inode를 활용하여 파일의 데이터 블럭을 찾는 과정 등의 내용은 다음 포스트에서 살펴보겠다.

2. OS가 파일을 관리하는 방법

2.1 File Table

File Table은 각 프로세스가 오픈한 파일의 정보를 저장하고 있는 리스트이다. 프로세스는 파일에 대해서 읽기 쓰기 작업을 진행하기 전에 파일을 open한 후 이 프로세스가 그 파일에 대해 해당 작업을 할 수 있는 권한을 인증한 후에 작업을 수행한다. 이 리스트의 각 Entry의 인덱스는 fd(File descriptor)이다. fd는 파일을 open 할때 반환받는 값으로 추후에 더 자세히 설명하겠다. 또한 리스트의 각 Entry에는 파일의 정보가 포함되며 대표적인 내용만 정리하면 아래와 같다.

 

각 Entry에 포함된 내용

  • inode ( File 의 meta data )
  • File offset ( read, write 작업이 실행되는 위치)
  • Access modes ( 이 파일에 대해서 해당 프로세스의 권한 )
  • fd는 이 리스트의 index 역할을 하는 것을 기억하자!

2.2 File descriptor

앞서 fd는 파일을 열었을때 반환받는 값이고, fd는 File table의 인덱스라고 하였다. fd의 특징은 아래와 같다.

  • c 언어에서 int type 으로 선언되어있다
  • 프로세스가 open한 파일들을 가리키기 위한 고유한 id 값
  • 프로세스가 열 수 있는 최대 개수는 정해져있다.
  • 프로세스가 생성되면 stdin, stdout, stderr 가 자동으로 할당된다.
    • 각각의 fd는 0, 1, 2 이다
  • os 는 사용하지 않은 fd 중 가장 작은 걸 가져와서 새로운 파일에 할당한다.

파일의 추상화란?

운영 체제는 다양한 입출력 장치를 일관된 방식으로 다루기 위해 I/O Device를 파일이라는 개념으로 추상화한다. 이를 통해 운영 체제는 키보드, 파이프, 스토리지, 네트워크 소켓 등 모든 I/O 장치를 동일한 방식으로 다룬다. 자세한 내용은 아래 글을 참고해보자.

2.3 Global File Table

글로벌 파일 테이블의 Entry에는 이 Entry가 몇개의 파일 디스크립터에 의해서 참조되고 있는지, 오프셋은 몇인지 등을 관리하다. 주요 내용을 구조체로 표현하면 아래와 같다.

struct file{
    ref = 2 // 두개의 파일 디스크립터가 참조중!
    readable
    writable
    *ip
    ...
    off = 10 // offset =10 
}

2.4 File Table & Global File Table 관계

2.5 예제코드1

int main() {
    int fd1, fd2;
    char c;

    fd1 = open("test.txt", O_RDONLY);
    fd2 = open("test.txt", O_RDONLY);

    read(fd2, &c, 1);
    read(fd1, &c, 1);

    close(fd1);
    close(fd2);

    return 0;
}
  • 이 예제코드에서 open 시스템콜은 동일한 파일을 열지만, fd1, fd2 에서 참조하는 글로벌 파일의 엔트리는 다르다. 프로세스에 존재하는 파일 테이블 역시 서로 다른 곳에 할당된다. 그림으로 나타내면 아래와 같다.
  • 그림의 offset 값은 임의로 부여한 것이다.

 

2.6 예제코드2

int main() {
    int fd1, fd2;
    char c;

    fd1 = open("test.txt", O_RDONLY);
      dup2(fd1, fd2);

    read(fd2, &c, 1);
    read(fd1, &c, 1);

    printf("c = %c\n", c);
    close(fd1);
    close(fd2);

    return 0;
}
  • dup2 시스템 콜은 주어진 파일 디스크립터를 복제하는 기능을 한다. 즉 fd1을 fd2에 복제한다고 생각하면 된다. 이 경우에는 두 파일 디스크립터가 참조하는 글로벌 파일 엔트리는 동일하다. 물론 프로세스에 존재하는 파일 테이블은 서로 다른 곳에 할당된다. 그림으로 나타내면 아래와 같다.
  • 그림의 offset 값은 임의로 부여한 것이다.

3. OS의 I/O 성능 개선 기법

운영체제가 파일 I/O의 성능을 개선하기 위해 사용하는 중요 기법들을 간단하게 알아보자

3.1 Delayed writes ( 지연 쓰기 )

OS는 write 시스템 콜을 리턴 후 하드웨어 디스크에 바로 쓰지 않고, 운영체제 내부 버퍼에 저장해둔 후 한번에 쓴다. 이는 시스템 콜을 요청 받을때마다 디스크에 바로 쓰게 되면 성능 저하를 초래할 수 있기 때문이다.

동작과정

  • 사용자가 시스템 콜 요청을 한다
  • OS는 I/O 내용을 page cache 에 저장후 시스템 콜을 리턴한다.
  • 이후, 특정 조건이 만족하게 되면 OS가 하드웨어적인 특성을 고려하여 디스크 스케줄링을 통해 최적의 방식을 계산한 후 Dirty 버퍼를 디스크에 반영한다.

장점

  • 파일로부터 읽어올 내용이 페이지 캐시에 있는지 먼저 확인한다. 페이지 캐시에 있는 경우 I/O 작업을 수행할 필요 없이 메모리 영역에서 데이터를 가져오게 되므로 더욱 빠르게 작업을 마무리할 수 있다.
  • 또한, write I/O의 내용이 디스크에 반영이 안되어있더라도 페이지 캐시에 있으므로 빠르게 읽어올 수 있다.

단점

  • 작업 중간에 컴퓨터가 꺼지면 I/O 내용이 디스크에 저장되지 않는다.
  • 이런 경우, 이를 어떻게 복구할지에 관한 내용을 추후 게시글에서 다루도록 하겠다.

3.2 Synchronized I/O

앞에서 설명한 페이지 캐시를 사용하지 않고 매 시스템 콜 요청마다 바로 디스크에 반영하는 방식이다.
아래의 시스템 콜을 사용하면 된다.

int fsync (int fd);

그런데 이 시스템 콜은 디바이스 드라이에게 요청을 하기 때문에 정말로 하드웨어에 반영되어있는지는 보장할수 없다. 이 시스템 콜은 응용 프로그램 레벨에서 디스크에 I/O 내용을 즉각적으로 반영할때 사용하면된다. 그런데 이러한 방식은 시스템의 Reliablity는 올라가겠지만 응용프로그램 성능이 안좋아질 수 있다. 참고로 열린 파일을 닫는다고 해서 캐시의 내용이 디스크에 반영되는 것은 아니다. 파일을 닫는 것과 디스크에 반영되는 시점은 독립적으로 관리된다.

4. 마치며

이번 글을 정리하며 3.1의 지연쓰기 및 페이지 캐시 내용은 JPA의 동작 방식과 매우 비슷하다고 느꼈다. JPA는 트랜잭션이 커밋될 때 엔티티의 변경사항을 한번에 데이터베이스에 반영하는 방식으로 동작한다. 또한 1차 캐시, 2차 캐시의 개념 역시 페이지 캐시와 비슷하다. 이처럼 운영체제가 제공하는 파일 관리 및 I/O 최적화 기법은 다른 곳에서도 비슷한 방식으로 사용되는 것 같다. 오늘 살펴본 내용은 기본적인 내용이니 잘 기억하도록 하자.