하나의 프로세스로 여러 디바이스를 처리할 수 있다면 좀 더 간단하게 해결할 수 있다는 생각에서 출발한 개념이 I/O Multiplexing(입출력 다중화) 이며,
Linux에서는 select, poll 같은 system call 함수를 지원한다.
I/O 작업은 user space에서 직접 수행할 수 없고,
user process가 kernel에 I/O 작업을 요청하고 응답을 받는 구조이다.
kernel로부터 응답을 어떤 순서로 받는지, (동기/비동기) 혹은
기다렸다가 받는지 (blocking / non-blocking)에 따라 여러 모델로 분류된다.
![](https://velog.velcdn.com/images/kafkaaaa/post/89ae6c8f-3d17-4704-a810-e482075d557e/image.png)
![](https://velog.velcdn.com/images/kafkaaaa/post/782994ad-bef8-420e-b267-e766fbf41296/image.png)
📌 Multiplexing & Demultiplexing
![](https://velog.velcdn.com/images/kafkaaaa/post/22b1ffd5-d9dd-4633-8b56-462add621a55/image.png)
- Multiplexing (다중화)
-
하나의 고속 통신 회선을 다수의 단말기가 공유할 수 있도록 하는 것.
-
다수의 프로세스를 생성하지 않고도 여러 클라이언트에게 서비스를 제공할 수 있는 기술.
-
다수의 신호들을 단일 데이터 링크로 동시에 전송하게 하는 기술.
-
MUX (Multiplexer)
- 다수의 스트림을 하나의 스트림으로 결합하는 기기.
- 여러 입력 中 하나를 선택해 출력으로 내보내는 논리 회로.
-
DEMUX (Demultiplexer)
- 하나의 스트림을 다수의 스트림으로 분리하는 기기.
- 하나의 입력을 여러 개의 출력 中 하나를 선택하여 내보내는 논리 회로.
-
Transport(전송)계층에서 다중화란, Application 계층의 여러 소켓에서 전송되는 데이터를 하나로 모으는 것.
- 전송 계층의 Segment, 네트워크 계층의 Packet
-
Demultiplexing (역다중화)는 반대로, 전송 받은 Segment를 적절한 소켓에 분배해 주는 것.
📌 I/O Multiplexing
-
입출력 다중화
-
하나의 통신 채널을 통해서 둘 이상의 데이터를 전송하기 위한 기술.
-
물리적 장치의 효율성을 높이기 위해,
최소한의 물리적 요소만을 이용하여 최대한의 데이터를 전달하기 위해 사용되는 기술.
-
각 파일을 처리할 때 마다 개별 I/O 통로를 만들어 프로세스(스레드)를 만들면 IPC, 동기화, 스레드, Context Switching 등의 Overhead 단점.
-
따라서 하나의 채널을 통해 여러 데이터를 송/수신 하여 프로세스 개수를 최소한으로 유지하는 I/O Multiplexing.
-
💡 I/O Multiplexing이 필요한 경우
- TCP Client가 다수의 Descriptor를 처리해야 하는 경우
- TCP Client가 다수의 Socket을 동시에 처리해야 하는 경우
- TCP Server가 Listening Socket과 Connected Socket을 모두 처리해야 하는 경우
- Server가 TCP와 UDP를 동시에 지원해야 하는 경우
- Server가 여러 서비스 혹은 프로토콜을 처리해야 하는 경우
📌 I/O Models
![](https://velog.velcdn.com/images/kafkaaaa/post/1cee1e24-731f-47c7-b3bb-981bdcca4932/image.png)
-
#3. I/O Multiplexing
![](https://velog.velcdn.com/images/kafkaaaa/post/68c9d108-bf98-4b72-9387-f56c24c08dd1/image.png)
-
select() 함수를 호출해서 여러 소켓 중 data ready 상태가 된 소켓이 있을 때 까지 대기.
-
select() 결과로 read() 함수를 호출할 수 있는 소켓의 목록이 반환되면 해당 소켓에 대해 read() 함수 호출.
-
여러 소켓을 동시에 확인하여 하나 이상의 소켓이 준비될 때 까지 대기.
-
(개념적으로) 여러 스레드가 Blocking I/O를 실행하는 것으로 구현되어 있음
-
📌 select ( )
![](https://velog.velcdn.com/images/kafkaaaa/post/028b260d-dbdf-4811-930c-462b5eb24240/image.png)
- I/O Multiplexing 서버를 구현할 때 사용하는 가장 대표적인 함수
- 이벤트(입출력, 예외)별로 감시할 파일들을 fd_set 이라는 File State Table (=File Descriptor 배열)에 등록하고 , 이 중에서 이벤트 발생 시 fd_set을 확인하는 방식으로 작동함 (0=변화X, 1=변화O)
- FD 배열 중 입출력이 준비된 파일이 발생하면 FD의 수를 반환하고, 해당 파일에 대해 입출력 수행.
- 즉, 한 곳에 여러 FD를 모아놓고 상태를 계속 관찰하는 함수
#include <sys/select.h>
int select (int nfds, fd_set *readset, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
-
nfds: FD 개수
-
readfds: 파일에 읽을 데이터가 있는지 관심있는 파일 목록(fd_set)
-
writefds: 파일에 데이터를 쓸 수 있는지 여부에 관심있는 파일 목록(fd_set)
-
exceptfds: 파일에 예외가 발생했는지 여부에 관심있는 파일 목록(fd_set)
-
timeout: 무한 대기 상태를 막기 위함. (NULL=무한정 block, 0=polling)
-
return값
- 1이상: 이벤트가 발생한 FD 개수
- 0: timeout
- -1: error
📌 fd_set
![](https://velog.velcdn.com/images/kafkaaaa/post/7cc151ed-e216-4b27-a991-c5f1ab78575c/image.png)
![](https://velog.velcdn.com/images/kafkaaaa/post/459eeab0-3d1b-4666-a7c6-aefd9283b8d3/image.png)
fd_set은 위 그림과 같이 0과 1로된 bit 배열이며 1이면 해당 FD(파일)이 읽을 데이터가 있다는 표시.
📌 <fd_set을 조작하는 매크로 함수들>
![](https://velog.velcdn.com/images/kafkaaaa/post/32333845-d0fc-49a1-8f3a-c02df70d3b8c/image.png)
- void FD_ZERO(fd_set *set) : fd_set의 모든 비트를 0으로 초기화 시킴.
- void FD_SET(int fd, fd_set *set) : fd_set에 fd를 추가 (감시 대상 추가)
- void FD_CLR(int fd, fd_set *set) : fd_set에서 fd 제거 (감시 대상에서 제거)
- int FD_ISSET(int fd, fd_set *set) : fd_set에 있는 fd가 준비되었는지 확인 (비트 1인지 확인)
-
😊 장점
- 단일 프로세스(스레드)에서 여러 파일의 입출력 처리가 가능하여 동시에 수만 개의 커넥션도 처리 가능.
이를 바탕으로 🧷C10k problem을 해결할 수 있음
- POSIX 표준을 따르기 때문에 -> 이식성이 좋음
- client 요청마다 처리하기 위한 별도의 thread를 만들지 않아 context switching 하는 overhead 발생 X
-
😥 단점
- select 함수를 호출해서 전달된 정보는 커널에 등록되지 않은 것이라서 호출할 때 마다 매번 관련 정보를 전달해야 함.
- select 함수의 리턴값이 이벤트가 발생한 FD의 '개수' 이기 때문에 어떤 FD에서 이벤트가 발생했는지 확인하려면 fd_set 테이블 전체를 검사해야 함.
- 검사할 수 있는 FD 개수에 제한이 있음 (최대 1024개)
- select 함수를 호출할 때 마다 데이터를 복사해야 함
(select 함수를 호출한 후 이벤트를 처리할 때 fd_set 테이블 변경이 필요하기 때문에 미리 복사가 필요함)
-
#4. Signal-Driven I/O
- SIGIO 시그널을 이용한 방법
- I/O가 가능해진 시점에 시그널을 발생시키고, I/O를 요청한 Process, Thread가 Catching하는 방식
-
#5. Asynchronous I/O
- POSIX의 aio()를 이용한 방법
- Real TIme Application을 지원하고자 만들어진 방법
- 시스템 간 호환성이 가장 떨어지는 방법.
- 프로그래밍적 장점, 퍼포먼스적 장점이 적음.
-
Synchronous I/O Model
- Blocking, Nonblocking, I/O Multiplexing, Signal-Driven
- I/O 작업이 끝날 때 까지 read() 계열 System Call에 의해 해당 User Process가 Block 됨.
-
Asynchronous I/O Model
- I/O를 요청한 User Process를 Block시키지 않음.
👀 더 알아볼 것
poll
- poll도 select와 마찬가지로 Multiplexing을 구현하는 System Call.
- 작동 원리는 select와 유사하지만 차이점은..
- select방식 처럼 표준 입력,출력,에러를 따로 감시할 필요가 없다.
- select에서는 timeval 구조체를 이용하여 timeout 값을 설정하지만, poll 방식은 다른 구조체 필요 X
- (단점) 일부 UNIX 시스템은 poll 지원 X
epoll (event poll)
- select와 poll의 단점을 해결할 수 있는 Multiplexing 지원
- kernel에 관찰 대상에 대한 정보를 한 번만 전달하고, 대상의 범위나 내용에 변경이 있을 때만 알려줌
- 비슷한 역할을 하는 System Call로 🧷Windows의 IOCP, FreeBSD의 Kqueue가 있음.
- select, poll과 차이점은..
- 상태 변화를 확인하기 위한 전체 FD대상 반복분 필요 X
- 커널에서 상태 정보를 유지하기 때문에 관찰 대상의 정보(fd_set)를 매번 전달할 필요 X
- (단점) Linux의 select 기반 서버를 -> Windows의 select 기반 서버로 변경하는 것은 비교적 간단하지만,
Linux의 epoll 기반 서버를 -> Windows의 IOCP 기반으로 변경하는 것은 어렵다.
Ref
https://ko.wikipedia.org/wiki/%EB%8B%A4%EC%A4%91%ED%99%94_(%ED%86%B5%EC%8B%A0)
https://engineering.linecorp.com/ko/blog/do-not-block-the-event-loop-part1
https://www.joinc.co.kr/w/Site/system_programing/File/select
https://reakwon.tistory.com/117
https://dad-rock.tistory.com/412
https://incredible-larva.tistory.com/entry/IO-Multiplexing-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-1%EB%B6%80
https://plummmm.tistory.com/68
https://www.techtarget.com/searchnetworking/definition/multiplexing
https://dev-nicitis.tistory.com/26