정글사관학교 개발일지/운영체제-PintOS

운영체제 - File system (1) File system (정글사관학교 85일차 TIL)

Woonys 2022. 1. 27. 00:34
반응형

 

오늘은 file system 이론 공부를 진행했다.

공부자료는 반효경 교수님의 운영체제(갓효경..)

 

https://woony.notion.site/File-system-9cb913a86d3a435fa92b8b9fe24fa687

 

File system

File and File system

woony.notion.site

 

 

File system

 

File이란?

파일은 하드디스크에 저장하는 정보의 단위(A named collection of related information)이다. 메모리가 주소를 통해 접근하는 장치라면 파일은 이름을 통해 접근하는 정보 단위로, 관련 정보를 파일 이름을 가지고서 저장한다. 파일은 일반적으로 비휘발성 보조 기억 장치에 저장한다.

 

리눅스의 경우, 파일이라는 형태를 단순히 데이터 저장 용도뿐만 아니라 장치를 관리하기 위해서도 다양한 저장 장치를 file이라는 동일한 논리적 단위로 볼 수 있게 한다.

 

File 연산에는 어떤 것들이 있나?

  • create, delete: 파일 생성/삭제
  • read/write: 읽고 쓰기
  • reposition(lseek): 파일을 읽거나 쓸 때 어느 위치부터 읽을지를 찾는 함수. 파일을 처음 읽을 때는 시작 부분부터 읽는다. 그런데 다음번에 이 파일을 또 읽으면 그때는 처음부터가 아니라 이전에 읽었던 위치(offset)부터 읽는다. 이 위치를 찾는 게 reposition이다. 
  • open, close: 파일을 열고 닫는 연산
    • 파일을 read/write하려면 그전에 반드시 open하고, read/write가 끝나면 반드시 close해줘야 한다.

 

File attribute(파일의 메타데이터)

파일 메타데이터는 파일 자체 내용이 아닌, 파일을 관리하기 위한 각종 정보를 말한다. 파일 이름, 유형, 저장 위치, 파일 사이즈부터 접근 권한(읽기/쓰기/실행), 시간, 소유자 등이 있다.

 

File system

파일 시스템은 운영체제에서 파일을 관리하는 부분이다. 파일 및 파일 메타데이터, 디렉토리 정보도 관리하고 있으며 파일 저장 방법을 결정하기도 한다. 파일 보호 기능 역시 들어있다.

 

Directory and Logical Disk

디렉토리 역시 하나의 파일로 취급한다. 파일의 메타데이터 중 일부(특히 디스크 내 파일 위치)를 보관하고 있는 일종의 특별한 파일 개념이다. 디렉토리에 대한 연산으로는 

  1. 파일을 찾고(search a file),
  2. 파일을 생성하고(create a file),
  3. 파일을 삭제하며(delete a file),
  4. 디렉토리 내에서 리스트를 보여주거나(list a directory - 대표적으로 ls 커맨드),
  5. 파일 이름을 변경(rename a file),
  6. 파일 시스템 내에서 파일을 탐색하는 (traverse the file system)

연산 등이 있다.

 

디스크는 물리적 개념인데, 이를 논리화 시킨 개념이 바로 파티션이다. 즉, 파티션이 논리 디스크(logical disk)라고 볼 수 있다. 운영체제는 물리적인 디스크에 직접 접근하는 게 아니라 논리적인 디스크, 즉 파티션을 본다.

 

하나의 물리적 디스크 안에는 여러 파티션이 존재하는데, 물리 디스크를 파티션으로 구분한 뒤, 각 파티션 별로 file system을 깔거나 혹은 swapping 용도로 쓰기도 한다.

 

open()

open() 함수는 파일의 메타데이터를 디스크에서 메인 메모리로 올리는 작업을 수행한다.

 

논리 디스크 안에는 파일 시스템이 있는데, 그 안에는 파일의 메타 데이터도 저장되어 있고 파일 정보도 저장되어 있다. 메타데이터에는 파일 정보 위치를 가리키는 포인터도 있는데 이걸 메모리로 올리는게 open() 함수.

 

