RDMA技术三:NetworkDirect

1.NetworkDirect定义

NetworkDirect是一种基于RDMA的用户编程模式编程接口规范,这套规范能实现本端和远端(客户端和服务端)的零拷贝数据传输、内核旁路的数据发送和接受等RDMA功能。其中,RDMA功能由具备RDMA能力的网络适配器提供,NetworkDirect可以基于ib、iwarp或roce三种协议中的一种,而NetworkDirect允许应用程序开发人员,通过访问和使用具备RDMA能力的网卡制造商通过提供的接口, 实现使用网卡的硬件功能。

特别说明:NetworkDirect是基于windows开发的。

2.NDSPI model

NetworkDirect Server Provider Interface 

2.1 NDSPI model特点

NetworkDirect客户端用用程序使用COM与Provider进行通信。COM接口通过IUnknown::QueryInterface方法提供了灵活的可扩展性,允许Provider返回其提供的任何接口,从而向外部暴露特定的硬件特性。NetworkDirect的Provider不是真正的COM对象,NetworkDirect的Provider不会再系统中注册特定的对象。

NDSPI 不支持marshaling处理,即Provider公开的COM接口需要在应用程序的进程中自己实现实例化。可参考Winows 驱动程序框架-用户模式驱动程序框架(UMDF)所使用的驱动程序模型。

2.2 NDSPI model 提供接口

IND2Provider

表示服务提供者。提供IN2Provider::OpenAdapter方法来实例化一个IN2Adapter对象。

IND2Overlapped

overlapped IO对象的基类。

IND2Adapter

Provider硬件适配器实例的接口,其他的类或者接口都由此适配器产生。

IND2CompletionQueue

通过IND2Adapter::CreateCompletionQueue创建,完成队列(CQ)实例的接口。

IND2MemoryRegion

通过IND2Adapter::CreateMemoryRegion创建,表示内存区接口,即适配器示例上注册的本地缓冲区,方便存储发送或者接受到的数据。

IND2SharedReceiveQueue

通过IND2Adapter::CreateSharedReceiveQueue方法创建,用于队列对QPair之间聚合接受缓冲区的共享接收队列。

IND2QueuePair

通过IND2Adapter::CreateQueuePair方法创建,用于执行IO操作队列实例的接口。

IND2Connector

通过IND2Adapter::CreateConnector方法创建,用于执行IO操作队列建立连接的的实例的接口。

IND2Listener

通过IND2Adapter::CreateListener方法创建,用于监听请求的接口。

3 编程流程

根据microsoft提供的关于ND开发的官方文档,基于ND开发的关键步骤为:

3.1 打开网络编程

	// 监听本地端口
	WSADATA wsaData;
	int ret = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (ret != 0)
	{
		printf("Failed to initialize Windows Sockets: %d\n", ret);
		exit(__LINE__);
	}

3.2 注册Provider

调用WSCInstallProvider 来实现注册,其中调用接口时输入的参数需要满足以下特点:

dwServiceFlags1:

  • XP1_GUARANTEED_DELIVERY
  • XP1_GUARANTEED_ORDER
  • XP1_MESSAGE_ORIENTED
  • XP1_CONNECT_DATA

dwProviderFlags: PFL_HIDDEN | PFL_NETWORKDIRECT_PROVIDERiVersion: 2

iAddressFamily: AF_INET or AF_INET6

iSocketType: -1 (0xFFFFFFFF)

iProtocol: 0

iProtocolMaxOffset: 0

补充说明:为什么networkdirect 提供的例子里没有这个步骤?

3.3 实例化Provider

NDSPI应用程序支持0个,1个或多个服务提供商库。对ND Provider的管理遵循为Windows Sockets Direct Provider和传统分层服务Provider定义的既定机制。

3.3.1 查找ND Provider

计算机上有很多个Provider,通过 WSCEnumProtocols 能找到计算机上所有的Provider,通过对比实例化时的参数,找到注册过的Provider。

3.3.2 实例化ND Provider

通过实例化IND2Provider,可以确定Provider是否是ND service Provider,即,如果有实例化IND2Provider,则Provider是ND service Provider;否则,Provider不是ND service Provider。

