본문으로 바로가기

Block IO/Non-Block IO 개념 정리

category Coding 2022. 12. 13. 18:01
반응형

I/O

Input/Output의 약자로써 데이터의 입출력을 의미한다. 네트워크(소켓), File, Pipe, Device와 관련된 IO가 존재한다.

Socket

네트워크 통신은 소켓을 통해 데이터가 입출력된다. 컴퓨터의 각 프로세스에서 서로 데이터를 주고 받을때는 반드시 소켓을 열고 데이터를 주고 받을 수 있다. 예를 들어 백엔드 서버의 경우 여러 클라이언트들과 각각 소켓을 열고 통신하며 요청을 처리한다고 볼 수 있다.

Block I/O

IO작업을 요청한 프로세스/스레드는 요청이 완료될 때까지 블락된다. 아래 그림을 통해 대략적으로 설명해보자면,

- 스레드가 실행되다가 read라는 시스템 콜을 호출한다.

- 만약 호출한 시스템 콜이 Blocking 시스템 콜이라면 호출한 스레드는 블락 상태가 된다. 그리고 시스템 콜에 의해 유저모드에서 커널 모드로 전환된다.

- 커널에서는 read IO를 실행시켜 관련있는 디바이스에 요청을 한다. 

- 디바이스에서 읽을 수 있다는 요청을 주게 되면 커널은 해당 응답을 통해 데이터를 커널 스페이스에서 유저 스페이스로 이동시킨다.

- 블락이 된 스레드에서 해당 데이터를 읽은 후 코드를 실행시킨다.

이를 소켓 관점에서 살펴보면 아래와 같다.

소켓 S에서 소켓 A로 데이터를 보내는 경우

- 소켓A는 read 시스템콜을 호출하여 recv_buffer에 데이터가 들어올 때 까지 블락된다.

- 소켓S는 write 시스템콜을 호출하여 send_buffer에 데이터를 쓰게된다. 가끔 send_buffer가 가득 찰수가 있는데, 이러한 경우 send_buffer가 다시 데이터를 쓸 수 있게끔 공간이 생길 때까지 write 시스템 콜 또한 블락된다. 

Non-Block I/O

프로세스/스레드를 블락시키지 않고 요청에 대한 현재 상태를 즉시 리턴한다. 

- 스레드에서 read 시스템 콜을 논 블락킹 모드로 호출한다.

- 이 때 커널 모드로 컨텍스트 스위칭이 되고 그 때의 커널모드에서는 read I/O 작업을 실행시킨다. 그리고 커널에서 바로 값을 리턴한다. 아직 데이터가 준비되지 않았기 때문에 리눅스 기준으로 -1를 리턴하고 동시에 EAGAIN 또는 EWOULDBLOCK라는 에러코드도 함께 리턴한다.

- 스레드는 논 블락 모드로 호출했기 때문에 블락되지 않고 추가적으로 다른 코드를 실행시킬 수 있게 된다.

- 코드를 실행하던 도중 I/O 디바이스로부터 준비가 됐다라는 응답이 커널로 오게되고 커널은 데이터를 준비한다.

- 스레드는 코드를 실행하다가 다시 한번 논 블락킹 모드로 read 시스템 콜을 호출한다. 이 때 다시 커널로 컨텍스트 스위칭이 발생한다.

- 이 때는 데이터가 준비되어있는 상태이기 때문에 커널이 다시 유저 스페이스에 있는 스레드쪽으로 요청했던 데이터를 전송한다.

- 스레드에서는 해당 데이터를 읽고 이어서 작업을 처리한다.

소켓S에서 소켓A로 데이터를 보내는 경우

- 소켓A에서 read를 통해 recv_buffer에 데이터가 있는지 확인한다. 이전 블락 IO의 경우 이상황에서 데이터가 아직 없다면 read 시스템콜을 호출한 스레드가 블락되지만 위 그림은 논 블락이기 때문에 데이터가 없다면 단순히 데이터가 없다고 알려주고 read 시스템 콜에 대한 호출은 즉시 종료된다.

- 마찬가지로 소켓S에서 write를 할 때에도 send_buffer에 데이터가 꽉 차있는 경우 write 시스템콜을 호출한 스레드가 블락됐었지만 위 그림은 논 블락이기 때문에 write를 호출한 스레드를 블락시키지 않고 적절한 에러코드와 함께 바로 반환된다. 

Non-Block I/O는 어떻게 작업 완료를 확인하는가?

Non-Block I/O 결과 처리 방식

1. 완료됐는지 반복적으로 확인

완료된 시간과 완료를 확인한 시간 사이의 갭으로 인해 처리 속도가 느려질 수 있다. 또한 반복적으로 확인하는 과정을 통해 CPU낭비가 발생한다. 

2. I/O Multiplexing(다중 입출력) 사용

관심있는 IO작업들을 동시에 모니터링하고 그 중 완료된 IO작업들을 한번에 알려준다. 한마디로 한번의 요청을 통해 여러개의 이벤트를 한번에 처리하는 방식을 말한다.

2번에 대해 부가적인 설명을 하자면 대표적으로 select, poll, epoll, kqueue, IOCP등이 있는데 select와 poll은 성능면에서 좋지 않기 때문에 자주 쓰이지 않고 epoll, kqueue, IOCP가 자주 쓰인다. epoll은 리눅스, kqueue는 OSX, IOCP는 윈도우 또는 솔라리스 계열에서 사용한다. 

- 위 그림에서는 총 8개의 소켓이 열려있다. 8개를 모두 epoll을 통해 하나라도 read 이벤트가 발생하면 알려달라고 등록한다.

- 만약 특정 클라이언트가 요청을 보냈다면 epoll을 호출한 스레드는 깨어나면서 동시에 요청을 보낸 소켓에 데이터가 있다는 것을 알려준다.

- 그리고 그 데이터를 바탕으로 데이터를 읽어서 처리한다. 만약 여기서 스레드 풀을 사용한다면 미리 여러개의 스레드를 만들어놓고 데이터를 읽는 요청을 스레드 풀로 넘겨서 각 스레드가 동시에 처리하도록 할 수도 있다. 

(I/O multiplexing의 경우 네트워크 통신에 많이 사용된다)

3. Callback/Signal 사용

- 스레드가 aio_read라는 시스템콜을 호출한다. (해당 시스템 콜은 논 블락킹 모드이다)

- 커널은 read I/O를 시작한다. 이 때 스레드는 독립적으로 코드를 시작하며 동작한다. 

- 디바이스로부터 응답이 오게 되면 데이터가 커널 스페이스에서 유저 스페이스로 이동되는데 이 때 콜백 또는 시그널을 통해 처리된다. 콜백의 종류로는 POSIX AIO, LINUX AIO가 있다.

참고로 Callback/Singal은 널리 사용되지는 않는다고 한다.

Reference

https://www.youtube.com/watch?v=mb-QHxVfmcs