IT 그리고 정보보안/Knowledge base

입출력 다중화 (I/O Multiplexing)

plummmm 2021. 4. 11. 16:07
반응형

이번에 배울 개념은 입출력 다중화, I/O Multiplexing 이다.

 

멀티플렉싱의 사전적 의미를 보면,

"하나의 통신채널을 통해서 둘 이상의 데이터(시그널)을 전송하는데 사용되는 기술" 이다.

다시 말하자면, 효율성을 높히기 위해 물리적인 장치를 최소한으로 사용하여 최대한의 데이터를 전송하는 기술이다.

 

이 개념을 네트워크 소켓에 적용 시키면,

다수의 프로세스를 생성하지 않고도 여러 개의 클라이언트에게 서비스를 제공할 수 있는 기술이 되겠다.

 

프로세스를 생성하는 데에는 상당히 많은 작업이 요구된다. 많은 양의 연산작업, 잡아먹는 메모리 공간 등등..

그리고 프로세스 간의 통신 (IPC)도 솔직히 복잡하다.

 

그래서 I/O 멀티플렉싱이 의미를 가진다. 기존의 I/O 모델들을 살펴보고 멀티플렉싱에 대해 자세히 알아보자.

 

Blocking I/O model

유닉스에서 사용가능한 입출력 모델을 한번 알아보기로 하자. 먼저 Blocking IO model 부터 알아보자.

가장 널리 사용되고 있는 입출력 모델이다. UDP를 이용하여 Blocking model을 그림으로 보자.

 

1. 프로세스는 recvfrom함수를 호출하여, 커널에게 데이터그램을 보내라고 요청한다.

2. 데이터그램이 도착하여 커널이 보낸 데이터그램을 사용자가 copy한다.

 

위 과정이 진행되는 동안 계속 또는 오류가 발생할 때 까지 계속 시스템콜은 끝나지 않는다.

 

recvfrom이 위 과정을 거쳐 성공적으로 return되면 어플리케이션은 데이터그램을 처리한다.

프로세스는 recvfrom을 호출하고 return 될 때 까지 blocking 되어 있다. 

 

무슨말이냐면, 데이터 줄 때까지 계속계속 내놓으라고 요청하는게 아니라 한번 요청하고

진득하게 기다린다는 말이다. 그 기다리는 시간이 프로세스가 block되는 시간이다.

 

​그래서 Blocking I/O 모델이다.  

 

 

Non-Blocking I/O model

이번엔 non-blocking I/O model에 대해 알아보자.

소켓을 non-blocking I/O 로 정할 때,

" 요청한 입출력 작업을 수행할 때 프로세스가 block 되어야 한다면, 대신 오류를 돌려줘 " 이런 모델?이다.

 

그림을 보면, 처음 3개 recvfrom함수의 호출은 돌려줄 데이터가 없어서 커널이 EWOULDBLOCK을 반환한다.

받을 때 까지 계속 요청을 한다. Blocking은 요청하고 데이터가 올때까지 기다렸는데,

이 놈은 "기다릴 바에야 오류를 내놔라 난 블록되기 싫다" 이런 느낌이다.

 

non-blocking I/O는 데이터가 있을 때 까지 시스템콜이 일어난다.

이렇게 계속계속 요청을 보내서 응답을 조사하는 걸보고 폴링(Polling) 이라고 한다. 이 개념은 좀 이따 설명함.

 

non-blocking I/O의 장단점을 보자면,

단점은 계속 요청을 보내니까 cpu에 부하가 일어난다는 것이다. 장점은 block되지 않는다는 것.ㅎㅎ

non-blocking I/O 모델은 주로 하나의 함수만을 수행하는 시스템에서 사용된다.

 

I/O Multiplexing model

자  I/O Multiplexing model 에 대해 알아보자. 우리가 이번에 배울 입출력 다중화 모델이다.

이 모델은 select() 함수와 poll() 함수를 이용한다.

select와 poll을 호출하여 실제 시스템콜 대신에 이 시스템이 block된다.

 

recvfrom 대신에 select부터 호출이 된다. 

 

1. select 호출에서 데이터가 생길 때까지 block 된다.

2. select가 소켓이 읽기 가능하다고 return하면 recvfrom을 호출하여 데이터그램을 복사한다.

 

