📁 File0. 파일의 기초01 — What is a File

01 — What is a File

한 줄 답: 파일은 이름이 붙은 바이트 시퀀스이며, UNIX는 이 단순한 추상화 위에 모든 입출력 인터페이스(파이프·소켓·디바이스)를 통일했다.


Why — 왜 이런 추상화가 필요했나

1960년대까지의 컴퓨터는 장치마다 다른 명령어로 데이터를 읽고 썼다.

  • 자기테이프: MOUNT TAPE; READ BLOCK; UNMOUNT
  • 천공카드: READ CARD; ADVANCE
  • 라인프린터: PRINT LINE; SKIP PAGE
  • 디스크: 트랙·섹터 주소 직접 지정

문제는 셋이다.

문제결과
프로그램이 장치 종류를 알아야 한다디스크용 코드를 테이프에 못 쓴다
프로그램이 장치 위치를 알아야 한다같은 장치에서도 트랙이 바뀌면 다시 짜야 한다
프로그램끼리 데이터를 못 주고받는다장치 간 형식 변환 코드가 필수

1969년, Ken Thompson과 Dennis Ritchie가 UNIX를 만들면서 하나의 추상화로 이 모두를 통합했다 — 파일이다.

“On UNIX, everything is a file.” — 모든 입출력은 바이트 스트림에 대한 read/write로 환원된다.


How — 어떻게 동작하나

1) 4가지 시스템 콜이면 충분하다 (POSIX)

int  fd = open("/path/to/file", O_RDONLY);  // 1. 열기 → 정수 핸들(fd)
ssize_t n = read(fd, buffer, 4096);          // 2. 읽기
ssize_t m = write(fd, buffer, 4096);         // 3. 쓰기
       close(fd);                             // 4. 닫기

이 네 함수는 다음 모두에 동일하게 동작한다:

대상경로 예정체
일반 파일/etc/passwd디스크 위 바이트
디렉토리/home파일들의 색인(디렉토리도 파일이다)
디바이스/dev/null, /dev/sda커널 디바이스 드라이버
파이프mkfifo /tmp/p메모리 버퍼
소켓(socket()로 생성)네트워크 endpoint
/proc, /sys/proc/cpuinfo커널 상태를 파일처럼 노출

2) 파일 디스크립터(fd)는 정수다

open()은 정수를 반환한다. 이 정수는 프로세스의 파일 디스크립터 테이블에 대한 인덱스다.

프로세스 PID 1234
┌─────────────────────────────┐
│ fd 0 → stdin  (terminal)    │
│ fd 1 → stdout (terminal)    │
│ fd 2 → stderr (terminal)    │
│ fd 3 → /etc/passwd          │  ← 방금 open()한 파일
│ fd 4 → socket(tcp:443)      │
└─────────────────────────────┘

stdin/stdout/stderr파일과 똑같이 다뤄진다는 것이 UNIX의 천재성이다. 이 덕에 program > file.txt처럼 리다이렉트가 가능하다 — fd 1을 다른 파일로 바꿔치기만 하면 끝.

3) 파일은 이름이 아니다

이것이 처음 들으면 혼란스러운 사실:

파일의 본체는 inode(메타데이터+데이터 블록 포인터)이고, 이름은 디렉토리 안의 entry일 뿐이다.

$ ls -li /tmp/a.txt
1234567 -rw-r--r-- 2 user staff 13 May 10 a.txt
  • 1234567 = inode 번호 (실제 파일)
  • 2 = 하드링크 수 — 같은 inode를 가리키는 이름이 2개 있다는 뜻
$ ln /tmp/a.txt /tmp/b.txt   # 하드링크: 같은 inode를 가리키는 새 이름
$ ls -li /tmp/{a,b}.txt
1234567 ... a.txt
1234567 ... b.txt 같은 inode!

a.txt를 지워도 b.txt는 살아 있다. 진짜 파일은 마지막 이름까지 사라질 때 비로소 지워진다.


What — 구체 사양

POSIX 파일 모델 (요약)

개념설명
파일 = 바이트 시퀀스0바이트 ~ 거대한 크기까지, 내부 구조에 대한 가정 없음
무지향성OS는 파일이 텍스트인지 이미지인지 모른다. 그 해석은 응용 프로그램의 책임
랜덤 액세스lseek(fd, offset, SEEK_SET)로 임의 위치로 점프 가능
메타데이터 분리권한·소유자·시간은 inode에, 이름은 디렉토리에
파일 = file descriptor한 번 열면 정수 하나로 추상화됨

핵심 시스템 콜 표 (Linux/macOS)

