locked page, non-paged pool

Posted 2012. 10. 17. 13:50

서버는 충분한 리소스가 없더라도 수천 개의 연결을 처리 할 수 있어야 한다.

서버가 연결을 더 많이 받아들일수록 점점더 리소스의 한계치에 도달하게 된다.

서버가 고려해야 되는 리소스의 종류가 locked page(잠겨진 메모리 페이지) 와 non-page pool(

페이징 되지않은 메모리풀) 두 가지 리소스가 있다.

locked pages의 한계는 non-paged pool 고갈보다 덜 심각하고 처리하기 쉽다.


1) locked pages

모든 overapped i/o 호출시에는 호출에 사용한 데이터 버퍼가 잠겨질(lock) 가능성이 있다.

메모리가 잠겨지면 물리적 메모리로 페이징되지 않는다.

OS는 잠겨질 수(locked)있는 메모리의 한계를 정해 놓고 있다. 이 한계에 도달하면

overapped i/o 호출은 WSAENOBUFS로 실패한다.

서버가 각 연결마다 많은 수의 overapped i/o를 호출하면 연결이 증가될수록 이 한계치에 도달할 확률

이 높아진다. 만일 서버가 동시에 아주 많은 수의 연결을 처리해야 한다면 각 연결별로 0 바이트 크기의

수신용 overapped i/o 를 한번씩만 호출하는 방법을 사용 할 수 있다.

이런 경우 수신용으로 할당된 버퍼가 없으므로 잠겨지는(locked) 메모리가 없다.

또한 이런 방식을 사용할때는 소켓의 수신 버퍼의 크기는 바꾸지 않아야 한다.

왜냐하면 0 바이트의 overapped i/o작업이 완료 되면 논블록킹 수신으로 소켓버퍼에 있는 데이터를

가져와야 하기 때문이다.이때 넌 블록킹 수신이 WSAEWOULDBLOCK를 반납하면 소켓 버퍼에 더이상

읽을 데이터가 없는 것이다.

이러한 설계는 각 연결별 데이터 처리 능력을 희생해서 서버의 연결 용량을 증가시키는 방법이다.

또 하나 고려해야 할 사항은 서버가 구동되고 있는 시스템의 메모리 페이지 크기이다.

시스템은 overapped i/o에 전달된 메모리를 페이지 단위로 lock한다. x86에서는 페이지는 4KB의

배수로 lock 된다. 만일 overapped i/o로 1KB의 버퍼를 전달했다면 시스템은 이 버퍼를 포함한 4KB를

lock한다. 따라서 메모리 낭비를 막기 위해서는 버퍼를 페이지 단위(페이지의 배수)로 사용 해야 한다.

GetSystemInfo함수를 호출하면 시스템에서 사용하는 페이지 크기를 얻어 올 수 있다.

SYSTEM_INFO sysInfo;

GetSystemInfo( &sysInfo );

해보면 sysInfo 안에 dwPageSize로 4096 으로 되어 있다.



2) non-page pool

한계치에 다다르는 경우는 문제를 해결하기가 어렵다.

non-page pool은 물리적 메모리에 상주하면서 페이징 되지 않는영역이다.

드라이버와 같은 커널모드 콤포넌트 들이 non-page pool을 사용하는데 윈속과 tcpip.sys와 같은

프로토콜 드라이버들도 여기에 속한다. 소켓을 생성 할 때마다 소켓의 상태정보를 저장하기 위한

용도로 작은 양의 non-page pool이 소비된다. 여기에 추가로 소켓이 특정 주소에 바인드되면

TCP/IP스택은 로컬 주소 정보를 저장하기 위한 용도로 non-page pool을 할당한다.

결과적으로 연결된 소켓은 2KB, accept나 acceptEX에 의해 리턴된 소켓은 1.5KB의 non-page pool을

소비한다.( accept, acceptEX에 의해 리턴도니 소켓은 리모트 주소만 저장하므로) 또한

각 overapped i/o 별로 IRP( I/O request packet)를 발생시키면서 대략 500바이트의 non-page pool을

소비한다. 연결별로 사용되는 non-page pool은 얼마되지 않는다.하지만 연결이 증가할수록 non-page 

pool의 문제는 심각해진다. 1GB의 물리적 메모리를 갖춘 windows에서 서버가 동작중이라 가정하면

이런경우 256MB가 non-page pool로 사용 될 수 있다. 보통 non-page pool은 windows2000 이상의

버전에서는 물리적 메모리의 4분의1이 한계이다. windows NT4.0에서는 128MB가 한계이다.

서버가 50,000개 이상의 연결을 관리한다고 생각해보면 여기에는 데이터의 송신과 수신용 overapped i/o뿐만 아니라 연결수락(accept)용 overapped i/o작업도 고려해야 한다. 이 경우 accept나 acceptex

에 의하여 생성된 소켓으로 75MB의 non-page pool을 소비한다. 0 바이트의 overapped I/O호출 기법을 사용한다고 가정하면 ITP에 의해 25MB의 non-page pool이 소비된다.



시스템에서 non-page pool이 부족하면 나타나는 증상은 두가지 이다.

운이 좋은 경우 윈속 함수가 WSAENOBUFS에러를 발생시킨다. 그렇지 않을 경우 시스템이

에러로 손상(crash)될 수 있다. 이런 에러는 non-page pool을 회복하는 방법이 없다.

더군다나 사용가능한 non-page pool을 알아보는 방법도 없다. 결국 얼마나 많은 수의 연결과

overlapped i/o작업이 가능한지 경정하는 프로그래밍 기법이나 정해진 한계치가 없다.

