
핵심 요약: 파일 시스템은 사용자가 섹터 번호를 직접 다루지 않고도, 파일 이름만으로 데이터에 접근할 수 있게 해주는 운영체제의 핵심 구성 요소입니다.

파일 시스템의 계층 구조는 다음과 같습니다:
┌─────────────────────────────┐
│ 응용 프로그램 코드 │ ← 사용자가 작성한 프로그램
│ (Application Code) │
├─────────────────────────────┤
│ C 라이브러리 함수 │ ← fopen, fclose, printf,
│ (C Library Functions) │ fgetc, getchar 등
├─────────────────────────────┤
│ 커널 파일 시스템 │ ← open, close, read,
│ (Kernel File System) │ write, seek (시스템 콜)
├─────────────────────────────┤
│ 시스템 콜 인터페이스 │
│ (System Calls for File Ops) │
└─────────────────────────────┘
fopen, fclose, printf, fgetc, getchar 등 고수준 함수들입니다. 이 함수들은 내부적으로 시스템 콜을 호출합니다.open, close, read, write, seek 등의 시스템 콜(system call) 을 제공합니다. 이들은 커널 내부에서 실제 파일 조작을 수행합니다.핵심 요약: 사용자 프로그램 → C 라이브러리 → 커널 시스템 콜 순서로 호출이 이루어지며, 각 계층이 추상화를 제공합니다.

디스크(Disk) 메모리(DRAM)
┌──────┐ ┌──────┐ ┌──────┐ ┌──────────────────┐
│ Id:0 │ │ Id:1 │ │ Id:2 │ │ 접근 시간 │
│a.txt │ │b.txt │ │c.txt │ │ 파일 크기 │ ← a.txt의
│ info │ │ info │ │ info │ │ 파일 타입 │ 메모리 내 정보
└──────┘ └──────┘ └──────┘ │ ... │
└──────────────────┘
↑
디스크립터(Descriptor)가
이 정보를 가리킴
핵심 요약: 파일 ID는 디스크 상의 파일 식별자(inode 번호), 파일 디스크립터는 열린 파일에 대한 메모리 내 핸들입니다.

파일 시스템이 각 파일에 대해 저장하는 정보(메타데이터)는 다음과 같습니다:
| 항목 | 설명 |
|---|---|
| 파일 권한 (file permissions) | 읽기/쓰기/실행 권한 (예: rwxr-xr-x) |
| 파일 날짜 (file dates) | 생성 시간, 접근 시간, 수정 시간 |
| 파일 소유자, 그룹, ACL | 파일을 소유한 사용자, 그룹, 접근 제어 목록 |
| 파일 크기 (file size) | 파일의 바이트 단위 크기 |
| 파일 데이터 블록 | 실제 데이터가 저장된 블록 또는 블록을 가리키는 포인터 |
핵심 요약: 파일 시스템은 파일의 실제 데이터뿐만 아니라, 권한·날짜·소유자·크기 등의 메타데이터도 함께 관리합니다.


int fd1, fd2; /* 파일 디스크립터 */
fd1 = open("/home/park/a.txt", O_RDONLY); // a.txt를 읽기 전용으로 열기
fd2 = open("/home/park/b.txt", O_RDONLY); // b.txt를 읽기 전용으로 열기
fd1은 디스크립터 테이블의 fd 3에 할당됨 (0, 1, 2는 stdin/stdout/stderr)fd2는 fd 4에 할당됨핵심 요약: 디스크립터 테이블 → 열린 파일 테이블 → v-node 테이블, 이 3단계 구조로 Unix 커널은 열린 파일들을 관리합니다.

