리눅스 스터디 #7 파일 시스템
대부분의 저장 장치는 파일 시스템을 통해 접근한다.
파일 시스템이 없다면? 데이터를 디스크 어떤 위치에 저장할지 직접 정해야하고, 다른 데이터를 훼손하지 않도록 영역 관리, 나중에 다시 읽어오기 위한 위치, 사이즈, 배치등을 기억해야한다.
파일 시스템은 이러한 정보들을 대신해서 관리해준다.
아래와 같이 사용자가 발표자료 0 - 10GiB의 데이터를 요청했다고 가정하자.
사용자 모드 | 커널 모드 | 하드웨어 |
프로세스(발표 자료 10기가 데이터 요청, 시스템 콜 호출) | 파일 시스템 코드(하드웨어 저장 장치 검색, 읽기, 사용자에게 넘기기) | 발표 자료 관련 data 저장되어 있음 |
파일 형식으로 데이터를 관리하는 저장 장치의 영역(관리 영역) + 저장 영역을 다루는 처리(파일 시스템 코드) 양쪽을 합쳐서 파일 시스템이라고 부른다.
리눅스 파일 시스템은 각 파일을 디렉토리라고 하는 특수한 파일을 사용해서 분류할 수 있다. 디렉터리가 다르면 동일한파일명을 사용할 수 있고, 디렉토리 안에 또 다시 디렉토리를 만들어 트리 구조를 만들 수 있다.
파일 시스템에는 data와 meta data 두 종류의 데이터가 있다. 데이터는 사용자가 작성한 문서나 영상, 동영상, 프로그램 등에 해당한다.
데이터는 사용자가 작성한 문서나 영상, 동영상, 프로그램 등에 해당한다. 메타 데이터는 파일을 관리할 목적으로 파일 시스템에 존재하는 부가적인 정보이다.
종류 | 내용 |
파일 이름 | |
저장 장치에서의 위치, 크기 | |
파일 종류 | 일반 파일, 디렉토리, 디바이스 파일 등 |
파일 시각 정보 | 작성 날짜, 최종 접근 날짜, 최종 수정 날짜 |
파일 권한 정보 | 어떤 사용자가 파일에 접근 가능한지 |
디렉터리 데이터 | 디렉토리 내부에 어떤 파일이 들어있는지 |
파일 접근 방법
파일 시스템에는 POSIX에서 정한 함수로 접근할 수 있다.
- 파일 조작
- 작성, 삭제: create(), unlink() 등
- 열고 닫기: open(), close() 등
- 읽고 쓰기: read(), write(), mmap() 등
- 디렉토리 조작
- 작성, 삭제: mkdir(), rmdir()
- 현재 디렉토리 변경: chdir()
- 열고 닫기: opendir(), closedir()
- 읽기: readdir() 등
이런 함수 덕분에 사용자는 파일 시스템에 접근할 때 파일 시스템 종류의 차이를 의식할 필요가 없다. 파일 시스템이 ext4 혹은 XFS라도 관계없이 파일을 만들때 create() 함수를 사용하면 된다.
bash를 통해 파일 시스템에 접근할 때 내부적으로는 아래와 같은 순서로 처리가 진행된다.
- 파일 시스템 조작용 함수가 내부적으로 파일 시스템을 조작하는 시스템 콜을 호출한다.
- 커널 내부 가상 파일 시스템(Virtual File System) 처리가 동작하고 각각의 파일 시스템 처리를 호출한다.
- 파일 시스템 처리가 디바이스 드라이버를 호출한다.
- 디바이스 드라이버가 장치를 조작한다.
예를 들어 동일한 디바이스 드라이버로 조작할 수 있는 블록 장치 A,B,C가 각각 ext4, XFS, Btrfs 파일 시스템이 있다고 가정하면 아래와 같을 것이다.
프로세스 요청 | ||
파일시스템 공통 계층(VFS 계층) | ||
ext4용 처리 | XFS용 처리 | Brtfs용 처리 |
디바이스 드라이버 | ||
장치 A | 장치 B | 장치 C |
메모리 맵 파일
리눅스에는 파일 영역을 가상 주소 공간에 매핑하는 메모리 맵 파일 기능이 있다.
mmap() 함수를 특정한 방법으로 호출하면 파일 내용을 메모리로 읽어서 그 영역을 가상 주소 공간에 매핑할 수 있다.
즉, memory-mapped file을 통해서, 특정 가상 주소 공간에 메모리를 확보하여 파일 data를 올려다 놓고 읽고, 쓰고 할 수 있다.
data를 변경하게 되면 나중에 실제 저장 장치에 있는 파일에도 정해진 타이밍에 반영이 된다.
일반적인 파일 시스템
리눅스는 ext4, XFS, Btrfs 같은 파일 시스템을 주로 사용한다.
파일 시스템 | 특징 |
ext4 | 예전부터 리눅스에서 사용하던 ext2, ext3에서 전환하기 편함 |
XFS | 뛰어난 확장성 |
Btrfs | 풍부한 기능 |
각 파일 시스템은 저장 장치에서 만드는 데이터 구조와 관리하는 처리 방식이 다름. 아래와 같은 차이점들이 있다.
- 파일 시스템 최대 크기
- 파일 최대 크기
- 최대 파일 개수
- 파일명 최대 길이
- 동작별 처리 속도
- 표준 기능 이외의 추가 기능 유무
쿼터(용량 제한)
다양한 용도로 시스템을 사용하다 보면 다른 기능 실행에 필요한 용량이 부족해지는 경우가 있다. 이런 문제를 방지하기 위해 용도별로 사용가능한 파일 시스템 용량을 제한하는 기능이 있다.
예를들어 전체 용량중 용도 A용 Data는 사용가능한 최대 용량을 한정해두고 사용하는 것.
쿼터에는 다음과 같은 종류가 있다.
- 사용자 쿼터: 파일 소유자인 사용자마다 용량을 제한한다. 예를 들어 일반 사용자 때문에 /home/ 디렉토리가 가득 차는 상태를 방지하는 목적. ext4와 XFS는 사용자 쿼터 기능 사용가능.
- 디렉토리 쿼터(프로젝트 쿼터): 특정 디렉토리마다 용량을 제한한다. 예를 들어 어떤 프로젝트 멤버가 공유하는 디렉토리에 용량 제한을 둔다. ext4와 XFS는 사용가능.
- 서브 볼륨 쿼터: 파일 시스템 내부의 서브 볼륨 단위마다 용량을 제한한다. 디렉토리 쿼터와 사용법이 비슷하고, Btrfs가 서브 볼륨 쿼터 기능을 사용한다.
특히 업무용 시스템에서 쿼터를 설정해서 특정 사용자나 프로그램이 저장 용량을 지나치게 사용하지 않도록 제어하는 경우가 많다.
파일 시스템 정합성 유지
예를 들어 파일 시스템 데이터를 저장 장치에서 읽거나 쓰는 도중에 시스템 전원이 강제로 끊기는 경우 파일 시스템에 오류가 생긴다.
특정 파일을 옮기는 과정을 생각해보자.
아래와 같은 파일 시스템이 있고, bar를 foo아래로 이동시킨다고 생각해보자.
- root
- foo
- hoge
- huge
- bar
- foo
1. foo에서 bar로 링크 작성
2. root에서 bar를 향한 링크 삭제
파일 시스템은 위의 순서로 처리하게 된다.
하지만 1번 순서가 끝나고, 2번째가 끝나기 전에 전원이 꺼진다고 생각해보자.
이후에 root -> bar, foo -> bar 링크가 중복하여 존재하는 오류상태가 될 것이다.
이후 파일 시스템이 오류를 감지하는데 마운트 작업 중이라면 파일 시스템이 마운트 불가능하거나, 읽기 전용 모드로 다시 마운트 한다. 또는 system panic이 일어난다.
파일 시스템의 오류 방지 기술에는 여러 종류가 있는데 널리 사용되는 건 journaling과 Copy On Write 두 가지 방식이다.
ext4/XFS는 저널링, Btrfs는 Copy On Write로 각각 파일 시스템 오류를 방지한다.
저널링을 사용한 오류 방지
저널링은 파일 시스템 내부에 저널 영역이라고 하는 특수한 메타 데이터 영역을 준비한다. 이때 파일 시스템 갱신 방법은 다음과 같다.
- 갱신에 필요한 아토믹한 처리 목록을 저널 영역에 기록한다.(저널 로그라고 함)
- 저널 영역에 기록된 내용에 따라 실제로 파일 시스템 내용을 갱신한다.
즉 위의 예제, bar를 foo 아래로 이동시킨다고 했을때,
저널영역에는 아래와 같은 내용이 기록될 것이다.
- foo에서 bar로 링크 작성
- root에서 bar를 향한 링크 삭제
예제 1)
만약 저널영역에 기록을 하다가(갱신) 전원이 끊긴 경우, 단순히 저널 로그를 버리기만 하면 실제 데이터의 정합성은 보장이 된다.(데이터 처리 전 상태임)
1. 저널 영역에 필요한 조작을 기록하던 도중에 강제로 전원 꺼짐
2. 재시작 후에 저널 영역을 파기하고 완료. 조작 이전의 정합성을 확보한다.
예제 2)
실제 데이터를 갱신하던 도중에 강제로 전원이 끊기면 저널로그를 다시 시작해서 처리를 완료 상태로 만든다.(예를들어 위의 저널로그 1,2 순서중 2번을 수행하다가 전원이 꺼진경우, 재부팅해서 1, 2번을 다시 수행하고 완료할 것임)
1. 저널 영역의 내용을 바탕으로 데이터를쓰던 도중에 강제 종료
2. 재시작 후에 파일 시스템은 오류 상태가 됨
3. 마운트할 때 다시 로그를 바탕으로 데이터를 갱신(1번을 수행했었어도, 1, 2번 저널 순서로 진행)
3. 저널 영역을 파기하고 완료.
Copy On Write로 오류 방지
파일 시스템의 데이터 저장 방법을 먼저 알아보자.
Copy On Write 방식이 아니라면 ext4/XFS등은 일단 저장 장치에 파일 데이터를 썼다면 이후 파일을 갱신할 때는 저장 장치의 동일한 위치에 데이터를 써넣는다.
1. 저장 장치에 파일을 작성해서 데이터를 쓴다.
2. 저장 장치의 파일을 갱신한다.(같은 장소에서 갱신)
한편 Brtfs 같은 Copy On Write 형식의 파일 시스템은 갱신할때마다 다른 장소에 데이터를 작성한다.
1. 저장 장치에 파일을 작성한다.
2. 갱신 후 파일 A의 새로운 데이터를 다른 장소에 쓴다.
3. 기존 링크를 다른 장소에 작성된 파일 A로 옮긴다.
단순히 생각하면 기존 백업본 + 갱신 데이터를 만들고, 기존 data 링크를 새로 만들어진 곳으로 이동시키는 것임.
Copy On Write 방식으로 위의 foo, bar mv 예제를 생각해보자.
갱신되는 데이터를 새로 작성하고, 기존 것을 -> 갱신된 데이터로 링크를 만든다고 했다.
1. 새로운 foo를 만들고, 기존 hoge huga bar에 링크를 만들 것이다.
2. 루트에서 링크를 foo에서 새로운 foo로 변경한다.
3. 기존 foo를 삭제하고 갱신을 완료한다.
처리 중에 전원이 끊기는 일이 발생해도 재시작 후에 그전에 만들고 있던 중간 데이터를 삭제하면 오류는 발생하지 않는다.(기존 것은 살아있으니)
Btrfs가 제공하는 파일 시스템의 고급 기능
Btrfs는 다른 파일 시스템에 없는 추가 기능이 있다.
Snapshot
Btrfs는 파일 시스템의 스냅샷을 만들 수 있다. 스냅샷 작성은 데이터 전체 복사가 아니라 데이터를 참조하는 메타 데이터 작성만으로 끝나기때문에 일반 복사 작업보다 훨씬 빠르다.
스냅샷은 원본 파일 시스템과 데이터를 공유하므로 차지하는 공간도 줄어든다. Copy On Write방식을 사용한 데이터 갱신 특성을 최대한으로 활용하는 기능이다.
예를 들어 아래의 파일 시스템의 스냅샷을 만들어보자
root | |
foo | bar |
아래와 같이, 스냅샷의 root는 foo, bar는 복사하지 않고 foo, bar를 향한 링크만 만든다.
root | root(snapshot 루트) |
foo | bar |
이후에 foo 데이터가 변경되면?
1. foo 데이터를 다른 새로운 영역에 복사
2. 새로운 영역 데이터를 갱신
3. 마지막으로 root에서 갱신한 영역으로 포인터를 다시 교체 한다.
root | root(snapshot 루트) | |
갱신된 foo | bar(root, snapshot루트에서 가리킴) | foo(복사해서 가리키게 됨) |
스냅샷은 원본 파일 시스템과 데이터를 공유하기 때문에 공유한 데이터에 문제가 생기면 스냅샷 데이터에도 문제가 생긴다.
스냅샷은 백업 목적으로 사용하는 기능이 아니다.
파일 시스템 수준에서 백업을 작성하려면 일반적으로 파일 시스템 입출력을 멈춰야 하는데, 스냅샷을 사용하면 그 시간을 줄일 수 있다. 스냅샷을 작성하는 짧은 시간 동안만 입출력을 멈추고, 그 이후에 스냅샷을 사용해서 백업을 만든다고 생각하면 된다.
멀티 볼륨
ext4나 XFS는 1개의 파티션에 1개의 파일 시스템을 작성한다.
Brtfs는 1개 또는 여러 저장 장치와 파티션으로 커다란 저장소 pool을 만들어서 거기에 마운트 가능한 서브 볼륨 영역을 작성한다.
저장소 풀은 Logical Volume Manager에서 다루는 볼륨 그룹과 비슷하고, 서브 볼륨은 LVM의 논리 볼륨과 파일 시스템을 합친 것에 가깝다.
XFS | XFS on LVM | Brtfs | ||||
XFS | XFS | |||||
논리 볼륨 A | 논리 볼륨 B | 서브 볼륨 A | 서브 볼륨 B | |||
XFS | 볼륨 그룹 | 저장소 풀 | ||||
sda | sda | sdb | sdc | sda | sdb | sdc |
Brtfs는 LVM처럼 RAID(하나의 장치에 동일한 data 2개를 작성하여 데이터 durability 보장하는 것) 구성도 만들 수있다.
RAID 1 구성이라면,
저장소 pool아래의 sda의 A data, sdb의 A data 로 구성되어 sda 가 고장난 경우, sdb의 A data는 무사할 것임.
결국 어떤 파일 시스템을 사용하면 좋은가?
대부분은 파일 작성 패턴 + 접근하는 방식이 이럴때 제일 파일 빠른 시스템이면 된다.(그때그때 다르고 무엇이 적합한지 고려해야함)
Data 손상 감지와 복구
하드웨어 비트 오류 등의 이유로 파일 시스템이 망가질 때가 있다.
Brtfs는 모든 데이터에 checksum이 있어서 데이터 손상을 감지할 수 있다. 데이터를 읽을 때 체크섬 에러를 감지하면 해당 데이터를 버리고, 읽기를 요청한 프로그램에 입출력 에러를 통지한다.
위의 예제처럼 RAID1 구성을 해두고, sda와 sdb에 각각 데이터 0, 데이터 1이 존재한다고 가정해보자.
sda에서 데이터 1이 손상된경우, 데이터 1 손상을 감지할 것이고, sdb의 데이터1을 sda로 카피하여 올바른 데이터를 제공할 것이다.
기타 파일 시스템
지금까지 소개한 ext4, XFS, Brtfs 파일 시스템 이외에도 리눅스가 제공하는 파일시스템 예시를 보자.
메모리 기반의 파일 시스템
저장 장치 대신에 메모리를 사용해서 작성하는 tmpfs 파일 시스템이 있다. tmpfs 파일 시스템에 저장된 데이터는 컴퓨터 전원이 꺼지면 사라지지만 저장 장치에 접근할 필요가 없어서, 접근 속도가 빠르다. 재시작할때 기존 파일이 남아 있지 않아도 되는 /tmp나 /var/run에서 주로 사용한다.
tmpfs가 사용하는 메모리도 역시, 데이터에 처음 접근했을때 페이지 단위로 메모리를 확보한다.
네트워크 파일 시스템
네트워크로 연결된 원격 호스트의 데이터에 파일 시스템 인터페이스를 사용해서 접근하는 네트워크 파일 시스템도 있다.
Network File System(NFS)나 Common Internet File System(CIFS)은 원격 호스트에 있는 파일 시스템을 로컬에 있는 파일 시스템처럼 조작할 수 있다. NFS는 리눅스를 포함한 유닉스 계통 OS의 원격 파일 시스템에 접근할 때 주로사용하고, CIFS는 윈도우 기기의 파일 시스템에 접근할때 사용한다.
여러기기의 저장 장치를 하나로 묶어서 커다란 파일 시스템으로 만드는 CephFS 같은 파일 시스템도 있다.(원격 저장 장치 추상화)
procfs
시스템에 있는 프로세스 관련 정보를 얻기 위해 procfs 라고 하는 파일 시스템에 존재한다. 보통 procfs는 /proc 아래에 마운트되고, /proc/pid/ 아래에 있는 파일에 접근하면, pid에 대응하는 프로세스 정보를 얻을 수 있다.
/proc/pid 이하 파일(일부)
파일명 | 의미 |
/proc/<pid>/maps | 프로세스 메모리 맵 |
/proc/<pid>/cmdline | 프로세스 명령줄 인수 |
/proc/<pid>/stat | 프로세스 상태, 지금까지 사용한 CPU 시간, 우선도, 사용 메모리 용량 등 |
/proc 이하 파일
파일명 | 의미 |
/proc/cpuinfo | 시스템에 설치된 CPU 관련 정보 |
/proc/diskstat | 시스템에 설치된 저장 장치 관련 정보 |
/proc/meminfo | 시스템 메모리 관련 정보 |
/proc/sys/ 디렉토리 이하 파일 | 커널의 각종 튜닝 파라미터, sysctl 명령어와 /etc/sysctl.conf로 변경하는 파라미터와 1대1 관계 |
sysfs
procfs에 프로세스 관련 정보 이외에도 커널이 관리하는 잡다한 정보를 아무 제한 없이 저장하는 문제가 생기다보니, 더이상 남용하지 않도록 커널이 관리하는 정보를 모아둘 장소로 만든 것이 sysfs다.
보통 /sys/ 디렉토리 아래에 마운트 된다.
/sys/block 아래에 시스템에 존재하는 블록 장치마다 디렉토리가 존재한다.
예를들어 /sys/block/nvme0n1/dev 파일에는 "메이저번호:마이너 번호" 가 들어있다.
블록 장치의 sysfs 파일(일부)
파일 | 설명 |
removable | CD나 DVD와 같은 장치에서 미디어를 꺼낼 수 있으면 1, 아니면 0 |
ro | 1이라면 읽기 전용, 0이라면 읽고 쓰기 가능 |
size | 장치 크기 |
queue/rotational | 접근할 때 디스크처럼 회전이 필요한 HDD, CD, DVD라면 1, 회전이 필요하지 않은 SSD 같은 기기라면 0 |
nvme0n1p<n> | 파티션에 대응하는 디렉토리, 각 디렉토리에는 위에서 설명한 파일들이 존재함. |