버퍼링·스풀링·폴링으로 I/O 제어 방식 이해하기

버퍼링, 스풀링, 폴링의 저장 위치와 처리 방식, 인터럽트·NAPI·버퍼 풀을 중심으로 I/O 제어 기법의 선택 기준을 정리한다.

2026-08-14 · 최초 발행 2026-01-06

I/O 병목을 다루는 서로 다른 위치

CPU와 I/O 장치의 처리 속도와 동작 방식은 다르다. 버퍼링은 메모리에 데이터를 잠시 담아 전송을 이어가고, 스풀링은 디스크를 작업 대기열로 사용해 프린터처럼 느린 장치를 관리한다. 폴링은 CPU가 장치 상태를 주기적으로 읽는 제어 방식이며, 인터럽트와 대비된다.

세 기법은 모두 I/O 흐름에 관여하지만, 저장하는 대상과 제어 주체가 다르다. 따라서 데이터 단위 전송인지, 장치 작업 대기열인지, 장치 준비 상태 확인인지부터 구분해야 한다.

메모리에서 전송 간격을 흡수하는 버퍼링

버퍼링은 데이터를 임시 저장하는 메모리 영역을 이용해 CPU와 I/O 장치 사이의 속도 차이를 완화한다. I/O 성능과 블록 단위 처리 효율을 높이고, CPU 유휴 시간을 줄이며 데이터 무결성을 보장하는 데 쓰인다.

입력 버퍼와 출력 버퍼가 대표적이며, 임시 저장 공간인 Buffer Memory를 중심으로 동작한다. 하나의 버퍼만 쓰면 CPU가 버퍼에서 데이터를 읽는 동안 I/O를 기다리게 된다. 두 버퍼를 교대로 쓰는 이중 버퍼는 한쪽을 읽는 동안 다른 쪽을 채울 수 있어 CPU와 I/O의 동시 동작을 지원한다.

순환 버퍼는 고정 크기 링 구조를 읽기·쓰기 포인터로 관리해 연속 데이터 스트림을 처리한다. 여러 버퍼를 풀로 관리하며 동적으로 할당·해제하는 버퍼 풀은 데이터베이스에서 주로 사용된다.

입력 버퍼링은 장치에서 읽은 데이터를 CPU가 처리할 공간에 담는 방식이다.

// 디스크에서 데이터 읽기
char input_buffer[4096];

// I/O 장치에서 버퍼로 데이터 읽기
read_from_device(input_buffer, 4096);

// CPU가 버퍼에서 데이터 처리
process_data(input_buffer);

출력 버퍼링에서는 데이터를 먼저 버퍼에 쓰고, 버퍼가 찼을 때 장치로 전송한다.

// 출력 데이터를 버퍼에 쓰기
char output_buffer[4096];
write_to_buffer(output_buffer, data);

// 버퍼가 가득 차면 I/O 장치로 전송
if (buffer_full(output_buffer)) {
    flush_to_device(output_buffer);
}

이중 버퍼링은 활성 버퍼를 바꾸며 처리와 채우기를 교차한다.

char buffer_a[BUFFER_SIZE];
char buffer_b[BUFFER_SIZE];
int active_buffer = 0;

while (has_data) {
    if (active_buffer == 0) {
        // Buffer A에서 읽기, Buffer B에 쓰기
        process_data(buffer_a);
        fill_buffer(buffer_b);
        active_buffer = 1;
    } else {
        // Buffer B에서 읽기, Buffer A에 쓰기
        process_data(buffer_b);
        fill_buffer(buffer_a);
        active_buffer = 0;
    }
}

파일 I/O에서는 stdio.hFILE 구조체와 fflush()가 관련되며, 기본 버퍼 크기는 4KB~8KB다. 네트워크 I/O에는 Socket 송수신 버퍼, TCP Window Size, 재전송 버퍼가 쓰인다. 비디오 스트리밍의 프레임 버퍼는 재생 끊김을 막기 위한 버퍼링 표시와 연결된다.

쓰기읽기전송수신전환전환CPUBuffer ABuffer BI/O장치

디스크 대기열로 느린 장치를 관리하는 스풀링

SPOOL은 Simultaneous Peripheral Operations On-Line, 즉 주변 장치의 동시 온라인 작업을 뜻한다. 스풀링은 느린 I/O 장치를 위해 디스크를 버퍼로 사용하고, 작업을 디스크 큐에 저장한 뒤 순차 처리한다. 이 과정에서 CPU와 I/O 장치가 동시에 작업할 수 있다.