시스템 콜하는 일비고
open(path, flags)fd 반환O_RDONLY/O_WRONLY/O_CREAT/O_APPEND
read(fd, buf, n)n바이트까지 읽기EOF 시 0 반환
write(fd, buf, n)n바이트 쓰기단축 쓰기(short write) 가능
lseek(fd, off, w)파일 오프셋 이동SEEK_SET/CUR/END
stat(path, &st)메타데이터 조회inode, 크기, 시간, 권한
fstat(fd, &st)fd로 메타데이터경로 없이 조회
unlink(path)이름 제거마지막 링크면 데이터도 회수
dup(fd) / dup2(old, new)fd 복제리다이렉트 구현에 사용
mmap(fd, ...)파일을 메모리에 매핑큰 파일을 효율적으로 다룰 때

”everything is a file”의 진짜 모습

# 디바이스도 파일
$ cat /dev/urandom | head -c 16 | xxd
00000000: 7a 1c 9b ... (랜덤 바이트)
 
# 커널 상태도 파일
$ cat /proc/cpuinfo
processor : 0
model name : Apple M2 ...
 
# 소켓도 fd → epoll/select가 파일과 소켓을 함께 다룬다
$ ls -l /proc/$$/fd/
0 -> /dev/pts/0
1 -> /dev/pts/0
3 -> socket:[12345] 소켓도 fd로 보인다

What-if — 잘못 이해하면

1) “파일은 텍스트 또는 바이너리다”라고 믿으면

실은 모두 바이너리다. 다음 챕터(02-encoding-binary.md)에서 자세히. “텍스트 모드”는 윈도우의 \r\n\n 자동 변환 같은 응용 레이어 약속일 뿐이다.

2) Windows에서 UNIX 가정을 그대로 쓰면

UNIX 가정Windows 현실
열린 파일을 삭제할 수 있다 (unlink)기본적으로 안 된다 — 핸들이 닫혀야 삭제됨
경로 구분자 /\ (다만 API 대부분 / 허용)
파일 이름 case-sensitive기본 case-insensitive (NTFS는 옵션)
권한 = rwx + user/group/otherACL (훨씬 복잡)
fork() 후 fd 상속별도 명시 필요

3) “파일 = 이름”이라고 믿으면

하드링크/심볼릭링크/inode를 이해 못 한다. 대표 사고: 백업 도중 파일을 옮겼더니 백업 도구가 같은 파일을 두 번 백업하거나 한 번도 안 함.

4) mmap을 남용하면

큰 파일을 메모리에 매핑하면 빠르지만, 페이지 폴트가 디스크 I/O를 일으켜 응답시간이 튄다. 실시간 시스템에서는 명시적 read가 더 안전.

5) fd 누수

open()만 하고 close()를 잊으면 fd 테이블이 가득 찬다 (EMFILE: too many open files). Node.js, Java 등 GC 언어에서 자주 발생 — using/finally/with 구문으로 강제 해제 권장.


Insight — 흥미로운 이야기

”에디(Eddie)와 Plan 9 — UNIX가 가지 못한 길”

UNIX의 everything is a file은 강력했지만 완벽하진 않았다. 1980년대 Bell Labs는 후속작 Plan 9에서 이 철학을 극단까지 밀어붙였다 — GUI 윈도우, 마우스, 네트워크 연결까지 전부 파일로 노출했다.

/dev/mouse     ← 마우스 좌표를 read하면 (x,y) 반환
/net/tcp/clone ← write하면 새 TCP 연결 생성
/proc/1/mem    ← 다른 프로세스 메모리를 파일처럼 read/write

Plan 9는 상업적으로 실패했지만, 그 사상은 살아남았다 —

  • Linux의 /proc, /sys, /dev
  • 컨테이너의 /proc/self/ns/* (namespace fd)
  • 9P 프로토콜 (WSL2에서 Windows ↔ Linux 파일공유에 쓰임)

“왜 fd는 정수인가”

C언어 시대, 가장 빠른 인덱싱 방법이었다. 50년 뒤 Rust도, Go도, 커널 인터페이스에서는 결국 정수 fd를 쓴다. 추상화는 한 번 굳으면 100년을 간다.

”0번 fd가 stdin인 이유”

UNIX 초기 PDP-11 시절, 하드코딩된 약속이었다. 0=입력, 1=출력, 2=에러 — 이 세 정수는 너무 깊이 박혀서 어떤 OS도 못 바꾼다. 컨테이너 런타임도, 파이프도, 셸도 이 약속 위에서 동작한다.


요약 + 다이어그램

파일은 바이트 시퀀스고, fd는 그 시퀀스에 접근하는 정수 핸들이다. UNIX는 이 둘만으로 디바이스·파이프·소켓·커널상태까지 모두 통합했다. “파일 = 이름”이라는 직관은 틀렸다 — 본체는 inode이고, 이름은 그것을 가리키는 디렉토리 항목이다.

다음 문서: 02-encoding-binary.md — 파일이 바이트라면, 그 바이트를 글자로 어떻게 해석하는가?