SPDK NVMe-oF target的主程序位于 (spdk/app/nvmf_tgt) 目录中,可以看到有个文件命名为nvme_main.c. 仔细一看相关的main函数,似乎也没做什么,只是调用了spdk_app_opts_init, 初始化了一下相应的参数; 然后调用了一下spdk_app_parse_args,用于解析命令行的相应参数。 接着调用了一下spdk_app_start, 如果有错误,最终会执行spdk_app_fini退出。
代码看上去非常简单,没有任何细节, 那么原因在哪里呢?
主要原因在于spdk_app_start函数中会调用spdk_subsystem_init进行所有subsystem模块的初始化。目前来讲, SPDK里面的subsystem有两个概念:
第一个subsystem的概念指模块的subsystem,主要位于代码目录spdk/lib/event/subsystems中,比如现在SPDK之中有以下9个模块subsystem, 分别是 bdev copy iscsi nbd net nvmf rpc scsi vhost。 这些模块subsystem有些有依赖关系,我们在对这些模块初始化的时候会先根据依赖关系,排序,然后进行初始化。
第二个subsystem的概念指NVMe-oF中的NVM subsystem。
另外在这里我们忽略模块subsystem 初始的流程。主要关注NVMe-oF这个模块的subsystem的处理流程,主要.c文件的代码位于(spdk/lib/event/subsystems/nvmf) 中。
其中有四个文件:
他们的功能如下:
Conf.c: 主要解析配置文件。其调用入口是spdk_nvmf_parse_conf函数,我们可以通过表1所提供的NVMe-oF target配置文件的用例,来具体分析一下这个函数的处理流程。这个函数主要调用了两个函数:
spdk_nvmf_parse_nvmf_tgt
1. 解析配置文件中的“[NVMf]” section, 比如会调用函数spdk_nvmf_read_config_file_tgt_conf,来解析配置文件中的AcceptorPollrate, 这个主要是用来控制g_acceptor_poller (即在nvmf_tgt.c中调用acceptor_poll的频率)。
2. 调用spdk_nvmf_tgt_create来创建全局的NVMe-oF target对象g_spdk_nvmf_tgt (数据类型是struct spdk_nvmf_tgt, 如表1所示)。 这里特别要注意的是我们调用spdk_io_device_register函数对g_spdk_nvmf_tgt 注册了相应的I/O device。
那么当用户调用spdk_get_io_channel(&g_spdk_nvmf_tgt) 的时候,在第一次I/O channel被创建的时候,spdk_nvmf_tgt_create_poll_group这个当初被传入的I/O channel的call back函数就会被触发。
(附注:这里稍微提一下I/O channel本质上是thread到一个io_device的mapping,也就是说对于一组(thread, io_device)会产生唯一的一个I/O channel,直到这个I/O device最终被调用spdk_io_device_unregister销毁掉。)
3. 调用spdk_add_nvmf_discovery_subsystem, 用于创建discovery NVM subsystem, 这个subsystem一般设置为所有的host可见。其主要用于实现相应的log discovery命令,告诉host端有多少NVM subsystem在线。当然这个实际会存储在g_spdk_nvmf_tgt中的变量discovery_log_page中 (如表1所示),并且会做相应的更新。
表1 struct spdk_nvmf_tgt

spdk_nvmf_parse_transports
其作用主要是初始化各个transport。 正常流程是:发现配置文件中有相应的 “[Transport]” section, 会调用以下函数:
1. spdk_nvmf_parse_transport 函数。
在spdk_nvmf_parse_transport中,每个transport会解析以下信息: 诸如Type, 这个类型是必要的,主要指明需要用哪个transport,目前也可指RDMA。 另外也可传入以下各种参数配置&