方法:
加载DLL:使用WSAPROTOCOL_INFO 数据结构中的ProviderId,调用 WSCGetProviderPath 获取dll路径。

调用库的DllGetClassObject入口实例化IND2Provider

service Provider库需要实现一个DllCanUnloadNow入口点,以允许客户端在不再使用时卸载service Provider库。Provider还必须能处理IND2Provider的多个调用。

3.4 通过Provider获取Adapter

不管是应用端还是客户端,都需要一个ip地址,有了ip地址,可以通过IN2Provider::OpenAdapter方法来实例化一个IN2Adapter对象。

3.5 Adapter调用不同的接口实现具体的功能

3.5.1 服务端读/写数据基本流程(伪代码)

   // Get the file handle for overlapped operations on this adapter.
    // 通过adapter获取overlapped的文件句柄
    hr = m_pAdapter->CreateOverlappedFile(&m_hAdapterFile);
    if (FAILED(hr))
    {
        LogErrorExit(hr, "IND2Adapter::CreateOverlappedFile failed", __LINE__);
    }


    // 获取adapter的相关信息,方便后续操作
    memset(pAdapterInfo, 0, sizeof(*pAdapterInfo));
    pAdapterInfo->InfoVersion = ND_VERSION_2;
    ULONG adapterInfoSize = sizeof(*pAdapterInfo);
    HRESULT hr = m_pAdapter->Query(pAdapterInfo, &adapterInfoSize);


    // 通过adapter创建CQ
    HRESULT hr = m_pAdapter->CreateCompletionQueue(
        IID_IND2CompletionQueue,
        m_hAdapterFile,
        depth,
        0,
        0,
        reinterpret_cast<VOID**>(&m_pCq)
    );

     
    // 通过adapter创建Connector
    HRESULT hr = m_pAdapter->CreateConnector(
        IID_IND2Connector,
        m_hAdapterFile,
        reinterpret_cast<VOID**>(&m_pConnector)
    );
    

    // 通过adapter创建QP
    HRESULT hr = m_pAdapter->CreateQueuePair(
        IID_IND2QueuePair,
        m_pCq,
        m_pCq,
        nullptr,
        queueDepth,
        queueDepth,
        nSge,
        nSge,
        inlineDataSize,
        reinterpret_cast<VOID**>(&m_pQp)
    );

       // 通过adapter创建MR
        HRESULT hr = m_pAdapter->CreateMemoryRegion(
        IID_IND2MemoryRegion,
        m_hAdapterFile,
        reinterpret_cast<VOID**>(&m_pMr)
    );


        // 通过MR开辟内存空间
        HRESULT hr = m_pMr->Register(
        pBuf,
        bufferLength,
        type,
        &m_Ov
    );
   
    // 通过adapter创建listener
        HRESULT hr = m_pAdapter->CreateListener(
        IID_IND2Listener,
        m_hAdapterFile,
        reinterpret_cast<VOID**>(&m_pListen)
    );

    // 通过listener 监听连接请求
    HRESULT hr = m_pListen->GetConnectionRequest(m_pConnector, &m_Ov);


    // post reveive for the terminate message
    // 发送准备接受数据的信息
	ND2_SGE sge = { 0 };
	sge.Buffer = m_pBuf;
	sge.BufferLength = x_PiggyBackXfer + sizeof(struct PeerInfo);
	sge.MemoryRegionToken = m_pMr->GetLocalToken();
	NdTestBase::PostReceive(&sge, 1, RECV_CTXT);
 
    // 建立连接
    HRESULT hr = m_pConnector->Accept(
        m_pQp,
        inboundReadLimit,
        outboundReadLimit,
        pPrivateData,
        cbPrivateData,
        &m_Ov
    );

    // 读数据
    hr = m_pQp->Read(requestContext, Sge, nSge, remoteAddress, remoteToken, flag);

    // 写数据
    hr = m_pQp->Write(requestContext, Sge, nSge, remoteAddress, remoteToken, flag);

3.5.2 客户端send/receive基本流程(伪代码)

