본문 바로가기

Job Notes/File System

파일 시스템의 원리와 리눅스에서의 제어구조 2

파일 시스템의 원리와 리눅스에서의 제어구조 2

(* 마이크로소프트웨어 2002년 11월호에 수록된 기사입니다.)

유닉스의 파일 시스템

파일 시스템의 역사
  초기의 상용 유닉스 버전들은 시스템 Ⅴ 파일 시스템(s5fs)이라고 알려진 단순한 파일 시스템을 포함하고 있었다. 모든 시스템 Ⅴ유닉스와 4.2BSD 이전의 버클리 유닉스 버전들은 이 파일 시스템을 지원한다. 4.2BSD는 새로운 파일 시스템인 FFS(Fast FileSystem)를 지원했는데, 이것은 s5fs보다 월등한 성능과 기능을 제공한다. 따라서 FFS는 그 이후부터 더 널리 사용됐다.

  FFS가 처음 소개됐을 때 유닉스 파일 시스템은 하나의 파일 시스템 유형만 지원할 수 있었다. 이것은 사용자들에게 s5fs와 FFS 중에서 하나의 파일 시스템만을 선택하게 만들었다. 사용자들은 월등한 성능과 기능을 제공하는 FFS를 선호했으나 이전 파일 시스템과의 호환성 때문에 s5fs도 버릴 수 없었다. 이 때 썬에서 소개한 vnode/vfs(Virtual File System) 인터페이스는 이러한 고민의 해결책이었다. 하나의 기계에서 다중 파일 시스템 유형이 같이 존재할 수 있게 허용됐기 때문이다(어떻게 그런 일이 가능한지는 뒤에서 다룬다).
  1970년대부터는 컴퓨터를 네트워크로 연결하는 것이 쉬어짐에 따라 개발자들은 원격 노드에 있는 파일을 접근할 수 있는 방법을 모색하기 시작했다. 그 결과 1980년대 중반 상호 연결된 컴퓨터 간에 투명하게 파일을 공유할 수 있게 하는 몇몇 경쟁적인 기술을 선보이게 됐다. NFS(Network File System), RFS(Remote File Sharing), AFS(Andrew File System) 등이 그 예다. 이 세 가지 파일 시스템은 비록 같은 문제를 해결하고자 했지만 설계 목적, 구조, 의미성 등에서 많은 차이를 보였다. 많은 시스템 기반 유닉스 시스템들은 RFS를 제공하고 있으며, NFS는 훨씬 더 많은 유닉스 및 비 유닉스 시스템들에서 사용되고 있다. ASF는 DFS(Distributed File System)로 발전했다.
  한편 리눅스에서는 미닉스 파일 시스템을 기반으로 한 ext 파일 시스템이 개발됐다. 기존 미닉스 파일 시스템은 파일 시스템의 최대 크기가 16비트인 64MB이고 최대 파일 이름 길이가 14로 제약이 있었다. ext2 파일 시스템은 파일 시스템의 최대 크기가 32비트로서 2GB이고, 최대 파일 이름 크기가 255자였다. 그러나 ext 파일 시스템은 별도 액세스(separate access), 아이노드 수정(inode modification), 데이터 수정 타임스탬프(data modification time stamps)에 대한 지원이 없었다. 후에 ext2 파일 시스템으로 발전하면서 이러한 문제들은 해결됐다. 현재에 와
서는 ext2에 저널링 개념을 도입시킨 ext3가 기본 파일 시스템이다(저널링 개념이란 데이터베이스에서 쓰이는 저널링 기술을 적용한 것으로서 이름에서도 알 수 있듯이 일정 부분을 기록을 위해 남겨두어 백업 및 복구 능력이 있는 파일 시스템을 말하며 시스템 크래시 후에 파일 시스템 복구에 드는 시간이 아주 짧다).


아이노드
  파일의 효율적인 관리와 접근을 위해 유닉스에서는 아이노드라는 구조를 이용한다. 아이노드는 보통 파일 시스템에서 가장 기본이 되는 단위다. 모든 파일과 디렉토리는 특정한 아이노드에 의해 표현할 수 있다. 즉, 각각의 파일이나 디렉토리에는 유일한 아이노드 번호(unique inode number)가 할당되는 것이다. 아이노드는 보통 <그림 5>와 같은 구조로 이루어져 있다.
사용자 삽입 이미지

아이노드의 구조


  아이노드 구조(inode structure)를 보면 여러 가지 항목이 있는데 각 항목에 저장되는 정보는 다음과 같다.

