File system
1. 개요
- 비휘발성 매체에 주로 저장되는, 속하는 디렉토리가 있고 이름 단위로 구분되는 정보의 모음(collection)을 파일이라 한다. 사용자는 OS를 통해 파일의 내용을 읽거나 변경할 수 있으며, 새로운 파일을 만들거나 기존 파일을 삭제할 수도 있다. 또한 OS는 파일을 디렉토리를 통해 트리와 유사한 계층 형태로 관리하며 파일을 저장하고 제어하는데, 이처럼 OS가 파일을 저장하고 제어하는 일련의 구조 내지는 방법을 file system이라 한다.
- 모든 파일은 OS가 그 파일을 관리하게 할 수 있는 정보(접근 권한, 형식, 파일이 만들어지거나 변경된 시간, 크기, 디스크 상 저장 위치 등)를 그 내용으로 포함하고 있다. 이를 file attribute 또는 metadata라 한다.
- OS는 보통 디렉토리 또한 파일의 형태로 관리한다(디렉토리 파일에는 그 디렉토리에 속하는 파일들의 metadata 일부가 저장된다). 또, LINUX 같은 OS는 여러 하드웨어 장치들 역시 파일의 형태로 관리하기도 한다.
- 하나의 컴퓨터 시스템에는 디스크 장치가 하나 이상 있을 수 있는데, OS가 인식하는 논리적인 디스크 개수는 물리적 장치의 개수와 달라질 수 있다. 즉, 물리적 디스크가 하나라 하더라도 OS가 인식하는 논리적 디스크 개수는 둘 이상일 수 있으며 반대로 물리적 디스크가 여러 개라 하더라도 OS가 인식하는 논리적 디스크 개수는 하나일 수 있다. 이때 논리적 디스크 한 단위를 partition이라 한다. partition은 그 안에 들어가는 데이터 종류에 따라 크게 (1)그 partition에 속하는 파일들에 관한 file system (2)메인 메모리에서 swap out 된 page들이 담기는 swap space 두 유형으로 나뉜다.
- 각 partition의 file system은 서로 독립적이므로, 한 partition에서 다른 partition의 file system으로 접근하려면 자료구조적인 방법이 필요하다. root file system의 root에 새로운 이름의 디렉토리를 만든 후 새로 로드할 partition의 file system 전체를 그 디렉토리에 연결(mount)하면 그 다음부터 그 디렉토리를 통해 그 partition의 file system에 접근할 수 있다. 이를 mounting이라 한다.
2. file operations
1) open()
- 프로세스로부터 ‘파일의 내용을 read/write하라’는 system call이 들어올 때마다 그 경로를 찾아가서 파일의 내용을 메인 메모리로 가져올 수도 있지만, 그 파일의 디스크 내 위치를 알려면 그 경로 상에 있는 모든 디렉토리 파일들의 내용을 메인 메모리로 가져와 보다 하위 디렉토리 파일의 디스크 내 위치를 열어보는 작업이 필요하므로 이렇게 할 경우 매 read/write 때마다 엄청난 overhead가 드는 작업을 반복해야 한다. 따라서 대부분의 OS는 file open system call을 별도로 두어, 반드시 file을 open한 뒤에야 비로소 read/write를 할 수 있게 하고 있다.
- file open 절차는 다음과 같다.
(1) 프로세스로부터 ‘특정 경로의 파일을 open하라’는 system call이 들어오면 OS는 file system을 탐색하여 그 경로에 있는 모든 디렉토리 파일들과 목표 파일의 metadata를 메인 메모리의 커널 영역의 system-wide open file table에 로드한다. (I/O 장치 접근은 모두 커널이 전적으로 담당하므로, 커널은 프로세스가 발생시킨 file open system call에 따라 open한 파일들을 관리하는 자료구조를 반드시 가져야 한다. 참고로 이 table에는 metadata뿐 아니라 현재 각 프로세스가 그 파일에서 읽고 있는 내용의 디스크 내 위치 또한 저장된다.)
(2) OS는 그 파일의 metadata가 저장된 system-wide open file table의 엔트리의 주소를 그 파일을 open하게 한 프로세스의 PCB 내 per-process file descriptor table에 저장하고, 그 파일을 open하게 한 프로세스에는 그 파일의 metadata의 메모리 내 주소값이 저장된 per-process file descriptor table의 엔트리의 인덱스를 리턴한다.
2) read()
- 프로세스로부터 특정 파일에 대해 read를 수행하라는 system call이 들어오면, OS는 그 파일에 접근해 파일 내용을 읽어와서는 일단 이를 메인 메모리 내 buffer에 저장하고, 그 다음에 그 값을 프로세스에 리턴한다. 이를 buffer caching이라 하며, 이는 다음 번에 그 파일의 내용을 다시 읽어오도록 하는 system call이 들어오면 디스크에 직접 접근하지 않고 buffer값을 리턴하도록 하여 수행 속도를 빠르게 하기 위함이다.
- buffer의 참조는 OS에 대한 system call이 발생한 다음에 이루어지므로, page replacement의 경우와는 달리 OS의 메모리 자원을 마음껏 사용할 수 있어 LRU, LFU 등의 알고리즘을 사용하는 데 제약이 없다.
3. file protection
한 파일에 대하여 프로세스(사용자)마다 접근 권한을 다르게 설정할 수 있다. 각 프로세스/사용자의 파일에 대한 접근 권한을 관리하는 방법은 다음과 같다.
1) access control matrix
- 각 파일마다 각 사용자가 그 파일에 대해 어떤 권한이 있는지를 기록하는 행렬을 사용하는 방식이다. 사용자 수보다 파일 수가 압도적으로 많으므로 이 행렬은 희소행렬인 경우가 대부분이며, 따라서 실제로 행렬의 모든 값을 일일이 저장하는 자료구조를 사용하는 것은 메모리 낭비가 된다. 각 행 또는 열에서 그 값이 0이 아닌 값들을 연결리스트로 저장하면 이 행렬을 메모리를 낭비하지 않으면서 구현할 수 있다. (각 파일마다 각 사용자가 어떤 권한이 있는지를 기록한 것을 연결한 리스트를 access control list, 각 사용자마다 각 파일에 대해 어떤 권한이 있는지를 기록한 것을 연결한 리스트를 capability list라 한다.)
- 사실 연결리스트를 사용한다 하더라도 다른 방식보다 overhead가 더 크므로 이 방식은 실제론 잘 쓰이지 않는다.
2) grouping
- 전체 사용자를 (1)시스템의 owner, (2)owner가 속한 group, (3)public 셋으로 나누어, 각 파일마다 각 그룹이 그 파일에 대해 어떤 권한이 있는지를 기록한다. 파일 접근 권한은 (a)read 가부 (b)write 가부 (c)execution 가부 세 가지 권한이 있으므로, 각 파일에 대한 접근 권한 정보는 각 그룹마다 3bit씩 총 9bit로 모두 표현 가능하다.
3) password
- 각 파일 또는 디렉토리마다 password를 두어, password를 알고 있는 경우에만 그 파일(또는 그 디렉토리에 속한 파일)에 접근할 수 있는 방식이다. 모든 권한에 동일한 password를 설정할 수도, 각 권한마다 다른 password를 설정할 수도 있다. 설정하는 password의 수가 많아질수록 관리하기 어려워진다는 단점이 있다.