non-page pool이 부족하거나 locked page가 초과된 경우 모두 WSAENOBUFS에러를 발생하므로

둘을 구별하는것도 불가능하다. 이런 이유로 개발자는 반드시 서버의 연결 용량과 overlapped i/o

작업의 용량을 테스트해봐야 한다. non-page pool의 부족이 프로그래밍적으로 일어나지 않도록 막았다면 WSAENOBUFS 에러가 locked page의 초과로 인해 발생됬다고 가정하고 더 이상의 overlapped

i/o작업을 금지하거나 몇개의 연결을 닫는것으로 에러를 피할수 있다.




서버는 대용량 처리용 서버와 대량 연결용 서버로 나눌 수 있다.

대용량 처리용 서버

1. 각 연결에 대하여 전송 시간을 최소화 시켜야 한다.

이런 목적으로 서버는 동시에 연결 할 수 있는 수를 제한해 놓은 경우가 많다.

제한 함으로써 많아 졌을때 연결별로 처리 능력이 떨어지는 현상을 방지 할 수 있다.

일정수의 연결을 세트로 해서 처리하는 방법이 있다.

예를 들어 서버는 클라의 연결을 100개씩 FIFO방식으로 처리한다. 첫번째 100개를 처리하면

큐에 쌓여있던 다른 100개의 클라를 처리한다. 송수신 I/O작업은 연결별로 일정수를 유지

할 수 있다. 따라서 클라는 연결이 증가됨에 따라 서버의 I/O작업이 무작정 늘어나서 리소스가

고갈되는 것을 방지 할 수 있다.

악의적인 클라에 의해 서버가 공격받는것을 방지하기 위해서는 서버에서는 각 연결별로

I/O작업이 발생되는 수를 체크해야 한다. 예를 들어 서버가 클라의 데이터를 수신하도록

설계되어 있다면 얼마나 보내졌는지 응답하는 구조로 사용한다. 클라는 서버에게 데이터를

마구 보내면서 응답하지 않는다면 많은 수의 overlapped i/o 송신이 누적 될 것이다.

이런경우 서버는 이 연결을 종료 해야한다.

IOCP와 버퍼의 관계

Posted 2012. 10. 17. 12:16

IOCP는 data를 전송하거나 수신할때 WSAOVERLAPPED 구조체 및 버퍼를 생성하여

넘겨주어야 하는데 클라가 수천개나 되는 경우 시스템 입장에서 보면 메모리를 너무 많이 잡아 먹는다.

NETWORK PROGRAMMING for Microsoft WINDOWS책에는.

대량 연결용 서버의 경우 0 SIZE 버퍼를 사용하여 IO작업을 요청하라. 고 되어있다.

0 SIZE 버퍼를 사용하면 메모리 사용은 현격히 줄어든다.

wsabuf.buf = NULL; << 버퍼를 제공하지 않는다.

wsabuf.len = 0; << receive할 최대 크기를 0로 만든다.

flag = 0;


이러면 해당 소켓으로 데이터가 들어오면 IOCP는 worker thread를 깨워 처리하도록 한다.

그런데 문제는 버퍼가 없으니 받은 데이터가 없다는 거다.

worker thread에선

일반적으로 bytes 값이 0 이면 연결이 끈긴 것으로 처리했지만 0 size버퍼를 사용했을 경우는

무조건 bytes값이 0으로 리턴된다.

이 경우 끈긴 것으로 판단하지 말고 일단 데이터가 들어온 것으로 파단해야 한다.

버퍼를 0로 만들었기 때문에 IOCP는 소켓으로 수신된데이터를 아직 손대지 않은 상태다

이것을 알아 낼 때엔 IOCP를 사용하지 않은 일반적인 socket프로그램으로 읽으면 된다.

nonblocking방식으로 WSAEWOULDBLOCK이 될때까지 recv를 하면 된다.

(block일경우 프로토콜의 정의된 데이터만큼 recv 하면되고..)

이렇게 데이터를 모두 읽어서 처리 했다면 WSARecv를 사용하여 IOCP에게 요청하면 된다.



으흠..

받는쪽은 일단 버퍼 크기를 좀 크게 설정해 준다.

받는쪽이 0이 되버리면 send에서 에러가 나는 이유기도 하고 보냇는데 못받으면 그게

더 큰 문제이기 때문에..

SNDBUF, RCVBUF

Posted 2012. 10. 17. 12:03

1) 소켓 버퍼의 싸이즈 : 디폴트 싸이즈 -> 8192(8KB)

2) 옵션 : SO_SNDBUF, SO_RCVBUF

3) 버퍼 복사 구조 SND과정 : User Buf -> Socket Buf -> TCP Buf

옵션으로 SND 버퍼를 0으로 만들게 되면 User Buf -> TCP Buf 로 Socket Buf를 건너 뛰게 된다.

한 단계의 copy를 줄이게 되는 것. RCV는 반대상황이고 마찬가지고 한단계의 copy를 줄이게 된다.

이렇게 되면 RCV할때 User Buf로 copy속도가 충분히 빠르지 않을 경우 RCV는 TCP의 허용가능한

window size를 줄이고, SND시 TCP로 지속적으로 데이터를 주는데 제약을 가하며

TCP 통신 처리량을 줄이는 병목의 원인이 된다.

이럴 경우 보다 충분한 수의 overapped i/o를 요청을 미리 호출해 줘야 한다.


또는 그냥 비동기 방식으로..


이건 IOCP에 써야겟다



« PREV : 1 : ··· : 56 : 57 : 58 : 59 : 60 : 61 : 62 : ··· : 77 : NEXT »