◆ 모드 : 파일의 타입과 접근권한에 대한 정보가 저장된다. <그림 5>를 보면 4비트의 타입 정보가 파일, 디렉토리, 심볼릭 링크, 블럭 장치, 문자 장치를 구분해준다.
◆ 소유자 정보 : 파일이나 디렉토리에 대한 사용자와 그룹을 식별해준다. i_uid는 사용자를, i_gid는 그룹을 나타낸다.
◆ 시간 : 파일에 마지막으로 접근한 시간과 수정한 시간, 만들어진 시간에 대한 정보가 담겨있다.
◆ 데이터 블럭 : 파일의 데이터가 저장된 블럭의 포인터를 나타낸다. 이것은 앞에서 언급된 인덱스 블럭 기법을 이용한 것이다. 앞의 열두 개의 직접 블럭(direct block)은 블럭을 직접적으로 가리키는 포인터이고, 나머지 세 개의 간접 블럭(indirect block)은 간접적으로 데이터를 가리키는 포인터다. 보통 간접 블럭 포인터(indirect block pointer)는 단일(single), 이중(double), 삼중(triple) 간접 블럭 포인터로 나뉜다.

  이와 같은 방식으로 데이터를 저장한다면 디스크 블럭의 크기가 커질수록 대용량의 파일을 저장할 수 있게 된다. 그러나 간접 블럭 포인터 개념을 사용하기 때문에 데이터를 담은 블럭이 여기 저기 산재해 있을 경우 데이터를 검색하는 속도가 상당히 느려질 수가 있다.

디렉토리
  파일 시스템에서 디렉토리는 파일 시스템 내의 파일에 대한 접근 경로와 정보를 저장한다. 그렇다면 어떤 모양의 계층구조가 파일시스템을 위해 제공되는가? <그림 6>은 파일 시스템에서의 대략적인 계층구조를, <그림 7>은 계층구조에서 아이노드 번호와 파일 이름이 어떻게 짝지어 있는지를 차례로 보여준다.
사용자 삽입 이미지

파일 시스템의 계층 구조


사용자 삽입 이미지

아이노드와 파일 이름의 상관관계


  유닉스에서 파일 시스템 계층구조는 디렉토리를 이용한 트리(tree) 구조다. <그림 6>을 보면 맨 위에는 루트 디렉토리가 있고, 아래로 내려가면서 여러 디렉토리가 차례로 위치해 있다. 역시 맨 마지막 계층에는 파일이 있는 것을 볼 수 있다. 그렇다면 <그림 6>의 파일 시스템에서 mark라는 파일은 어떤 순서로 찾아갈 수 있을까? 우리가 /usr/member/mark라는 파일을 찾고 싶다면 우선 여러 디렉토리를 거쳐 가야 할 것이다. 처음으로 필요한 아이노드가 파일 시스템의 / 아이노드다. <그림 7>을 보면 아이노드 1이 루트의 아이노드라는 것을 짐작할 수 있을 것이다. 아이노드 1의 데이터를 보면 그 아이노드에 대한 데이터 블럭 인덱스가 1이라는 것을 알 수 있고, 디스크 블럭의 내용을 보기 위해 디스크 블럭 1을 찾아 간다. 디스크 블럭에 담겨 있는 정보를 보면 루트 디렉토리 하위 계층에 있는 여러 디렉토리 이름과 아이노드 번호가 짝을
이뤄 있는 것을 볼 수 있다.
  다음으로 usr이라는 디렉토리를 찾아야 하는데 디스크 블럭 1을 보면 알겠지만 usr 디렉토리의 아이노드 번호는 3이다. 그래서 제어는 아이노드 3으로 가게 되고, 역시 아이노드 3의 내용을 참고해 디스크 블럭 7로 찾아간다. 그 곳에서 member라는 디렉토리의 아이노드 번호를 얻게 되고 같은 방법으로 데이터 블럭 39로 가서 아이노드 번호 37인 mark 파일을 찾게 되는 것이다(유닉스에서는 디렉토리 역시 하나의 파일로 간주되어 파일 시스템에서 관리된다. 그래서 디렉토리가 가지고 있는 파일에 대한 엔트리(entry)는 그 디렉토리의 데이터 블럭에 저장되어 있는 것이다. 또 한 가지 알아두어야 할 것은 모든 디렉토리는 항상 두 개의 엔트리를 가지고 있는데 바로‘.’과‘..’이다. ‘.’은 현재 디렉토리를,‘ ..’는 상위 디렉토리를 의미한다).


사용자 계층에서 파일 접근
  사용자 계층에서 어떻게 파일에 접근할 수 있을까? 즉, 태스크(프로세스)와 아이노드의 관계는 어떻게 이루어지는가? 여기서 태스크는 동작중인 프로그램을 의미한다. 파일 열기를 예로 들어보자. <그림 8>은 파일을 open()하는 것을 보여주는 것이다.
사용자 삽입 이미지

파일 열기


  <그림 8>에서 보듯이 태스크 테이블은 fd(file descriptor)를 가리킨다. 여기서 fd는 파일 테이블을 가리키는 배열이다. 흔히 fd[0]은 표준 입력(stdin), fd[1]은 표준 출력(stdout), fd[2]는
