1일 1커밋🌱

5 분 소요

1. 파일과 파일시스템

파일들은 하드디스크나 SSD와 같은 저장 장치에 저장된다. 메모리와 마찬가지로 사용자가 직접 저장을 하게 되면 중요한 정보를 훼손할 수 있기 때문에 사용자가 운영체제를 통해 요청해야 한다. 운영체제는 파일을 관리하기 위해 파일 관리자를 뒀는데, 이를 파일 시스템이라고 한다. 파일 관리자는 가상 메모리에서 메모리 관리자가 페이지 테이블을 이용해서 가상 주소를 물리 주소로 변환하는 것처럼 파일 테이블을 이용해 파일을 관리한다.

파일 시스템의 기능은 다음과 같다.

  • 첫번째 기능은 파일과 디렉토리를 만드는 것이다.
  • 두번째 기능은 파일과 디렉토리의 수정, 삭제를 하는 것이다.
  • 세번째 기능은 다른 사용자로부터 파일 시스템을 보호하기 위해 접근 권한을 관리한다.
  • 네번째 기능은 파일의 내용이 손상되지 않도록 무결성을 보장하는 것이다.
  • 다섯번째 기능은 예기치 못한 사고로부터 백업과 복구를 한다.
  • 여섯번째 기능은 파일을 암호화해 파일을 보호하는 것이다.

주변 장치는 캐릭터 디바이스와 블록 디바이스로 구분할 수 있는데, 하드디스크와 플래시 메모리는 블록 디바이스에 속한다. 파일 시스템은 하드디스크나 플래시 메모리 같은 저장장치에 저장되기 때문에 전송 단위가 블록이다. 저장장치의 전송 단위는 블록이지만 사용자는 바이트 단위로 파일에 접근이 가능해야 하기 때문에 파일 관리자가 중간에서 변환해줘야 한다.

유닉스 운영체제는 파일의 확장자가 없지만 윈도우즈의 경우 파일마다 확장자가 있다. 이 확장자로 파일의 성격을 알 수 있는데, exe 파일은 실행 파일, jpg, bmp, png 파일은 이미지 파일, mp3, wav 파일은 음악 파일, mp4, avi 파일은 동영상 파일이다. 이렇게 확장자가 있으면 파일을 더블 클릭했을 때 바로 연결되는 프로그램을 설정할 수 있다. exe 파일은 실행 파일이기 때문에 운영체제가 바로 프로세스를 만들어 실행하고, 이미지 파일은 포토샵이나 그림판, 음악파일은 곰플레이어나 iTunes가 실행된다.

파일은 헤더와 데이터로 이루어져 있는데, 헤더에는 파일의 속성들이 담겨있다. 운영체제는 파일을 관리하기 위해 정보를 보관하는 파일제어블록(File Control Block, FCB)을 가지고 있는데, 이를 파일 디스크립터(File Descriptor)라고 부른다. 파일 디스크립터는 파일마다 독립적으로 존재하고, 저장장치에 존재하다가 파일이 오픈되면 메모리로 이동한다. 파일 디스크립터는 파일 시스템이 관리하고 사용자가 직접 참조할 수는 없다. 사용자는 파일 시스템이 건내준 파일 디스크립터로 파일에 접근할 수 있다.

int main()
{
  int fd;
  fd = open("./test/txt", O_RDONLY);
  close(fd);
}

C언어를 예시로 살펴보면, 사용자는 파일에 접근하기 위해 open(), close() 라는 시스템 콜을 이용한다. 네번째 줄에서 사용자가 open() 함수를 이용해 test.txt라는 파일을 읽기 전용으로 열기 요청을 하면, 운영체제는 해당 파일을 찾아 파일 디스크립터를 전달해준다. 다섯번째 줄에서는 사용자가 close() 함수를 이용해 닫기 요청을 하면 운영체제는 해당 파일 디스크립터를 참조해 파일을 안전하게 닫아준다.

