InfiniBand 의 QP 상태 전이의 이해

작성일:2014.04.12
수정일:2014.05.11

이 페이지에서는 InfiniBand Verbs 프로그래밍에 필수적인 Queue Pair(QP) 상태와, 그 전이에 대해 설명한다.


1. 소개

TCP 학습을 진행해 가면, 반드시 TCP 커넥션 상태 다이어그램에 이른다. 예를들어, 아래와 같은 그래프가 TCP 커넥션 상태 다이어그램이다. TCP 소켓은 내부에 그래프와 같은 상태를 가지고 있고, 이것을 바꾸면서 통신을 확립한다.

Fig 1: A Simplified TCP State Diagram From Wikipedia

반면에, 단지 TCP를 소켓에서 사용하는것 뿐이라면, TCP 상태에 관한 자세한 이해는 필요하지 않다. socket(), bind(), listen(), accept(), close() 와 같은 시스템콜만 이해하고 있다면, TCP를 사용하는데 충분하다. UDP를 사용하는 경우, 상태 다이어그램도 시스템콜도 더 간단하게 된다.

그렇다면 InfiniBand는 어떨까?

RDMA Communication Manager(CM) 는 IB Verb QP의 연결에 관련된 부분을 감싸서, TCP 커넥션 상태 다이어그램에 대한 소켓 시스템 호출 레벨까지 InfiniBand를 사용하기 쉽게 해준다. 이것을 사용하는 것이 사실 편하다.

2. QP 상태의 종류

QP 에는 Reset, Init, Ready To Receive(RTR), Ready To Send(RTS), Send Queue Drain(SQD), Send Queue Error(SQE), Error 의 7 가지 QP 상태가 존재한다.

QP 상태 전이는 ibv_modify_qp() 으로 실행한다.

QP 상태를 알기 위해서는IBV_QP_STATE 를 지정하고 ibv_query_qp() 를 호출하면qp_state 로 반환된다.

2.1 Reset

Reset 상태는 ibv_create_qp() 로 생성된 직후의 상태로, 서비스 타입(RC/RD/UC/UD 등), QP 번호, CQ 와의 연결, SQ, RQ 의 최대수 등이 정해져 있는 이외에, 다른 것은 아무 것도 정해지지 않은 상태이다.

규격상, 사용자가 ibv_modify_qp() 를 사용해 임의의 상태에서 이 상태로 전이할 수 있다. 하지만, 일부 장비에서 Error 상태 이외에서 ibv_modfiy_qp() 로 전이하려고 해도 실패하는 경우가 있다. 이유는 나중에 자세히 설명한다.

2.2 Init

Init 상태는 Reset 상태의 다음 상태이다. ibv_modify_qp() 로 사용자가 전환하면 이 상태로 전이한다.

이 상태로 전이할 때에 포트와의 연결이 행해지는 점이 가장 중요하다. 이기서 설정한 포트가 QP의 입력처이자 출력처가 된다.

또한, Init 상태에서 Receive Queue(RQ) 에 ibv_post_recv() 로 Receive Work Request(WR) 를 등록할 수 있게 된다. 이 단계에서는 아직 수신은 일어나지 않지만, Init 상태의 단계에서 Receive WR을 등록하는 것으로, 다음의 RTR 상태로 전이한 순간부터 빠짐없이 패킷을 수신할 수 있도록 하려는 것이다.

2.3 Ready To Receive (RTR)

RTR 상태는 Init 상태의 다음 상태이다. 수신만 하는 경우, 이 상태에서 머무는 것도 가능하다. ibv_modify_qp() 로 사용자가 전환하는 것으로 이 상태로 전이한다.

이 상태에서 패킷 수신이 시작된다. 하지만, 송신은 아직이다.

UD 서비스 이외의 서비스 타입에서는, 이 상태로 전이 후 최초로 패킷을 수신한 시점에서 Communication Established(IBV_EVENT_COMM_EST) 비동기 이벤트가 발생한다. 일반적으로 이 비동기 이벤트는 무시해도 좋지만, 잘 사용하면 4. 에서 설명하는 바와같이 커뮤니케이션 확립에 도움이 되도록 할 수 있다. InfiniBand Architecture Specification Volume 1 Release 1.2.1에는 UD 서비스의 경우, 이 비동기 이벤트가 발생되지 않는 것을 권장하고 있으며, 실제 장비는 이것을 지키고 있는 것으로 생각된다.