버퍼링이 메모리에 작은 데이터를 저장하는 방식이라면, 스풀링은 디스크에 큰 작업 단위를 보관한다. 특히 스풀링은 작업 전체를 저장한다는 점에서 구별된다.

작업은 사용자나 프로그램의 요청으로 시작해 스풀 디렉토리에 저장된다. 스풀러는 이를 큐에 넣고 우선순위나 FIFO로 관리한다. 장치가 준비되면 큐에서 작업을 가져와 I/O를 수행하며, 완료된 뒤에는 스풀 파일을 삭제하고 다음 작업을 처리한다.

작업작업작업순차 처리순차 처리프로세스 1스풀디렉토리프로세스 2프로세스 3작업스풀러데몬프린터플로터

Windows Print Spooler는 spoolsv.exe 서비스와 C:\Windows\System32\spool\PRINTERS\ 디렉토리를 사용하며, .SHD는 헤더, .SPL은 데이터 파일이다. Linux/Unix의 CUPS는 Common Unix Printing System이며 /var/spool/cups/ 디렉토리를 사용하고 cupsd 데몬이 관리한다.

프린터 스풀링은 여러 프로그램의 동시 인쇄, 작업 취소·재개, 우선순위 조정을 지원한다. 배치 처리 시스템에서는 작업을 디스크에 쌓아 순차 실행하며, 이는 메인프레임 시대의 주요 기법이었다. 메일 서버의 송신 큐도 실패 시 재시도를 위해 스풀링을 사용하며 Postfix와 Sendmail에는 큐 디렉토리가 있다.

데이터베이스의 Write-Ahead Log(WAL)는 트랜잭션 로그를 디스크에 스풀링하고 커밋 시 일괄 처리한다.

CPU가 장치 상태를 확인하는 폴링

폴링은 CPU가 I/O 장치의 상태를 주기적으로 확인하는 Busy-Waiting 방식이다. 인터럽트 방식의 대안으로 쓰이며, CPU가 상태 레지스터를 읽고 장치의 준비 여부를 판단한다. 준비되지 않았다면 대기 후 다시 확인하고, 준비되면 데이터를 전송한다.

하드웨어 구성이 단순하고 응답 시간을 예측할 수 있으며, 고빈도 I/O에서는 인터럽트 오버헤드를 피할 수 있다는 이유로 사용된다.

장치가 준비될 때까지 상태를 계속 확인하는 Busy-Wait 폴링은 다음과 같다.

// 장치가 준비될 때까지 반복 확인
while (!(device_status & READY_BIT)) {
    // 계속 확인 (CPU 낭비)
}
read_data_from_device();

Sleep Polling은 일정 시간 기다린 뒤 상태를 다시 읽는다.

// 일정 시간 대기 후 확인
while (!(device_status & READY_BIT)) {
    sleep(10);  // 10ms 대기
}
read_data_from_device();

Adaptive Polling은 대기 시간을 지수적으로 늘리되 최대값을 넘지 않도록 조정한다.

int wait_time = 1;

while (!(device_status & READY_BIT)) {
    sleep(wait_time);
    wait_time *= 2;  // 지수적 증가
    if (wait_time > MAX_WAIT) {
        wait_time = MAX_WAIT;
    }
}

폴링은 구현이 단순하고 하드웨어 오버헤드가 없으며 실시간 시스템에서 예측 가능하다. 고빈도 I/O에 효율적일 수 있지만, Busy-Waiting에서는 CPU 시간을 낭비하고 다른 프로세스 실행을 방해하며 전력 소비와 확장성 문제를 낳을 수 있다.

항목 폴링 인터럽트
CPU 사용 주기적 확인으로 낭비 이벤트 발생 시만 사용
응답 시간 폴링 주기에 의존 즉시 응답
구현 복잡도 간단 복잡 (ISR 필요)
하드웨어 요구사항 낮음 높음 (인터럽트 라인)
적합한 경우 고빈도, 예측 가능 저빈도, 비동기
전력 효율 낮음 높음
I/O 제어방식폴링인터럽트CPU가주기적 확인Busy-Waiting간단한구현장치가신호 발생CPU즉시 응답복잡한구현

저장 위치와 처리 단위로 보는 차이

