 |
|
난이도 : 초급 M. Tim Jones, 컨설턴트 엔지니어, Emulex Corp.
옮긴이: 박재호 이해영 dwkorea@kr.ibm.com
2008 년 5 월 13 일 파일 시스템에 관한 한, 리눅스(Linux®)는 맥가이버 칼과 같은 운영체제입니다. 리눅스는 저널링에서 클러스터링과 암호화까지 다양한 파일 시스템을 지원합니다. 리눅스는 표준 파일 시스템과 실험적인 파일 시스템 활용은 물론 파일 시스템 개발에도 훌륭한 플랫폼입니다. 이 기사에서는 종종 가상 파일 시스템 스위치라고 불리는 리눅스 커널의 가상 파일 시스템(VFS, Virtual File System)을 살펴본 후, 파일 시스템을 하나로 엮어 주는 주요 구조체 중 일부를 검토합니다.
기본적인 파일 시스템 아키텍처
리눅스 파일 시스템 아키텍처는 추상화로 복잡함을 다루는 흥미로운 예다. 일반적인 API 함수 세트를 이용하면 매우 다양한 저장 장치 위에서 매우 다양한 파일 시스템을 지원할 수 있다. 주어진 파일 기술자에서 요청된 만큼 바이트를 읽어 들이는 read 함수 호출을 예로 들어보자. read 함수는 ext3나 NFS와 같은 파일 시스템 유형을 알지 못한다. 또한 ATAPI(AT Attachment Packet Interface) 디스크나 SAS(Serial-Attached SCSI) 디스크, SATA(Serial Advanced Technology Attachment) 디스크와 같은, 해당 파일 시스템이 마운트되어 있는 특정 저장 매체에 대해서도 알지 못한다. 하지만 오픈(open)된 파일에 대해 read 함수가 호출되면 예상대로 자료를 가져다 준다. 이 기사는 이러한 일이 어떻게 이루어지는지 살펴보고, 리눅스 파일 시스템 계층의 주요 구조체를 조사한다.
파일 시스템이란?
가장 기본적인 질문에 대한 대답인 파일 시스템의 정의부터 시작하겠다. 파일 시스템은 저장 장치에서 자료와 메타자료의 조직이다. 이와 같은 모호한 정의로부터 여러분은 관련 코드가 흥미진진하리라는 사실을 알 수 있다. 앞서 언급했듯 파일 시스템과 매체는 종류가 많다. 이러한 다양성으로 인해 리눅스 파일 시스템 인터페이스는,
파일 시스템 구현과 관련 저장 장치를 조정하는 드라이버로부터 사용자 인터페이스 계층을 분리한, 계층화된 아키텍처로 구현되어 있으리라 추측할 수 있다.
 |