2.4 Ready To Send (RTS)

RTS 상태는 RTR상태의 다음 상태이다. ibv_modify_qp() 로 사용자가 바꾸면 이 상태로 전이한다. 이것이 일반적으로 통신을 수행하는 정상적인 상태이다.

이 상태에서 패킷 송신이 시작된다. 수신도 가능하다.

2.5 Send Queue Drain (SQD)

SQD 상태는, 이미 전송 중인 Send WR에 대해서는 완료를 기다리지만, 아직 전송을 시작하지 않은 새로운 Send WR에 대해서는 보류하고, Send WR의 처리가 멈추는 시점을 기다리는 특수한 상태이다. RTS 상태에서 ibv_modify_qp() 로 사용자가 전환하는 것으로 상태가 전이된다.

SQD 상태는 송신 중의 Send WR이 모두 완료한 시점을 비동기 이벤트로 통지한다. 비동기 이벤트는 Send Queue Drained(IBV_EVENT_SQ_DRAINED) 이라 부르고, QP 에 연결되어 있다. 이 비동기 이벤트가 도착한 후, SQD 상태는 RTS 상태로 전이할 수 있다. RTS 상태로 전이하면, 보류되어 있는 새로운 Send WR의 송신이 재개된다.

또 한가지, 비동기 이벤트가 도착한 후, SQD 상태는 SQD상태로 전이할 수 있다. 언뜻 쓸 데 없어 보이지만, Init 상태일 때에 정한 포트 등 중요한 것들을 포함한 파라미터를 변경할 수 있다.

SQD 상태는 수신에 대해서는 RTR 상태나 RTS 상태와 마찬가지로 가능하다.

또한 ibv_post_send() 로 새로운 Send WR을 등록할 수 있다. 하지만, SQD 상태에 있는 동안은 송신이 시작되지 않는다.

2.6 Send Queeu Error (SQE)

SQD 상태는 Send Queue(SQ)의 처리가 에러가 되어 송신 불능이 된 상태이다. 이 상태는 ibv_modify_qp() 가 아니라, QP에 발생한 에러 현상에 의해서만 전이한다. 전이전 상태는 RTS 상태, 또는 SQD 상태가 된다. 수신에 관해서는 RTR 상태나 RTS 상태와 마찬가지로 가능하다.

SQE 상태에 빠지면, SQ에 있던 Send WR은 모두 완료 에러로 CQ에 옮겨진다. 완료 에러는 Flush Error (IBV_WC_WR_FLUSH_ERR)가 된다.

SQE 상태에서 RTS 상태로 되돌릴 수 있다.

한가지 예외는, RC QP 가 SQE 상태가 되는 경우는 없다. RC QP 의 SQ 에 발생한 에러는 SQE 상태가 아닌 Error 상태로 전이한다.

2.7 Error

Error 상태는 Receive Queue(RQ) 의 처리에 에러가 방생한 경우, RC QP 에 에러가 뱅생한 경우, 그 외의 원인으로 에러가 발생한 경우에 빠지는 상태이다. 송신도 수신도 멈춘다. ibv_modify_qp() 로 사용자가 바꾸는 것으로 이 상태로 전이하는 경우도 있다.

Error 상태에 빠지면, RQ 와 SQ 에 있는 Send WR 은 모두 완료 에러로 CQ에 옮겨진다. 완료 에러는 Flush Error (IBV_WC_WR_FLUSH_ERR) 가 된다.

일단 Error 상태로 빠지는 경우, Init 상태로 전이시키거나, ibv_destroy_qp() 로 QP 를 파기할 수 밖에 없다.

3. QP 상태 전이

Fig 2 는 QP 상태 전이도이다.

