변수의 기록

(OS) Block I/O vs Non-block I/O (소켓 I/O 기반 설명) 본문

CS지식/운영체제 (Operating System)

(OS) Block I/O vs Non-block I/O (소켓 I/O 기반 설명)

불광동 물주먹 2025. 4. 17. 00:58

Block I/O vs Non-block I/O (소켓 I/O 기반 설명)

1. I/O란?

I/O(Input/Output)는 데이터의 입출력을 의미합니다. 일반적으로 아래와 같은 형태가 있습니다:

  • 네트워크 I/O (소켓): 네트워크 통신은 소켓을 통해 데이터를 송수신합니다.
  • 파일 I/O: 디스크에서 파일을 읽거나 쓰는 작업.
  • 파이프 I/O: 프로세스 간 통신.
  • 디바이스 I/O: 키보드 입력, 화면 출력 등 하드웨어 장치와의 데이터 송수신.

소켓 I/O는 네트워크 프로그램에서 가장 핵심적인 요소이며, 클라이언트-서버 간 통신에 사용됩니다.


2. Block I/O vs Non-block I/O

🔸 Block I/O (Blocking I/O)

  • I/O 요청을 하면 작업이 완료될 때까지 쓰레드는 대기(블로킹) 상태가 됩니다.
  • 예: read() 호출 시, 데이터가 수신되기 전까지 해당 쓰레드는 멈춥니다.
  • 장점: 코드가 단순하고 직관적.
  • 단점: 많은 동시 연결 처리에 비효율적 (쓰레드 낭비).

사진 출처- 유튜브 쉬운코드

 

Block I/O 흐름 (이미지 기반 설명)
스레드(유저 공간)에서 read() 시스템 콜 발생 → 커널 모드로 전환
→ read blocking system call

커널(커널 공간)은 I/O 작업을 실제 디스크나 네트워크 장치에 요청
→ initiate read I/O
→ 이 작업은 하드웨어 컨트롤러(DMA 등)를 통해 진행됨

I/O 작업이 완료될 때까지, 사용자 스레드는 block 상태로 멈춤
→ 쓰레드는 CPU를 사용하지 않고, 대기 큐에 들어가며 ready queue 밖으로 이동

하드웨어에서 I/O 완료 시, 인터럽트 발생
→ 커널이 I/O 결과를 유저 공간으로 복사
→ data moved from kernel space to user space

스레드가 block 상태에서 벗어나 다시 ready → running으로 돌아와 실행 재개
→ read() 함수 호출이 이제야 반환(return)

 

 

SOCKET - BLOCK I/O

1. read(socket A) 호출 시 동작 (Blocking I/O)
상황:
recv_buffer에 도착한 데이터가 아직 없음
이 상태에서 read(socket A)가 호출되면 어떻게 될까?

흐름:
read() 시스템 콜 진입 → 커널로 전환
커널이 확인함:
❌ recv_buffer가 비어 있음
→ 아직 읽을 데이터가 없음

그러면 스레드는 block 상태로 멈춤 (sleep 큐로 감)
CPU는 다른 스레드 실행
상대방(socket S)에서 write()로 데이터를 보냄
TCP 스택을 통해 socket A의 recv_buffer에 데이터 도착
커널은 해당 스레드를 깨움 (wake up)
read() 재개 → recv_buffer로부터 데이터 읽음
사용자에게 데이터 전달 → read() 함수 리턴

🔒 즉, 블로킹 I/O란?
recv_buffer에 데이터가 없으면 스레드가 멈춘다(blocked)



2. write(socket S)가 block 되는 상황
 Blocking I/O 상황
send_buffer가 가득 찼을 때
즉, 아직 보낸 데이터가 **상대방 recv_buffer(socket A)**에 도달하지 못했거나,
상대방이 read()를 안 해서 버퍼가 비워지지 않았을 때
→ 더 이상 보낼 공간이 없기 때문에 write() 호출한 스레드가 block 상태로 대기

 

🔸 Non-block I/O (Non-blocking I/O)

  • I/O 요청을 하면 즉시 반환되고, 결과가 준비되지 않았다면 **"아직 안 됐다"**는 메시지만 반환됩니다.
  • 쓰레드는 멈추지 않으므로 다른 작업을 계속 수행할 수 있습니다.
  • 하지만, 데이터가 준비되었는지를 반복적으로 확인(polling) 해야 함.
    • 이때 생기는 문제:
      • 낭비: 준비되지 않은 데이터를 계속 확인 → CPU 사용 비효율.
      • 타이밍 갭: 데이터가 도착했지만 확인이 늦어질 수 있음.

→ 이 문제를 해결하기 위해   I/O Multiplexing  , Signal 기반 I/O  , AIO (Asynchronous I/O)  기법이 사용됩니다.
                                              위 해결법 설명 아래에 있음.

 

 


SOCKET - NONBLOCK I/O

 

 

✅ Non-blocking I/O (소켓 read() 기준) - 이미지 기반 설명
📍1. 스레드에서 read() 호출 (non-blocking system call)
소켓에 대해 non-blocking 모드가 설정되어 있음
스레드는 커널에 read() 요청을 보냄

