[ CS > 운영체제 ]
[운영체제] 파일 시스템
[운영체제] 파일 시스템
UNIX 파일 시스템은 File과 Directory 객체를 지원한다. 그런데 Directory는 사실 File의 특별한 형식이기 때문에 파일 시스템은 File의 표현을 위한 개념이라고 보면 된다.
1. Blocks and Fragments
대부분 파일 시스템은 data block들로 채워지며 이 블록에는 사용자의 파일들이 존재한다. 그리고 어떻게 이 데이터 블록들이 디스크에 저장되는지 알아보자!
디스크(보조 기억 장치)의 섹터 크기는 보통 512 bytes다. (요즘에는 대부분 4kb다!!) 그래서 data block의 크기는 이 512bytes(4 kb)보다 큰 게 속도적으로 좋아보이지만 실제로는 아주 많은 수의 작은 파일이 존재한다면 내부 단편화가 심해질 수 있다. 그래서 초기에는 파일의 모든 블럭을 큰 블록 크기(ex. 8kb)를 사용하고 마지막 블록만 작은 단(1kb)*n개를 사용하여 파일을 끝맺었다.(ex. 26kb 파일 => 8+8+8+1+1)
이러한 블록과 단편의 크기는 파일 시스템 생성시 결정할 수 있고, 본인의 파일 시스템 사용 목적에 따라 적절히 설정하면 좋다. 예를 들면 아래와 같은 방식으로 설정값을 고려하면 된다.
작은 파일이 많이 생성될 것으로 예상되면, 단편 크기를 작게 설정하는 것이 좋고
큰 파일을 자주 전송하는 경우에는 블록 크기를 크게 설정하는 것이 좋다.
구분 | 블록 (Block) | 단편 (Fragment) |
---|---|---|
의미 | 파일 시스템의 기본 할당 단위 | 블록을 더 잘게 나눈 서브 단위 |
크기 | 보통 4KB, 8KB 등 | 블록보다 작음 (예: 512B, 1KB 등) |
목적 | 빠른 디스크 접근 및 큰 파일 처리용 | 작은 파일 저장 시 공간 낭비 줄이기 위해 |
용도 | 파일의 대부분이 블록 단위로 저장됨 | 파일의 마지막 일부에만 사용됨 |
Why | 디스크 성능 향상을 위해 블록을 크게 | 블록이 너무 크면 작은 파일이 공간 낭비되기 때문에 생김 |
근데 실제로는 [블록 크기:단편 크기] 비율은 최대 [8:1]이여야 하고, 최소 블록 크기는 4kb이여야 한다. 그래서 일반 적인 설정은 아래와 같다.
작은 파일 위주 세팅: 4kb 블록 / 512byte 단편 조합
큰 파일 위주 세팅 : 8kb 블록 / 1kb 단편 조합
이제 DB 테이블을 만들때, 필드의 데이터 크기를 2의 배수를 많이 사용하는지 알 것 같은 느낌적인 느낌이 든다. 메모리든, 디스크든, 버퍼든, 페이지든 모두 2의 배수를 사용하니 딱 알맞게 처리하여 효율적인 프로그래밍이 될 것 같다. 웹사이트 만들때 이미지같은 파일 크기도 4kb의 배수로 제한하면 훨씬 효율적이지 않을까?
2. Inode
파일은 inode로 표현된다. inode는 어떤 파일에 대한 정보를 저장하는 기록이다. inode는 "index node"를 말하며 원래 i-node라고 표현했는데 inode로 서서히 표현했다.