Fig 2: QP 상태 전이도

  • 파란 실선은 ibv_modify_qp() 로 사용자가 전환한 전이를 의미한다.
  • 빨간색 점선은 에러 상태에 따라 자연스럽게 전이한 경우가 있음을 의미한다. Error 상태에는 ibv_modify_qp() 로 사용자가 전환할 수도 있다.
  • 이 그림에서는 일부로 쓰지 않았지만, InfiniBand의 규격에는 Error 이외의 상태에서 Reset 상태로 전이할 수 있다. 정말 가능한 것인지는 HCA 하드웨어에 의존한다.

다음의 Table 1 은 각 전이에 따른 변경 가능한 속성을 나타내고 있다. 이것은 ibv_modify_qp() 의 세번째 인수 attr_mask 에IBV_QP_XXXX 매크로의 논리합으로 지정한다. Required Attributes 는 반드시 지정할 필요가 있는 속성으로, Optional Attributes 로 지정해도 괜찮은 속성이다. Required Attributes 도 Optional Attributes 도 아닌 속성은 지정할 수 없다. 이 표에는 없지만, IBV_QP_STATE 는 매번 설정할 필요가 있다.

덧붙여 Table 1 은 InfiniBand Architecture Specification Volume 1 Release 1.2.1 의 Table 91 QP State Transition Properties 의 표와 비슷하지만, 규격과 실제 구현이 다른 부분은, 구현(Linux 커널 3.12)쪽을 싣고 있다.

각 속성은 ibv_modify_qp() API 설명에서 한다.

Table 1. QP 상태 전이시 설정하는 속성

IBV_QP_CUR_STATE 는 주의가 필요하다. ibv_modify_qp() 는 IBV_QP_STATE 로 「전이 할」 상태를 지정하지만, IBV_QP_CUR_STATE 는 「현재 상태」를 지정할 수 있다. 「현재 상태」는 현재 QP 상태이므로 지정할 필요가 없다고 생각할 수 있지만, IBV_QP_CUR_STATE 를 지정하는 것으로, 현재 QP 상태 이외에서 전이한 것처럼 가장할 수 있다고 생각된다. ibv_modify_qp() 로 IBV_QP_CUR_STATE 를 지정하려면, Table 1. 에 Optional Attributes 지정된 것을 제외하고, HCA가 이 기능을 지원할 필요가 있다. 기능이 지원되는 경우,ibv_query_port() 내에 port_cap_flags 에 IBV_DEVICE_CURR_QP_STATE_MOD 속성이 설정되어 있다.

Table 1 에 기재되지 않았지만, HCA에 따라 ibv_create_qp() 로 지정한 케이퍼빌리티(max_send_wr, max_recv_wr, max_send_sge, max_recv_sge)를 변경할 수 있는 기능을 지원하는 것이 있다. 기능 지원은 ibv_query_port() 내의 port_cap_flags 에 IBV_DEVICE_RESIZE_MAX_WR 속성이 설정되어 있다. 케이퍼빌리티를 변경하는 경우, ibv_modify_qp() 에 IBV_QP_CAP 를 지정하여 변경한다.

Mellanox ConnectX-3 는 IBV_DEVICE_CURR_QP_STATE_MOD 기능도 IBV_DEVICE_RESIZE_MAX_WR 기능도 모두 지원하지 않는다.

4. 커뮤니케이션을 확립하려면?

InfiniBand 에서 QP 와 QP 간의 통신 경로를커뮤니케이션(Communication)이라고 부르는데, 튜토리얼의 마지막에 언급한 것처럼 커뮤니케이션 확립은 꽤 복잡하다. 특히 RC 서비스는 QP의 RTR 상태에서 통신 상대의 정보를 설정할 필요가 있기 때문에 골칫거리이다.

Fig 3 은 RC 서비스 타입의 QP 커뮤니케이션을 확립하기 위한 흐름의 한 예이다. 그 밖에도 다양한 방법을 생각할 수 있다.

  1. 서버는 IPoIB 등으로 소켓을 준비한다.
  2. 통신 경로를 열고 싶은 클라이언트는 QP를 작성한 후 서버에 요청을 보낸다. 당연히 요청에는 LID, QP 번호, PSN 등을 포함한다.
  3. 서버는 요청을 수신하고 자신도 QP를 만들고, 요청 내의 정보에 따라 RTR 상태까지 전이 시킨다. 이것으로 서버는 수신 가능 상태가 된다.
  4. 서버는 요청에 응답을 보낸다. 당연히 응답에는 LID, QP 번호, PSN등을 포함한다.
  5. 클라이언트는 응답을 수신하고, 응답 내에 정보에 따라 RTS 상태까지 전이 시킨다. 이것으로 클라이언트는 송신 가능하게 된다.