open('/a/b/c'): 디스크로부터 파일 c(파일 c는 디렉토리/a/b에 있음)의 메타데이터를 메모리로 가져온다. 이를 위해 directory path를 찾는다. 흐름을 살펴보면

 

  • 루트 디렉토리 "/"를 open하고 그 안에서 파일 "a"의 디스크 내 저장된 위치를 획득한다. 이때 주의할 것은, 파일 a는 디렉토리지만 파일이라는 점!
  • 파일 a를 open 후 read해 그 안에서 파일 b의 저장된 위치 획득
  • 파일 b를 open 후 read해 그 안에서 파일 c의 저장된 위치 획득
  • 파일 c를 open한다.

이를 시스템 콜 과정 전반으로 살펴보자.

 

  1. 사용자 프로세스에서 open() 시스템 콜을 운영체제에 요청한다. (open("/a/b")). open()은 시스템 콜이므로 cpu 제어권이 운영체제로 넘어간다.
  2. 운영체제 내에는 각 프로세스를 관리하기 위한 자료구조인 PCB와 현재 오픈한 파일이 어떤 것인지 전역적으로 관리하는 테이블(open file table)이 존재한다.
    • open()을 실행하면 가장 먼저 접근하는 곳이 root 디렉토리이다. 루트 디렉토리의 메타데이터 위치는 처음부터 알려져있어 운영체제는 이곳에 바로 접근 가능하다.
  3. 운영체제가 root 디렉토리의 메타데이터를 먼저 메모리에 올린다.
  4. root 메타데이터에는 root contents에 대한 위치 정보가 들어있다. 여기로 접근해 root 디렉토리 파일의 contents를 찾는다.
    • root 역시 디렉토리 "파일". 파일 내부에는 디렉토리 안에 들어있는 파일의 메타데이터를 보관하고 있다.
    • 이때, 메타데이터에는 content 위치에 대한 포인터를 들고 있지 실제 파일을 들고 있지 않다.
    • root 디렉토리 내용에 가면 a라는 파일의 메타데이터가 들어있다.
  5. 이 a 메타데이터를 디스크로부터 읽어서 메모리에 올린다. 이제 a의 메타데이터는 메모리에 올라와있다.
  6. a 메타데이터 정보를 통해 a contents 위치에 접근한다. 여기서 b의 메타데이터를 찾는다.
  7. b 메타데이터를 메모리에 올린다. 여기까지 오면 open("/a/b")가 끝난다. 즉, 디렉토리 끝에 있는 최종 타겟 파일을 메모리에 올리는 작업이 open()!
  8. open()은 시스템 콜로, 결과값을 반환한다. 각 프로세스마다 그 프로세스가 오픈한 파일에 대한 메타데이터 포인터를 갖고 있는 배열이 PCB 내에 들어있는데 이것이 파일 디스크립터 테이블(fd table)이다.
    • open()을 통해 메모리에 올린 b의 메타데이터 주소값을 가리키는 포인터가 fd 테이블 내에 들어간다.
    • 테이블 내 몇 번째 인덱스에 b의 메타데이터 주소값이 들어가있는지가 나오는데 이 인덱스값이 바로 fd이다.
  9. 이 fd값이 open()에 대한 반환값이 된다.
    • 우리는 b를 open했기 때문에 fd를 통해 fd 테이블에 접근해 곧바로 b의 메타데이터가 들어있는 메모리 내 주소값으로 이동이 가능하다. 즉, 루트 디렉토리로부터 다시 1~7 과정을 반복할 필요가 없이 여기서 반환받은 fd만으로 read/write 요청이 가능하다!

 

 

read()

read()는 어떨까? 흐름을 살펴보자.

 

  1. 인자로 fd를 넣어서 read()를 실행한다.
    • 곧바로 b의 메타데이터로부터 b의 content 위치에 접근한다.
  2. read에서 인자로 넣어준 용량만큼 디스크에서 메모리로 읽어온다.
  3. 사용자 프로세스한테 읽어온 파일을 다이렉트로 전달하지 않는다. 운영체제가 자신의 메모리 공간 일부에 먼저 읽어놓는데, 이게 바로 버퍼 캐시(buffer cache)!
  4. 그 다음에 사용자 프로그램한테 내용을 카피해서 전달해준다.