파일은 데이터의 집합으로 볼 수 있는데, 이 데이터의 집합을 어떻게 구성하느냐에 따라 종류를 나눌 수 있다.

  • 첫번째는 순차 파일 구조이다. 순차 파일 구조는 파일의 내용이 연속적으로 이어진 형태이다. 사용자가 파일을 쓰기 위해서는 open() 함수를 이용하는데, 이때 파일은 파일 디스크립터를 사용자에게 전달해준다. 파일 디스크립터는 파일의 맨 앞에 위치해서 사용자가 쓰거나 읽기를 시작하면 처음부터 진행한다. 만약 파일의 다른 영역으로 가고 싶다면 lseek 함수를 이용해 파일 디스크립터의 위치를 옮긴다. 순차 파일 구조의 장점은 모든 데이터가 순서대로 기록되기 때문에 공간의 낭비가 없고 구조가 단순한 것이다. 단점으로는 특정 지점에 바로 이동이 어려워 데이터를 삽입하거나 삭제하려면 탐색하는데 시간이 많이 걸린다는 것이다.
  • 두번째는 직접 파일 구조이다. 직접 파일 구조는 저장하려는 데이터를 해시 함수를 통해 저장 위치를 결정하는 파일 구조이다. 이 구조는 자료 구조에서는 해시 테이블이라는 이름으로 불리는 방식이고, 요즘 많이 쓰이는 데이터 포맷인 JSON도 이 방식이다. 직접 파일 구조의 장점은 해시 함수를 이용하기 때문에 데이터 접근이 굉장히 빠르다는 것이다. 단점으로는 해시 함수의 선정이 굉장히 중요하기 때문에 해시 함수를 잘 골라야 한다는 점과 저장 공간이 낭비될 수 있다는 점이다.
  • 세번째는 인덱스 파일 구조이다. 인덱스 파일 구조는 순차 접근 방식과 직접 접근 방식의 장점을 취한 것으로, 두가지 방식 모두 가능하다. 인덱스 파일 구조는 음악 재생 프로그램에서 재생 목록으로 예를 들 수 있다. 재생 목록을 그냥 재생시키면 처음부터 마지막 곡까지 순차적으로 실행된다. 하지만 원하는 곡이 있다면 해당 곡을 클릭하면 실행되는데, 이같은 구조를 인덱스 파일 구조라고 부른다. 예시로 든 재생 목록으로 인덱스 파일 구조를 알아보면 재생 목록은 전부 순차 데이터로 저장되어 있다. 보통 재생을 누르면 앞에서부터 뒤로 순차적으로 재생하는데, 사용자가 2번 노래를 듣고 싶다고 노래를 재생하면 인덱스 테이블의 2번에 접근해 블록 번호를 알아낸다. 그리고 순차 데이터의 해당 블록 번호로 이동해 바로 2번 음악을 재생한다.

2. 디렉토리

파일을 하나의 공간에 보관하면 파일이 많아지면서 굉장히 복잡해질 것이다. 그래서 관련 있는 파일을 모아둘 수 있도록 디렉토리가 등장했다. 디렉토리는 1개 이상의 파일을 가질 수 있고, 자식 디렉토리도 가질 수 있다. 디렉토리는 여러 층으로 구성되는데, 최상위에 있는 디렉토리를 루트 디렉토리라고 부른다. 유닉스나 리눅스의 경우 루트 디렉토리를 /로 표시하고 디렉토리와 디렉토리 구분을 위해서도 /를 사용한다. 윈도우즈의 경우 루트 디렉토리는 파티션 이름으로 사용하는데, 보통 C:로 표시한다. 윈도우즈는 디렉토리와 디렉토리 구분을 \로 한다.

디렉토리라고 해서 파일과 다른 구조가 아니며 디렉토리 또한 파일이다. 단지 일반 파일에는 데이터가 저장되어 있고, 디렉토리에는 파일 정보가 저장되어 있다. 디렉토리 헤더는 디렉토리 정보가 시작하는 위치를 나타낸다. 디렉토리에 점 1개와 점 2개가 있는데, 이는 현재 디렉토리와 상위 디렉토리를 나타낸다. 루트 디렉토리의 경우 상위 디렉토리가 없기 때문에 점 2개도 자기 자신을 가리킨다.

초기 파일 시스템의 디렉토리는 단순한 구조였다. 루트 디렉토리에만 디렉토리가 존재할 수 있었고 다른 디렉토리에서는 하위 디렉토리를 만들 수 없었다. 하지만 파일이 많아지면서 불편함이 생겨 다단계 디렉토리 구조가 등장했다. 다단계 디렉토리는 어떠한 디렉토리에서도 하위 디렉토리를 만들 수 있는 구조이다. 우리가 쓰는 운영체제는 트리구조에서 순환이 생기는데, 그 이유는 바로 가기 기능이 있기 때문이다. 윈도우즈는 바로 가기 아이콘을 만들어 특정 디렉토리에서 다른 디렉토리로 바로 이동하는 기능이 있기 때문에 순환이 있는 트리구조이다.