여기까지 하고, 클라이언트는 서버에 InfiniBand 로서의 오퍼레이션을 송신한다. 이것은 실제 메시지라도, 더미 메시지라도 상관없다.

서버 QP는 메시지를 수신한 때, (RTR 상태의 QP 가 맨먼저 패킷을 수신했기 때문에) Communication Established 비동기 이벤트를 발생시킨다. 비동기 에러 & 이벤트를 감시하는 루틴은 ibv_get_async_event() 로 비동기 이벤트를 골라, Communication Established 비동기 이벤트라고 판단할 수 있다면, 연결된 QP를 RTR 상태에서 RTS 상태로 전이 시키면 된다. 이것으로 서버의 QP도 전송가능하며, 커뮤니케이션이 양방향 통신 가능하게 된다.

Fig 3: RC 서비스 커뮤니케이션 확립 흐름

이 Communication Established 비동기 이벤트를 사용하는 방법의 장점으로, 만약 Communication Established 비동기 이벤트가 없으면 completion channel 을 사용하지 않는 CQ 에 폴링으로 감시할 수 밖에 없다. 하지만, Communication Established 비동기 이벤트가 있으므로 비동기 에러 인터럽트형 감시를 이용할 수 있다.

또한, 커뮤니케이션 확립의 마지막을 소켓이 아니라 InfiniBand 통신으로 끝내는 것은, QP와 InfiniBand 네트워크가 정상 동작하고 있는 것을 확인할 수 있다는 장점이 있다. Ready To Use 까지 IPoIB 소켓으로 하면, IPoIB는 통과하지만, RC QP에서는 통신할 수 없는 등의 위험성이 있다.

5. 일단 Error 상태에 빠진 QP를 어떻게 하면 좋을까?

일단 QP가 Error 상태에 빠진 통신 경로를 다시 통신 가능하게 하려면 어떻게해야 좋을까? 두가지 방법이 있다.

첫번째 방법은 ibv_destroy_qp() 로 QP를 회수하고, ibv_create_qp() 로 다시 만드는 방법이다. 압도적으로 이쪽을 추천한다.

어쩔 수 없이 두번째 방법으로 QP를 재사용할 수도 있다. Fig 2 를 보면 Error 상태에서 Reset 상태를 통해 상태 머신을 도는 것이다. 하지만, Reset 상태로의 전이는 상당히 번거롭다.

5.1 Error 상태로의 전이에서 어떤 일이 일어날까?

QP가 Error 상태에 빠질 때, 송신도 수신도 정지하고, ibv_post_send() 로 Send Queue(SQ) 에 등록한 Send WR과 ibv_post_recv() 로 Receive Queue(RQ) 에 등록한 Receive WR 이 Completion Queue(CQ) 으로 보내진다. 이 동작은 에러 발생에 의해 QP가 Error 상태로 빠지거나, 혹은 ibv_modify_qp() 를 사용해 자발적으로 Error 상태로 빠진 경우도 마찬가지이다.

이 동작은 중요하다. 왜냐하면 InfiniBand 는 ibv_post_send()ibv_post_recv() 로 등록한 WR을 꺼내는 시스템콜이 존재하지 않는다. 하지만 튜토리얼 부분에서 설명한 것과 같이, Send WR 이나 Receive WR에는 wr_id 가 있어, 프로그램은 이것을 사용해 버퍼 관리를 수행하거나 한다. SQ나 RQ에 들어 있는 미처리 WR을 CQ로 보내기 위해, Error 상태로 자발적으로 전이시키는 것은 자주 수행된다.