📍2. 커널의 응답: 읽을 데이터 없음
아직 읽을 수 있는 데이터가 준비되지 않았기 때문에,
커널은 데이터를 기다리지 않고 즉시 리턴
리턴값은 -1이며, 에러코드는 EAGAIN 또는 EWOULDBLOCK
❗ 즉, **"지금은 읽을 수 있는 게 없지만 기다리지 마라"**는 의미

📍3. 스레드는 멈추지 않고 계속 실행 (block 상태 X)
이전의 Blocking I/O처럼 sleep 상태로 빠지지 않고
그대로 다음 작업이나 반복적인 read() 재시도 가능
→ 이때의 실행 상태: thread run

📍4. 이후 데이터가 도착했을 경우
스레드는 다시 read() 호출
이번엔 커널에 읽을 수 있는 데이터가 존재
→ 데이터를 읽고 유저 공간으로 전달
→ read() 호출이 정상적으로 return

 

 

 

📌 Non-blocking 상황에서의 동작
1. read(socket A)
Non-block 모드에서는 recv_buffer에 데이터가 없으면 block되지 않음

즉시 -1과 함께 EAGAIN 또는 EWOULDBLOCK 리턴

→ 스레드는 대기하지 않고, 다음 로직을 계속 실행 가능
→ 이후에 상대방이 write(socket S)를 하면 데이터가 도착

2. write(socket S)
마찬가지로 Non-block 모드에서는,

send_buffer가 꽉 찼을 경우 block되지 않고 즉시 실패 반환 (EAGAIN)

→ 스레드는 기다리지 않고 다른 일 수행

 

 


3. I/O Multiplexing (다중 I/O 감시)

여러 소켓(파일 디스크립터)을 동시에 감시(모니터링)하여, 준비된 소켓만 처리하게 도와주는 방식입니다.

  • 작동 방식: 등록된 I/O 작업 중 준비된 작업만 감지하여 알려줌
  • 대표적인 기술:
    • select: 오래된 방식. 등록 수 제한 있음.
    • poll: 제한 없지만 비효율적 (모든 파일디스크립터 순회).
    • epoll (Linux): 효율적인 이벤트 기반. 대규모 네트워크 서버에 적합.
    • kqueue (macOS), IOCP (Windows): OS별 고성능 방식.

I/O 준비 상태를 OS가 알려주기 때문에, 반복 확인 없이도 효율적으로 처리 가능.

 

 

 

 

일반 Non-blocking I/O는 유저가 반복적으로 read()를 호출해 데이터 준비 여부를 직접 확인해야 한다.
반면 I/O Multiplexing은 OS가 여러 소켓의 상태를 감시하고, 준비된 소켓이 생기면 유저에게 알려주기 때문에,
반복 확인 없이도 효율적으로 I/O 처리가 가능하다.

 


4. Callback & Signal 방식

  • POSIX AIO, Linux AIO 등은 I/O 완료 시 callback 함수나 signal로 알려줌.
  • 장점: 완전한 비동기.
  • 단점: 구현 복잡도 높고, 실제 사용 사례는 적음 (많이 쓰이지 않음).

 

 

① 스레드가 aio_read() 호출 (non-blocking system call)
비동기 읽기 요청만 전달

스레드는 바로 리턴받고 계속 실행 (thread run 상태)

② 커널은 내부적으로 I/O 작업 진행
디스크 or 네트워크 I/O를 백그라운드에서 수행

③ I/O 완료 시점
커널이 데이터를 준비하고

유저 공간으로 복사 (data moved from kernel to user space)

④ 완료된 사실을 알려주는 방식
callback 함수 호출 또는

signal (시그널) 전송

→ 스레드는 이때까지 block되지 않고 계속 동작 중이었음

5. io_uring (리눅스 신기술)

  • 최근 Linux에서 도입된 고성능 비동기 I/O 인터페이스.
  • 기존 AIO보다 효율적이며, 파일 I/O에 특히 강력.
  • 커널과 사용자 영역 간의 시스템 콜 비용을 줄여 성능 향상.
  • Non-block I/O와 함께 사용하면 고성능 서버 구현 가능.

핵심 요약


방식 쓰레드 블로킹   성능 구현 난이도  비고
Block I/O O 낮음 쉬움 코드 단순
Non-block I/O X 중간 중간 반복 확인 필요
I/O Multiplexing X 높음 중간~높음 epoll 권장
Callback 방식 X 높음 높음 실무에서 드뭄
io_uring X 매우 높음 중간 최신 기술, 파일 I/O에 적합

 

 

 

    AIO vs 일반 Non-blocking vs Multiplexing 비교

방식 감시 방식  스레드 block 여부 완료 통보 방식 반복 호출 필요
Non-blocking 없음 없음 (EAGAIN) ⭕ 직접 계속 확인
Multiplexing OS가 감시 ❌ or 감시용 block OS가 알려줌 (준비됨)
AIO 없음 OS가 완료되면 callback/signal