본문 바로가기
네트워크, 서버/IOModel

(IOModel)Epoll

by 흥부와놀자 2020. 10. 5.

멀티플렉싱 io 모델의 select는 아무래도 한계가 뚜렸했다. 매 순간 해당 소켓이 이벤트를 받았는지 직접 확인해 줘야 했고 커널로 감시하고자 하는 FD의 모음을 넘겨야 했다. 

 

만약 해당 FD_SET을 처음 한번 등록하고 우리는 FD의 추가,삭제만 해주고 커널에 넘겨주지 않아도 알아서 관리해서 이벤트가 통지된 FD들만 따로 알려주면 어떨까? 이것이 바로 리눅스에서 제공하는 epoll이다. 

 

1. epoll 과정

먼저 epoll_create(epoll 인스턴스의 사이즈)를 통해 커널이 따로 관리하는 파일디스크립터의 저장소(epoll인스턴스)를 만든다. 해당 함수의 반환은 epoll인스턴스의 fd 넘버이다.

 

그 후 관찰대상이 되는 fd를 등록하는 과정으로 epoll_ctl(epoll인스턴스, 추가할지 삭제할지 옵션, 관찰대상 fd, 관찰대상 이벤트)함수를 통해 두번째 옵션인자에 EPOLL_CTL_ADD면 등록을, EPOLL_CTL_DEL이면 삭제를, EPOLL_CTL_MOD면 해당 FD의 이벤트 발생상황을 변경시킨다.

 

위의 epoll_ctl에 해당 fd의 이벤트는 따로 epoll_event라는 구조체가 존재하며 해당 구조체의 events에 호출받기 원하는 이벤트를 설정할 수 있다. EPOLLIN은 해당 소켓에 수신할 데이터가 존재하는 상황을, EPOLLOUT은 해당 소켓의 출력버퍼가 비워져 당장 데이터를 전송할 수 있는 상황을, EPOLLPRI는 OOB데이터가 전송된 상황을, EPOLLRDHUP는 연결이 종료되거나 Half-close된상황을, EPOLLERR은 에러가 발생한 상황을, EPOLLET는 이벤트감지 동작을 엣지트리거로 바꿔주고, EPOLLONESHOT은 한번 이벤트 감지후 더이상 이벤트가 감지되지 않게 한다. 만약 하나의 소켓에 여러 이벤트 상황을 적용하고자 한다면 | or연산자를 통해 여러개 지정해 주면 된다.

 

그리고 해당 구조체의 data안에 있는 fd변수에 해당 소켓의 fd를 등록한다. 소켓이 접속하거나 끊기거나 할때마다 위의 epoll_ctl로 감시소켓 대상을 변경해준 후 epoll_wait(epoll인스턴스, 해당 fd들의 이벤트들이 저장될 event포인터객체, 두번째 객체의 사이즈, timeout)의 함수를 호출해준다. 

 

해당 함수는 커널이 관리하는 fd객체들중 이벤트 발생이 일어난 fd들의 event구조체들을 반환시켜 인자로 넣은 구조체 포인터 변수에 저장해준다. 사용자는 통지된 이벤트들을 확인하고 그에 맞게 처리해 주면 된다. 해당 구조체 포인터변수에는 변화된 이벤트들만 들어가기에 select와 달리 모든 fd 배열을 돌면서 확인해 줄 필요가 없다.

 

2. 레벨 트리거와 엣지 트리거

epoll은 이벤트를 통지받는 방식에서 레벨트리거 방식과 엣지 트리거 방식을 사용한다. 

레벨트리거와 엣지트리거

레벨트리거는 epoll의 디펄트 옵션으로써 수신된 소켓의 입력버퍼에 데이터가 남아있는 동안 계속해서 epoll_wait함수를 반환시킨다. 반면에 엣지트리거는 데이터가 입력버퍼에 처음 수신됬을 때만 반환하고 그 이후엔 반환 하지 않는다.

 

만약 엣지 트리거로 사용시 tcp의 특성상 데이터의 경계가 없음을 고려 할때 원격호스트가 보낸 데이터를 커널이 임의로 나눠서 보낼 경우 로컬 호스트는 처음 받은 데이터는 수신 가능하지만 그 이후로 온 데이터는 수신할 수 없게 된다.

 

read함수는 입력 버퍼가 비어서 읽을 데이터가 없을때 errno로 EAGAIN을 반환하는데, 이를 이용해 EAGAIN을 반환할때까지 받아내야 한다. 하지만 만약 블로킹 소켓으로 받을시 데이터가 남아있지 않으면 계속 블로킹 되므로 엣지트리거에서는 논블로킹 소켓으로 바꾼후 우의 과정을 수행해야 한다. 

 

소켓을 논브로킹으로 바꾸는건 fcntl(소켓fd, F_SETFL, flag|O_NONBLOCK)함수를 호출해 주면 된다. 

 

만약 데이터가 수신됬을때 바로 처리하지 않고 나중에 처리하고 싶다할때 레벨 트리거는 그때 그때 들어오는 수신 데이터를 읽어 내지 않으면 계속 통지된 이벤트로인해 부하가 커져서 감당하지 못하게 된다. 하지만 엣지트리거는 데이터가 입력버퍼에 들어오는 동시에 받지 않고 나중에 한번에 처리가능하다는 장점이 있다. 물론 코드는 많이 복잡해질 것이다.

 

3. 총평

오늘은 epoll에 대해 살펴 보았는데 이러한 발전된 IO모델이 OS별로 존재한다. Windows의 IOCP, BSD의 kqueue, 솔라리스의 /dev/poll 등이 있다. 게임서버에서 가장 많이 사용하고 성능이 좋은건 IOCP라고 한다. 오늘 정리한 epoll은 IOCP를 이해하는데 큰 도움이 될것 같다.

'네트워크, 서버 > IOModel' 카테고리의 다른 글

(IOModel)멀티플렉싱 - select  (0) 2020.10.05