실제로 시스템콜도 2번이나 해서 오히려 퍼포먼스가 좋다고 말할 순 없다. 

하지만 I/O Multiplexing model이 유용한 이유는 하나이상의 지정 번호가 준비되는 것을 기다릴 수 있다는 것.

 

 

Signal-driven I/O model

이번에 알아볼 모델은 Signal-driven I/O model, 신호로 구동되는 입출력 모델이다.

말 그대로 시그널을 이용하여 입출력을 제어 하는 것이다.

 

 

SIGIO 라는 시그널로 데이터그램이 준비되었는지 커널에 알려달라고 하는 것이다.

 

1. sigaction 시스템 콜로 커널에 SIGIO로 알려달라고 신호처리를 요청한다.

2. 시그널 핸들러로 커널이 SIGIO 신호를 보내면 recvfrom을 호출하여 데이터를 읽어들인다.

3. 다 읽고나면 프로세스에게 읽어라고 알려준다.

 

이 과정 중에 프로세스는 데이터그램이 도착하기 전까지 block되지 않는다는 장점이 있다.

SIGIO 알려달라고 시스템 콜을 할 때도 신호를 알려달라고만 하니까 응답이 즉시 오므로 block이 안된다고 보면된다.

 

 

Asynchronous I/O model

Asynchronous I/O model , 비동기 입출력 모델에 대해 알아보자.

aio_ 또는 lio_ 로 시작되는 함수들을 사용한다. 그림부터 보고 자세히 알아보자.

 

Signal-driven I/O model 과 비슷하다고도 말할 수 있다.

시그널 모델은 입출력 작업이 시작되었을 때 커널에게 알려 달라고 하는 것이고,

Asynchronous I/O model은 전체 작업이 완료 되었을 때 알려 달라고 하는 것이다.

 

1. aio_read 함수를 호출하여 커널에게 지정번호, 버퍼 포인터, 버퍼 크기, 파일 offset, 

   작업 완료를 알리는 방법 등을 전달한다. 시스템콜은 즉시 응답이 온다.

2. 커널이 작업을 완료하면 일정한 신호를 보낸다.

 

Asynchronous I/O model 은 blocking이 전혀 되지 않는다.

뭐 일단 여기서 중요한것은  작업이 완료되고 커널이 알려준다는 점 정도가 되겠다.

 

select()

자 그럼 여러가지 모델을 알아봤으니 이제 I/O 멀티플렉싱 서버를 구현할 때 가장 대표적인 방법인

select 함수를 한번 알아봅시다.

 

아 헤더가 2개 선언된거 부터 뭔가 심상치 않다. 양이 좀 된다.

실제로도 select 함수 자체가 그냥 멀티플렉싱 서버의 전부라고 봐도 무관하다.

먼저 select가 뭐하는 놈인지 부터 좀 알아야 겠다.

 

한 곳에 여러개의 파일 디스크립터를 모아놓고 동시에 이놈들을 관찰할 수 있도록 하는 함수이다.

 

너무 두루뭉술 하다. 자세히 말하자면,

커널에게 관심있는 파일 디스크립터가 어떤 것이고, 얼마나 기다려야 하는지 알려주는 함수이다. 

select 함수가 어떤 식으로 동작하는지 한번 알아보자.

 

select 함수를 호출하기 전에 먼저 예비 동작이 필요하다.

1. 파일 디스크립터 설정

2. 관찰 범위 지정

3. 타임아웃 설정

 

총 3가지가 필요하다. 과정별로 한번 알아보자.

 

1. 파일 디스크립터 설정

select 함수로 여러개의 파일 디스크립터를 관찰할 수 있다고 하였다. (= 소켓 디스크립터)

일단 소켓 디스크립터들을 모아야 하는데, 모을 때도 읽기, 쓰기, 예외처리 별로 따로 모은다.

위에 *readset, *writeset, *exceptset 세가지 인수가 따로 있는 것을 보면 알 수 있다.

 

이들 자료형을 보면 fd_set 이라는 자료형을 쓰는데, 이건 또 뭔가. (진짜 암걸리겠다)

fd_set 자료형은 0과 1로 표현되는 비트단위 배열이다.

 

왼쪽부터 fd0, 1, 2, 3 순으로 간다.

