운영체제 강의노트 - 파일 시스템

조해빈·2023년 5월 26일
0

OS

목록 보기
7/9

LECTURE is here

KOCW 온라인에서 제공되는 이화여대 반효경 교수님의 OS 강의에 대한 정리 요약

  • 노션에 기록했듯 CSAPP과 함께 천천히 병행
  • 연관 게시글은 강의 진행 순서대로 정렬되어 있지 않고, 내 필요에 따라 강의의 주제를 선택해 듣는다.
  • 온라인 상의 타인들이 올려놓은 연관 자료 역시 함께 참고한다.

파일

"하드디스크에 저장하는 정보의 단위."
메모리는 주소를 통해 접근한다면 디스크에 저장되는 파일이라는 것은 이름을 통해 접근하는 단위라고 볼 수 있다. 관련 정보를 이름과 함께 저장하는 것이다.

  • 파일은 비휘발성의 보조기억장치, HHD에 저장된다.
  • 운영체제는 실로 다양한 저장 장치를 관리하기 위해 file이란 동일한 논리적 단위로 볼 수 있게 한다. 하드디스크 1, 하드디스크 2 등 여러가지 장치들을 서로 다른 file(자세히 말한다면 device special file이라는 조금 별개의 file) 형태로 인식하고 관리한다.

파일 연산

  • create, delete: 파일 생성하고 삭제한다.
  • read, write: 파일을 읽고 쓴다.
  • reposition(lseek): 파일은 여러 개의 바이트로 구성된다. 파일을 읽거나 쓸 때에 필요에 따라 처음부터가 아닌 곳부터 읽거나 쓰고 싶은 경우가 있을 것이다. 고로 파일에 대해 현재 접근하고 있는 포인터 위치를 수정하는, 어느 위치부터 접할 지를 찾는 연산이다.
  • open, close: 파일을 열고 닫는다.
    • read/write를 하려면 그전에 반드시 open하고 read/write이 끝나면 반드시 close해줘야!

✅ 여기서 File attribute(파일의 메타데이터)란?

: 파일 자체의 내용이 아니라 파일을 관리하기 위한 각종 정보들.
- 파일 이름, 유형, 저장 위치, 파일 사이즈 등
- 접근 권한(읽기/쓰기/실행), 시간, 소유자 등
- 파일의 저장 방법 결정 - 어떻게 저장하고 관리할지 등
- 파일 보호

  • open: 정확히 말하면, 파일을 디스크에서 메모리로 파일의 메타데이터를 올려놓는 연산이다.

Directory and Logical Disk

디렉토리 역시 하나의 파일이다!
파일로서의 디렉토리의 내용은 그 디렉토리 밑에 존재하는 파일들이 뭔지, 그 파일들의 메타데이터들을 자신의 내용으로써 가지는 파일이다.

디렉토리 파일에 대한 연산

  • search for a file(찾기), create a file(생성), delete a file(삭제)
  • list a directory(목록 열람- 대표적으로 ls 커맨드), rename a file(리네임), traverse the file system(파일 시스템 전체를 탐색하는 연산)

파티션(== 논리 디스크)

결국 이 파일이라는 것들은 디스크에 저장이 될 텐데, 디스크라는 개념에는 논리 디스크와 물리 디스크가 나뉠 수 있다.

운영체제가 보는 디스크란, 논리 디스크이다. 운영체제는 논리 디스크를 본다.
다른 말로 이를 파티션이라 한다. 디스크를 하나 사서 c drive, d drive 등으로 파티션을 나누면 그 각각이 논리 디스크가 된다.

하나의 물리적 디스크 안에 여러 파티션을 두는 게 일반적이며, 여러 개의 물리적인 디스크를 하나의 파티션으로 구성되기도 한다.

우리는 물리 디스크를 파티션으로 구성한 뒤, 각 파티션에 file system을 깔거나 virtual memory의 swapping area 용도로 쓴다.

open()

✏️ 파일의 메타데이터를 메인 메모리로 올려놓는 연산.

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