inode는 파일의 사용자 및 그룹 식별자, 마지막 파일 수정/접근 시간, 하드 링크 개수, 파일의 종류(일반 파일, 디렉토리, 심볼릭 링크, 문자 디바이스 등)의 정보를 포함한다. 그리고 inode는 파일 데이터가 저장된 디스크 블록을 가리키는 15개의 포인터를 가진다. 이중 12개의 포인터는 direct blocks를 가리키고 이 포인터들은 데이터가 저장된 블록의 주소를 담고 있다. 이게 의미하는 것은 작은 파일(12개 블록 이하)는 데이터를 바로 참조할 수 있다는 것이다. 왜냐하면 inode 복사본이 메인 메모리에 유지되기 때문이며, 만약 블록 크기가 4kb라면 inode를 통해 최대 48kb까지 데이터를 직접 접근할 수 있다. 만약 파일의 크기가 크다면 나머지 3개의 포인터가 가르키는 indirect blocks도 사용하여 접근해야 한다. 이 나머지 3개의 포인터들은 각각 단일 간접 블록(single indirect), 이중 간접 블록(Double Indirect Block), 삼중 간접 블록(Triple Indirect Block)을 참조한다. 단일 간접 블록은 만약 한 블록이 4kb로 할당되며, 주소가 4바이트라 가정한다면, 단일 간접 블록에는 1024개의 데이터 블록의 주소가 저장되어 그 주소를 통해 데이터 블록에 접근하게 되는 것이다.
직접 블록 : 4kb * 12 pointer = 48kb 접근 가능
단일 간접 블록 : (4kb / 블록 포인터 크기 4byte) * 4kb = 4096kb 접근가능
이중 간접 블록 : (4096 kb/ 블록 포인터 크기 4byte) * 4kb = 4194304kb 접근가능
삼중 간접 블록 : (4194304kb/ 블록 포인터 크기 4byte) * 4kb = 42942967296kb 접근가능 (너무 비효율적이라 이론적으로만 존재)
이때 과거의 4.2BSD 파일 시스템은 이중 간접 블록까지만 썼다고 하며, 그래서 inode로 4194304kb => 232바이트보다 살짝 크게 접근이 가능하며 232 값은 메인 메모리에서 파일 구조체의 파일 오프셋이 32비트 단어로 저장되어 중요하다고 한다. 파일 포인터가 부호가 있는 정수이며, 파일 내에서 앞뒤로 탐색할 수 있어야 하기 때문에 실제 최대 파일 크기가 232-1바이트(4GB)라고 한다.
오늘날에는 64비트 운영체제를 쓰기 때문에 아래와 같으며, 사실 최신 운영체제들은 inode 개념은 쓰지 간접블록 구조를 사용하지 않는다.
항목 | 32비트 시스템 | 64비트 시스템 (사실상 간접블록 구조 안씀) |
---|---|---|
최대 파일 크기 | 약 2~4 GB | 이론상 약 8 EB |
파일 오프셋 크기 | 32비트 (signed) | 64비트 (signed) |
아래는 좀 더 최신 운영체제에서 쓰이는 Extent data 구조다.

Extent 연속된 블록의 offset과 개수를 저장하는 방식이다. 연속된 블록으로 할당하여 파편화가 덜되며, 적은 수의 Extent(데이터 블록 방식보다)로 inode 데이터 접근을 처리할 수 있어 더 효율적이다.
3. Directory
일반 파일과 디렉토리는 구현 수준에서 구분이 되지 않는다. 무슨 말이나면 디렉토리의 내용도 데이터 블록에 저장되며 inode로 표현된다는 의미다. 다만 inode의 type이 일반 파일과 다르고 구조를 가진다.
3.1 파일 이름과 inode
유저는 파일을 path name으로 구분하짐만, 파일 시스템은 inode로 파일을 정의한다. 그래서 OS는 path name과 inode를 연결시켜줄 수 있어야 하며, directory가 이 역할을 한다. 그리고 파일 이름이 inode로 변환되는 과정은 다음과 같다.
먼저 시작 디렉터리(starting directory) 를 결정합니다.
경로 이름이 /로 시작하면 루트 디렉터리부터 시작합니다.
슬래시(/)로 시작하지 않으면 현재 프로세스의 현재 디렉터리를 시작점으로 삼습니다.
시작 디렉터리의 파일 타입과 접근 권한을 확인하고, 문제가 있으면 오류를 반환합니다.
경로의 다음 요소(다음 /까지 또는 문자열 끝까지)는 파일 이름입니다.
시작 디렉터리에서 이 이름을 찾아보고, 없으면 오류를 반환합니다.
경로에 또 다른 요소가 있다면 현재 inode는 디렉터리여야 하며,
디렉터리가 아니거나 접근이 거부되면 오류를 반환합니다.
그런 다음, 그 디렉터리를 위와 같은 방식으로 다시 검색합니다.
이 과정을 경로 이름의 끝까지 반복하여 원하는 inode를 찾으며, 이렇게 단계별로 진행하는 이유는 마운트 포인트나 심볼릭 링크를 만나면 다른 디렉토리 구조로 전환하는 과정이 필요하기 때문이다.
만약 inode가 open() 시스템 콜로 발견된다면, 해당 inode가 가르키는 파일 구조체(file structure)가 할당되고, file descriptor가 그 파일 구조를 참조하여 보여준다. 그리고 보통 directory name -> inode로 바꾸는 작업이 많아 directory name cache를 만들어 활용한다.
3.2 마운트 포인트, 하드 링크, 심볼릭 링크, 윈도우 바로가기
여기서는 위 4가지에 대해서 비교 요약표로 마무리 하겠습니다. 하드 링크, 심볼릭 링크를 사용하는 주된 이유는 용량을 낭비하지 않고, 경로 값만 사용할 수 있도록 하여, 다른 디렉토리에서도 같은 파일을 쓸 수 있도록 하기 위해서 씁니다.
항목 | 마운트 포인트 (Mount Point) | 하드 링크 (Hard Link) | 심볼릭 링크 (Symbolic Link) | 윈도우 바로가기 (Shortcut) |
---|---|---|---|---|
정의 | 다른 파일 시스템을 연결하는 경로 | 같은 파일을 inode 기준으로 공유 | 다른 파일 경로를 문자열로 참조 | 파일 정보를 담은 GUI용 참조 파일 |
작동 방식 | 디렉터리에 다른 파일 시스템을 연결 (USB) | 하나의 inode에 여러 디렉터리 엔트리가 연결됨 | 경로 문자열 따라가서 재탐색 | 파일 경로와 실행 옵션 등을 .lnk 파일에 저장 |
참조 대상 | 디스크, 파티션, 디렉터리 | 동일한 파일 (같은 inode) | 파일 또는 디렉터리 (경로 기반) | 파일 또는 디렉터리 (경로 기반) |
파일 시스템 | 서로 다른 파일 시스템 가능 | 동일한 파일 시스템 내에서만 가능 | 파일 시스템 경계 넘을 수 있음 (클라우드) | 일반적으로 윈도우 NTFS 상에서 사용 |
대상 삭제 시 | 마운트 해제되면 접근 불가 | 한 링크라도 남아 있으면 파일 유지됨 (inode 연결 수) | 대상이 삭제되면 끊어진 링크 됨 | 대상 삭제 시 경고만, 실행은 실패 |
디렉터리 링크 | 디렉터리만 가능 | 보통 디렉터리에 못 씀 (., .. 문제) | 디렉터리도 가능 | 디렉터리도 가능 |
루프 가능성 | 불가능 | 불가능 | 있음 (8단계 제한) | 없음 |
명령어 예시 | mount /dev/sdb1 /mnt/usb | ln file.txt file2.txt | ln -s file.txt link.txt | 마우스 우클릭 → "바로가기 만들기" |
4. File Descriptor와 inode 맵핑