프로토콜로서 파일 시스템
파일 시스템을 이해하기 위해 프로토콜로 보는 다른 방법도 있다. (IP와 같은) 네트워크 프로토콜이 인터넷을 이동하는 자료 흐름에 의미를 부여하듯
파일 시스템은 특정 저장 매체에 위치한 자료에 의미를 부여한다. |
|
마운트하기
리눅스에서 파일 시스템을 저장 장치와 연결하는 과정을 마운트하기라 부른다. 파일 시스템을 현재 파일 시스템 체계(루트 디렉터리)에 붙이는 데는 mount 명령을 이용한다. 마운트 과정에서 파일 시스템 타입과 파일 시스템, 마운트 지점을 정해준다.
리눅스 파일 시스템 계층의 기능(과 mount 사용법)을 보여주기 위해, 현재 파일 시스템 내에 포함된 파일에 파일 시스템을 만든다. 이를 위해, 우선 Listing 1과 같이 (/dev/zero로부터 파일을 복사하는 방식으로) dd를 이용하여 주어진 크기의 파일, 즉 0으로 초기화된 파일을 만든다.
Listing 1. 초기화된 파일 생성하기
$ dd if=/dev/zero of=file.img bs=1k count=10000
10000+0 records in
10000+0 records out
$
|
이제 file.img라는 10MB의 파일이 생겼다. 이 파일을 losetup 명령으로 루프(loop) 장치에 연결한다. (이렇게 되면 해당 파일은 파일 시스템 내 일반적인 파일이 아니라 블록 디바이스처럼 보인다.)
$ losetup /dev/loop0 file.img
$
|
이제 (/dev/loop0로 표시되어) 블록 디바이스처럼 보이는 해당 파일에 mke2fs로 파일 시스템을 만든다. Listing 2에서 보듯, 이 명령은 주어진 크기로 새로운 ext2 파일 시스템을 만든다.
Listing 2. 루프 디바이스에 ext2 파일 시스템 생성하기
$ mke2fs -c /dev/loop0 10000
mke2fs 1.35 (28-Feb-2004)
max_blocks 1024000, rsv_groups = 1250, rsv_gdb = 39
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
2512 inodes, 10000 blocks
500 blocks (5.00%) reserved for the super user
...
$
|
루프 디바이스(/dev/loop0)로 나타나는 file.img 파일은 mount 명령으로 이제 마운트 지점 /mnt/point1에 마운트되었다. 파일 시스템을 ext2로 명시해야 한다는 사실에 유념한다. 마운트하고 나면 Listing 3과 같이 ls 명령어를 이용하여 해당 마운트 지점을 새로운 파일 시스템처럼 다룰 수 있다.
Listing 3. 마운트 지점을 만들고 앞서 만든 파일 시스템을 루프 디바이스를 통하여 마운트하기
$ mkdir /mnt/point1
$ mount -t ext2 /dev/loop0 /mnt/point1
$ ls /mnt/point1
lost+found
$
|
Listing 4에서 볼 수 있듯이 새로 마운트된 파일 시스템 내에 새로운 파일을 만들어 이를 루프 디바이스에 연결하고, 거기에 또 다른 파일 시스템을 생성함으로써 계속 동일한 절차를 반복할 수 있다.
Listing 4. 루프 파일 시스템 내에 새로운 루프 파일 시스템 만들기
$ dd if=/dev/zero of=/mnt/point1/file.img bs=1k count=1000
1000+0 records in
1000+0 records out
$ losetup /dev/loop1 /mnt/point1/file.img
$ mke2fs -c /dev/loop1 1000
mke2fs 1.35 (28-Feb-2004)
max_blocks 1024000, rsv_groups = 125, rsv_gdb = 3
Filesystem label=
...
$ mkdir /mnt/point2
$ mount -t ext2 /dev/loop1 /mnt/point2
$ ls /mnt/point2
lost+found
$ ls /mnt/point1
file.img lost+found
$
|
이와 같은 간단한 시범을 통해 리눅스 파일 시스템(과 루프 디바이스)이 얼마나 강력한지 쉽게 알 수 있다. 이와 같은 방법으로 파일에 루프 디바이스를 연결해 암호화 파일 시스템을 만들 수 있다. 이는 필요할 때 루프 디바이스를 이용하여 일시적으로 파일을 마운트함으로써 자신의 자료를 보호하는 데 유용하다.
파일 시스템 아키텍처
파일 시스템이 정상적으로 만들어지는 모습을 보았으니, 리눅스 파일 시스템 계층의 아키텍처로 돌아와 보자. 이 기사에서는 리눅스 파일 시스템을 두 가지 관점에서 살펴본다. 첫째는 고차원 아키텍처 관점이다. 둘째는 좀더 깊이 들어가서 파일 시스템을 구현한 주요 구조체를 통해 파일 시스템 계층을 탐험한다.
고차원 아키텍처
대다수 파일 시스템 코드가 커널에 있지만(이후에 설명할 사용자 영역 파일 시스템은 예외) 그림 1의 아키텍처는 사용자 영역과 커널 모두에서 파일 시스템 관련 주요 구성 요소 사이의 관계를 보여준다.
그림 1. 리눅스 파일 시스템 구성 요소에 대한 구조도
사용자 영역은 응용 프로그램(이번 예에서는 파일 시스템의 사용자)과 함께, 파일 시스템 호출(open, read, write, close)에 대한 사용자 인터페이스를 제공하는 GNU C 라이브러리(glibc)를 포함한다. 시스템 호출 인터페이스는 스위치처럼 시스템 호출을 사용자 영역에서 커널 영역의 적절한 대응점으로 흘려보낸다.
VFS는 기반 파일 시스템에 대한 기본 인터페이스다. 이 구성 요소는 인터페이스 집합을 외부로 공개(export)하며, 그런 다음 이를 개별 파일 시스템에 대해 추상화하는데, 파일 시스템마다 매우 다른 방식으로 동작할 수도 있다. 파일 시스템 객체에 대해 두 가지 캐시(아이노드와 덴트리)가 존재하며, 이들에 대해서는 곧 설명할 것이다. 각각은 최근에 사용된 파일 시스템 객체를 풀(pool)로 관리한다.
ext2와 JFS 등과 같은, 각각의 개별 파일 시스템 구현은 VFS가 이용(하고 요구)하는 공통적인 인터페이스를 외부에 공개한다. 버퍼 캐시는 파일 시스템과 해당 파일 시스템이 조정하는 블록 디바이스 사이에서 요청을 버퍼링한다. 예를 들면, 기반 디바이스 드라이버에 대한 읽기와 쓰기 요청은 버퍼 캐시를 통해 전달된다. 이를 통해 요청은 (물리적인 장치로 사라져 버리는 대신) 더 빠른 처리를 위해 캐시에 기록된다. 버퍼 캐시는 LRU(least recently used) 목록 형태로 관리된다. sync 명령을 이용하여 버퍼 캐시의 내용을 저장 매체에 반영할 수 있음에 주목한다(기록되지 않은 모든 자료를 강제로 디바이스 드라이버로, 다음에는 저장 장치로 내보낸다).
 |