메타데이터(Metadata)는 파일 데이터에 대한 데이터(정보) 입니다. 커널이 유지 관리하며, 사용자는 stat과 fstat 함수로 접근할 수 있습니다.
struct stat {
dev_t st_dev; /* 장치(device) */
ino_t st_ino; /* inode 번호 */
mode_t st_mode; /* 보호 모드 및 파일 타입 */
nlink_t st_nlink; /* 하드 링크 수 */
uid_t st_uid; /* 소유자 사용자 ID */
gid_t st_gid; /* 소유자 그룹 ID */
dev_t st_rdev; /* 장치 타입 (inode가 장치인 경우) */
off_t st_size; /* 총 크기 (바이트) */
unsigned long st_blksize; /* 파일시스템 I/O 블록 크기 */
unsigned long st_blocks; /* 할당된 블록 수 */
time_t st_atime; /* 마지막 접근 시간 */
time_t st_mtime; /* 마지막 수정 시간 */
time_t st_ctime; /* 마지막 변경 시간 */
};

int stat(const char *path, struct stat *buf); // 경로로 파일 정보 조회
int fstat(int filedes, struct stat *buf); // 파일 디스크립터로 파일 정보 조회
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
void Stat(char* path, struct stat* pStat)
{
if (stat(path, pStat) < 0) {
perror("stat"); // 에러 메시지 출력
exit(-1); // 프로그램 종료
}
}
핵심 요약: 메타데이터는 파일의 크기, 소유자, 권한, 시간 정보 등을 담고 있으며,
stat/fstat시스템 콜로 조회할 수 있습니다.

파일 오프셋(File Offset) 은 파일 내에서 현재 읽기/쓰기 위치를 나타내는 값입니다.
파일을 처음 열었을 때:
┌─────────────────────────────────────┐
│ 파일 내용 │
└─────────────────────────────────────┘
↑
파일 오프셋: 0 (파일의 맨 처음)
512바이트를 읽은 후:
┌─────────────────────────────────────┐
│ 파일 내용 │
└─────────────────────────────────────┘
↑
파일 오프셋: 512
char buf[512];
int fd; /* 파일 디스크립터 */
int nbytes; /* 읽은 바이트 수 */
/* 파일 fd를 연 후... */
/* 파일 fd에서 최대 512바이트를 읽기 */
if ((nbytes = read(fd, buf, sizeof(buf))) < 0) {
perror("read");
exit(1);
}
// 읽기 후 파일 오프셋은 0에서 512로 이동
핵심 요약: 파일 오프셋은 파일 내 현재 위치를 추적하며,
read나write호출 시 자동으로 이동합니다.

논리 파일 시스템 (Logical File System)
파일 구성 모듈 (File-Organization Module)
기본 파일 시스템 (Basic File System)
I/O 제어 (I/O Control)


write(fd, pBuffer, length) ← 사용자의 시스템 콜
│
▼
파일 시스템 인터페이스
│
▼
VOP_WRITE(...) ← VFS 인터페이스 (공통)
│
┌────┼────────┐
│ │ │
▼ ▼ ▼
ntfs:: fat:: nfs:: ← 각 파일 시스템별 구현
write write write
│ │ │
▼ ▼ ▼
디스크 디스크 네트워크
핵심 요약: VFS는 다양한 파일 시스템을 하나의 통일된 인터페이스로 추상화하여, 사용자와 상위 계층이 파일 시스템 종류를 신경 쓰지 않도록 합니다.

