• page-locking & non-paged pool

    2015. 6. 19.

    by. xxx123

     

     

     

    1. page-locking

    WSASend()나 WSARecv()의 경우 상황에 따라 제공된 버퍼가 페이징되지 않도록 lock을 걸게 된다.

    WSASend()의 경우는 소켓 버퍼가 가득찰 경우일 것이고, WSARecv()의 경우 소켓버퍼로 부터 받을 데이터가 없는 경우일 것이다.

     

    OS는 이렇게 잠겨진 메모리의 한계를 정의하고 있으며, 이 한계를 넘어가면 WSAENOBUFS로 Overlapped I/O는 실패하고 만다. 또 이렇게 전달된 유저 버퍼는 페이지 단위로 lock을 걸게된다. 1KB의 버퍼를 WSARecv()의 인자로 전달 했다면 OS는 4KB 만큼의 메모리를 잠근다는 뜻이다.

     

    메모리의 낭비를 줄이기 위해서는 버퍼를 페이지 단위(4KB의 배수)로 잡는것이 효율적이다.

     

     

     

     

     

     

    2. non-paged pool

    page-locking이 일반 메모리를 페이징 안되도록 막는 개념이라면, non-paged pool은 말그대로 애초부터 물리 메모리에 상주하며 페이징 되지 않도록 만들어 놓은 영역이라고 볼 수 있겠다.

    이렇게 페이지 폴트가 없다보니 접근 속도 또한 빠르며, 고레벨 IRQL(Interupt Request Level)접근이 오류 없이 가능해진다.

    그렇다보니 드라이버나 프로그램들이 non-paged pool을 남용하여 사용하게 되고, 제한된 리소스는 손쉽게 고갈되어 문제가 발생하게 된다.

     

    non-paged pool 사용되는 경우는 다음과 같다.

     

    ① 드라이버와 같은 커널모드 컴포넌트에서 사용. 윈속과 tcpip.sys와 같은 프로토콜 드라이버도 이에 속한다.

    ② 소켓을 생성할때마다 소켓의 상태 정보를 저장하기 위한 용도로도 작은 양의  non-paged pool이 소비.

    ③ 소켓이 특정 주소로 바인딩되면 TCP/IP 스택은 로컬 주소 정보를 저장하기 위한 용도로도 non-paged pool을 할당.

    ④ Overlapped I/O연산시 IRP (I/O request packet)를 발생시키면서 약 500바이트의 non-paged pool 할당.

     

    보통 non-paged pool은 windows 2000 이상의 버전에서는 물리메모리의 4분의 1이 한계이다.

    page-locking에 비해 사용되는 곳이 더 많으며 한계치에 다다르는 경우, 문제 해결 또한 더 어렵다.

    non-paged pool이 부족한 상황에서는 두 가지 증상중 하나가 나타나는데.

    운이 좋으면 윈속 함수가 WSAENOBUFS 에러를 발생 시킬것이고, 운이 좋지 않다면 시스템 에러로 손상이 될 수 있다.

     

     

     

     

     

    3. zero byte recv

     

    일단 Overlapped I/O 작업이 일어나면 page-locking + non-paged pool의 소비가 일어난 다는것을 알 수 있다.

    대규모의 유저를 받아 들이는 서버에서 이런 소중한 리소스의 소비를 줄이는 방법이 있는데 그 중하나가 이 "zero byte recv"이다.

     

    아이디어는 매우 간단하다. 

    우리는 IOCP를 사용하는 서버에서 preactor라는 개념으로 WSARecv()를 미리 걸어 놓게 된다.

    이때 버퍼 사이즈를 0으로 잡아서 걸어 놓는것이다.

     

    다음 코드를 보자.

    <Accept()가 떨어지게 되면 WSARecv()를 걸게 되는데 이때 0바이트 사이즈의 버퍼를 내건다.>

     

     

    <GQCS를 통해 통보가 떨어졌을때, 0바이트 상태의 recv라면 제대로된 버퍼 사이즈로 다시건다.>

     

     

    위 코드는 아주 간단하게 짠 IOCP zero byte recv 코드 이다.

    처음 WSARecv()를 걸때는 무조건 0바이트를 걸어 놓는다. 그러면 page-locking이 발생하지 않은 채로 WSARecv()를 거는 효과를 볼 수 있다.

     

    그후 실제로 데이터가 상대로부터 도착하여 GQCS로 통보가 왔을때 현재 걸어둔 WSARecv()가 zero byte state 였다면, 제대로된 버퍼 크기(4096이라던지, 원래 걸어두려 했던 버퍼크기)로 한 번더 WSARecv()를 건다.

     

    이렇게 하기위해 주의해서 처리해야할 부분은 소켓 종료를 의미하는 0 byte recv와 잘 구분하여 처리하는 것과 recv의 상태를 체크하는 변수에 대한 처리이다.

    똑같이 0byte가 GQCS로 떨어지더라도, page-locking을 피하기 위해 일부로 걸은 zero byte recv 상황에서 소켓을 종료해버리는 일이 발생해서는 안되기 때문이다.

    또한 WSARecv()로 실제 데이터를 다 받았다면 recv state 변수를 다시 zero-recv로 바꾸고 0바이트 WSARecv()를 건다.

    위와 같은 recv state 처리를 함으로써 계속해서 필요한 순간에만 page-locking을 걸 수 있게 되는 것이다.

     

     

     

     

    또, Network Programming for microsoft Windows책에서 제안하는 방법이 있는데,

    다음과 같은 동작을 갖는다.

    ① 0바이트 WSARecv()를 걸어둔다.

    ② GQCS를 통해 I/O 통지가 일어난다.

    ③ Non-blocking recv() 함수를 통해, WSAEWOULDBLOCK이 떨어 질때까지 데이터를 수신한다. (데이터를 다 읽어 올때까지)

     

     

    이렇게 Overlapped I/O 호출을 줄이는 방식으로도 추천하고 있다.

     

     

     

     

     

    [참고]

    Network Programming for microsoft Windows : chapter 6. 228~232

    http://bugtruck.blogspot.kr/2009/03/non-paged-memory.html

    http://www.ingmarverheij.com/nonpaged-memory-pool-limit/ // Windbg로 메모리 확인하는 방법. 추가하자

     

     

     

     

     

     

    'Programming > Network' 카테고리의 다른 글

    SO_SNDBUF 0  (0) 2016.12.04
    TCP Header  (0) 2015.06.19
    shutdown()과 closesocket()  (1) 2015.06.15
    IOCP - 1 (I/O CompletionPort)  (0) 2014.12.14
    Overlapped I/O  (0) 2014.09.18

    댓글