블록 디바이스란 무엇인가?
블록 디바이스는 자료가 (디스크 섹터와 같은) 블록 단위로 이동하는 디바이스로, 버퍼링과 임의 접근 같은 특징을 지원한다(블록을 연속적으로 읽어야 한다는 말이 아니고, 언제 어느 블록이라도 접근할 수 있다는 말이다). 블록 디바이스는 하드 드라이브와 CD-ROM, RAM 디스크를 포함한다. 이는 물리적으로 주소 지정이 가능한 매체를 가질 수 없는 문자 디바이스와 큰 차이가 있다. 문자 디바이스는 시리얼 포트와 테이프 장치를 포함하며, 자료를 문자 단위로 스트리밍한다. |
|
VFS와 파일 시스템 구성 요소를 2만 피트 상공에서 살펴보았다. 이제 이 하위 시스템을 구성하고 있는 주요 구조체를 살펴보자.
주요 구조체
리눅스는 모든 파일 시스템을 일반적인 객체 모음이라는 관점에서 본다. 슈퍼블록과 아이노드, 덴트리, 파일이 이런 객체다. 각 파일 시스템의 루트에는 슈퍼블록이 있어서 해당 파일 시스템의 상태를 나타내며 보존한다. 파일 시스템 내에서 관리되는 모든 객체(파일이나 디렉터리)를 리눅스에서는 아이노드로 표현한다. 아이노드에는 파일 시스템 내부 객체를 다루기 위한 (해당 객체에 대해 허용된 연산을 포함하여) 모든 메타자료가 들어 있다. 이름과 아이노드 사이의 변환을 위해 덴트리라는 또 다른 구조체 집합을 이용하며, 이 때 가장 최근에 사용된 것을 가까이 두기 위해 디렉터리 캐시가 존재한다. 덴트리는 파일 시스템 탐색을 위해 디렉터리와 파일 사이의 관계도 유지한다. 마지막으로 VFS 파일은 오픈된 파일을 표현한다(쓰기 오프셋 등과 같은 오픈된 파일의 상태를 유지한다).
가상 파일 시스템 계층
VFS는 파일 시스템 인터페이스의 최상위 수준에서 동작한다. VFS는 현재 마운트된 파일 시스템뿐만 아니라, 현재 지원 가능한 파일 시스템에 대한 정보도 계속 파악한다.
리눅스에서는 등록 관련 함수들을 이용하여 파일 시스템을 동적으로 추가하거나 제거할 수 있다. 커널은 현재 지원 가능한 파일 시스템의 목록을 유지하는데, /proc 파일 시스템을 통해 사용자 영역에서도 볼 수 있다. 이 가상 파일은 그 파일 시스템과 관련된 장치들도 보여준다. 리눅스에서는 새로운 파일 시스템을 추가하기 위해 register_filesystem을 호출한다. 이 함수는 파일 시스템 이름과 속성 집합, 슈퍼블록 함수를 두 개 명시한 파일 시스템 구조체(file_system_type)에 대한 참조만을 유일한 인수로 정의한다. 파일 시스템은 등록을 해지할 수도 있다.
신규 파일 시스템을 등록하면 해당 파일 시스템은 관련 정보와 함께 file_system 목록에 들어간다(그림 2와 linux/include/linux/mount.h를 참고). 이 목록은 지원 가능한 파일 시스템을 나타낸다. 명령행에서 cat /proc/filesystems라고 입력하면 이 목록을 볼 수 있다.
그림 2. 커널에 등록된 파일 시스템
VFS에서 관리하는 또 다른 구조체는 마운트된 파일 시스템이다(그림 3 참고). 이를 통해 현재 마운트된 파일 시스템을 알 수 있다(linux/include/linux/fs.h 참고). 다음에 살펴볼 superblock 구조체가 여기에 연결되어 있다.
그림 3. 마운트된 파일 시스템 목록
슈퍼블록
슈퍼블록은 파일 시스템을 표현하는 구조체다. 여기에는 연산 과정에서 파일 시스템을 운영하기 위해 필요한 정보가 있다. 또한 (ext2와 같은) 파일 시스템 이름과 파일 시스템 크기, 상태, 블록 디바이스에 대한 참조, (free list 등의) 메타자료 정보도 포함하고 있다. 슈퍼블록은 일반적으로 저장 매체에 저장되지만 존재하지 않는 경우에는 실시간으로 생성할 수 있다. 슈퍼블록 구조체(그림 4 참고)는 ./linux/include/linux/fs.h에서 볼 수 있다.
그림 4. 슈퍼블록 구조체와 아이노드 연산
슈퍼블록 연산에 대한 정의는 슈퍼블록의 중요한 구성요소 중 하나다. 이 구조체는 파일 시스템 내 아이노드를 운영하기 위한 함수 집합을 정의한다. 예를 들면, alloc_inode로 아이노드를 생성하고, destroy_inode로 아이노드를 제거할 수 있다. read_inode와 write_inode로 아이노드를 읽고 쓸 수 있으며, sync_fs로 파일 시스템을 동기화할 수 있다. super_operations 구조체는 ./linux/include/linux/fs.h에서 찾을 수 있다. 모든 파일 시스템에는 자신만의 아이노드 메서드가 있어서, 관련 연산을 구현하고 VFS 계층에 공통적인 추상화를 제공한다.
아이노드(inode)와 덴트리(dentry)
아이노드는 파일 시스템 내 개별 객체를 고유 식별자로 나타낸다. 각 파일 시스템은 파일 이름을 고유 아이노드 식별자로, 다시 아이노드 참조로 변환하는 메서드를 제공한다. 그림 5에 아이노드 구조체의 일부가 몇몇 관련 구조체들과 함께 표현되어 있다. 특히 inode_operations와 file_operations에 주목하자. 두 구조체 각각은 아이노드에 대해 수행될 개별 연산을 나타낸다. 예를 들어, inode_operations는 아이노드에 직접 작용하는 연산을 정의하고, file_operations는 파일과 디렉터리에 관련된 메서드들(표준 시스템 호출)을 나타낸다.
그림 5. 아이노드 구조체와 관련 연산
가장 최근에 사용된 아이노드와 덴트리는 각각 아이노드 캐시와 디렉터리 캐시에 보관된다. 아이노드 캐시 내 아이노드 각각에 대해 디렉터리 캐시 내에 상응하는 덴트리가 있다는 사실에 주목하자. inode와 dentry 구조체는 ./linux/include/linux/fs.h에서 찾을 수 있다.
버퍼 캐시
(./linux/fs 아래에 있는) 개별 파일 시스템 구현을 제외하고는 버퍼 캐시가 파일 시스템 계층의 가장 하부다. 해당 요소는 개별 파일 시스템 구현과 (디바이스 드라이버를 통한) 물리적 디바이스로부터 읽기/쓰기 요청을 계속적으로 파악한다. 효율성을 위해, 리눅스는 요청에 대한 캐시를 유지하여 모든 요청에 대해 물리적 디바이스를 참조하지 않도록 만든다. 대신 가장 최근에 이용한 버퍼(페이지)를 이곳에 캐시하여 개별 파일 시스템에게 신속하게 다시 제공할 수 있다.
흥미로운 파일 시스템
이 기사에서는 리눅스에서 이용할 수 있는 개별 파일 시스템을 살펴보지 않았다. 하지만 적어도 주마간산 식으로라도 여기서 언급할 가치는 있다. 리눅스는 MINIX와 MS-DOS, ext2 등의 오래된 파일 시스템부터 시작하여 광범위한 파일 시스템을 지원한다. 리눅스는 ext3와 JFS, ReiserFS 등의 새로운 저널링 파일 시스템도 지원한다. 또한 리눅스는 CFS 같은 암호화 파일 시스템과 /proc 같은 가상 파일 시스템을 지원한다.
마지막으로 언급할 만한 파일 시스템은 FUSE(Filesystem in Userspace)다. 이는 흥미로운 프로젝트로서, VFS를 통해 전달된 파일 시스템 요청을 다시 사용자 영역으로 보낼 수 있게 한다. 따라서 자신만의 파일 시스템을 만들어 볼까 생각한 적이 있었다면, 시작하기 좋은 방법이다.
정리
파일 시스템 구현은 결코 단순한 일이 아니지만 확장 가능성이 좋은 아키텍처의 훌륭한 예다. 파일 시스템 아키텍처는 수년 동안 발전해왔으며 많은 상이한 종류의 파일 시스템과 저장 디바이스도 성공적으로 지원해왔다. 다양한 수준의 함수 간접 지정을 제공하는 플러그인 기반 아키텍처를 이용한, 가까운 장래에 있을 리눅스 파일 시스템의 발전을 지켜보면 흥미로울 것이다.
참고자료 교육
제품 및 기술 얻기
-
FUSE(Filesystem in Userspace)는 커널 모듈로 사용자 영역에서 파일 시스템 개발이 가능하도록 만들어준다. 이 파일 시스템 드라이버 구현은 VFS로 들어온 요청을 사용자 영역으로 다시 되돌려준다. FUSE는 커널을 뜯지 않고서도 파일 시스템 개발을 실험할 수 있는 훌륭한 도구다. 파이썬을 사용한다면 LUFS-Python을 사용해 파일 시스템을 작성할 수도 있다.
-
DB2®, Lotus®, Rational®, Tivoli®, WebSphere®와 같은 최신 IBM 평가판 소프트웨어를 포함하는 두 장짜리 DVD 세트인 SEK for Linux를 주문하자.
-
IBM 평가판 소프트웨어: developerWorks에서 직접 내려 받아 다음번 리눅스 프로젝트에 활용하자.
토론
필자소개  | 
|  | M. Tim Jones는 임베디드 소프트웨어 아키텍트이자 GNU/Linux Application Programming, AI Application Programming, BSD Sockets Programming from a Multilanguage Perspective의 저자이기도 한다. Jones의 공학 배경은 정지 위성을 위한 커널 개발에서 시작해 임베디드 시스템 아키텍처와 네트워크 프로토콜 개발에 이르기까지 다양한 분야를 아우른다. Jones는 콜로라도 주, 롱몬트 소재 Emulex 사에서 컨설턴트 엔지니어로 활약한다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|