struct vnode {
u_short v_flag; /* V_ROOT 등의 플래그 */
u_short v_count; /* 참조 카운트 */
struct vfs *vfsmountedhere; /* 마운트 포인트 */
struct vnodeops *v_op; /* vnode 연산 벡터 */
struct vfs *v_vfsp; /* 소속 파일 시스템 */
struct stdata *v_stream; /* 관련 스트림 포인터 */
struct page *v_page; /* 상주 페이지 리스트 */
enum vtype v_type; /* 파일 타입 */
dev_t v_rdev; /* 장치 파일의 장치 ID */
caddr_t v_data; /* 비공개 데이터 구조체 포인터 */
...
};
struct vnodeops {
int (*vop_open)(); /* 파일 열기 */
int (*vop_close)(); /* 파일 닫기 */
int (*vop_read)(); /* 파일 읽기 */
int (*vop_write)(); /* 파일 쓰기 */
int (*vop_ioctl)(); /* I/O 제어 */
int (*vop_getattr)(); /* 속성 가져오기 */
int (*vop_setattr)(); /* 속성 설정하기 */
int (*vop_access)(); /* 접근 권한 확인 */
int (*vop_lookup)(); /* 이름으로 찾기 */
int (*vop_create)(); /* 파일 생성 */
int (*vop_remove)(); /* 파일 삭제 */
int (*vop_link)(); /* 링크 생성 */
int (*vop_rename)(); /* 이름 변경 */
int (*vop_mkdir)(); /* 디렉터리 생성 */
int (*vop_rmdir)(); /* 디렉터리 삭제 */
...
};

// NTFS 파일 시스템의 구현
struct vnodeops ntfs_vnodeops = {
ntfs_open, // NTFS 방식의 open
ntfs_close, // NTFS 방식의 close
...
};
// FAT 파일 시스템의 구현
struct vnodeops fat_vnodeops = {
fat_open, // FAT 방식의 open
fat_close, // FAT 방식의 close
...
};
각 inode/rnode 구조체 내부에 v_op 포인터가 있어, 해당 파일이 속한 파일 시스템의 vnodeops를 가리킵니다. 이를 통해 같은 open() 호출이라도 NTFS 파일은 ntfs_open()을, FAT 파일은 fat_open()을 실행합니다.
핵심 요약: 다형성(polymorphism)의 원리를 활용하여, 각 파일 시스템은 동일한 인터페이스(vnodeops)를 자신만의 방식으로 구현합니다.

디렉터리(Directory)는 모든 파일에 대한 정보를 담고 있는 노드들의 모음입니다. 파일들을 체계적으로 관리하기 위한 특수한 파일입니다.

디렉터리 파일을 열면 커널에게 해당 디렉터리에 접근할 준비가 되었음을 알립니다.
DIR* dirp; /* 디렉터리 포인터 */
if ((dirp = opendir("/home/park")) == NULL) {
perror("opendir");
exit(1);
}
opendir() 함수는 원하는 디렉터리 파일에 대한 디스크립터(포인터) 를 반환합니다.
현재 위치에서 디렉터리 엔트리를 메모리로 읽고, 다음 위치로 이동합니다.
struct dirent {
char d_name[256]; /* 파일 이름 */
unsigned char d_type; /* 파일 타입 */ file , Dir
ino_t d_ino; /* inode 번호 */
off_t d_off; /* 오프셋 */
};
struct dirent* dentry;
DIR* dirp;
if ((dirp = opendir("/home/park")) == NULL) {
perror("opendir");
exit(1);
}
// 디렉터리의 모든 엔트리를 순회
while ((dentry = readdir(dirp)) != NULL) {
printf("이름:%s, 타입:%d\n", dentry->d_name, dentry->d_type);
}
readdir()은 에러가 발생하거나 끝(end-of-file) 에 도달하면 NULL을 반환합니다.
if (closedir(dirp) < 0) {
perror("closedir");
exit(1);
}
커널에게 디렉터리 접근이 끝났음을 알리고, 관련 자원을 해제합니다.

if ((mkdir("/home/park/programs", S_IRUSR | S_IWUSR)) < 0) {
perror("mkdir");
exit(1);
}
mkdir()은 지정된 경로에 새 디렉터리를 생성합니다.

open(), create(), delete(), mkdir(), rmdir() 등의 성능이 저하됩니다.
파일 시스템의 파일 구성 모듈(File-Organization Module)이 논리 블록 번호를 물리 블록 번호로 변환하는 역할을 합니다.

