파일 시스템이 발전해 나가는 과정을 보며 각 파일 시스템에 핵심적인 특징을 살펴본다. 파일 시스템이 데이터를 관리하기 위해 어떤 자료구조를 사용했는지, 무슨 문제가 있었는지, 왜 이런 파일시스템이 등장했는지를 중점적으로 파악한다.
1. UNIX File System
초기 UNIX File System에서는 가장 기본적인 파일과 디렉터리 개념만 지원한다. 하지만 기존의 파일 시스템은 레코드 기반 저장 시스템이었으며 이와 비교하면 엄청난 혁신이다. 파일과 디렉터리를 계층적으로 구성하여 데이터를 효율적으로 조작할 수 있으며 아이노드를 통해 파일에 대한 다양한 정보를 효율적으로 관리할 수 있게 하였다.
1) 자료 구조
- 디스크를 블록(block)단위로 나누어 사용자 데이터를 저장한다. 사용자 데이터가 있는 디스크 공간을 데이터 영역(data region)이라고 한다. 전체 크기 중 일부분을 데이터 영역으로 확보한다.
- 파일 시스템은 각 파일에 대한 정보를 관리한다. 이러한 정보를 메타데이터(metadata)라고 하며 데이터 블록들과 그 파일의 크기, 소유자, 접근 권한, 접근과 변경 시간 등의 정보를 아이노드(inode)라는 자료 구조에 저장한다.
- 아이노드들을 저장하기 위해 디스크 공간이 필요하다. 이 영역을 아이노드 테이블(inode table)이라고 한다. 여기에 아이노드가 배열 형태로 저장된다.
데이터 블록과 아이노드 블록만 있으면 기본적인 파일시스템을 구현하는 데 충분할까? 그렇지 않다. 아이노드나 데이터 블록 사용 여부를 나타낼 수 있어야 한다.
- 블록이 사용 중인지 아닌지를 표현하는 데 다양한 방법이 존재한다. 여기에선 단순한 비트맵(bitmap)을 사용하여 블록의 할당 구조를 표현한다. 데이터 또는 아이노드가 할당되었는지 확인하기 위해 데이터 비트맵(data bitmap)과 아이노드 비트맵(inode bitmap)을 사용한다.
- 추가로 슈퍼블록(super block)이 필요하다. 슈퍼블록은 파일 시스템에 대한 정보를 담고 있다. 예를 들어 파일 시스템에 몇 개의 아이노드와 데이터 블록이 있는지, 아이노드 테이블은 어디에서 식별하는지 등의 정보를 가진다. 파일 시스템을 마운트할 때 운영체제는 우선 슈퍼블록을 읽어 파일 시스템의 여러 가지 요소를 초기화하므로 필수적이다.
2) 아이노드와 멀티 레벨 인덱스
- 파일 시스템 자료 구조 중 inode는 매우 중요하다. 대부분의 파일 시스템이 비슷한 구조로 되어 있다. 아이노드는 인덱스 노드(index node)의 줄임말이다.
- 아이노드에 파일의 종류(일반 파일, 디렉터리 등), 크기, 할당된 블록 수, 보호 정보(파일의 소유, 접근 권한 등), 시간 정보와 더불어 데이터 블록이 디스크 어디에 존재하는지 같은 파일에 대한 정보들이 담겨있다.
- 아이노드 설계 시 중요한 것은 데이터 블록의 위치를 표현하는 방법이다. 간단한 방법은 아이노드 내에 여러 개의 직접 포인터(direct pointer)를 두는 것이다. 하지만 이 방법에는 제한이 있다. 파일 크기가 포인터 개수(아이노드 크기 / 포인터 크기) * 블록 크기로 제한된다. 어떻게 이 문제를 해결할 수 있을까?
- 간접 포인터를 사용하여 문제를 해결한다. 데이터 블록을 가리키는 포인터를 직접 포인터라고 하며, 간접 포인터를 가리키는 블록을 간접 포인터라고 한다. 아래 그림처럼 간접 포인터는 데이터 블록을 가리키는 것이 아니라 간접 포인터들을 모아둔 블록을 가리킨다. 예를 들어 블록이 4KB이고 디스크 주소가 4바이트라면 1024개의 포인터를 추가할 수 있다. 최대 파일 크기는 직접 포인터의 개수가 12개라면 12 4KB + 1024 4KB로 계산하여 4144KB임을 알 수 있다. 파일 크기가 부족하다면 이중 간접 포인터(4KB∗220) 더 커야 한다면 삼중 간접 포인터(4KB∗230})을 적용할 수 있다.
- 대부분 파일의 크기가 매우 작기 때문에 direct blocks 자료구조로 직접 데이터를 가리키게 하여 공간 효율성을 극대화하였다.
3) 현대 파일 시스템의 근간
- 현재 파일 시스템은 UNIX 파일 시스템 기본 구조를 가지고 최적화한 것이다.
- boot block: 부팅에 필요한 정보(bootstrap loader)가 있다.
- super block: 파일 시스템에 관한 총체적인 정보(블록의 크기, inode의 수, data block의 수, free block list의 head)를 담고 있다.
- inode: 파일 이름을 제외한 파일의 모든 메타데이터 저장(실제 데이터 블록의 위치 정보를 포함)한다.
- data block: 파일의 실제 내용 보관한다.
2. FFS(Fast File System)
- 초창기 UNIX 파일 시스템 구조는 아래와 같다.
1) 초기 UNIX 파일 시스템의 문제점
(1) 디스크를 RAM처럼 사용
- 디스크를 마치 임의 접근 기억 장치(RAM)처럼 사용한다는 것이다. 데이터를 저장하는 매체가 디스크라는 사실을 무시하고 여기저기에 데이터를 저장하고 있기 때문에 디스크 헤드를 이동시키는 데 많은 시간이 소요된다. 예를 들어, 아이노드를 읽은 후 파일의 데이터 블록에 접근해야 하는데, 이 둘의 위치를 전혀 고려하지 않는다.
(2) 외부 단편화
- 파일 시스템이 빈 공간을 효율적으로 관리하지 않기 때문에 공간이 단편화된다. 빈 공간들이 디스크 전역에 흩어져 있어 파일을 순차적으로 읽더라도 실제로는 디스크 전역을 오가며 블록을 접근한다.
- 위의 데이터 블록에서 B와 D가 삭제되고 새로운 E(크기 4)가 추가된다고 해보자.
- E를 읽거나 쓸 때 디스크 헤드를 움직여야 하기 때문에 순차 접근 성능을 보장하지 않는다.
(3) 작은 블록 크기(512byte)
- 블록의 크기가 작다는 건 입출력 단위가 작다는 것이고, 더욱 빈번하게 입출력 요청을 해야하므로 비효율적이다.
2. 개선사항
1) 파일 시스템 구조
- FFS는 디스크를 여러 개의 실린더 그룹(cylinder group)으로 나눈다. (Linux의 ext2, ext3에서는 블록 그룹(block group)으로 표현) 파일과 파일이 속한 디렉터리 블록을 같은 그룹에 할당한다. 이는 한 파일을 읽고 다른 파일에 접근할 때 디스크 전역에 걸친 긴 탐색이 발생하지 않도록 한다.
- 슈퍼 블록 복사본이 그룹마다 존재하여 높은 가용성을 확보한다. 그룹별로 아이노드 비트맵과 데이터 비트맵이 존재하여 큰 크기의 연속된 빈 공간도 쉽게 찾을 수 있게 설계하였다.
2) 파일과 디렉터리 할당 정책
- 디렉터리 위치는 할당된 디렉터리의 수가 적고 프리 아이노드의 수가 많은 실린더 그룹을 선택하여 디렉터리 데이터와 아이노드를 그룹에 저장한다.
디렉터리를 생성할 때 상대적으로 사용량이 적은 실린더 그룹에 할당하여 파일 시스템의 전반적인 성능을 균일하게 유지할 수 있고, 같은 그룹에 저장하여 긴 탐색을 방지하는 것이다.
-
파일의 경우 아이노드와 파일의 데이터 블록을 같은 그룹에 할당하여 아이노드와 데이터 간 긴 탐색을 방지하고, 동일한 디렉터리 내 모든 파일을 해당 디렉터리가 존재하는 실린더 그룹에 함께 저장한다.
-
대용량 파일의 경우 예외가 발생한다. 하나의 파일이 블록 그룹을 모두 채울 수 있고, 그 뒤의 관련 파일들이 다른 블록 그룹에 저장되도록 만들어 탐색 시간을 증가시킬 수 있기 때문에 다른 방법이 필요하다. 이 경우 FFS는 첫 번째 블록 그룹에 일정 수의 블록을 할당한 후에 파일의 큰 청크를 다른 블록 그룹(사용률이 낮은 그룹)에 저장한다. 그리고 파일의 다음 청크는 마찬가지로 다른 블록 그룹에 저장한다.
-
위의 그림처럼 디스크 전반에 걸쳐 청크 단위로 저장된다. 물론 파일 블록들으르 디스크에 흩어놓았기 때문에 접근 성능이 떨어지지만, 같은 그룹에 있는 다른 파일들에 대한 접근에 대해선 기존의 효율성을 유지할 수 있다.
3) 기타
- FFS는 서브블록(sub-block)의 개념을 도입하여 512byte 단위로 파일 크기에 맞게 할당할 수 있도록 하였다. 예를 들어 4KB 블록을 요청할 때는 서브블록이 4KB가 될 때까지 계속해서 할당한다. 파일시스템에서 이러한 추가 I/O를 방지하기 위해 libc를 수정하여 조건이 만족하면 I/O 작업을 하도록 성능 최적화를 하였다.
- 또한 순차 접근의 경우 0번에 대한 읽기를 요청하고, 1번에 대한 읽기를 요청할 때 0번을 읽었을 때 이미 디스크 헤드가 지나가서 1번을 바로 읽을 수 없이 회전 지연이 발생하는 경우가 있었다. FFS는 추가 회전을 피하기 위한 디스크 위치 정보를 정확하게 알 수 있었기 때문에 효율적으로 동작하였다. 물론 이러한 기법을 적용하면 같은 정보를 얻기 위해 트랙을 두 바퀴 돌아야 하지만(최대 대역폭 50%) 디스크는 한 트랙을 내부적으로 캐시에 버퍼링하기 때문에 대역폭 문제는 문제 되지 않는다.
3. LFS(Log Structured file System)
1990년대 초반에는 메모리 크기가 증가 추세이며, 전송 대역폭은 크게 개선되었지만, 탐색과 회전 비용은 매우 천천히 감소하여 디스크를 순차적으로 접근할 수 있다면 상당한 성능 개선 효과가 예상되었다. 이런 목적으로 LFS 시스템이 개발되었다.
1) 순차 쓰기와 아이노드 위치
- LFS는 모든 갱신 정보(메타 데이터를 포함한)를 세그먼트라 불리는 메모리 자료구조에 보관한다. 세그먼트가 가득 차면 디스크에서 빈 공간을 찾아 한 번에 기록한다. 세그먼트가 크기 때문에 디스크 기록 작업을 효율적으로 할 수 있다.
- LSF는 파일의 최신 버전만을 유지하므로 주기적으로 이전 버전의 데이터와 아이노드 그리고 다른 자료구조들을 찾아 제거한다. 쓰기 작업의 효율성을 위해 큰 공간 단위로 공간을 해제한다.
- 디스크에 데이터 블록과 아이노드 정보 등을 순차적으로 기록한다는 것이 LFS의 핵심이다. 하지만 순차적으로 기록한다고 해서 효율적인 쓰기를 보장하는 것은 아니다. 첫 쓰기와 두 번째 쓰기에 간격이 있어 두 번째 쓰기에서 플래터를 회전시켜야 한다면 회전 시간만큼 기다려야 한다. 디스크의 최대 성능을 끌어내려면 순차 쓰기 정보를 한 번에 디스크에 내려보내야 한다. 이를 위해 LFS는 디스크에 쓰기 전에 갱신 내용을 메모리에 보관한다(쓰기 버퍼링).
- 전통적인 파일 시스템에서는 아이노드의 위치가 정해져 있었다. 하지만 LSF의 경우 원위치에 덮어쓰지 않기 때문에 최신 아이노드의 위치는 계속 변하고 있으며, 이는 아이노드가 디스크 전역에 흩어져 있음을 의미한다.
- 아이노드의 위치를 파악하기 위해 LSF 설계자들은 아이노드 맵(inode map, imap)이라는 자료구조를 개발하였다. imap 자료 구조는 아이노드 번호를 이용하여 가장 최신 아이노드의 디스크 위치를 구한다. 디스크에서 아이노드가 기록될 때 imap은 새로운 위치를 가리키도록 갱신한다. LSF에서는 아이노드 맵을 새로이 기록된 데이터와 아이노드들 옆에 함께 기록한다.
- 아이노드 맵 역시 블록으로 나누어져 디스크 상에 흩어져 있게 된다. 어떻게 아이노드 맵을 찾을까? LFS는 디스크 상에서 약속된 위치에 각 imap 블록들의 위치를 기록한다. 이를 체크포인트 영역(Checkpoint Region, CR)이라고 한다. 체크포인트 영역은 최신 아이노드 맵을 이루는 블록들을 가리키는 포인터를 갖고 있다. CR영역을 읽어 아이노드 맵의 조각들을 찾을 수 있다.
2) 동작 방법
(1) 디스크에서 읽기
- 메모리에는 캐시 정보가 없다고 하자. 디스크에서 파일을 읽는 과정을 살펴본다.
- 가장 처음 읽어야 하는 디스크 상 자료구조는 체크포인트 영역이다. 체크포인트 영역은 전체 아이노드 맵 블록들을 가리키는 포인터를 갖고 있다.
- 아이노드 맵 전체를 읽어 메모리에 캐시한다.
- 파일의 아이노드 번호를 구한다.
- imap의 아이노드 디스크 주소 매핑에서 아이노드 번호를 찾아서 가장 최신의 아이노드를 읽는다.
- 파일에서 블록을 읽으려면 일반적인 LINUX 파일 시스템이 수행하는 동일한 절차를 밟는다.
(2) 블록 최신 여부 판단
- LFS는 각 데이터 블록 D에 대해 D가 속한 파일의 아이노드 번호(어떤 파일에 속하는지)와 파일 내에서 오프셋을 저장한다. 이 정보는 세그먼트의 첫머리에 세그먼트 요약 블록(segment summary block)이라고 부르는 자료구조에 기록된다.
- 디스크 주소 A에 위치한 블록 D의 유효성을 판단하는 과정을 살펴보자.
- 세그먼트 요약 블록에서 블록 D의 아이노드 번호 N과 오프셋 T를 파악한다.
- imap에서 아이노드 N의 위치를 찾고, 디스크에서 그 N을 읽는다.
- 아이노드를 이용하여 오프셋에 해당하는 블록의 디스크 위치를 알아낸다.
- 파악된 위치가 주소 A와 일치하면 블록 D는 유효한 블록이다. 만약 위치가 다르다면 D는 유효하지 않음을 알 수 있다.
- 위 방식은 복잡하다. LFS는 버전 번호(version number)를 관리하여 그 새로운 버전 번호를 imap과 디스크 상의 세그먼트에 같이 기록을 해두면 디스크 상의 버전 번호와 imap의 버전 번호를 비교해서 위 과정을 단순화할 수 있다. 기록을 한 뒤에는 버전 번호를 증가시켜 새로운 번호로 매핑한다.
(3) 크래시 복구
- LFS는 쓰기 데이터를 세그먼트 버퍼에 먼저 기록하고 해당 세그먼트 버퍼를 디스크에 기록한다. 이러한 쓰기들을 로그로 구성한다. 체크포인트 영역에 첫번째와 마지막 세그먼트를 가리키는 포인터를 둔다. 각 세그먼트트 다음 세그먼트를 가리키는 포인터가 있다. 따라서 전체 세그먼트들이 링크드 리스트처럼 연결되는 것이다.
1. 체크포인트 영역을 갱신할 때 크래시
- 체크포인트 영역은 원자적으로 갱신되어야 한다. 이를 보장하기 위해 두 개의 체크포인트 영역을 둔다. 이를 디스크 양 끝에 두고 교대로 갱신한다. LSF는 먼저 체크포인트 헤더(현재 시간 값을 포함)를 기록한 후 체크포인트 영역에 내용을 쓰고 최종적으로 체크포인트 영역의 마지막 블록을 갱신한다. 마지막 블록에도 현재 시간 값이 포함되어 있으므로 성공적으로 체크포인트 영역이 갱신될 경우에는 헤드 블록의 시간 값이 마지막 블록의 시간 값보다 작게 된다. 만약 헤더에 저장된 시간 값이 크다면 크래시가 발생했다는 것을 알 수 있다.
2. 세그먼트를 쓸 때 크래시
- LFS는 약 30초마다 체크 포인트 영역을 갱신한다. 디스크에 저장된 체크포인트 영역의 내용(파일 시스템 스냅샷)은 크래시 시점을 기준으로 30초 이전의 상태를 반영할 수도 있다.
- 이를 해결하기 위해 데이터베이스에서 사용되는 롤 포워드(roll forward) 기법을 적용한다. 최신 체크포인트 영역에서 시작하여 복구 모듈은 체크포인트 영역을 읽어 들여 세그먼트 리스트의 마지막 세그먼트 위치를 파악한다. 해당 세그먼트가 가리키는 다음 세크먼트의 존재 여부를 검사하여 마지막 세그먼트 위치를 갱신할 수 있다. 마지막 세그먼트를 찾으면 데이터와 메타데이터를 복구하면 된다.
4. EXT3 저널링
EXT3는 기존 파일시스템에서 크래시 문제를 해결하기 위해 저널링 기법을 적용하였다. 기존 파일 시스템에서 발생하는 크래시 문제는 어떤 것이 있었고, 이를 해결하기 위한 방법에는 무엇이 있는지 알아본다.
- 파일 시스템은 전력 손실이나 시스템 크래시가 발생하는 상황에서도 디스크 내용을 안전하게 관리할 수 있어야 한다.
1) 크래시 시나리오
- 위와 같은 형태가 되기 위해서 파일 시스템은 디스크에 세 번의 쓰기(아이노드, 비트맵, 데이터 블록)를 수행해야 한다. write()의 결과는 디스크에 즉시 반영되지 않는다. 변경된 아이노드와 비트맵 그리고 데이터는 일정 기간 메인 메모리상에서 존재(버퍼 캐시 또는 페이지 캐시)하다가 파일 시스템이 실제로 디스크를 실행할 때(5초나 30초 이후) 기록된다.
(1) 한 번의 쓰기만 성공
1. 데이터 블록(Db)만 디스크에 기록된 경우
- 데이터는 디스크에는 있지만 그것을 가리키고 있는 아이노드가 없으며 할당 여부를 나타내는 비트맵도 없다. 이 경우 파일 시스템의 일관성 문제에는 이상이 없다. 하지만 사용자 입장에서는 데이터가 손실되었다.
2. 갱신된 아이노드(I[v2])만 디스크에 기록된 경우
- 비트맵은 할당되어 있지 않다고 하지만 아이노드는 할당되었다고 하는 일관성 문제가 발생한다.
3. 갱신된 비트맵(B[v2])만 디스크에 기록된 경우
- 비트맵은 할당되었다고 표시하지만, 아이노드가 가리키고 있는 블록은 없다. 일관성이 손상되었다. 해결하지 않고 그대로 둔다면 비트맵으로 할당된 블록은 사용될 수가 없다.
(2) 두 번의 쓰기가 성공한 경우
1. 데이터 블록(Db)을 제외한 아이노드(I[v2])와 비트맵(B[v2])이 디스크에 기록된 경우
- 파일시스템 메타데이터의 일관성은 보장된다. 하지만 사용자 데이터가 손실되었다.
2. 아이노드(I[v2])를 제외한 비트맵(B[v2])과 데이터 블록(Db)이 디스크에 기록된 경우
- 아이노드와 비트맵 간 내용이 일치하지 않는다. 블록이 기록되고 비트맵은 사용 중이라고 되어 있지만 아이노드가 파일을 가리키고 있지 않기에 해당 블록이 어느 파일에 속한 것인지 알 수 없다.
3. 비트맵(B[v2])을 제외한 아이노드와 데이터 블록(Db)이 디스크에 기록된 경우
- 아이노드는 제대로 가리키고 있지만 비트맵과 아이노드 간 일관성이 없다.
파일 시스템이 항상 일관성을 유지하려면 어떻게 해야 할까? 파일 시스템 검사기와 저널링 방법에 대해서 알아본다.
2) 파일 시스템 검사기(fsck)
- 파일 시스템이 일관성이 없더라도 그대로 두었다가 리부팅 시에 일관성 문제를 해결하는 방식을 선택하였다. fsck는 일관성 불일치를 발견하고 수정하는 UNIX 도구다. 먼저 이 방식이 문제들을 전부 해결할 수 없다는 것에 유의하자.
- fsck는 슈퍼 블럭, 프리 블럭, 아이노드 상태, 아이노드 링크, 중복, 배드 블럭, 디렉터리에 대해 기초 검사를 실시하여 일관성 불일치를 찾아낸다. 제대로 동작하는 fsck를 만드는 작업은 파일 시스템 전반적인 이해가 필요하다.
- fsck는 디스크의 용량이 커지고 RAID가 대중화되면서 실질적으로 사용이 불가능할 정도로 느려지게 되었다. 위의 예시를 볼 때 3개의 블럭을 갱신하다 생긴 문제를 해결하기 위해 디스크 전체를 다 읽어본다는 것은 엄청난 비용이다.
3) 저널링(journaling)
데이터베이스에서 일관성을 확보하기 위한 방법으로 write-ahead logging이라는 방법을 사용했는데 파일 시스템에선 이를 저널링이라고 말한다.
- 디스크 내용을 갱신할 때 해당 자료구조를 갱신하기 전에 먼저 수행하고자 하는 작업을 요약해서 기록(write-ahead)해둔다. 디스크에 갱신할 값을 저장하기에 디스크 페이지를 새값으로 갱신하는 과정에서 크래시가 발생하면 로그를 확인해서 다시 갱신하면 된다. 구체적인 동작 과정을 linux ext3 기준으로 알아본다.
(1) 데이터 저널링
-
아이노드와 비트맵 그리고 데이터 블럭을 갱신하는 평범한 파일 시스템 연산으로 생각해보자.
-
그림에서 보듯 5개의 블록을 기록한다. 트랜잭션 시작 블럭(TxB)은 연산에 대한 정보를 기록한다. 여기에 기록되는 정보는 갱신될 블록들에 대한 정보(ex: I[v2], B[v2], Db의 최종 주소)와 트랜잭션 식별자(TID)같은 것들이 있다. 갱신해야 할 물리적 내용을 저널에 기록하기 때문에 물리 로깅이라고도 불린다. (명령어 자체를 저장하는 논리 로깅도 존재한다)
-
트랜잭션 종료 블록(TxE)은 트랜잭션의 종료를 알리며 마찬가지로 TID를 포함하고 있다. 트랜잭션 종료 블록이 로그에 기록되면 트랜잭션은 커밋(commit)되었다고 말한다.
-
트랜잭션이 디스크에 안전히 기록된 후 파일 시스템상의 자료구조들은 이제 갱신될 수 있다. 저널에 기록된 내용을 실제 위치에 반영하는 과정을 체크포인팅이라고 한다. 저널이 기록한 직후 바로 체크포인트를 진행하지는 않는다.
-
다섯 개의 요청을 한꺼번에 전송해서 디스크가 이들을 순차로 쓰게 하는 도중에 크래시가 발생할 수 있다. 디스크는 임의로 스케줄링하기 때문에 만약 데이터를 저장하는 과정에서 크래시가 나면 데이터 손실이 발생한다. 하지만 그 이후에 트랜잭션 종료 블록이 실행되었다면 성공적으로 실행된 것처럼 보인다. 이 문제를 어떻게 해결할 수 있을까?
파일시스템은 트랜잭션을 두 단계로 나누어 기록한다. 먼저 TxE를 제외한 모든 블록을 한 번의 쓰기 요청으로 저널에 쓴다. 해당 쓰기가 완료되면 TxE 블록에 대한 쓰기를 요청하여 저널을 최종적이고 안전한 상태로 만든다. 다음과 같이 세 동작으로 구분할 수 있다.
1. 저널 쓰기: 트랜잭션 내용을 로그에 쓴다. (TxB와 메타데이터 그리고 데이터 포함)
2. 저널 커밋: 트랜잭션 커밋 블록을 로그에 쓴다.
3. 체크포인트: 갱신한 내용을 디스크 상 최종 위치에 쓴다.
(2) 메타데이터 저널링
- 데이터 블록을 거의 두 번씩 쓰는 방법은 오버헤드를 수반한다. 메타데이터 저널링 방식은 저널에 데이터 블록을 기록하지 않는다는 점을 제외하면 데이터 저널링 방식과 거의 동일하다.
- 메타데이터만 저널링하는 경우 데이터 블록을 디스크에 내려보내는 시점이 매우 중요하다. 예를 들어 I[v2]와 B[v2]가 저널에 커밋된 후에 디스크에 Db를 기록해도 될까? 이 경우 저널에 기록되었지만, 크래시가 난다면 파일 시스템이 복구를 시도할 수가 없다. Db가 로그에 기록되지 않았기 때문이다. I[v2]와 B[v2]를 재실행하여 체크포인트 하여 시스템의 일관성을 유지해야 한다. 하지만 I[v2]가 크래시로 인해 쓰레기값(이전 값)을 가리키고 있다.
이 경우 메타데이터를 저널에 기록하기 전에 반드시 관련 데이터 블럭들을 디스크에 먼저 쓴다.
데이터가 먼저 기록되는 것을 강제하여 아이노드 포인터가 쓰레기 데이터를 가리키지 않는 것을 보장할 수 있다. "포인터의 대상이 되는 객체를, 그것을 가리키는 객체보다 먼저 써라"
이미 삭제된 디렉터리 참조 문제
- 메타데이터 저널링에서 삭제된 디렉토리 참조 문제가 발생한다. 예를 들어 사용자가 foo라는 디렉터리에 파일을 생성하였다고 하자. 디렉터리 항목이 추가되고 foo의 데이터 블록이 로그에 기록된다. 저널링이 완료되면 다음과 같은 정보를 갖게 된다.
- 이 시점에서 사용자가 디렉터리의 모든 파일을 디렉터리 자체와 함께 삭제하였다. 그럼, 블럭 1000번은 free 블럭이 되며, 사용자가 새로운 파일을 생성할 때 기존 100번을 할당받을 수 있게 된다. 단, 메타데이터 저널링을 사용하기에 새로운 파일의 아이노드만 저널에 포함되고 있다는 것을 명심하자. 이때 크래시가 발생하면 복구 과정에서 현재 파일이 이전의 디렉터리 정보로 덮어 써지게 되는 문제가 발생한다.
ext3에서는 디렉터리를 삭제하면 저널에 철회 레코드를 기록하도록 만들었다. 저널을 재실행할 때 시스템은 먼저 철회 레코드의 존재 여부를 먼저 탐색하고, 철회된 내용은 재실행하지 않는 방식으로 위의 문제를 회피하였다.
5. VFS(Virtual File System)와 NFS(Network File System)
- Virtual File System: 서로 다른 다양한 file system에 대해 동일한 시스템 콜 인터페이스(API)를 통해 접근할 수 있게 하는 OS layer를 말한다.
- Network File System: 분산 시스템에서 네트워크를 통해 파일이 공유될 수 있다. NFS는 분산 환경에서 대표적인 파일 공유 방법이다.
- 두 개의 컴퓨터가 네트워크로 연결. 어떤 파일시스템이든지 상관없이 VFS 인터페이스로 접근. 내 컴퓨터에 파일 시스템이 없다면 NFS를 통해서 서버 쪽에 접근한다. 마치 자기 사용자가 접근하는 것처럼 외부 컴퓨터가 NFS를 이용해 접근한다.
참고 자료
- 이화여대 반효경 운영체제 강의
- 운영체제, 아주 쉬운 세 가지 이야기