다음은 파일에 대한 open() 연산의 진행 과정이다.1.
사용자 프로세스에서 파일에 대한 open() 콜을 한다 → open(”/a/b”)가 호출되었다고 하자. → open()은 시스템 콜이다. 고로 cpu 제어권이 운영체제로 넘어간다.
2.
운영체제 내에는 각 프로세스를 관리하기 위한 자료구조인 PCB와 현재 오픈한 파일이 어떤 건지 전역적으로 관리하는, 전역적인 테이블(Open file table)이 유지되고 있다. open()을 실행하면 가장 먼저 접근하는 곳이 root 디렉토리이다. 루트 디렉토리의 메타데이터 위치는 처음부터 알려져 있다. 운영체제는 root 디렉토리의 메타데이터를 먼저 메모리에 올린다.
3.
root 메타데이터에는 root contents에 대한 위치 정보가 들어있다. 여기로 접근해 root 디렉토리 파일의 contents를 찾는다.
root 역시 디렉토리 "파일". 파일 내부에는 디렉토리 안에 들어있는 파일의 메타데이터를 보관하고 있다.
이때, 메타데이터에는 content 위치에 대한 포인터를 들고 있지 실제 파일을 들고 있지 않다. root 디렉토리의 '내용'에 가면 a라는 파일의 메타데이터가 들어 있을 것이다.
4.
이 a 메타데이터를 디스크로부터 읽어서 메모리에 올린다. 이제 a의 메타데이터는 메모리에 올라와 있다. 즉 a를 open()한 것.
5.
이제, 메모리 위의 a 메타데이터 정보를 통해 a의 파일시스템 상의 위치에 접근할 수 있다. 그런데 보니까 a라는 파일도 디렉토리 파일이다. 디렉토리 파일인 a는 자신의 '내용'으로 그 디렉토리 밑에 존재하는 파일들이 뭔지, 그 파일들의 메타데이터들을 가질 것이다. 우리는 고로 a의 '내용'에서 b의 메타데이터를 찾을 수 있을 것이다.
6.
b 메타데이터를 메모리에 올린다. b가 open()된 것. 여기까지 오면 open("/a/b")가 끝난다. 즉, 디렉토리 끝에 있는 최종 타겟 파일을 메모리에 올리는 작업이 open()!
7.
끝이 아니다. 이제 open()은 시스템 콜로써 결과값을 반환해야 할 것이다.
각 프로세스마다 그 프로세스가 오픈한 파일에 대한 메타데이터 포인터를 갖고 있는 배열이 PCB 내에 들어 있다. 이것이 파일 디스크립터 테이블(FDT)이다. open()을 통해 메모리에 올린 b의 메타데이터 주소값을 가리키는 포인터가 fd 테이블 내에 들어간다.
테이블 내 몇 번째 인덱스에 b의 메타데이터 주소값이 들어가 있는지가 나오는데, 여기서 이 인덱스 값이 바로 fd이다. 이 fd값이 사용자 프로세스에게 반환되는, open()에 대한 반환값이 된다.

우리는 이제 b를 open했기 때문에 fd를 통해 fd 테이블에 접근해, 곧바로 b의 메타데이터가 들어 있는 메모리 내 주소값으로 이동이 가능하다.
즉, CPU는 이제 루트 디렉토리로부터 다시 1~7 과정을 반복할 필요가 없이 여기서 반환받은 fd만으로 read/write 요청이 가능하다.

read()

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

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

버퍼 캐시(buffer cache)

여기서 잠시 운영체제 메모리 관리 전략에 대한 고찰이 가능하다.

운영체제 메모리 관리 전략 중 페이징 기법의 경우, 이미 메모리에 올라온 페이지에 대해서는 운영체제가 중간에 끼어들지 못하고 그저 하드웨어 단에서 주소를 변환해서 바로 접근하는 방식을 취한다. 페이지 폴트가 나면 그제서야 CPU가 커널로 넘어와서, 스왑 영역에서부터 해당 페이지를 읽어오게 된다.

이 때문에 LRU, LFU 등의 페이지 관리 전략을 실행하지 못하는 것. 운영체제가 페이지의 모든 현황을 알지 못하기 때문이다. 왜? 이렇게 하면 느리기 때문.

