본문 바로가기

학교/os

파일 시스템

들어가기 전에

학교 수업 때 배운걸 다시 정리해서 쓴 글입니다.

오타 및 잘못된 부분 지적은 언제나 환영입니다!

파일 시스템이란?

파일이 디스크에 관리될 수 있도록 해주는 자료구조들의 집합

디스크에서 파일 시스템 구성

디스크의 연속된 블록 공간을 할당 받아, 그곳에 파일들의 메타 데이터들을 저장한다.
파일의 메타데이터 집합을 Inode라 하고, Inode가 담긴 블록을 Inode 블록이라 한다.
Inode
블록 포함 파일 시스템과 관련한 블록을 제외한 나머지 블록은 데이터 블록이다.

디스크 내에 32개의 블록이 있을 때 각 요소들을 대략적으로 표현하면 다음과 같다.


대략적인 디스크 구조

inode(index node)


unix
기반 시스템에서 파일에 대한 메타 데이터들의 집합.
고유한 숫자로 이루어졌으며, 파일 시스템에서 파일을 식별하는데 사용.
이 숫자는 하나의 파일 시스템 내에서만 유효하다. 즉, 디스크간 식별이 되지 않는다.

 

흔히 파일 입출력에서 사용되는 파일 디스크립터는
프로세스의 open file table에서 inode와 매칭된 값이다.

디렉토리 역시 하나의 파일로 구분되기 때문에 inode 값을 가진다.
다만, 파일이 바이트 배열로 이루어졌다면 디렉토리는 <inode, 파일 이름> 쌍의 배열로 이루어졌다.

 

디스크에서 Inode를 찾는 과정

스펙은 다음과 같다고 가정한다.

inode 크기 : 256 byte
inode 블록 개수 : 10개
디스크 블록 크기 : 1KB


총 inode 개수 : (1KB / 256byte) * 10 = 40 개

inode 블록은 4번 블록에서 시작한다. (이전 블록들은 파일 시스템의 다른 요소들을 위한 블록이다.)

여기서 32번 inode를 읽는다고 할 때의 inode 영역에서의 오프셋 계산은 다음과 같다.
offset = (32 * sizeof(inode)) = 32 * 256 = 8192
inode 테이블의 시작 위치(4 KB)를 더하면 12KB의 바이트 주소를 구할 수 있다.


디스크는 블록 단위로 데이터를 전송하므로, 32번 inode가 존재하는 inode 블록을 가져와야 한다.
파일 시스템은 바이트 주소로 섹터 주소를 구해 해당 inode 블록을 가져온다.

데이터 블록을 찾는 과정

Inode는 여러 데이터 블록 접근에 필요한 포인터들에 대한 정보를 담고 있다.
하지만, 큰 파일의 경우, 많은 블록 포인터가 필요하지만 Inode가 모든 데이터 블록에 대한 포인터를 가질 수 없기에

특정 포인터는 다른 포인터들이 모여있는 블록을 가리킨다.

 

이런 기법을 멀티 레벨 인덱스라 한다.

그림으로 표현하면 다음과 같다.
Direct Pointer의 경우, 해당 데이터 블록을 직접 가리키지만
간접 포인터들의 경우, 다른 데이터 블록 포인터들이 담긴 데이터 블록을 가리킨다. 즉, 간접 참조한다.

만약, 데이터 블록이 512 byte, 블록 포인터의 크기가 4byte일 때,
한 데이터 블록에는 128개의 블록  포인터를 담을 수 있게 된다.
이렇게 한 번의 간접 참조로 512 * 128 byte = 65,536 byte를 저장할 수 있게 되고,
간접 포인터 단계가 늘어날 수록 저장할 수 있는 크기는 비약적으로 증가한다.

 

파일의 삭제

inode 내부에는 참조 횟수에 대한 정보가 있고, 이 정보를 바탕으로 파일의 삭제 여부를 결정한다.
참조 횟수가 0이면 파일이 더 이상 존재하지 않는 것으로 간주한다.
이 참조 횟수의 경우 링크를 이용해 조절할 수 있다.

임시로 만든 test2.c 파일의 참조 횟수

하드 링크

원본과 같은 아이노드를 복제한다. 정확히는 해당 파일의 아이노드 block이 있는 곳을 가리킨다.