3. 파일과 디스크

파일 시스템은 메모리와 비슷하다. 페이징과 같이 전체 디스크 공간을 일정한 크기로 나누고 그 공간에 주소를 할당해 관리한다. 일정한 크기로 나눈 공간을 메모리에서 페이지라 부르고, 파일 시스템에서는 블록이라고 부른다. 한 블록의 크기는 1KB에서 8KB 정도이다.

파일 시스템은 파일 정보를 파일 테이블로 관리하는데, 여기에는 파일이 시작하는 블록의 위치 정보도 담겨있다. 하나의 파일은 여러 개의 블록으로 이루어져 있는데, 이 블록들을 어떻게 연결하는지에 따라 연속할당과 불연속할당으로 나눌 수 있다.

연속할당은 파일을 구성하는 블록들을 디스크에 연속적으로 저장하는 방식이다. 따라서 파일의 시작 블록만 알면 파일의 전체를 차증ㄹ 수 있다. 이 방식은 메모리에서 세그멘테이션 기법처럼 외부 단편화가 발생하기 때문에 실제로 사용되지 않는 방식이다.

불연속할당은 디스크에 비어 있는 공간에 데이터를 분산해 저장하는 방식이다. 이 분산된 블록은 파일 시스템이 관리한다. 불연속 방식으로는 연결 할당과 인덱스 할당이 있다.

먼저 연결 할당을 알아보자. 자료구조에는 연결 리스트라는 것이 있다. 연결 리스트는 각 노드가 데이터와 포인트를 가지고 있다. 데이터는 말그대로 해당 노드가 저장하고 있는 데이터이고, 포인트는 이전 노드와 다음 노드의 주소를 가리킨다. 이렇게 구성하면 하나의 노드와 포인터를 이용해 다른 노드로 이동하여 모든 노드를 순환할 수 있다. 연결 할당은 파일에 속한 데이터를 연결 리스트로 관리한다. 파일 테이블에는 시작 테이블에 대한 정보만 저장하고 나머지는 연결 리스트를 이용해 다른 블록에 접근하는 방식이다.

인덱스 할당 방식은 테이블의 블록 포인터가 데이터 블록에 직접 연결하는 것이 아니라 데이터들의 인덱스를 가지고 있는 인덱스 블록을 연결한다. 인덱스 할당은 데이터가 많아서 테이블이 꽉 찬 경우 인덱스 블록을 더 만들어 연결하기 때문에 테이블을 확장할 수 있다. 덕분에 파일의 크기가 작다면 데이터를 바로 참조하는 블록 포인터를 이용하고, 파일의 크기가 크다면 간접 포인터를 이용해 많은 데이터에 접근할 수 있다. 만약 더 큰 데이터가 필요하다면 이중 간접 포인터, 3중 간접 포인터를 이용할 수 있다. 이 방식은 i-node라는 이름으로 유닉스와 리눅스에서 많이 사용되고 있다.

디스크는 일정한 크기의 블록으로 나뉘고 블록의 크기는 1KB부터 8KB 정도이다. 디스크를 1KB 정도로 나누면 낭비되는 공간을 줄일 수 있지만 관리해야 할 블록의 수도 많아진다. 반면에 8KB 정도로 나누면 관리해야 하는 블록의 수는 적지만 관리해야 할 블록의 수도 많아진다.

디스크에 파일을 저장할 때마다 빈 공간을 찾으려 모든 공간을 뒤지는 방식은 비효율적이다. 파일 시스템은 효율적인 관리를 위해 빈 공간을 모아둔 free block list를 가지고 있다. 만약 특정 파일을 삭제한다면 파일 시스템은 파일의 모든 정보를 지우는 것이 아니라 파일 테이블의 헤더를 삭제하고 free block list에 추가한다. 이렇게 처리하면 사용자는 파일이 삭제된 것처럼 느끼는데 사용했던 블록의 데이터는 그대로 남아있기 때문에 범죄를 저질러서 증거를 인멸하더라도 포렌식을 통해 데이터를 복구할 수 있다.

참고

카테고리:

업데이트:

댓글남기기