할당 방법은 파일을 위해 디스크 공간을 어떻게 배분할 것인가를 결정하는 방식입니다. 효율적인 디스크 공간 활용과 빠른 접근은 이 할당 방법에 달려 있습니다.
| 전략 | 설명 |
|---|---|
| 연속 할당 (Contiguous) | 파일이 연속된 블록들을 차지 |
| 연결 할당 (Linked) | 각 블록이 다음 블록을 가리키는 연결 리스트 |
| 인덱스 할당 (Indexed) | 별도의 인덱스 블록이 모든 블록 주소를 보관 |







a.txt가 block (2, 5, 12) , b.txt가 block (7, 9, 14)에 있다는 것을

디렉터리 엔트리:
┌──────┬─────┬────────────┐
│ test │ ... │ 시작블록:217 │
└──────┴─────┴──────┬─────┘
│
▼
FAT 테이블:
┌─────┬───────┐
│ 0 │ │
│ ... │ │
│ 217 │ 618 │──→ 블록 217의 다음은 블록 618
│ ... │ │
│ 339 │ │──→ 블록 339이 마지막 (체인의 끝)
│ ... │ │
│ 618 │ 339 │──→ 블록 618의 다음은 블록 339
│ ... │ │
│ n │ -1 │──→ -1은 파일의 끝(EOF)을 의미
└─────┴───────┘
파일 "test"의 블록 체인: 217 → 618 → 339 → 끝

연결 할당에서 직접 파일 접근(direct file access) 이나 랜덤 파일 접근(random file access) 의 문제를 해결하기 위함입니다.

디렉터리:
┌──────┬─────────────┐
│ jeep │인덱스블록: 19 │
└──────┴──────┬──────┘
│
▼
인덱스 블록 (블록 19):
┌──────┐
│ 9 │ → 1번째 데이터 블록
│ 16 │ → 2번째 데이터 블록
│ 1 │ → 3번째 데이터 블록
│ 10 │ → 4번째 데이터 블록
│ 25 │ → 5번째 데이터 블록
│ -1 │ → 미사용
│ -1 │ → 미사용
│ -1 │ → 미사용
└──────┘

인덱스 블록 1 ──→ 인덱스 블록 2 ──→ nil
│ │
▼ ▼
4개 데이터 블록 5개 데이터 블록

1단계 인덱스 블록 ──→ 2단계 인덱스 블록 ──→ 파일 블록

인덱스 블록이 다음으로 구성됩니다:
| 구성 요소 | 설명 |
|---|---|
| 직접 블록 포인터 12개 | 작은 파일을 위해 직접 데이터 블록을 가리킴 |
| 단일 간접 블록 (single indirect) | 한 단계의 인덱스 블록을 거쳐 데이터 접근 |
| 이중 간접 블록 (double indirect) | 두 단계의 인덱스 블록을 거쳐 데이터 접근 |
| 삼중 간접 블록 (triple indirect) | 세 단계의 인덱스 블록을 거쳐 데이터 접근 |
이 결합 기법은 UNIX/Linux의 inode에서 실제로 사용되는 방식입니다!

┌────────────────────┐
│ mode │ ← 파일 타입 및 접근 모드
├────────────────────┤
│ owners (2) │ ← 소유자 및 그룹 접근 식별자
├────────────────────┤
│ timestamps (3~4) │ ← 수정 시간 등
├────────────────────┤
│ size │ ← 파일 크기 (바이트)
├────────────────────┤
│ │──→ 데이터 블록
│ │──→ 데이터 블록
│ direct blocks │──→ 데이터 블록 (12개의 직접 포인터)
│ (12개) │──→ ...
│ │──→ 데이터 블록
├────────────────────┤
│ single indirect │──→ [인덱스 블록] ──→ 데이터 블록들
├────────────────────┤
│ double indirect │──→ [인덱스] ──→ [인덱스] ──→ 데이터 블록들
├────────────────────┤
│ triple indirect │──→ [인덱스] ──→ [인덱스] ──→ [인덱스] ──→ 데이터 블록들
├────────────────────┤
│ block count │ ← 할당된 블록 수
├────────────────────┤
│ reference count │ ← 이 파일을 참조하는 디렉터리 엔트리 수
├────────────────────┤
│ flags (2) │
├────────────────────┤
│ generation number │
├────────────────────┤
│ blocksize │
├────────────────────┤
│ extended attr. size │ ← 확장 속성 정보 크기
├────────────────────┤
│ extended attribute │──→ 데이터
│ blocks │──→ 데이터
└────────────────────┘

