본문 바로가기

게임서버(Game Server)

[게임서버] IOCP Page-Locking

 

 

Page-Locking

IOCP에서 WSASend 혹은 WSARecv를 할 때 기본적으로 Overlapped IO 방식으로 IO를 수행한다.

WSASend 와 WSARecv는 버퍼를 직접 제공한다.(WSABUF) 이 때 이 버퍼는 IO 과정에서 커널이 직접 접근해서 읽고 쓸 수 있어야한다. (IO 작업은 커널이 하기 때문)

 

이 때문에 해당 버퍼가 있는 위치의 물리적 메모리(Physical Memory)에는 Page Lock이 걸린다. 이 부분 메모리는 커널이 계속 읽고 쓸 수 있어야 하므로 해당 영역에서 Page Out(페이지를 메모리에서 백업 장치로 이동시키는 것)이 생기면 큰일나기 때문이다.

 

 

 

Locked Page Limit

비동기 IO 작업을 할 때 Page-Locking이 일어나는데 OS 입장에서 봤을 때 이 Page-locking은 문제가 될 수 있다. 

만약 동시에 엄청난 양의 Send-Recv가 진행돼서 물리적 메모리(Physical Memory) 전체에 락이 걸렸다고 하면 OS는 다른 프로그램을 실행시킬 수가 없다.  

 

그래서 OS는 Locked page Limit라는 것을 두었다. 한 프로세스가 페이지에 락을 걸수 있는 양에 한계를 둔 것이다. 이 제한은 대략 램의 8분의 1 정도 양이라고 하는데, 이 제한에 도달하면 IOCP를 사용한 작업들은

ERROR_INSUFFICIENT_RESOURCES 에러를 내며 실패하게 된다.

 

 

 

 

Non-Paged Pool Limit

Non-Paged pool 이라는 특별한 영역이 있는데 이 영역은 항상 물리적 메모리 위에 존재하며, 절대 Page Out이 되지 않는다. 이 영역은 보통 커널이 드라이버 정보 등 다양한 커널 모드 컴포넌트들에게 필요한 정보를 저장하는데 쓰인다.

 

문제는 소켓을 생성할 때도 이 풀의 공간을 약간씩 소모한다는 것이다.(Bind, Connect, WSASend, WSARecv)등의 함수 호출시에도 조금씩 소모가 되는데, 접속자 수가 굉장히 많아지면 이 공간이 가득차서 문제를 일으킬 수 있다. 

문제는 이 영역의 크기를 정확히 알 수가 없다는 것이다. 결국 에러를 피하기 위해서는 한 번에 연결을 맺을 수 있는 최대 세션 개수를 적절히 정해서 그걸 관리하는 방법 밖에 없다.

 

 

Page-Locking 최소화

Lock이 걸리는 단위는 버퍼 크기가 아닌 Page(4KB) 크기이다. 버퍼를 최대한 페이지 크기에 맞춰서 페이지 공간을 낭비없이 사용하면 더 효율적 일 것이다.

 

Zero Byte Recv

Page-Locking을 해결하기 위한 방법으로 최초에 WSARecv() , WSASend() 를 실행할 때 WSABUF의 크기를 0으로 전달하는 방법이다. 읽는 크기가 0바이트 이기 때문에 Page-locking이 일어나지 않는다. 이 이후 실제 데이터가 도착하여 입출력 완료큐에 온 Recv이벤트가 Zero Byte 라면 정상 버퍼 크기로 한 번더  WSARev()를 건다. (이 때 소켓 종료의 0 Byte Recv 인지 구분하는 처리가 필요하다.) 이 방법은 Page-Locking을 줄일 수 있는 굉장히 효율적인 방식이다.  

IOCP는 Proactor 방식이기 때문에 실제 읽기 작업이 일어나지 않고 있더라도 WSARecv()를 하고 있어야 하기 때문에 많은 메모리가 아무 작업도 안하지만 Page-Locking이 될 수 있다는 걸 보면 Zero Byte Recv는 굉장히 효율적인 방식이 될 수 있다.

 

SO_RCVBUF, SO_SNDBUF

이 두 옵션은 Setsockpot()함수에서 사용가능한 옵션으로 수신 버퍼와 송신 버퍼의 크기를 조정할 수 있다. 이 크기를 0으로 지정하면 커널 레벨에서의 송수신버퍼를 이용하지 않고 애플리케이션에서 제공하는 버퍼로 바로 복사를 해버리기 때문에 복사 횟수가 한 번 줄어 속도가 굉장히 빨라진다. 다만 커널에서 어플리케이션이 제공한 버퍼로의 직접 복사 때문에 Page-Locking이 걸린다.

 

 

 

 

후기

IOCP의 Page-Locking에 대해서 정리하였다. 관련 내용을 가지고 있는 책이 없어서 다른 분들이 정리하거나 발표한 PPT 내용을 중심으로 정리를 진행하였다. 굉장히 중요하지만 생각보다 잘 언급이 없는 그런 내용이였다.

 

요악하자면 Page-Locking이 걸리는 부분은 WSABUF가 있는 물리 메모리 부분이고 IOCP는 Proactor 패턴이기 때문에 실제 IO작업이 이루어지지 않더라도 최초에 WSARecv() 를 걸어야한다. 이 부분에서 Page-Locking이 걸리기 때문에 성능저하가 발생하게 되며 이걸 방지하기 위해 최초의 WSARecv()에서 0바이트를 지정해 Page-Locking이 발생하는 것을 방지하고 실제 IO통지가 오면 그때 WSARecv에 실제 사이즈를 넣어서 패킷을 받는 방식을 사용한다.

 

출처 및 레퍼런스

출처

[1] Iocp advanced (slideshare.net)

[2] 비밀상자 :: page-locking & non-paged pool (tistory.com)

[3] aBiLiTy BLoG :: Page Locking, Zero Byte Recv, SO_SNDBUF, SO_RCVBUF (tistory.com)