nvmf_rpc.c、rdma.c、transport.c、nvmf.c
NVMf RPC接口文件
nvmf_rpc.c
1、创建RDMA Port监听
rpc_nvmf_subsystem_add_listener
nvmf_rpc_listen_paused
spdk_nvmf_tgt_listen
spdk_nvmf_transport_listen
nvmf_rdma_listen
2、创建NVMf链接
nvmf_ctrlr_cmd_connect
该函数是SPDK NVMe over Fabrics(NVMf)实现中,
用于处理连接命令(Connect Command)的函数。
该函数的作用是根据连接请求中的信息,判断是否可以建立连接,
如果可以建立连接,则返回成功,否则返回失败。
该函数首先获取连接请求中的连接数据(connect data)和响应数据(response data),
接着获取请求所在的传输层(transport),然后根据连接请求中的子系统名称(subnqn),在传输层的目标(tgt)中查找相应的子系统(subsystem)。如果未找到,则返回无效连接数据错误;否则,继续执行下面的步骤。
该函数接着检查子系统的状态,如果子系统处于不可用状态,则返回控制器繁忙错误;
否则,继续执行下面的步骤。
该函数接着检查连接请求中的主机名称(hostnqn)是否符合规范,如果不符合规范,
则返回无效连接数据错误;否则,继续执行下面的步骤。
该函数接着检查是否允许该主机建立连接,如果不允许,则返回无效主机错误;
否则,继续执行下面的步骤。
最后,该函数调用_nvmf_ctrlr_connect函数,建立连接,返回成功。
需要注意的是,该函数并不是直接处理连接请求的函数,
而是通过调用_nvmf_ctrlr_connect函数来建立连接。
该函数主要是对连接请求进行检查和过滤,确保只有符合要求的连接才能够建立。
_nvmf_ctrlr_connect
该函数是它是一个底层函数,用于处理命令与响应。
这是一个非常重要的函数,主要用于处理NVMe over Fabrics (NVMf)的连接请求。
在该函数中,首先解析了客户端发送来的Connect Command,
包括RECFMT、SQSIZE、CNTLID等字段,并对其进行一系列的校验。
然后,查找到了指定的Subsystem,
并调用nvmf_ctrlr_create()函数创建了一个Controller控制器对象。
若创建失败,则返回 INTERNAL_DEVICE_ERROR;
否则保留了qpair、ctrlr等信息,并异步发送一个_add_io_qpair消息。
该消息将作为一个事件被加入到Subsystem对应的工作线程的事件队列中,
等待被处理。
值得一提的是,在处理过程中,函数调用了一些SPDK提供的工具函数和接口,
如Debug日志,错误日志、统计信息等,
展示了SPDK提供了丰富的辅助开发的接口和工具函数。
总之,该函数是NVMf协议连接请求的关键处理函数,
处理过程中的校验、创建Controller对象以及发送异步消息等操作,
非常重要,需要开发人员仔细分析和识别其中的逻辑。
nvmf_ctrlr_create
这个函数是在上一个函数 nvmf_ctrlr_connect() 中被调用,
作用是创建一个 Controller 控制器的实例,并添加到对应 Subsystem 子系统中。
在函数中,首先为该 Controller 角色的实例分配内存,
并用相应的关键信息进行初始化,
如处理器、设备列表、命名空间列表、属于该 Controller 角色的 SQ 队列等。
其中的 SQ 队列是 NVMf 协议中的一个比较重要的概念,
用于管理数据进出的请求队列。创建 SQ 队列的过程中会涉及到内存池的管理。
除此之外,该函数还会调用一些辅助的接口函数,
如 spdk_nvmf_request_complete() 等。
整个过程较为复杂和关键,因此开发人员需要仔细分析实现逻辑和调用过程。
需要说明的是,该函数中涉及到的 mempool 和内存分配方式,
一般在 SPDK 应用程序中都会经常使用到。
这也是 SPDK 极大加快了存储 IO 系统性能的原因之一。
_nvmf_ctrlr_add_io_qpair
该函数用于为控制器添加一个新的IO队列。
该函数是 SPDK库中 nvmf_rpc.c 文件中的一个函数
用于向指定的 NVMf(控制器添加一个 I/O 队列对。
I/O 队列对用于处理主机发出的 I/O 请求。
函数的输入参数是一个 void 类型的指针 ctx,
实际上是一个 spdk_nvmf_request 结构体指针,
该结构体包含了与客户端建立连接的请求信息、响应信息和连接数据等。
函数首先从请求信息中获取到与客户端建立连接的控制器 ID 和
子系统 NQN(Name Qualifier),
然后根据这些信息找到对应的子系统和控制器。
如果控制器不存在,函数会返回一个错误响应,表示无法添加 I/O 队列对。
如果控制器正在销毁过程中,函数也会返回一个错误响应,
因为在销毁过程中不能添加 I/O 队列对。
如果控制器存在并且不在销毁过程中,
函数会将当前的 I/O 队列对与控制器进行绑定,
并将请求发送到控制器的管理队列(Admin Queue)所在的线程中,
以便控制器线程可以处理这个请求。
总的来说,这个函数是 SPDK 库中用于管理 I/O 队列对的一个重要函数,
它可以在存储设备和主机之间建立通信通道,
以便主机可以向存储设备发送读写请求,并得到相应的响应。
3、自动发现
nvme_fabric_ctrlr_discover
这个函数的主要作用是向控制器发送命令以获取NVMe over Fabrics (NVMf)的发现日志,
该发现日志包含有关已发现的NVMf子系统的信息,
然后对发现日志进行解析,并将结果填充到给定的探测上下文中。
首先,函数声明了一些变量来存储发现日志的内容。
然后,它初始化了一个缓冲区,然后计算缓冲区可以容纳的最大发现日志条目数。
然后,在 do-while 循环中,
函数使用 nvme_fabric_get_discovery_log_page 函数向控制器发送命令以获取发现日志。
如果获取失败,函数将返回一个错误代码并退出。
如果成功,它将检查发现日志的记录格式,如果它不是0,
函数将返回一个协议错误代码。
接下来,函数使用从发现日志页中提取的记录数遍历日志条目,
然后将结果填充到给定的探测上下文中。
最后,函数返回0以表示成功执行。
总体来说,这个函数是SPDK的NVMf子系统中的一个重要组件,
它提供了获取NVMf发现日志的能力,并解析和填充探测上下文中的信息,
这对于识别可用的NVMf子系统非常重要。
nvmf_get_discovery_log_page
这个函数实现了获取NVMf Target的发现日志页(discovery log page)的功能。
该函数接收的参数包括:
NVMf Target对象(tgt)、发出请求的Host NQN(hostnqn)、
IO向量(iov)、IO向量的数量(iovcnt)、请求的偏移量(offset)以及请求的长度(length)。
函数内部首先调用 nvmf_generate_discovery_log() 函数
生成一个 discovery log page,
并通过参数 log_page_size 返回其大小。
如果 discovery log page 为空,
则说明生成失败,函数直接退出。
接下来,该函数遍历 iov 数组中的所有 IO 向量,
对于每个 IO 向量,先将长度限制在请求的长度内,
再将长度限制在 discovery log page 的大小减去请求偏移量内,
避免读取无效的数据。
然后将有效的部分从 discovery log page 中拷贝到 IO 向量的缓冲区中,
并更新请求的偏移量和长度。
如果当前 IO 向量无法完全拷贝所有数据,
则将剩余部分置为 0。如果 discovery log page 已经读取完毕,
或者请求的长度已经达到了指定的长度,则退出遍历循环。
最后,将没有被拷贝的 IO 向量的数据全部置为 0,
释放 discovery log page 内存,函数返回。
4、执行命令
spdk_nvmf_request_exec
这是SPDK中的lib/nvmf/ctrlr.c文件中的函数spdk_nvmf_request_exec(),
用于执行NVMf(NVMe over Fabrics)请求。
该函数首先从请求的队列指针req->qpair中获取请求队列对象qpair,
然后判断该队列对象是否有关联的控制器qpair->ctrlr,
如果有,则获取所属的子系统(Subsystem)并将其指针存储在sgroup中。
如果没有关联的控制器,则检查请求是否是来自fabric连接请求,
如果是,则从连接命令中获取子系统对象并将其指针存储在sgroup中。
接下来,函数检查请求队列对象qpair的状态是否处于SPDK_NVMF_QPAIR_ACTIVE,
如果不是,则将请求的响应标记为SPDK_NVME_SC_COMMAND_SEQUENCE_ERROR,
并将请求插入到qpair的outstanding列表中以跟踪它。
如果sgroup非空,则将io_outstanding递增,以保持I/O的计数与实际值的同步,
并将请求完成以释放资源。
如果请求队列对象qpair的状态处于SPDK_NVMF_QPAIR_ACTIVE,
则检查sgroup的状态是否处于SPDK_NVMF_SUBSYSTEM_ACTIVE,
如果不是,则将请求插入到sgroup的队列中等待后续处理。
最后,函数调用内部的_nvmf_request_exec()函数,
该函数将请求的执行委托给适当的控制器或子系统处理程序。
综上所述,spdk_nvmf_request_exec()函数用于处理NVMf请求,
包括处理请求队列对象、检查队列状态和处理子系统/控制器相关的逻辑。
_nvmf_request_exec
它的参数是一个 NVMf 请求对象和一个 NVMf subsystem poll group 对象。
下面我来详细解释每一个步骤。
首先,函数会通过 SPDK_DEBUGLOG_FLAG_ENABLED 函数开启一个日志标记,
这个标记是在 debug 模式下才会启用。
它会打印出当前请求的 NVMe 命令,以便我们在 debug 时可以追踪问题。
接下来,如果传入了一个 NVMf subsystem poll group 对象,
它会将 io_outstanding++,以表示当前 I/O 请求已经被提交到该 poll group 中。
然后,它将这个请求加入到队列中,以便能够跟踪它的状态。
这里用到了 TAILQ_INSERT_TAIL 函数。
接下来,函数会根据请求的具体类型来决定如何处理请求。
如果是一个 Fabric 命令,则会调用 nvmf_ctrlr_process_fabrics_cmd 函数。
如果是一个 Admin 命令,则会调用 nvmf_ctrlr_process_admin_cmd 函数。
否则,则会调用 nvmf_ctrlr_process_io_cmd 函数。
这些函数均是 SPDK 中用于处理不同类型请求的函数。
在这些函数中,它们会根据请求的具体内容,将请求变成对底层设备的 I/O 操作。
最后,当请求处理完毕时,执行 _nvmf_request_complete 函数来完成这个请求。
5、rdma命令接收并处理
nvmf_tgt_create_poll_group
这个函数是用来创建一个NVMf(NVMe over Fabrics)的Poll Group(轮询组)的。
在SPDK中,Poll Group是用于管理连接、完成队列等数据结构的重要组件。
当一个新的Poll Group被创建时,需要将它绑定到一个线程上,这样才能执行I/O操作。
该函数的输入参数io_device指向的是一个spdk_nvmf_tgt类型的结构体,
表示当前所在的NVMf Target。
ctx_buf指向的是一个spdk_nvmf_poll_group类型的结构体,
表示要创建的Poll Group。
这里定义了一个指向当前NVMf Target的指针tgt和指向要创建的Poll Group的指针group,
还定义了一个指向当前支持的传输层transport的指针以及一个用于循环的sid。
初始化Poll Group中的传输组和队列对,这两个数据结构用于管理连接和完成队列。
遍历当前NVMf Target支持的传输层,将其添加到Poll Group中。
初始化Poll Group中的子系统组,其中num_sgroups表示支持的子系统组数量,
sgroups是指向子系统组数组的指针。如果分配空间失败,返回-ENOMEM错误码。
遍历当前NVMf Target中的子系统,
将其添加到Poll Group中。如果添加失败,则销毁已经创建的Poll Group,并返回-1错误码。
将新创建的Poll Group添加到当前NVMf Target中的Poll Group列表中,
需要加锁避免多线程冲突。
注册Poll Group的轮询函数nvmf_poll_group_poll,将Poll Group绑定到一个线程上,
并返回0表示创建成功。
这个函数的主要作用是创建一个NVMf的Poll Group,
并将其绑定到一个线程上,从而可以执行I/O操作。
在函数中,首先初始化Poll Group中的传输组和队列对,
并将支持的传输层添加到Poll Group中。
然后初始化Poll Group中的子系统组,
并将支持的子系统添加到Poll Group中。
最后将Poll Group添加到当前NVMf Target中的Poll Group列表中,
并注册Poll Group的轮询函数,并将其绑定到一个线程上。
需要注意的是,这个函数并不是用来处理I/O请求的,
而是用来创建和管理Poll Group的。实际的I/O请求处理函数是nvmf_poll_group_poll,
这个函数被注册到了Poll Group的轮询器中,用来处理Poll Group中的I/O请求。
除了创建Poll Group并绑定到线程上,这个函数还做了一些额外的工作。
首先,它初始化了Poll Group中的传输组和队列对,并将支持的传输层添加到Poll Group中。
这是因为在NVMf中,传输层是负责传输I/O请求的基本单位,
因此需要为Poll Group分配传输组和队列对,以便接收和处理I/O请求。
其次,这个函数还初始化了Poll Group中的子系统组,
并将支持的子系统添加到Poll Group中。
这是因为在NVMf中,子系统是用来管理命名空间和控制器的逻辑单元,
每个子系统对应着一个或多个命名空间和一个或多个控制器。
因此,需要为Poll Group分配子系统组,并将支持的子系统添加到Poll Group中,
以便进行命名空间和控制器的管理。
最后,这个函数将Poll Group添加到当前NVMf Target中的Poll Group列表中,
并注册Poll Group的轮询函数nvmf_poll_group_poll,并将其绑定到一个线程上。
这是因为在NVMf中,一个Target可以有多个Poll Group,
每个Poll Group都有自己的轮询器和线程,
用来处理不同的命名空间和控制器。
因此,需要将新创建的Poll Group添加到Target的Poll Group列表中,
并为其注册轮询函数和线程,以便进行I/O操作的处理。
nvmf_poll_group_poll
其作用是对一个 NVMf的轮询组(Poll Group)执行轮询(Poll)操作。
以下是对该函数的详细解释:
首先,
将传递给该函数的参数ctx解析为一个指向spdk_nvmf_poll_group结构体的指针group。
然后,
遍历轮询组中的每个传输轮询组(Transport Poll Group),
使用nvmf_transport_poll_group_poll函数对其进行轮询操作,
并将返回值累加到变量count中。
如果任何一个传输轮询组的轮询操作返回了负数,
则函数会直接返回SPDK_POLLER_BUSY。
最后,如果count大于0,则说明有事件发生,函数返回SPDK_POLLER_BUSY;
否则,函数返回SPDK_POLLER_IDLE,表示没有事件发生。
该函数实现的实际功能是对所有与 NVMf 相关的 I/O 事件进行轮询,以便相应地处理它们。在 SPDK 中,
轮询操作是由 SPDK 的异步事件驱动引擎(Asynchronous Event-driven Engine)实现的。
该引擎会周期性地(通常是每1ms或10ms)调用所有已注册的轮询组的轮询操作,以便处理所有的 I/O 事件。
nvmf_transport_poll_group_poll
函数作用:调用特定传输层实现的轮询组(Poll Group)轮询函数
函数实现:
该函数实现非常简单,
仅仅是调用传入的轮询组(Poll Group)的 transport 对应的 ops(传输层操作)中的 poll_group_poll 函数。
其中,传入的轮询组(Poll Group)通过结构体指针group进行传递,
而该轮询组(Poll Group)的 transport 对应的传输层实现,
则可以在该轮询组(Poll Group)的 transport 成员中找到。
在 SPDK 中,每种传输层都有自己的实现,并提供了一组传输层操作(ops)函数,用于实现该传输层的所有功能。
因此,通过调用传输层操作中的 poll_group_poll 函数,可以确保对于不同的传输层,都能够正确地进行轮询操作。
该函数的实际作用是将轮询操作委托给了传输层实现中的 poll_group_poll 函数,
以便在不同的传输层实现中,实现自己独特的轮询操作。
nvmf_rdma_poll_group_poll
函数作用:对一个 NVMf RDMA 传输层的轮询组(Poll Group)执行轮询(Poll)操作
函数实现:
首先,
将传递给该函数的参数group解析为一个指向spdk_nvmf_rdma_poll_group结构体的指针rgroup,
同时从rgroup中获取与之相关的RDMA传输层的rtransport结构体指针。
然后,遍历轮询组中的每个RDMA轮询器(Poller)
使用nvmf_rdma_poller_poll函数对其进行轮询操作,
并将返回值累加到变量count中。
如果任何一个RDMA轮询器的轮询操作返回了负数,则函数会直接返回该负数值。
最后,函数返回count,表示此次轮询操作期间接收到的RDMA传输层的I/O事件的数量。
该函数实现的实际功能是对NVMf RDMA传输层中所有的I/O事件进行轮询,
以便相应地处理它们。NVMf RDMA传输层使用RDMA(Remote Direct Memory Access)技术进行远程数据访问。
在 RDMA 技术中,数据直接从内存中移动到远程节点,无需CPU的参与,
因此 RDMA 传输层的轮询操作需要特别处理,需要使用nvmf_rdma_poller_poll函数对RDMA轮询器进行轮询操作。
6、线程运行
reactor_run
这个函数是SPDK中的一个关键函数,负责运行一个Reactor(反应器)实例,是SPDK事件驱动架构的核心组件。
在这个函数中,SPDK使用事件驱动的方式来进行异步编程,以实现高效的存储性能。
以下是该函数的详细说明:
参数说明
该函数的参数是一个指向SPDK反应器结构体的指针,该结构体定义了反应器的属性和状态信息。
线程命名
该函数首先给POSIX线程重命名为“reactor_lcore”,这是因为SPDK事件库将反应器绑定到POSIX线程上。
反应器循环
该函数的核心逻辑在while循环中,通过调用_reactor_run()函数来运行反应器。在_reactor_run()函数中,
反应器会监听事件,并在事件到达时调用相关的回调函数来处理事件。
反应器会不断地循环监听和处理事件,直到g_reactor_state的状态不是SPDK_REACTOR_STATE_RUNNING时,
该循环结束。
线程退出
当循环结束时,该函数会遍历所有线程并让它们退出,然后销毁线程。这是通过遍历线程链表来实现的。
在遍历过程中,如果线程已经退出,则将其从线程链表中删除,并调用spdk_thread_destroy()函数来销毁该线程。
线程轮询
如果线程没有退出,那么它会被轮询以处理可能仍在等待的事件。
轮询是通过调用spdk_thread_poll()函数来实现的。
该函数会让线程处理所有未处理的事件,并在等待时间为0时立即返回。
返回值
该函数的返回值始终为0,因为它没有返回任何错误代码。
总之,这个函数是SPDK事件驱动架构的核心部分,负责反应器的循环和线程管理。
它通过异步编程实现高效的存储性能,并具有良好的可扩展性和灵活性。
7、RPC函数接口注册
SPDK_RPC_REGISTER rpc.h rpc.c监听
SPDK_RPC_REGISTER是一个宏定义,用于注册RPC方法。
注册RPC方法后,其他程序通过RPC请求可以调用该函数。
8、启用NVMf_tgt
nvmf_main.c spdk_app_start 启用监听,可以接受rpc命令
这是 SPDK中实现 NVMe over Fabrics (NVMf)的目标(target)程序,
主要作用是接受远程的请求,将其转化为本地的 NVMe 请求并传递给 NVMe 设备,
然后将结果返回给远程请求的发起者。让我们来逐行分析一下这个文件:
#include "spdk/stdinc.h" #include "spdk/env.h" #include "spdk/event.h"
这里引入了 SPDK 的头文件,stdinc.h 包含了一些 C 语言标准库和 SPDK 特定的宏定义,
env.h 和 event.h 分别定义了环境相关的函数和事件相关的函数。
static void nvmf_usage(void) { }
该函数定义了打印 nvmf 程序使用方法的函数,但这个函数没有任何输出。
static int nvmf_parse_arg(int ch, char *arg) { return 0; }
该函数定义了解析命令行参数的函数,但是这个函数不做任何处理。
static void nvmf_tgt_started(void *arg1) { if (getenv("MEMZONE_DUMP") != NULL) { spdk_memzone_dump(stdout); fflush(stdout); } }
该函数是回调函数,当 SPDK 应用程序成功启动后调用该函数。
如果环境变量 MEMZONE_DUMP 被设置为非 NULL 的值,则将内存区域的状态打印到标准输出。
int main(int argc, char **argv) { int rc; struct spdk_app_opts opts = {}; /* default value in opts */ spdk_app_opts_init(&opts); opts.name = "nvmf"; if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, nvmf_parse_arg, nvmf_usage)) != SPDK_APP_PARSE_ARGS_SUCCESS) { exit(rc); } /* Blocks until the application is exiting */ rc = spdk_app_start(&opts, nvmf_tgt_started, NULL); spdk_app_fini(); return rc; }
该函数是程序的入口点。首先定义了一个 spdk_app_opts 结构体并将其初始化,
设置了程序名称为 nvmf。然后解析命令行参数,如果解析失败则直接退出程序。
接下来调用 spdk_app_start 函数启动 SPDK 应用程序,并传入一个回调函数 nvmf_tgt_started。
该函数将一直阻塞直到程序退出。
最后调用 spdk_app_fini 函数释放 SPDK 应用程序所占用的资源并返回程序退出码。
9、创建Transport
nvmf_rpc_create_transport
nvmf_rpc_create_transport函数用于在SPDK中创建NVMf传输层。
该函数初始化NVMf传输层,并将传输层信息存储在SPDK管理的结构体中。
10、rdma_create_event_channel
event channel是RDMA设备在操作完成后,或者有连接请求等事件发生时,
用来通知应用程序的通道。
其内部就是一个file descriptor, 因此可以进行poll等操作
11、接收QP命令
nvmf_process_cm_event
该函数是nvmf(NVMe over Fabrics) RDMA(Remote Direct Memory Access)传输层的事件处理函数,
用于处理RDMA连接管理(CM)事件。
该函数首先将传输层转换为其基本类型(即rdma_transport),
然后检查事件通道是否为空。如果为空,则退出该函数。
接下来,该函数循环执行以下步骤:
event_acked变量初始化为false,以指示事件是否已被确认。
使用rdma_get_cm_event函数从事件通道中获取CM事件。
如果获取失败,则检查错误号是否为EAGAIN或EWOULDBLOCK。
如果错误号不是这两种情况,则打印错误日志并退出循环。
如果成功获取CM事件,则记录跟踪信息并根据事件类型执行相应的操作。
可以执行以下操作:
RDMA_CM_EVENT_ADDR_RESOLVED/RDMA_CM_EVENT_ADDR_ERROR/RDMA_CM_EVENT_ROUTE_RESOLVED/RDMA_CM_EVENT_ROUTE_ERROR:不需要执行任何操作。目标从不尝试解析路由。
RDMA_CM_EVENT_CONNECT_REQUEST:调用nvmf_rdma_connect函数处理连接请求。
RDMA_CM_EVENT_CONNECT_RESPONSE:目标不会启动新连接,因此不会发生此事件。
RDMA_CM_EVENT_CONNECT_ERROR:不确定这是否可能发生。文档中说可能会发生,但不确定原因是什么。
RDMA_CM_EVENT_UNREACHABLE/RDMA_CM_EVENT_REJECTED:仅在客户端发生。
RDMA_CM_EVENT_ESTABLISHED:暂未处理此事件。
RDMA_CM_EVENT_DISCONNECTED:调用nvmf_rdma_disconnect函数处理断开连接事件。
RDMA_CM_EVENT_DEVICE_REMOVAL:如果事件具有有效的qp指针,则该事件指的是相应的qpair,
否则事件指的是侦听设备。如果事件指的是qpair,则调用nvmf_rdma_disconnect函数处理断开连接事件。
如果事件指的是侦听设备,则调用nvmf_rdma_handle_cm_event_port_removal函数处理端口删除事件。
RDMA_CM_EVENT_MULTICAST_JOIN/RDMA_CM_EVENT_MULTICAST_ERROR:不使用多播。
RDMA_CM_EVENT_ADDR_CHANGE:调用nvmf_rdma_handle_cm_event_addr_change函数处理地址更改事件。
RDMA_CM_EVENT_TIMEWAIT_EXIT:目标从不重用队列对,因此暂未处理此事件。
如果事件未被确认,则使用rdma_ack_cm_event函数确认事件。
总之,该函数是nvmf RDMA传输层中的一个核心函数,用于处理RDMA连接管理事件并执行相应的操作。
它与RDMA事件通道交互,并根据事件类型调用相应的处理函数。
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习