파일 식별자, 원자적 연산, 32 bit OS와 64 bit OS, Segmentation Fault

jhj603·2025년 9월 12일
0

파일 식별자(File Descriptor)

  • 파일 식별자(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가 제공하는 하드웨어 수준의 원자적 연산에 있음.


32 bit OS vs 64 bit OS

근본적인 차이 : CPU가 한 번에 처리할 수 있는 데이터의 크기와 메모리 주소를 표현하는 방식

  • 32 bit OS : 메모리 주소를 32개의 비트로 표현.
    따라서 최대로 인식하고 사용할 수 있는 메모리의 크기는 2322^{32}바이트, 즉 4GB로 제한
  • 64 bit OS : 메모리 주소를 64개의 비트로 표현
    이론적으로 2642^{64}바이트라는 무한에 가까운 메모리 공간 사용 가능.

64 bit로의 전환이 가져온 변화

  1. 더 넓어진 레지스터 공간 : 64비트 아키텍처(x86-64)는 범용 레지스터의 개수를 8개에서 16개로 늘림. 컴파일러가 함수 호출 시 데이터를 메모리(스택) 대신 빠른 레지스터를 통해 더 많이 전달할 수 있게 해 프로그램의 성능을 향상시킴.

  2. 거대한 파일 처리 : 메모리 매핑(memory mapping) 을 통해 수십, 수백 기가 바이트에 달하는 거대한 파일을 메인 메모리의 일부인 것처럼 다룰 수 있음. 대용량 데이터 분석, 영상 편집 등 고성능 컴퓨팅 환경에서 필수적인 기능.

  • PintOS와의 연관성
    카이스트 PintOS는 64비트 교육용 OS이므로 프로그램의 모든 포인터(메모리 주소) 변수의 크기는 64비트(8바이트). 유저 스택에 인자를 전달하거나 메모리 주소 유효성 검사할 때, 모든 주소와 데이터의 크기를 64비트 시스템에 맞춰 생각하고 코드를 작성해야 함.

Segmentation Fault

  • 세그멘테이션 오류 : 프로그램이 자신에게 할당되지 않았거나 접근이 허용되지 않은 메모리 영역을 침범하려 할 때 발생하는 치명적인 에러
    운영체제는 각 프로세스에게 독립된 가상 메모리 공간을 할당해 서로를 보호.
    세그멘테이션 오류는 이 보호 경계를 침범했다는 신호
    커널은 시스템 전체를 보호하기 위해 해당 오류를 일으킨 프로그램을 즉시 강제 종료
    흔한 원인으로는 NULL 포인터 접근, 배열의 범위를 벗어난 접근 등이 있음.
    소프트웨어적인 에러가 아닌 CPU의 메모리 관리 장치(MMU)가 감지하는 하드웨어 이벤트

프로그램이 *ptr = 10; 같은 코드를 실행하면, 이 가상 주소 ptr은 CPU의 MMU로 전달됨. MMU는 해당 프로세스의 페이지 테이블을 참조해 이 가상 주소가 어느 물리 주소에 매핑되는지, 그리고 해당 페이지에 쓰기(write) 권한이 있는지 하드웨어적으로 검사
MMU가 다음과 같은 상황을 발견하면, CPU에게 예외(Exception)을 발생시켜 커널 호출
1. 해당 가상 주소가 페이지 테이블에 존재하지 않을 때
2. 주소는 존재하지만, 읽기 전용(Read-only)으로 표시된 페이지에 쓰려고 할 때(예 : 코드 영역)

이때 커널이 이 예외를 받아 처리하는 것이 바로 'Segmentation Fault' 시그널을 보내고 프로그램을 종료시키는 과정
운영체제가 제공하는 메모리 보호 기능이 하드웨어 수준에서 매우 효과적으로 동작하고 있다는 증거

  • PintOS와의 연관성
    PintOS 과제의 핵심 목표 중 하나는 robust(강건한) 커널을 만드는 것.
    사용자가 시스템 콜을 통해 악의적이거나 잘못된 메모리 주소(예 : NULL 포인터, 커널 영역 주소)를 인자로 넘기더라도 커널 전체가 죽어서는 안됨.
    잘못된 주소를 감지하고, 해당 접근이 세그멘테이션 오류를 일으킬 경우, 시스템을 멈추는 대신 해당 프로세스만 깔끔하게 종료시키는 예외 처리 루틴을 구현해야 함.
profile
자라나라 실력 실력

0개의 댓글