QP 가 SRQ 를 사용하고 있는 경우, 동작이 조금 다르다. SRQ는 독립한 오브젝트이므로, QP가 Error 상태가 되었다고 해서 ibv_post_srq_recv() 로 등록한 Receive WR 가 CQ 로 보내지는 것은 아니다. 하지만, QP가 처리하고 있는 마지막 수신 패킷이 있는 경우, 이것을 CQ에 저장할 때까지 다소 시간이 걸릴 수도 있다. QP를 Error 상태로 하고, 모든 WQE가 CQ에 보내질 때까지 한숨 걸릴 것이다. HCA는 이 시점을 프로그램에 알리기 위해 Last WQE Reached 비동기 이벤트(IBV_EVENT_QP_LAST_WQE_REACHED) 를 던진다.

즉, 다음과 같다.

  • SRQ 에 연결되어 있지 않은 QP는 QP를 Error로 한 후 ibv_poll_cq()로 CQ를 체크하러 간다.
  • SRQ 에 연결 되어 있는 QP 는 QP 를 Error로 한 후 비동기 이벤트를 검출하고 ibv_get_async_event() 로 Last WQE Reached 비동기 이벤트가 도착할 때까지 기다린다. 도착하면 ibv_poll_cq() 로 CQ를 체크하러 간다.

5.2 Reset 상태로의 전이에서 어떤 일이 일어날까?

Reset 상태는 ibv_create_cq() 로 만들어진 직후의 초기 상태이다. Reset 상태로의 전이는 이 초기 상태로 돌아가는 것을 의미한다.

따라서 Reset 상태로의 전이시에는 SQ와 RQ에 쌓여 있는 Work Request는 (CQ에 보내지지 않고) 소멸된다. Work Request 의wr_id 을 사용해 자원 관리를 하고 있다면, 이는 매우 잘못된 것이 된다. 임의의 상태에서 Reset 상태의 전이가 가능함에도 불구하고, Fig 2 에 그 경로를 쓰지 않았던 것은, Error 상태를 경유하지 않고, 갑자기 Reset 상태로 전이시키는 것은 프로그램적으로 있을 수 없기 때문이다.

Fig 4: Reset 상태로 전이시 CQ의 상태

다음으로 Reset 상태로 전이시에는 ibv_create_qp() 시에 send_cq 나 recv_cq 로 지정한 CQ 내에서 이 QP에 소속한 CQE만 제거한다. 이것은 굉장한 것으로, CQ는 여러개의 QP에 연결하는 것이 가능하므로, CQ 내에는 여러개의 QP에 소속한 CQE가 들어있지만, 본래 FIFO인 CQ의 중간에서 해당CQE만을 뽑는 것이다.

어쨋든 CQE 제거는 프로그램에서 보이지 않는 곳에서 멋대로 행해지므로, 4.1 에 QP를 Error 상태로 바꾸는 단계에서 Work Completion은 전부 회수해 둘 필요가 있다.

여러개의 QP에서 CQ가 공유되고 있는 경우, 특정 QP의 CQE를 전부 꺼냈다고 판단하는 것이 어려운 경우가 있다. 특히 SRQ를 사용하고 있는 경우, 수신한 메시지 수를 알 수 없으므로, CQ에 들어 있는 CQE 수도 알 수 없기 때문이다.

이것을 극복하기 위해, CQ에서 꺼내는 방법을 연구할 필요가 있다. InfiniBand Architecture Specification Volume 1 Release 1.2.1 의 10.3.1 QUEUE PAIR AND EE CONTEXT STATES 에는 2가지 방법이 제시되고 있다.

  1. CQ 가 비어 있다고 보고 할 때(반환값으로 0을 반환)까지 ibv_poll_cq()를 계속 호출한다.
  2. 마커가 되는 다른 Work Request 의 결과를 CQ 에 들어가서 그것이 꺼낼 때까지 ibv_poll_cq()를 계속 호출한다.

  3. 은 CQ를 공유하는 다른 QP에 끊김없이 통신이 계속되어, CQ가 비게 되는 순간이 오지 않을 수 있다. 따라서, 실용적으로는 2.를 사용해야 한다.

이 밖에 CQ 최대 CQE수를 한번에 ibv_poll_cq() 하는 방법이 있지만, IB 디바이스의 구현에 따라서는 ibv_create_cq() 의 두번째 인수 cqe 에 지정한 값보다 실제 CQ 최대 CQE수가 크게 잡히는 경우가 있기 때문에, 확실한 방법은 아니다.

results matching ""

    No results matching ""