# 사용자의 task에서 file system으로의 접근
: open(), read(), write(), close(), ... 호출
# POSIX 표준 시스템
: Portable Operating System Interface. 서로 다른 UNIX OS의 공통 API를 정리하여 이식성이 높은 유닉스 응용 프로그램을 개발하기 위한 목적으로 IEEE가 책정한 애플리케이션 인턴페이스 규격.
-> https://ko.wikipedia.org/wiki/POSIX
- 운영체제와 관련하여 IEEE에서 만들 규약
- UNIX를 고려하여 만들어졌기 때문에 X를 붙임임
- OS들의 호환성을 고려함.
- 시스템콜, 프로세스 환경, 파일/디렉터리, 시스템 데이터베이스, tar압출 포맷 등을 다룸
# VFS
: Virtual File System
- POSIX 표준 시스템 호출을 이용
- 파일시스템과 사용자 태스크 사에에 가상층을 도입.
- 인자에 담겨있는 파일의 이름을 보고 파일을 관리하고있는 파일시스템이 무엇인지 판단.
- 해당 파일시스템의 함수를보고 해당하는 함수를 호출.
- 함수로부터 결과를 리턴받아 사용자 태스크에게 전달.
-> VFS를 통해 리눅스에서 다양한 파일시스템을 지원
- UFS, 유닉스 파일시스템
- ext2(3), 리눅스 기본 파일시스템
- NFS(Network File System), SUN에서 개발한 네트워크 파일시스템
- LFS(Log Structured File System), small write에 좋은 성능을 제공하는 파일 시스템
- CODA, CMU대학에서 개발하였으며 disconnected 연산 기능을 제공하는 파일시스템
- iso9660, CD를 위한 파일 시스템
- msdos, VFAT, MS의 DOS 파일 시스템
- 윈도우 NT 파일 시스템
- proc, 커널의 내부 상태를 볼 수 있는 파일 시스템
- sysfs, 장치를 통합 관리하는 파일 시스템
그림 출처. http://e2fsprogs.sourceforge.net/ext2intro.html
# VFS 동작 원리
: 예시. ext2 file system, /dev/hda2, b.txt
- 사용자 태스크가 b.txt라는 인자로 open()시스템 콜을 호출
- VFS가 b.txt의 정보를 저장하기위해 구조체를 만듬
- 생성한 구조체를 인자로 ext2 파일시스템 내부의 open()함수를 호출
- ext2는 자신의 inode table에서 b.txt의 inode를 찾아 해당 정보를 인자로 받은 구조체에 저장하여 리턴
- VFS는 리턴받은 구조체의 내용을 바탕으로 사용자 태스크에게 필요한 정보를 전달
# VFS의 4가지 객체
: VFS에서 다양한 파일시스템과의 호환을 위해 사용하며 사용자 태스크에게 제공할 일관된 인터페이스를 정의
1. 수퍼블럭 객체
: 파일시스템 자신이 관리하는 파티션에 파일시스템 마다 고유한 정보를 수퍼블록에 저장. 이를 관리하기위해 VFS에서 수퍼블럭 객체라는 범용적인 구조체를 정의
- 현재 마운트 되어 사용 중인 파일시스템당 하나씩 주어짐
2. 아이노드 객체
: 특정한 파일과 관련한 정보를 저자하기위해 정의된 구조체
- 파일에 대한 메타데이터 정보를 저장하기 위해 아이노드객체를 생성하고 파일시스템에 특정파일에 대한 정보를 요청하면
파일 시스템은 자신의 관리영역에서 해당 정보를 아이노드 객체에 저장
- ext2의 경우 해당 파일의 디렉터리 엔트리와 inode를 찾아서 아이노드 객체에 저장
- msdos의 경우 해당 파일의 디렉터리 엔트리를 읽어서 아이노드 객체에 저장
3. 파일 객체
: 태스크가 open한 파일과 연관되어 있는 정보를 관리하기 위해 사용하는 구조체
- 2개 이상의 태스크가 하나의 파일에 접근할 때, 각각의 offset등과 같은 정보를 관리해야함
- 태스크 관련 정보를 유지하는 용도로 파일 객체를 사용
- 각 태스크가 아이노드 객체에 접근하는 동안만 메모리상에 유지
4. 디엔트리 객체
: 사용자 태스크가 파일에 접근하게 하기위해 아이노드 객체와 파일객체의 연결이 필요, 이때 빠른 연결을 위해 캐시역할을 수행하는 객체
# task_struct
: 프로세스는 자신이 사용하는 자원, 자원에서 수행되는 수행흐름으로 구성(resource + flow of control). 이를 관리하기 위해 각 프로세스마다 생성하는 자료구조.
- 프로세스, 스레드 마다 생성, 수행 이미지 공유여부 및 스레드 그룹정보를 통해 구분
- 리눅스에서는 프로세스와 쓰레드 모두 커널내부에서 태스크로 관리
fork(), clone(), pthread_create() --> sys_clone() --> do_fork() ,task_struct
vfork() --> sys_vfork() --> do_fork() ,task_struct
cf. task_struct 구조체 내부 필드 중
- pid : 태스크 별로 유일한 값
- tgid : 한 프로세스내의 쓰레드는 동일한 PID를 공유해야한다는 POSIX 표준에 따라 리눅스에서는 Thread Group ID라는 개념을 도입
- files : file_struct라는 자료구조를 가리킴(file_struct의 max_fds : 한 태스크가 오픈할 수 있는 파일의 최대 갯수를 나타내는 변수)
- fd_array : file_struct구조체의 file이라는 자료구조에 대한 포인터를 갖는 배열
유닉스 계열의 운영체제에서 일반적으로 fd, file descriptor라고 부름
fd는 함수의 리턴값을 받는 변수로, fd_array에서 해당되는 인덱스로 사용됨
일반적으로 fd_array[0]은 표준입력(stdin), fd_array[1]은 표준 출력(stdout), fd_array[2]는 표준 에러출력(stderr)으로 설정
# fd_array
: task_struct의 files변수가 가리키는 file_struct구조체내의 값으로 fd_array의 각 값들은 파일 객체를 가리킴
- 유닉스 운영체제에서는 파일 테이블이라고 부름
- 사용자 태스크에게 fd라는 정수로 파일에 접근할 수 있도록 하는 추상화 계층을 제공하는 변수
- f_dentry : 디엔트리 객체를 가리킴(해당 디엔트리 객체는 다시 연결되어있는 아이노드 객체를 가리킴)
- f_pos : 현재 피일에서 읽거나 쓸 위치를 저장(처음 파일을 오픈하였을 때는 값이 0), lseek호출을 통해 f_pos값을 바꿀 수 있음
- f_op : file_operations라는 자료구조를 가리키는 포인터(8장)
# file_operations와 inode_operations
- file_operations 구조체 : 가상적인 파일연산이 요청되면 커널에서는 요청된 파일의 유형정보 및 파일의 고유 함수를 사용하여 서비스를 제공해야하는데 이때 사용하는 구조
각 파일 유형에 적합한 파일 연산들이 저장되는 변수
- inode_operations 구조체 : 아이노드 객체에 있는 i_op 포인터 변수가 inode_operations를 가리킴
사용자가 create(), mkdir() 등 파일시스템의 메타데이터 관련된 연산을 요청하면,
커널은 요청한 연산이 어떤 파일시스템에서 발생하였는지 파악하고,
적절한 파일시스템의 고유한 함수를 사용하여 서비스를 제공하는데 이때 사용하는 구조체
각 파일시스템 유형에 적합한 파일시스템 연산들이 저장되는 변수
=> 리눅스가 다양한 파일과 파일시스템을 지원할 수 있는 비밀병기
# task_struct와 VFS
그림 출처. http://www.redirfs.org/docs/thesis/
- 파일의 이름을 인자로 시스템콜을 호출
- VFS가 아이노드 객체를 인자로 파일시스템 내부의 해당 함수를 호출
- 필요한 정보를 아이노드 객체에 채운 후 리턴
- VFS는 해당 아이노드 객체를 디엔트리 객체에 연결시킨 후 사용자 태스크 구조와 연결
# f_op
: 파일시스템마다 구현된 함수들이 다른데, 해당되는 파일 유형에 맞는 연산이 등록되는 변수
예. file -> f_op -> open()
- ext2 : ext2의 open(), linux/fs/ext2/file.c에 구현
- NFS : nfs_file_open()
- 파이프 : fifo_open()
- 장치파일 : chrdev_open() / blkdev_open(), linux/fs/device.c에 구현
# sys_open()
- 특정파일 각각의 open()함수들의 호출 후 filp_open()함수 리턴
: 태스크에서 현재 사용하지 않는 fd_array의 한 항을 할당
- 해당 항이 생성된 파일 객체를 가리키도록 설정
- 태스크 구조와 VFS이 연결됨
# sys_read()함수
: 인자로 전달된 fd를 이용해 파일 객체를 찾고 해당 f_op에 등록된 read함수를 호출, linux/fs/reead_write.c에 구현
- 우선 요청 데이터가 캐시에 있는지 찾음
- 캐시에 있으면 디스크에 안가고 바로 데이터를 사용자에게 전달, 없다면 디스크로 접근
- 이때 각 파일시스템은 서로 다른 방식으로 디스크를 관리하므로 각 파일시스템은 서로다른 디스크 연산함수를 사용하며 해당 처리가 필요함
-> 특정 파일시스템에 맞는 디스크 연산 함수를 호출할 수 있도록 inode_operations구조체를 사용
-> include/linux/fs.h에 정의
-> inode와 관련한 연산을 나타내는 변수로 구성(create, lookup, link, mkdir, mknod, readpage...)
-> 즉, 각 파일세스템에 고유한 연산들이 저장
# 파일의 사용과 파일시스템의 사용
- f_op와 i_op : 각 파일과 파일시스템에 고유한 연산을 제어를 전달하는 진입점 역할을 수행하는 변수
- 파일에 접근(새로운 장치를 위한 장치파일 연결)을 위해 디바이스 드라이버를 사용하여 새로운 file_operations 구조를 작성해 커널에 등록
- 파일시스템의 경우 file_operations구조 및 register_filesystem()이라는 커널내부함수를 사용하여 inode_operations구조도 작성하여 커널에 등록
# register_filesystem()
- linux/fs/filesystems.c에 구현
- struct_file_system_type 자료구조를 인자로 받음, include/linux/fs.h에 정의
<항목>
- name : 파일시스템을 나타내는 변수. ext2, ext3 ...
- fs_flags : 속성정보 저장 변수. 물리장치필요여부, 읽기전용여부...
- mount : 수퍼블록을 읽어 파티션을 마운트 하는 함수의 포인터 저장 변수
- 복수개의 file_system_type구조들의 연결을 위한 리스트와 모듈 정보 등
- ...
- register_filesystem()으로 커널에 등록된 파일시스템은 하나의 file_system_type 자료구조를 가짐
- 커널의 존재하는 모든 file_system_type은 리스트로 연결
- file_systems라는 커널 내의 전역변수가 file_system_type리스트의 시작을 가리킴
# File System의 마운트 요청,
- file_systems에서 검색하여 요청된 file_system_type 자료구조 찾기
- get_sb에 기록된 함수를 호출하여 파일시스템의 수퍼블럭정보를 얻어옴
- 얻어온 수퍼블럭정보를 VFS의 수퍼블럭 객체에 저장
- 수퍼블럭을 읽어 해당 파일시스템의 자세한 정보를 얻어옴
- 이 자세한 정보로 inode_operations, file_operations에 접근이 가능
# VFS의 내부구조
그림 출처. http://www.read.cs.ucla.edu/111/2006fall/notes/lec14
그림 출처. https://www.usenix.org/legacy/event/usenix01/full_papers/kroeger/kroeger_html/node8.html
- 태스크는 시스템 콜 인터페이스로 VFS와 통신
- 태스크가 원하는 내용은 커널내부의 페이지캐시(캐시역할)을 통할수도 있음
- 캐시에 없다면 실제 디스크와 I/O 수행
- 이때 디엔트리 객체와 아이노드 객체를 위한 캐시를 일반데이터블럭을 위한 캐시공간과 분리하여 성능향상을 도모
- VFS내에 존재하는 객체의 연결관계를 이용해 inode_operations, file_operations 구조체의 함수를 호출
- 가상계층과 물리는 실제 디스크와 I/O하는 부분에서는
커널내의 일반 블록 게층(Generic Block Layter)의 gendisk를 관리하는 블록 디바이스 드라이버에게 I/O 요청을 해야함
- 성능향상을 위해 리눅스는 자체적인 디스크 I/O 스케줄링 알고리즘(3가지 Elevator알고리즘)을 통해 요청을 보냄
참고.
리눅스 커널 내부구조(책)