참조된 inode의 참조 횟수는 1 증가한다.
한 쪽이 수정되면, 다른 쪽도 수정된다.
링크한 원본 파일이 삭제돼도, 실행이 가능하다.
하드 링크끼리 순환 구조를 이룰 수 있어 디렉토리에는 사용이 불가하며, 아이노드를 복제한단 점에서

다른 파일 시스템(디스크)에 대해 하드링크도 불가하다.

test2.c를 하드 링크해 test3.c을 만들었을 때의 결과다.

 

심볼릭 링크

하드 링크의 제약을 완화한 링크. 디렉토리나 다른 파일 시스템에도 링크가 가능하다.
링크된 파일의 크기는 참조한 파일명에 따라 달라진다.
하드 링크와는 다르게 inode의 참조 횟수를 증가시키지도 않고, 참조한 파일이 사라지면 링크 파일도 사라진다.

둘의 차이점을 간단히 정리하자면, 하드 링크는 ctrl+c로 복사한 파일이고, 심볼릭 링크는 바로 가기 파일이다.

 

test2.c를 심볼릭 링크한 파일 test3.c에 대한 결과다.

inode와 용량이 다르다.

 

파일을 읽는 과정

/tmp/test.txt 파일을 읽는 과정이다.

1. 파일 시스템은 루트 아이노드 2번을 포함하는 블록을 읽고 데이터 블록 포인터들을 추출한다.

2. 디스크에 해당 데이터 블록을 요청한다. 데이터는 루트 디렉토리 정보를 가지고 있고, 여기서 tmp 항목을 찾는다.

3. tmp의 아이노드를 찾아 메모리로 읽어들인다.

4. 프로세스의 open file table에서 파일 디스크립터를 할당받아 반환한다.

 

파일을 쓰는 과정

쓰기의 경우 내용이 추가되는 경우, 다음 과정을 거친다.

1. 새로운 데이터 블록이 필요한 경우 데이터 비트맵 블록을 확인한다.
(비트맵 블록에선 해당 블록이 사용중인지 아닌지를 0, 1로 표시한다. 아이노드와 데이터 각각 1개씩 있다.)

2. 비트맵 블록에서 사용 가능한 블록을 읽고, 해당 비트맵을 사용 중으로 쓴다.

3. 파일의 아이노드를 읽어 새로운 데이터 블록 포인터를 쓴다.

4. 새로 받은 데이터 블록에 내용을 쓴다. 

이 글에서 설명은 안 했지만, 단순 쓰기가 이 정도면 파일 생성은 더 많은 I/O 작업이 요구된다.

 

문제점

디스크 관점

아이노드 비트맵, 블록과 데이터 비트맵, 블록간은 서로 다른 위치에 있기 때문에

파일 접근이 발생할 때마다 디스크 헤드는 여기저기 움직여야 한다.

여기에 대해선 실린더 그룹별로 블록들을 배치하는 식으로 해결한다.

os 관점

우리가 흔히 아는 그 캐시를 사용한다.

파일에 접근하기 위해선, 매번 디렉토리 아이노드를 읽고, 디렉토리의 데이터를 읽어야 한다.
만약, 경로가 길 수록 이 과정은 비싸진다.
따라서, 자주 사용하는 디스크 블록의 일부를 메모리 프레임 단으로 올리고

거기서도 자주 사용되는 프레임을 캐시 테이블에 등록한다.

 

하지만 아직 쓰기에 관한 해결은 되지 않았다.
운영체제는 이를 해결하기 위해, 쓰기 연산을 바로 디스크에 적용하지 않고
작업을 잠깐 미뤄뒀다, 다른 요청들이 오면 그것을 묶어 일괄(batch)처리를 한다.

DB에서 벌크 삽입을 할 때 batch를 쓰는 것을 그 예시로 들 수 있다.

또한, 쓰기 작업을 하다 그 파일을 삭제해버리면, 해당 파일에 대한 쓰기 작업은 반영할 필요가 없어지니
os나 디스크 입장에선 부담이 훨씬 적어진다.

'학교 > os' 카테고리의 다른 글

스레드  (0) 2022.07.10
주소 변환  (0) 2022.07.10
문맥 교환(context switching)  (0) 2022.06.27
프로세스의 생성과 통신  (0) 2022.06.26
프로세스 구조  (0) 2022.06.26