I/O 작업
I/O 란 대개 입력(Input)/출력(Output)의 약자로, 다양한 분야에서 사용되고 있지만 여기서는 주로 파일 입출력을 다룰 때의 I/O 를 다루기로 한다. 네트워크에서의 소켓 read/send를 생각하면 I/O 가 어떤 작업인지 이해할 수 있다. 더 쉽게 말한다면 컴퓨터끼리 통신할 때 이뤄지는 데이터(파일이나 이미지 등)의 입력과 출력을 받는 과정을 I/O 라고 부를 수 있다.
I/O 작업은 User 레벨에서 이뤄질 수 없고, (어떻게 보면 당연하다.)
실제 I/O 작업을 수행하는 것은 Kernel 레벨에서만 가능하다.
유저 프로세스 또는 쓰레드의 경우 커널에 요청하고 작업 완료가 되어 커널에 반환될 때까지 기다릴 뿐이다.
1. Blocking I/O 모델
가장 기본적인 I/O 모델이다. linux의 모든 소켓 통신은 기본 Blocking I/O로 동작한다.
I/O 작업이 진행되는 동안 요청한 유저 프로세스는 작업을 중단한 채 반환을 기다리면서 대기하는 방식이다.
유저는 커널에 작업을 요청하고, 데이터가 입력될 때까지 대기한다.
그러다 데이터가 입력되면 유저에게 반환값이 전달되는데 그 이후에 유저는 자신의 작업을 다시 수행할 수 있다. 말 그대로 요청을 보낸 경우, 작업이 중단되는 Blocking 상태가 되는 것이다.
이런 경우에는 애플리케이션에서 다른 작업을 수행하지 못하고 대기하게 되므로 자원이 낭비된다.
흔한 예로는 카카오톡 메세지를 보냈는데 메세지를 전송하고 응답할 때까지 대기하는 거라고 생각하면 이해하기 쉽다. 반환값이 오기 전까진 아무 작업도 못한다면 누가 카카오톡을 쓰겠는가?
2. Non-Blocking I/O 모델
위와 같은 Blocking 방식의 비효율성을 극복하고자 도입된 방식이다. I/O 작업이 진행되는 동안 유저 프로세스의 작업을 중단시키지 않는 방식이다.
말 그대로 Blocking이 되지 않기 때문에 유저가 커널에게 작업을 요청하여도 데이터가 입력이 되었든 안되었든 간에 결과 메세지(EWOULDBLOCK)를 반환한다.
이러한 경우, I/O의 진행시간과 관계가 없기 때문에 애플리케이션은 중지 없이도 I/O 작업을 진행할 수 있다. 하지만 반복적으로 시스템 호출이 발생되기 때문에 불필요한 경우에도 자원이 쓰이므로 낭비라고 볼 수 있겠다.
그러니까 결론적으론 궁금하지 않아도 옆에서 자꾸 알려주는 앵무새 같은 친구가 되는 것이다.
역시나 Non-Blocking의 문제를 해결하기 위해 사람들은 또다른 대안을 내놓았고 I/O 이벤트 통지 모델이 도입되었다. 이벤트란 수신 버퍼나 출력 버퍼에 데이터를 처리하는 동작을 의미한다.
3. I/O 이벤트 통지 모델
개발에서 흔히 말하는 '이벤트'의 의미는 수신 버퍼나 출력 버퍼의 이벤트를 통지한다는 의미다.
수신 버퍼의 이벤트는 말 그대로 입력 버퍼에 데이터가 수신되었다는 것을 알려주는 것이고,
반대로 출력 버퍼의 이벤트는 출력 버퍼가 비었으니 데이터 전송이 가능하다는 상황을 알려주는 것을 의미한다.
이런 이벤트 통지 모델을 이용하게 되면 다중 I/O 모델을 효과적으로 처리할 수 있다.
계속해서 응답이 왔는지 물어보는 것보단 입력 버퍼에서 '메세지가 도착하였습니다.'라고 알림이 와 확인하는 것이 좀 더 효율적이지 않은가?
I/O 이벤트 통지 방식에 따라서 또 동기/비동기 모델로 분류할 수 있다.
유저 프로세스 ----> 커널 [I/O 작업 요청]
커널 ----> 유저 프로세스 [I/O 작업 상황(결과) 반환]
여기서 우리가 주의깊게 살펴봐야 하는 부분이 [I/O 작업 상황(결과) 반환]이다.
I/O 작업 상황(결과) 반환 방식에 따라 동기, 비동기 방식으로 분류된다.
4. Synchronous(동기) 방식의 모델
I/O 작업이 진행되는 동안 유저 프로세스는 결과를 기다렸다가 이벤트(결과)를 직접 처리하는 방식이다. Blocking은 아닌 것이 유저 프로세스가 작업을 수행하지 못하는 것은 아니기 때문이다.
여기서 중요한 것은 기다린다는 것이다. 유저 프로세스는 주체적으로 진행하며, 커널은 유저 프로세스의 요청에 의해 수동적으로 응답한다.
5. Asynchronous(비동기) 방식의 모델
I/O 작업이 진행되는 동안 유저 프로세스는 관심이 없다.
그저 자신의 일을 하다가 이벤트 핸들러에 의해 알림이 오면 처리하는 방식이다.
유저 프로세스는 요청 후에도 I/O 동기화를 신경 쓸 필요가 없고,
이벤트 핸들러(또는 Callback함수)에 의해 운영체제에서 처리 결과를 통지받는 방식이다.
Sync 방식과 반대로 커널이 주체적으로 담당하여 진행하고, 유저 프로세스는 수동적인 입장에서 통지가 들어오면 그때 I/O 작업을 진행한다. (마치 개발하고 있는 개발자에게 Slack 메세지가 오면 그때서야 무슨 이슈인지 확인하는 것과 비슷하다.)
Link
https://junshock5.tistory.com/148
https://ju3un.github.io/network-basic-1/
https://ju3un.github.io/network-basic-2/
https://sjh836.tistory.com/109