// Get the file handle for overlapped operations on this adapter.
    // 通过adapter获取overlapped的文件句柄
    hr = m_pAdapter->CreateOverlappedFile(&m_hAdapterFile);
    if (FAILED(hr))
    {
        LogErrorExit(hr, "IND2Adapter::CreateOverlappedFile failed", __LINE__);
    }


    // 获取adapter的相关信息,方便后续操作
    memset(pAdapterInfo, 0, sizeof(*pAdapterInfo));
    pAdapterInfo->InfoVersion = ND_VERSION_2;
    ULONG adapterInfoSize = sizeof(*pAdapterInfo);
    HRESULT hr = m_pAdapter->Query(pAdapterInfo, &adapterInfoSize);


    // 通过adapter创建CQ
    HRESULT hr = m_pAdapter->CreateCompletionQueue(
        IID_IND2CompletionQueue,
        m_hAdapterFile,
        depth,
        0,
        0,
        reinterpret_cast<VOID**>(&m_pCq)
    );

     
    // 通过adapter创建Connector
    HRESULT hr = m_pAdapter->CreateConnector(
        IID_IND2Connector,
        m_hAdapterFile,
        reinterpret_cast<VOID**>(&m_pConnector)
    );
    

    // 通过adapter创建QP
    HRESULT hr = m_pAdapter->CreateQueuePair(
        IID_IND2QueuePair,
        m_pCq,
        m_pCq,
        nullptr,
        queueDepth,
        queueDepth,
        nSge,
        nSge,
        inlineDataSize,
        reinterpret_cast<VOID**>(&m_pQp)
    );

       // 通过adapter创建MR
        HRESULT hr = m_pAdapter->CreateMemoryRegion(
        IID_IND2MemoryRegion,
        m_hAdapterFile,
        reinterpret_cast<VOID**>(&m_pMr)
    );


        // 通过MR开辟内存空间
        HRESULT hr = m_pMr->Register(
        pBuf,
        bufferLength,
        type,
        &m_Ov
    );


   // 客户端请求与服务端建立连接
   hr = m_pConnector->Connect(
        m_pQp,
        reinterpret_cast<const sockaddr*>(&v4Dst),
        sizeof(v4Dst),
        inboundReadLimit,
        outboundReadLimit,
        pPrivateData,
        cbPrivateData,
        &m_Ov
    );


    // 判断连接是否建立
    HRESULT hr = m_pConnector->CompleteConnect(&m_Ov);

    // 创建MW
       HRESULT hr = m_pAdapter->CreateMemoryWindow(
        IID_IND2MemoryWindow,
        reinterpret_cast<VOID**>(&m_pMw)
    );

   // mr mw buff数据绑定
   if (m_pQp->Bind(context, m_pMr, m_pMw, pBuf, bufferLength, flags) != ND_SUCCESS)
    {
        LogErrorExit("Bind failed\n", __LINE__);
    }

   //  等待CQ
   WaitForCompletion([&pResult](ND2_RESULT *pCompRes)
    {
        *pResult = *pCompRes;
    }, bBlocking);
   

        // prepare peerinfo
        struct PeerInfo *peerInfo;
        peerInfo = reinterpret_cast<struct PeerInfo *>(m_pBuf);
        peerInfo->m_remoteAddress = reinterpret_cast<uint64_t>(m_pBuf);
        peerInfo->m_remoteToken = m_pMw->GetRemoteToken();

        ND2_SGE sgl[2];
        sgl[0].Buffer = m_pBuf + x_ReadBase;
        sgl[0].BufferLength = x_PiggyBackXfer;
        sgl[0].MemoryRegionToken = m_pMr->GetLocalToken();

        sgl[1].Buffer = peerInfo;
        sgl[1].BufferLength = sizeof(PeerInfo);
        sgl[1].MemoryRegionToken = m_pMr->GetLocalToken();

        // 发送信息
        HRESULT hr = m_pQp->Send(requestContext, Sge, nSge, flags);


     //  数据发送完,清理数据
    m_pConnector->Disconnect(&m_Ov);
    m_pMr->Deregister(&m_Ov);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值