만약 이 프로그램 혹은 다른 프로그램에서 동일 파일을 읽으려고 하면 어떻게 될까? 다시 디스크에 방문하지 않고 커널에 올려놓은 파일을 사용자 프로세스에 복사하는데, 이것이 바로 버퍼 캐싱(buffer caching)이다!

 

운영체제 메모리 관리 전략에서 페이징 기법의 경우, 이미 메모리에 올라온 페이지에 대해서는 중간에 운영체제가 끼어들지 못하고 하드웨어 단에서 주소 변환해서 바로 접근하는 방식을 취한다. 이 때문에 LRU, LFU 등의 페이지 관리 전략을 실행하지 못하는 것. 운영체제가 페이지의 모든 현황을 알지 못하기 때문이다. 왜? 이렇게 하면 느리기 때문.

 

반면, 파일에 대한 read/write 시스템에서는 운영체제가 버퍼 캐시를 갖는다. 파일 시스템에서의 버퍼 캐시는 요청한 내용이 버퍼 캐시 안에 있든 없든 간에 운영체제한테 무조건 cpu 제어권이 넘어간다. 지금은 데이터를 요청했는데 메모리에 올라와있지 않기 때문에 버퍼 캐시를 읽어놓는다. 프로그램이 나중에 read를 요청하면 cpu 제어권은 운영체제한테 넘어간다(read 역시 시스템 콜이다). 왜? 이미 메모리에 올라와있으니까.

 

메모리에 없다면 I/O 요청해서 디스크에 가야하지만 이미 메모리에 올려놨다면 그럴 필요가 없다. 따라서 운영체제가 모든 정보를 알고 있기 때문에 버퍼 캐시를 관리하는 전략에서는 LRU, LFU 알고리즘을 모두 적용할 수 있다.

 

File Descriptor Table / Open File Table

위 이미지를 보면 메모리 위에 두 개의 테이블이 올려져있는 것을 볼 수 있다. fd 테이블과 open file table이 그것인데, fd 테이블의 경우 프로세스당 하나씩 갖고 있는 테이블로, 프로세스별로 지역적으로 관리한다. 반면 open file table은 전역적으로 관리하는데, 따라서 system-wide open file table이라고도 불린다. 글로벌하게 관리되며 따라서 시스템 전체에서 딱 하나만 갖고 있다.

 

왜 이렇게 관리할까? 프로세스당 지역적으로 관리하는 테이블만 쓰거나 시스템 전체를 관장하는 테이블 하나만 쓰거나 하면 될 것 아닌가? 둘 모두 각자 상황에 맞게 필요하기 때문이다. 파일을 프로세스당 지역적으로도, 시스템 전반에서 전역적으로도 함께 관리할 필요가 있다.

 

파일에 대한 메타데이터가 디스크에 있을 때는 전역적으로 공유되는 정보(파일 이름, 유형, 저장 위치, 파일 사이즈 등)만 이 메타데이터에 해당한다. 근데 이걸 메모리에 올려놓게 되면 어떨까? 전역 정보에 추가적으로 한 가지 메타데이터가 더 필요하다. 바로 현재 프로세스가 이 파일의 어느 위치에 접근해있는지에 대한 offset이다.

 

이 offset은 각 프로세스마다 다를 것이다. 어디는 여기를 읽고 저 프로세스는 저기를 읽고 있다던지 등. 즉, 파일 내에서 어디로 접근하고 있는지에 대한 값은 프로세스마다 별도로 갖고 있어야 한다. 따라서 프로세스와 무관한 전역적인 메타데이터를 보관하는 open file table은 시스템 전체에서 하나만 갖고, 각 프로세스마다 갖고 있어야 할 지역적인 메타데이터(ex - offset)는 각 프로세스별로 지역적으로 보관하는 file descriptor table에 갖는다. 이때, pcb에 들어있는 fd에 대응하는 값에는 open file table에 들어있는 메타데이터 주소값만 들어있다.

 

파일이 메모리에 올라오면
- 프로세스별로 지역적으로 관리해야 할 정보(ex-offset)는 프로세스마다 파일 디스크립터 테이블을 갖고서 거기에 저장한다.
- 전역적으로 관리해야 할 정보는 시스템 전체에서 단 하나만 갖고 있는 open file table을 갖고서 거기에 저장한다.

 

반응형