표준 에러 출력(stderr)으로 설정된다. 파일을 열 때는 사용하지 않는 fd를 파일 테이블과 연관시킨다(열려진 파일이 없다면 fd[3]이 파일 테이블을 가리킨다. 즉, 사용하지 않는 가장 작은 번호를 사용한다). 그리고 sys_open()시 인자로 넘어간 파일 이름으로 디렉토리를 검사해 그 파일에 해당되는 아이노드를 얻어온다. 파일 테이블은 해당되는 아이노드를 가리킨다. 아이노드는 앞서 아이노드의 구조에서 봤듯이 그 파일의 블럭 포인터를 가지고 있다(<그림 8>에서 밑의 세 개는 간접 블럭 포인터를 가리킨다).
  <그림 8>은 커널 안에서 논리적인 제어 흐름을 보여주는 것일 뿐이고, 실제적인 디스크에 데이터를 저장하는 것은 중간에 장치 드라이버나 디스크 컨트롤러가 해주게 된다.


vnode/vfs
  앞서 언급했지만 썬은 여러 타입의 파일 시스템을 지원하기 위해 vnode/vfs를 소개했다. 이후 vnode/vfs는 널리 받아 들여졌다. vnode/vfs의 목적은 다음과 같다.

◆ 여러 타입의 파일 시스템을 동시에 지원해야 한다. 이러한 파일 시스템에는 유닉스 파일 시스템(s5fs, ufs)과 비유닉스 파일 시스템(도스, A/UX)을 포함한다.
◆ 다른 디스크 파티션은 다른 타입의 파일 시스템을 가질 수 있다. 그러나 일단 디스크 파티션이 마운트(mount)되면, 이들 디스크 파티션은 전형적인 하나의 동질적인 파일 시스템의 모습을 보여야 한다. 사용자들은 전체 파일 트리의 일관된 모습을 볼 수 있고, 하부 트리의 디스크 상의 표현의 차이에 대해서는 알 필요가 없어야 한다.
◆ 네트워크를 통한 파일 공유를 완벽하게 지원해야 한다. 원격 시스템에 있는 파일 시스템도 로컬 파일 시스템과 똑같은 방식으로 접근할 수 있어야 한다.
◆ 사업자들은 자신들만의 파일 시스템 타입을 만들 수 있어야 하고, 모듈 방식으로 커널에 첨가할 수 있어야 한다.


vnode와 open 파일들
vnode는 커널 내의 하나의 활성화된 파일을 나타내는 추상화다. vnode는 파일에 대한 인터페이스를 정의하고, 파일에 대한 모든 연산을 파일 시스템과 관련된 적당한 함수로 전달한다. 즉, 모든 파일 시스템에서 동일한 인터페이스를 제공하고 vnode에서 각각의 파일 시스템에 적당한 함수로 전달한다(s5fs인지 FFS인지 사용자들은 상관할 필요가 없다).
  <그림 9>에서 struct file은 다음과 같은 필드를 가지고 있다.
◆ 파일 내에서 다음번에 읽거나 쓸 때 시작해야 하는 위치(offset)
◆ 자신을 가리키고 있는 fd를 나타내는 참조 계수
◆ 해당 파일의 vnode를 가리키는 포인터
◆ 파일을 열었을 때의 모드

사용자 삽입 이미지

파일 시스템에 독립적인 데이터 구조



유닉스에서 디스크 관리
  파일 시스템은 디스크를 관리한다. 유닉스에서 디스크는 시스템에 인스톨되면 특수 장치 파일로 접근할 수 있다(보통 /dev 디렉토리에 위치한다). <그림 10>은 두 개의 IDE 방식 디스크가 인스톨된 시스템의 구조를 도식화한 것이다. 각 디스크에는 /dev 디렉토리의‘hd’라는 이름의 블럭 장치 파일로 접근한다(물론 IDE가 아니라 SCSI 방식 디스크라면‘sd’라는 이름으로 접근한다).

사용자 삽입 이미지

파티션과 파일 시스템


  디스크가 시스템에 인스톨되면 디스크는 몇 개의 논리적인 영역(partition)으로 분할할 수 있다(독자들은 fdisk를 이용해 파티션을 나눈 기억이 있을 것이다. 즉, 물리적으로는 하나라도 논리적으로는 여러 개로 나눌 수가 있다). <그림 10>은 첫 번째 디스크가 세 개의 파티션으로 분할된 예를 보여주고 있다. 현재 유닉스에서는 최대 64개까지 파티션을 분할할 수 있다.
  각 파티션에도 장치 파일의 이름이 붙는데 첫 번째 디스크의 첫번째 파티션은 hda1, 두 번째 파티션은 hda2, 두 번째 디스크의 첫 번째 파티션에는 hdb1 등으로 이름을 붙인다. 그리고 각 장치 파일 이름마다 장치의 주 번호(major number)와 부 번호(minor number)가 할당된다. IDE 디스크의 경우 3을 주 번호로, SCSI 디스크의 경우 8을 주 번호로 갖는다. 예를 들면 hda1은 주 번호 3과 부 번호 1을 갖는다. 마찬가지로 hda2는 주 번호 3과 부 번호 2를 갖는다(그럼 hdb1은 어떻게 될까? 앞에서 언급했듯이 최대 64개까지 논리적인 영역을 나눌 수 있으므로 주 번호 3과 부 번호 65를 갖는다).
  이상으로 유닉스의 파일 시스템에 관해 살펴봤다. 지금부터는 리눅스에서 파일 시스템 구현 및 제어 흐름을 살펴보자.