파일 식별자(File Descriptor, fd) : 커널이 열려 있는 파일을 구분하기 위해 사용하는 고유한 음이 아닌 정수
프로그램이 open()
시스템 콜로 파일을 열면, 커널은 이 파일을 내부적으로 관리하는 테이블에 등록하고 그 테이블의 인덱스 번호, 즉 파일 식별자를 프로그램에게 돌려줌. 프로그램은 이후 파일을 읽거나(read()
) 쓸 때(write()
) 파일 이름 대신 이 간단한 정수 번호를 사용해 커널과 소통
모든 것은 파일이다.
키보드, 모니터, 네트워크 연결(소켓), 다른 프로세스와의 통신 채널(파이프)까지, 시스템의 거의 모든 입출력(I/O) 대상이 파일처럼 취급됨.
커널은 이 모든 다양한 대상에 대한 복잡한 내부 동작을 숨기고, 일관된 파일 식별자(fd) 인터페이스를 프로그래머에게 제공.
그 결과, 프로그래머는 대상이 실제 파일이든, 네트워크 소켓이든 상관없이 read()
, write()
라는 동일한 시스템 콜을 사용해 데이터를 주고 받을 수 있음.
이를 통해, 코드의 재사용성과 확장성을 극적으로 높임.
예를 들어, 웹 서버는 클라이언트와의 네트워크 연결을 나타내는 파일 식별자에 write()
를 함으로써 HTTP 응답을 보내고, 파일에 로그를 남길 때도 다른 파일 식별자에 write()
를 할 뿐임.
PintOS와의 연관성
각 프로세스마다 자신만의 파일 디스크립터 테이블을 가질 수 있도록 자료구조를 설계해야 함. open()
, read()
, write()
, close()
같은 시스템 콜을 구현할 때, 이 테이블을 이용해 파일 디스크립터 번호를 실제 파일 객체로 변환하는 로직을 작성함. 0번은 표준 입력(키보드), 1번은 표준 출력(화면)으로 약속되어 있음.
원자적 연산 : 실행 도중에 다른 어떤 것에 의해 중단되지 않고 완전히 한 덩어리로 실행되는 것이 보장되는 연산
여러 프로세스나 스레드가 공유 변수 count
를 동시에 1씩 증가시키려 할 때, count
를 레지스터로 읽고, 1을 더하고, 다시 메모리에 쓰는 과정이 중간에 중단되면 경쟁 상태(Race Condition)가 발생해 값이 누락될 수 있음.
원자적 연산은 이 읽고-수정하고-쓰는
전 과정을 누구의 방해 없이 한 번에 처리해 데이터의 무결성을 보장
PintOS와의 연관성
여러 프로세스가 동시에 시스템 콜을 호출하며 커널의 공유 자료구조(예 : 파일 시스템 정보)에 접근할 때 데이터가 꼬이는 것을 막기 위해 이 개념이 필요.
인터럽트 비활성화(intr_disable()
)가 원자성을 보장하는 가장 기본적인 방법 중 하나
현대 멀티코어 CPU 환경에서는 인터럽트를 끄는 것만으로는 동시성을 완벽하게 제어 불가능.
(A 코어에서 인터럽트를 꺼도 B 코어에서는 계속해서 동작하기 때문)
CPU는 하드웨어 수준에서 원자성을 보장하는 특별한 명령어들을 제공.
대표적인 예로 Compare-And-Swap(CAS).
CAS는 "메모리의 현재 값이 내가 예상하는 값과 같다면, 새로운 값으로 바꿔라. 그렇지 않으면 아무것도 하지 마라"는 동작을 누구의 방해도 받지 않고 한 번에 처리
이런 CAS와 같은 원자적 명령어를 기반으로 뮤텍스(Mutex), 세마포어(Semaphore)와 같은 복잡한 동기화 프리미티브(Synchronization Primitives)가 만들어짐.
우리가 프로그래밍에서 사용하는 lock()
, unlock()
같은 편리한 동기화 도구들의 가장 근본적인 뿌리는 CPU가 제공하는 하드웨어 수준의 원자적 연산에 있음.
근본적인 차이 : CPU가 한 번에 처리할 수 있는 데이터의 크기와 메모리 주소를 표현하는 방식
64 bit로의 전환이 가져온 변화
더 넓어진 레지스터 공간 : 64비트 아키텍처(x86-64)는 범용 레지스터의 개수를 8개에서 16개로 늘림. 컴파일러가 함수 호출 시 데이터를 메모리(스택) 대신 빠른 레지스터를 통해 더 많이 전달할 수 있게 해 프로그램의 성능을 향상시킴.
거대한 파일 처리 : 메모리 매핑(memory mapping) 을 통해 수십, 수백 기가 바이트에 달하는 거대한 파일을 메인 메모리의 일부인 것처럼 다룰 수 있음. 대용량 데이터 분석, 영상 편집 등 고성능 컴퓨팅 환경에서 필수적인 기능.
프로그램이 *ptr = 10;
같은 코드를 실행하면, 이 가상 주소 ptr
은 CPU의 MMU로 전달됨. MMU는 해당 프로세스의 페이지 테이블을 참조해 이 가상 주소가 어느 물리 주소에 매핑되는지, 그리고 해당 페이지에 쓰기(write) 권한이 있는지 하드웨어적으로 검사
MMU가 다음과 같은 상황을 발견하면, CPU에게 예외(Exception)을 발생시켜 커널 호출
1. 해당 가상 주소가 페이지 테이블에 존재하지 않을 때
2. 주소는 존재하지만, 읽기 전용(Read-only)으로 표시된 페이지에 쓰려고 할 때(예 : 코드 영역)
이때 커널이 이 예외를 받아 처리하는 것이 바로 'Segmentation Fault' 시그널을 보내고 프로그램을 종료시키는 과정
운영체제가 제공하는 메모리 보호 기능이 하드웨어 수준에서 매우 효과적으로 동작하고 있다는 증거