반면, 파일에 대한 read/write 시스템에서는 운영체제가 버퍼 캐시를 갖는다. 파일 시스템에서의 버퍼 캐시는 요청한 내용이 버퍼 캐시 안에 있든 없든 간에 운영체제한테 무조건 CPU(제어권)가 넘어가게 된다. read를 요청하면, 이또한 시스템 콜이기 때문에 무조건 CPU 제어권은 운영체제한테 넘어간다. 그럼 운영체제가 판단하는 거다. 메모리에 없다면 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을 갖고서 거기에 저장한다.

파일 접근 권한(File Protection)

메모리 프로텍션은, read/write 권한 여부에 대해서만 이야기했었다.

메모리라는 건 프로세스마다 별도로 갖고 있으니 애당초 프로세스 혼자만 볼 수 있다. 그래서 메모리 프로텍션은 연산이 무엇이냐, read/write이 가능하냐의 이야기였다.

하지만 파일 프로텍션은?
여러 프로그램이 하나의 파일을 같이 사용할 수 있다. Open File Table을 놓고 같이 사용하고, 이는 마치 메모리처럼 독단적으로 사용하는 게 아니다...

그러다 보니 파일 접근 권한이라 함은, 1. "접근 권한이 누구한테 있냐", 2. "연산이 어떤 게 가능하냐" 두 가지를 갖고 있어야 한다.

접근에 대한 통제에 방법론 3가지가 있다.

1. Access control matrix

행렬에 행/열 → 사용자/파일을 쫙 나열하여 각 사용자가 각 파일에 대해 어떤 권한을 갖고 있는지, 읽기 권한이 있지, 쓰기 권한이 있는지 등등을 표시한다.
특정 사용자가 특정 파일에 접근했을 때 그 파일에 권한 체크하고 허락/불허한다.

그러나 문제가 있다. 파일이 엄청 많을텐데 사용자가 아예 접근 안하는 파일도 많고... → 그럼에도 정해진 규격의 빈 메모리 공간이 생성되어 자리를 차지하기 때문에 이는 공간 사용의 비효율, 낭비이다.

고로, 이런 식으로 하지 않고 연결 리스트로 만드는 방식을 생각해볼 수 있다.

가. Access control list

  • 각 파일별로 연결 리스트를 생성하여, 파일 별로 누구에게 어떠한 접근 권한이 있는지 연결 리스트로 만들어 표시한다.
    → file 3에 대한 링크드 리스트 만들면 user 1, user 2, ... → 각 파일 별로 사용자가 줄줄이 달린다.
  • 사용권한이 없는 사용자는 리스트에 달 필요 없으니 공간 낭비 X

나. Capability list:

  • 유저 별로 어떠한 접근 권한이 있는지 연결 리스트로 만들어 표시한다.
    여기서는 특정 사용자 체크해서 거기 안에 줄줄이 달린 파일 체크하면서 권한 체크한다.

이렇게 해서 모든 파일에 대한 접근 권한 제어가 가능하다.
하지만 여전히 오버헤드가 크다! 고로 일반적인 OS에서는 아래의 방법을 채택한다.

2. Grouping → 운영체제가 일반적으로 채택하는 방식!

모든 사용자에 대해서가 아니라, 사용자 그룹을 세 가지로 분류하여 ➡️ owner / group / public ➡️ 각 파일에 대해 세 그룹의 접근 권한(rwx)(읽, 쓰, 실행)를 3비트로 표시한다. (마치 모두 공개, 친구, 친한 친구 느낌이다...) 고로 모든 파일에 대한 접근 권한 제어에 단 9비트만을 필요로 하는 것이다.

3. Password 방식

모든 파일마다 password를 두는 방법, 또는 디렉토리 파일마다 password를 두는 방법이다.
그러나 동시에 모든 접근 권한 종류에 대해서도 하나씩의 password를 필요로 하게 된다. 이는 사람의 입장에서 매우 관리가 어렵다.

profile
UE5 공부하는 블로그로 거처를 옮겼습니다!

0개의 댓글