기법 저장 위치 단위 관리 주체
버퍼링 메모리 바이트/블록 커널/라이브러리
스풀링 디스크 작업 스풀러 데몬
폴링 N/A N/A CPU/드라이버

버퍼링은 속도 차이를 완화하며 블록 단위 처리를 위해 메모리를 사용한다. 스풀링은 느린 장치를 대상으로 작업을 큐잉하고 디스크를 활용한다. 폴링은 I/O 상태를 CPU 주도로 확인하는 동기적 제어 방식이다.

버퍼링의 지연 시간은 마이크로초밀리초이고 처리량은 매우 높으며 메모리 오버헤드는 중간이다. 스풀링의 지연 시간은 초분 단위이고 처리량은 디스크 속도에 의존하며 디스크 오버헤드는 높다. 폴링의 지연 시간은 폴링 주기에 의존하고 처리량은 I/O 빈도에 따라 달라지며, Busy-Wait 시 CPU 오버헤드가 높다.

운영체제와 네트워크에서의 조합

Linux I/O 버퍼링에서 Page Cache는 파일 I/O를 버퍼링하며 기본 크기는 4KB 페이지다. write()는 버퍼에 쓰고 fsync()로 플러시한다. Windows Print Spooler는 백그라운드 서비스로 여러 프린터를 동시에 관리하고 작업 우선순위를 지원한다.

Linux의 NAPI(New API)는 네트워크 패킷 처리에서 저부하일 때 인터럽트 방식을 쓰고 고부하일 때 폴링 방식으로 전환하는 하이브리드 접근이다.

Socket 버퍼 크기는 다음처럼 설정할 수 있다.

// Socket 버퍼 크기 설정
int buffer_size = 65536;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
           &buffer_size, sizeof(buffer_size));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
           &buffer_size, sizeof(buffer_size));

NAPI 폴링은 인터럽트가 발생하면 폴링 모드로 전환해 패킷을 일괄 처리하고, 처리가 끝나면 인터럽트 모드로 복귀한다.

데이터베이스에서는 InnoDB Buffer Pool이 데이터 페이지를 캐싱하고 LRU 알고리즘 및 innodb_buffer_pool_size 설정과 연결된다. Write-Ahead Log는 트랜잭션 로그를 먼저 디스크에 기록한 뒤 체크포인트에서 일괄 적용한다.

워크로드에 맞춘 조정

버퍼 크기는 워크로드 특성, 메모리와 성능의 트레이드오프, 벤치마크 테스트를 기준으로 조정한다. 처리량, 지연 시간, 메모리 사용량이 튜닝 지표다.

대용량 파일 복사에는 큰 버퍼를 사용할 수 있다.

// 대용량 파일 복사 시 큰 버퍼 사용
#define BUFFER_SIZE (1024 * 1024)  // 1MB

char buffer[BUFFER_SIZE];
while ((bytes_read = read(fd_in, buffer, BUFFER_SIZE)) > 0) {
    write(fd_out, buffer, bytes_read);
}

폴링 주기는 부하에 따라 바꿀 수 있다. 저부하에서는 긴 주기로 전력 절약을 고려하고, 고부하에서는 짧은 주기로 응답성을 확보한다. 임계값 기반 조정은 다음과 같다.

#define LOW_THRESHOLD 10
#define HIGH_THRESHOLD 1000

int poll_interval = 100;  // 초기값 100ms

if (request_rate < LOW_THRESHOLD) {
    poll_interval = 1000;  // 1초
} else if (request_rate > HIGH_THRESHOLD) {
    poll_interval = 1;  // 1ms
}

NAPI 방식에서는 저부하에서 인터럽트를 기다리고, 첫 패킷이 도착하면 인터럽트가 발생한다. 이후 폴링 모드에서 일괄 처리하며, 큐가 비면 다시 인터럽트 모드로 복귀한다. Adaptive Polling은 예측 가능한 I/O 패턴에 폴링을, 불규칙한 패턴에 인터럽트를 사용한다.

짧은 시간 동안 폴링한 뒤 타임아웃 시 인터럽트를 기다리는 방식도 가능하다.

// 짧은 시간 폴링, 타임아웃 시 인터럽트
for (int i = 0; i < MAX_POLL_ATTEMPTS; i++) {
    if (device_ready()) {
        return read_data();
    }
    udelay(1);  // 1μs 대기
}

// 폴링 실패 시 인터럽트 대기
wait_for_interrupt();
운영체제I/O버퍼링스풀링폴링NAPI