reference: https://pages.cs.wisc.edu/~remzi/OSTEP/, 시스템 프로그래밍, 운영체제 수업(최종무 교수님)
"리눅스 커널 내부구조" / 백승재, 최종무
사용자 태스크들은 open(), read(), write(), close() 등의 시스템 콜을 사용해 파일시스템에 접근한다.
반대로 파일시스템은 함수를 구현하여 상위계층에 제공해야하는 데 이는 구현하기 나름이다. 파일시스템 자체는 특정 운영체제에 구애받지 않고, 심지어 운영체제가 없는 상황에서도 동작 가능한 독립적인 구조로 설계되고 구현되어야 한다.
그럼에도 불구하고 대부분의 경우 사용자 태스크는 POSIX 표준 시스템 콜을 이용하면 파일시스템에 접근할 수 있을거라 가정하기에 파일시스템은 외부로 함수를 제공할 때 이를 고려해야 한다.
ext2에서 제공하는 함수 일부
ext2_create()
ext2_lookup()
ext2_link()
ext2_unlink()
ext2_symlink()
ext2_mkdir()
ext2_rmdir()
ext2_readpage()
ext2_readpages()
ext2_writepage()
ext2_writepages()
..
..
msdos에서 제공하는 함수 일부
msdos_create()
msdos_lookup()
msdos_unlink()
msdos_mkdir()
msdos_readpage()
msdos_writepage()
..
..
각각의 파일시스템이 상위로 인터페이스를 제공하지만 사용자 태스크 입장에서 이런 함수들을 직접 호출하여 사용한다는 것은 성가신 일이다. 예를 들어 하나의 디스크를 여러 파티션으로 나누고 서로 다른 파일시스템(각각 자신만의 고유한 함수를 구현한)이 올려져있다면, 파일을 쓰고, 읽고, 생성하고, 삭제하는 작업에서 파일이 저장된 파일시스템에 맞게 달리 함수를 호출해야 한다는 불편함이 있다.
실제 파일이 어느 파일시스템에 속해있던 상관없이 단순히 open(), read(), write(), close() 등의 함수만을 통해 접근하면 한결 수월할 것이다.
따라서 파일시스템과 사용자 태스크 사이에 가상적인(virtual) 층을 하나 도입하여 상위 계층에서 open(), read() 등의 단일한 함수를 통해 파일시스템에 접근하려하면, 인자에 담겨있는 파일 이름을 보고 파일을 관리하고 있는 파일시스템이 무엇인지 판단하도록 한다. 그리고 해당 파일시스템의 고유한 함수를 호출하는 것이다. 이러한 접근 방식을 리눅스가 채택한 VFS(Virtual File System)이라 한다.
이 VFS를 통해 리눅스에서 다양한 파일시스템을 지원하는 것이 가능해졌다. 심지어 커널의 내부 상태를 볼 수 있는 proc 파일시스템, 장치를 통합 관리하는 sysfs 등의 가상(pseudo) 파일시스템(memory-based)도 지원한다.
간단한 VFS 동작원리 예시
a.txt 파일이 저장된 디스크의 한 파티션을 관리하고 있는 ext2에 읽기 요청을 하려는 태스크가 있다 가정.
1) 사용자 태스크가 read("a.txt"...) 시스템 콜을 호출.
2) VFS는 a.txt 파일이 어떤 파일시스템에 속해있는지 판단.
3) a.txt 정보를 담을 목적으로 여러 가지 정보를 담기에 충분한 구조체 생성.
4) 이 구조체를 인자로 하여 ext2 파일시스템 내부에 구현되어 있는 고유한 읽기 함수 호출.
5) ext2 자체의 관리 방법을 통해 요청된 파일 내용을 읽음.
6) 읽은 내용을 바탕으로 넘겨받은 구조체에 채워서 리턴
VFS는 다양한 파일시스템과 데이터를 주고받아야 한다. 이를 위해 리눅스는 VFS 내에 4개의 객체를 도입하였으며, 이를 통해 사용자 태스크에게 일관된 인터페이스를 정의한다. VFS도 일종의 파일시스템이기에 기존 파일시스템에서의 용어와 비슷하다.
아이노드 객체(inode 객체): 특정 '파일'과 관련된 정보를 담기 위한 구조체이다. 각 파일시스템은 파일을 저장하기 위해 각자 정의한 메타데이터를 저장해 놓는다. VFS가 아이노드 객체를 생성하고 파일시스템에 특정 파일에 대한 정보를 요청하면 파일시스템은 자신이 관리하고 있는 파티션에서 파일의 메타데이터를 읽어서 아이노드 객체를 채운다.
만약 msdos라면 해당 파일의 디렉터리 엔트리를 읽어서 객체를 채울 것이고, ext2라면 해당 파일의 디렉터리 엔트리와 inode를 찾아서 객체를 채울 것이다.
VFS의 아이노드 객체와 ext2,3에서 디스크에 기록해 둔 inode와는 다른 것.
파일 객체: 이 객체는 태스크가 open한 파일과 연관되어 있는 정보를 관리한다. 두 개의 태스크가 한 개의 파일에 동시에 접근하는 경우를 가정하면, 물리적으로 한 개의 파일이기 VFS는 하나의 아이노드 객체를 만들어 유지할 것이다. 이 때 두 태스크가 접근하는 오프셋(offset)으로 접근한다면 이러한 정보를 태스크마다 다르게 유지해야 한다. 이러한 태스크와 연관된 정보를 유지하는 용도로 만들어진 것이 파일 객체이다.
파일 객체는 각 태스크가 아이노드 객체에 접근하는 동안만 메모리 상에 유지되는 구조체이다.
디엔트리(directory entry 약자) 객체: 태스크가 파일에 접근하려면 해당 파일의 아이노드 객체를 자신의 태스크와 연관된 객체인 파일 객체에 연결시켜야 한다. 이 관계를 조금 더 빠르게 연결하기 위한 일종의 캐시 역할을 한다.
덴트리(Dentry)는 디렉토리 엔트리(Directory Entry)의 약자이다. 덴트리는 아이노드의 번호와 파일 이름을 관련하여 파일과 아이노드를 연결시켜주는 역할을 한다. 또한 덴트리는 캐시를 유지하여 자주 접근되는 경로를 더 빠르게 접근할 수 있도록 도와준다. 또한 디렉토리와 그 디렉토리에 있는 파일들의 관계를 유지하기도 한다.
예를들어 /home/yun/Document/hello.txt 라는 경로가 있다고 하면, 각각의 경로를 덴트리 객체로 유지한다. /, home, yun, Document, hello.txt와 같이 각각의 모든 경로들을 객체화시킨다. 왜냐하면 경로 탐색은 문자열 비교가 필요한 작업이기 때문에 상당히 비용이 큰 작업이다. 따라서 이러한 비교 작업을 계속해서 반복하는 것은 비효율적이므로 이를 객체화시켜 반복되는 작업을 없애는 것이다.
덴트리 객체는 슈퍼블록이나 아이노드와 같이 디스크에 저장되는 것이 아닌, 메모리에서 사용자 패턴에 동적으로 생기고 없어진다.
source: https://hyoje420.tistory.com/53