inode 맵핑은 열려있는 파일을 참조하는 System Call은 file descriptor를 인자로 전달하여 해당 파일을 지정하며 시작한다.
커널이 File Descriptor를 이용하여 현재 열려있는 프로세스의 tables of open files를 인덱싱한다.
테이블의 각 항목은 file structure를 가리키는 포인터를 가진다.
이 file-sturcture는 다시 inode를 가리키는 포인터를 가진다.
이 inode는 disk의 메모리 복사본이며, 추가적으로 몇개의 프로세스가 참조하고 있는지와 같은 값을 가지며, 개별 프로세스마다 같은 file을 open하면 그 offset(읽거나 쓰는 위치)는 공유되지 않으나, fork해서 사용하면 공유됨. (락이 필요)
5. 디스크 구조
사용자가 보는 파일 시스템은 디스크같은 대용량 저장 장치 위에 구성되어 있다. 사용자는 보통 하나의 파일 시스템만 인지하지만, 그 하나의 논리적 파일 시스템은 실제로는 여러개의 실제 존재하는 물리적 파일 시스템으로 이루어져 있을 수 있다. 그런데 보통 하나의 큰 디스크 장치를 여러개의 논리 장치로 파티셔닝하기를 원하며, 각 논리 장치는 하나의 물리적 파일 시스템을 정의한다.

위 그림을 보면 디렉터리 구조가 파일 시스템으로 나뉘고, 각 파일 시스템ㅁ이 논리 장치에 맵핑되며, 이 논리 장치들은 물리 장치의 파티션으로 구성되는 구조를 보여준다.
파티셔닝의 장점
용도 분리: 각 파티션을 서로 다른 용도로 사용할 수 있다.
스왑 공간: 일부 파티션은 가상 메모리의 스왑 공간으로 사용할 수 있다.
신뢰성 향상: 소프트웨어 손상이 있어도 해당 파일 시스템만 손상되므로 전체 시스템 피해를 줄일 수 있다.
효율성 증가: 각 파티션마다 블록/조각 크기를 조정해서 성능을 최적화할 수 있다.
공간 남용 방지: 하나의 프로그램이 모든 공간을 차지하지 않게 한다. 파일은 파일 시스템을 넘어 저장되지 않기 때문이다.
백업 성능 향상: 파티션 단위로 백업되기 때문에, 작은 파티션일수록 백업 테이프에서 원하는 파일을 더 빠르게 검색하고, 전체 복원도 더 빠르다.
5.1 마운트 처리(다른 디바이스 파일 어떻게 볼까??)
모든 시스템에는 반드시 하나의 루트 파일 시스템(root file system)이 있으며, 나머지 파일 시스템은 마운트(mount)되어 디렉토리 구조에 통합되는 것이다.
inode 구조체 안에는 해당 inode에 다른 파일 시스템이 마운트되었는지 여부를 나타내는 비트가 있다. 어떤 경로가 마운트된 파일 시스템을 참조한다고 가정하면, 마운트 테이블(mount table)을 검색하여 마운트된 장치의 디바이스 번호를 찾고, 이 번호로 마운트된 파일 시스템의 root directory의 inode를 찾아 그 inode를 사용한다.
위와 같은 방식으로 마운트된 디바이스의 root directory의 inode를 연결시켜 준다.(이 연결 때문에 반드시 루트 파일 시스템이 있어야 한다는 의미기도 하다.)
6. 프론트엔드 (내가 좋아하는)PNPM과 Symbolic Link, Hard Link, Inode 설명
내가 프론트엔드를 하면서 처음 썼던 패키지 매니저 NPM이다. 매우 느려 터졌고, Hoisting을 하다가 유령 의존성이 생기기도 한다. 그러다 알게 된것이 yarn berry와 pnpm이 있는데 이 중에서 Symbolic Link와 Hard Link를 써서 용량 낭비 없이 사용할 수 있는 pnpm이 너무 좋아 보여서 pnpm을 사용하게 되었다.
pnpm은 아래와 같은 저장소 구조를 가진다. (직접 그려서 틀렸을 지도..?)