해당 비트가 1로 설정되면 관찰 대상으로 지정된 것이고, 0이면 관찰 대상이 아니란 말이다.

위 표에서는 fd1 과 fd3, fd4 가 관찰 대상으로 지정되어 있는 것이다.

 

근데 fd_set에 관찰할 파일 디스크립터의 번호를 알아내서 직접 등록을 해야하나?

-> 아니요. fd_set을 조작하는 메크로 함수들이 미리 정의되어 있다.

 

FD_ZERO : fd_set의 모든 비트를 0으로 초기화 시킨다.

FD_SET : *fdset 로 전달된 주소의 변수에 fd로 전달된 파일디스크립터 정보를 등록

FD_CLR : *fdset 로 전달된 주소의 변수에 fd로 전달된 파일디스크립터 정보를 삭제

FD_ISSET *fdset 로 전달된 주소의 변수에 fd로 전달된 파일디스크립터 정보가 있으면 양수를 반환

 

예를 들어 보자

fd_set value; // fd_set 형 변수를 선언하고 

 

FD_ZERO(&value); // 변수가 초기화 될 것이다. 아래 그림처럼 된다.

 

FD_SET(2, &value); //  fd 2번의 비트를 1로 셋팅하는 것이다.

 

FD_CLR(2, &value); //  fd 2번의 비트를 0으로 셋팅하는 것이다.

 

2. 관찰 범위 지정

fd_set 변수에 어떻게 값을 할당하는 지도 알았으니, 어떠한 범위로 관찰할지 대한 내용이 있어야한다.

파일 디스크립터의 관찰 범위는 maxfd 인자와 관련 있다.

최대 파일디스크립터 크기 +1 이다.

fd가 늘어날 때마다 값이 1씩 증가하니까. 시작은 0이니 +1

 

3. 타임아웃 설정

말그대로 타임아웃을 설정하는 것이다. select 함수는 fd_set에 설정된 비트에 변화가 있어야 반환한다.

그래서 변화가 생기지 않으면 무기한 block 상태가 되어버린다. 이런 상황을 막기 위해 timeout을 설정한다.

timeval 이라는 구조체이다.

 

timeout 값에 NULL을 주면 무한정 block, 0을 주면 기다리지 않고 (polling),

timeout 값을 주면 그 시간만큼 기다리고 이후에는 반환된다.

 

그럼 이제 위에 2,3,4번 인자에 대한 얘기를 좀 하자.

*readset : 수신된 데이터의 존재 여부에 관심있는 fd정보를 fd_set 변수로 넘긴다.

*writeset : blocking 없는 데이터 전송의 가능 여부에 관심있는 fd정보를 fd_set 변수로 넘긴다.

*exceptset : 예외상황 발생 여부에 관심있는 fd정보를 fd_set 변수로 넘긴다.

 

예를 들어, fd1,4,5 가 readset으로 설정되어 있는데, 이 중 임의의 fd에서 읽을 준비가 되면 반환된다.

체크안하려면 그냥 비어두면 된다.

 

기존의 str_cli 이다.

select 함수를 적용시켜 수정

 

select 함수를 이용하여 표준 입력과 소켓이 읽기 가능할 때까지 기다리도록 바꾼것이다.

FD_SET으로 fd_set형 변수값을 지정해준다. fileno함수는 입력스트림을 읽어 파일디스크립터를 반환하는 함수다.

fp에 있는 파일디스크립터와 소켓 디스크립터를 rset에 등록한다.

 

그 다음, FD_ISSET 메크로 함수를 이용하여 해당 디스크립터에 비트1로 셋팅된 부분이 있으면 if문 안으로 드가고

밑에도 마찬가지로 표준입력에 대한 체크를 하고 fgets를 이용하여 읽고, writen으로 소켓에 쓴다.

 

 

참고 도서 : 열혈강의 TCP/IP 소켓 프로그래밍 (윤성우님 저)

 

반응형

'IT 그리고 정보보안 > Knowledge base' 카테고리의 다른 글

정보보호 정책 및 조직  (0) 2021.04.12
정보보호 관리  (0) 2021.04.12
시그널 핸들링(SIGCHLD)  (0) 2021.04.11
TCP echo Server/Client  (1) 2021.04.11
소켓 프로그래밍  (0) 2021.04.11