핵심 요약: Unix/Linux inode는 결합 기법(combined scheme)을 사용하여 작은 파일은 빠르게(직접 블록), 큰 파일도 효율적으로(간접 블록) 접근할 수 있게 합니다.

디스크:
┌──────────────┬──────────────────────────────────────┐
│ inode 리스트 │ 데이터 영역 │
│ (1)(2)(3)(4)(5)│ │
└──────┬───────┴──────────────────────────────────────┘
│
│ inode 번호와 역할:
│ (1) root inode → 디렉터리 블록 (섹터 10에 위치)
│ (2) bin inode → bin 디렉터리 블록
│ (3) lib inode → lib 디렉터리 블록
│ (4) vi inode → vi 데이터 블록
│ (5) xv inode → xv 데이터 블록
/root/bin/vi를 찾는 과정:
1. root inode(1번) → 디렉터리 블록에서 "bin" 찾기 → inode 2번
2. bin inode(2번) → 디렉터리 블록에서 "vi" 찾기 → inode 4번
3. vi inode(4번) → 실제 vi 데이터 블록 접근

파일 시스템은 삭제된 파일의 공간을 새 파일에 재사용해야 합니다.

빈 공간을 비트맵(bit map) 또는 비트 벡터(bit vector) 로 표현합니다.
규칙:
bit[i] = 0 → 블록 i는 비어 있음 (free)
bit[i] = 1 → 블록 i는 할당됨 (allocated)
블록 2, 3, 4, 5, 8, 9, 10, 12, 13, 17, 25, 26, 27이 할당된 경우:
블록 번호: 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 0 0 0 0
인덱스: 0 1 2 3 4 5 6 7 8 9 ...
부팅 시:
Disk의 bitmap → DRAM으로 복사 (캐시)
파일 생성/삭제 시:
DRAM의 bitmap 수정 (빠름!)
↓ 나중에
Disk의 bitmap에도 반영 (영구 저장)

버퍼 캐시는 곧 다시 사용될 블록들을 메모리에 보관하여 디스크 접근을 줄입니다.

응용 프로그램 (Application)
↕
파일 시스템 (File System)
↕
버퍼 캐시 (Buffer Cache) ← 메모리에 위치
↕
디스크 (Disk)
핵심 요약: 버퍼 캐시는 디스크와 파일 시스템 사이에 위치하여, 자주 접근하는 블록을 메모리에 유지함으로써 느린 디스크 접근을 최소화합니다.
| 주제 | 핵심 내용 |
|---|---|
| 파일 시스템 목적 | 사용자가 섹터 번호 없이 파일 이름으로 데이터 접근 가능 |
| 파일 ID | 커널의 파일 식별자 (inode 번호) |
| 파일 디스크립터 | 열린 파일에 대한 메모리 내 핸들 |
| VFS | 다양한 파일 시스템을 통일된 인터페이스로 추상화 |
| vnode | VFS에서 파일을 표현하는 가상 inode |
| 연속 할당 | 빠르지만 외부 단편화 문제 |
| 연결 할당 | 유연하지만 랜덤 접근이 느림 |
| FAT | 연결 할당의 변형, 테이블을 메모리에 캐싱 |
| 인덱스 할당 | 인덱스 블록으로 직접 접근 가능 |
| inode | 직접+간접 블록의 결합 기법 사용 |
| 비트맵 | 빈 공간을 비트로 관리, 단순하고 효율적 |
| 버퍼 캐시 | 시간적 지역성을 활용해 디스크 접근 최소화 |