일부 native 자원을 사용하는 패키지(OS 의존성이 있는 패키지)를 제외하고는 모두 pnpm store의 hard link로 복사본을 만든 뒤, hoisting없이 구조 그대로 의존성 패키지들을 symbolic link로 복사한다. 그래서 경로만을 복사하여 사용하기 때문에 디스크 용량을 절약할 수 있다.
아래는 pnpm 사이트에서 가져온 사진이다.

프로젝트별로 파일을 가지고 있지만, 실제로는 1개의 파일들이다.

ls -li 명령어로 봤을 때도, LICENSE 파일은 51개의 Hard Link가 존재하는 것을 확인할 수 있다.

그리고 위는 symbolic 링크가 어디로 연결된 것인지 까지 확인할 수 있다.
둘의 차이점은 hard link는 같은 inode를 가진 링크 개수가 여러개인 반면에, symbolic 링크는 다른 inode의 파일이 원본 파일에 접근할 수 있게 만드는 것이다.
하드 링크 (Hard Link)
하드 링크는 새로운 인덱스 노드(inode)를 생성하지 않음. 원본 파일과 하드 링크는 동일한 inode를 가리킴.
하드 링크는 디렉토리를 지원하지 않고, 파일 수준에서만 작동. 또한 디스크 파티션 간에는 사용할 수 없음.
원본 파일과 모든 하드 링크가 삭제되기 전까지는 파일이 실제로 삭제되지 않음.
심볼릭 링크( Symbolic Link)
심볼릭 링크는 원본 파일의 경로를 저장하며, 이를 통해 원본 파일을 가리킴. 바로가기(Shortcut) 와 유사하지만 다름.(코딩중 경로 유지가 불가!!! 경로 문제 해결이 안됌)
심볼릭 링크는 디렉토리와 파일 모두 지원하며, 원본 파일과는 다른 파일. 즉, inode 값이 다르고 파일 유형도 다름. 따라서 파티션을 넘어서도 접근 가능. (GIT에서 심볼릭 링크 폴더 만드는 개념, 클라우드 접속 개념)
원본 파일이 삭제된 후에도 심볼릭 링크는 그대로 존재하지만, 그 링크를 통해 원본 파일에는 접근할 수 없음.
pnpm이 그러면 무적이냐?? 라고 하면은 아닙니다!! 왜냐하면 실제 여러 라이브러리들은 pnpm 사용이 불가한 경우가 있었기 때문입니다. 저의 경우에는 React-Native가 pnpm을 지원하지 않았는데, 최근에야 지원하기 시작해서 사용한 경험이 있습니다. 반드시 지원 여부를 확인하고 사용하시기 바랍니다
마무리
오늘은 파일 시스템에 대해서 알아봤습니다. 사실 OS 별로 많이 발전해왔기 때문에, 그 모든것을 다루진 못했습니다. 하지만 자료 조사하면서 그 방식은 대동 소이 하다는 것을 알았고, 정말 필요하다면 그때 보면 좋을 것이라 생각합니다. 실생활에서도 유용하게 쓸 수 있습니다. 심볼릭 링크, 하드 링크는 같은 프로그램을 여러개 키는 것이 필요할때, 직접 복사 붙여넣기 하는 방식이 아니라 심볼릭 링크, 하드링크로 복사하면 여러개 킬 수 있습니다.(10GB 게임이라면, 10GB 복사가 아니라 경로만 복사하기 때문에 개이득)
이상 저의 글이 많은 도움이 되길 바랍니다!!
