SPDK线程

(一)SPDK模型的初始化

在这里插入图片描述
初始情况下是:
SPDK_REACTOR_STATE_INVALID状态,在spdk app(任意一个target,比如nvmf_tgt)启动时,即调用了spdk_app_start方法,会调用spdk_reactors_init,
在这个方法中将会初始化所有需要被初始化的reactors(可以在配置文件中指定需要使用的Core,CPU Core 和reactor是一对一的)。
并且会将g_reactor_state设置为SPDK_REACTOR_STATE_INITIALIZED。
具体代码如下:

int spdk_reactors_init(void)
{
     // 初始化所有的event mempool
     g_spdk_event_mempool = spdk_mempool_create();
     // 为g_reactors分配内存,g_reactors是一个数组,管理了所有的reactors
     posix_memalign((void **)&g_reactors, 64,  (last_core + 1) * sizeof(struct spdk_reactor));
     // 这里设置了reactor创建线程的方法,之后需要初始化线程的时候将会调用该方法
     spdk_thread_lib_init(spdk_reactor_schedule_thread, sizeof(struct spdk_lw_thread));
     // 对于每一个启动的reactor,将会初始化它们
     // 初始化reactor过程,即为绑定lcore,初始化spdk ring、threads,对rusage无操作
     SPDK_ENV_FOREACH_CORE(i) 
     {
          reactor = spdk_reactor_get(i);
          spdk_reactor_construct(reactor, i);
     }
     // 设置好状态返回
     g_reactor_state = SPDK_REACTOR_STATE_INITIALIZED;
     return 0;
}

在进入SPDK_REACTOR_STATE_INITIALIZED状态且spdk_app_start在创建了自己的线程并绑定到了reactors后,
会调用spdk_reactors_start方法并将g_reactor_state设置为SPDK_REACTOR_STATE_RUNNING状态并会创建所有reactor的线程且轮询。

void spdk_reactors_start(void) 
{
    SPDK_ENV_FOREACH_CORE(i) 
    {
         if (i != current_core) 
         {    
            // 在非master reactor中
            reactor = spdk_reactor_get(i); // 得到相应的reactor
            // 设置好线程创建后的一个消息,该消息为轮询函数
            rc = spdk_env_thread_launch_pinned(reactor->lcore, _spdk_reactor_run, reactor);            
            // reactor创建好线程并且会自动执行第一个消息
            spdk_thread_create(thread_name, tmp_cpumask);
         }
    }
    // 当前CPU core得到reactor,并且开始轮询
    reactor = spdk_reactor_get(current_core);
    _spdk_reactor_run(reactor);
}

之前提到spdk_reactors_init方法中调用了spdk_thread_lib_init方法传入了创建thread的spdk_reactor_schedule_thread方法,
在调用spdk_thread_create会回调该方法。这个方法它主要的功能就是告诉这个新创建的线程绑定创建该线程的reactor。

spdk_reactor_schedule_thread(struct spdk_thread *thread)
{
    // 得到该线程设置的cpu mask
    cpumask = spdk_thread_get_cpumask(thread);
    for (i = 0; i < spdk_env_get_core_count(); i++) 
    {
        // 遍历cpu core
        // 通过cpu mask找到对应的核心,并产生event
        if (spdk_cpuset_get_cpu(cpumask, core))
        {
            evt = spdk_event_allocate(core, _schedule_thread, lw_thread, NULL);
            break;
        }
    }
    // 传递该event,即对应的reatcor会调用_schedule_thread方法,
    spdk_event_call(evt);
}
_schedule_thread(void *arg1, void *arg2)
{
     struct spdk_lw_thread *lw_thread = arg1;
     struct spdk_reactor *reactor;
     // 消息传递到对应的reactor后将该thread加入到reactor中
     reactor = spdk_reactor_get(spdk_env_get_current_core());
     TAILQ_INSERT_TAIL(&reactor->threads, lw_thread, link);
}

在SPDK_REACTOR_STATE_RUNNING后,此时所有reactor就进入了轮询状态。_spdk_reactor_run函数为线程提供了轮询方法:

static int _spdk_reactor_run(void *arg) 
{
     while (1) 
     {
         // 处理reactor上的event消息,消息会在之后讲到
         _spdk_event_queue_run_batch(reactor);
         // 每一个reactor上注册的thread进行遍历并且处理poller事件
         TAILQ_FOREACH_SAFE(lw_thread, &reactor->threads, link, tmp) 
         {
              rc = spdk_thread_poll(thread, 0, now);
         }

         // 检查reactor的状态
         if (g_reactor_state != SPDK_REACTOR_STATE_RUNNING)  break;
    }
}

在这之后,主线程的_spdk_reactor_run会返回到spdk_reactors_start中,
并将g_reactor_state赋值为SPDK_REACTOR_STATE_SHUTDOWN,返回到spdk_app_start中等待应用退出。

(二)SPDK线程

1.2 thread

当Reactors进行轮询时,除了处理自己的事件消息之外,还会调用注册在该reactor下面的每一个线程进行轮询。不过通常一个reactor只有一个thread,在spdk应用中,更多的是注册多个poller而不是注册多个thread。具体的轮询方法为:

int spdk_thread_poll(struct spdk_thread *thread, uint32_t max_msgs, uint64_t now)
{
    // 首先先处理ring传递过来的消息
    msg_count = _spdk_msg_queue_run_batch(thread, max_msgs);

    // 调用非定时poller中的方法
    TAILQ_FOREACH_REVERSE_SAFE(poller, &thread->active_pollers,                                                       active_pollers_head, tailq, tmp) 
    {
        // 调用poller注册的方法之前,会对poller状态检测且转换
        if (poller->state == SPDK_POLLER_STATE_UNREGISTERED) 
        {
            TAILQ_REMOVE(&thread->active_pollers, poller, tailq);
            free(poller);
            continue;
        }

        poller->state = SPDK_POLLER_STATE_RUNNING;
        // 调用poller注册的方法
        poller_rc = poller->fn(poller->arg);
        // poller转换状态
        poller->state = SPDK_POLLER_STATE_WAITING;
    }

    // 调用定时poller中的方法
    TAILQ_FOREACH_SAFE(poller, &thread->timer_pollers, tailq, tmp) 
    {
        // 类似非定时poller过程,不过会检查是否到了预定的时间
        if (now < poller->next_run_tick)  break;
    }
    // 最后统计时间
}

(三)NVMe-oF实例

nvmf_tgt 是spdk中一个重要的模块,这里详细的写一下它作为一个target实例是如何使用thread、io_device以及io_channel的。
在spdk应用刚启动的时候,reactor模块就会自动加载起来,然后在加载nvmf subsystem的时候,
会调用spdk_nvmf_subsystem_init(lib/event/subsystems/nvmf/nvmf_tgt.c)方法,nvmf_tgt其实也是有生命周期,并且有一个状态机去管理它的生命周期。

enum nvmf_tgt_state {
        NVMF_TGT_INIT_NONE = 0,
        NVMF_TGT_INIT_PARSE_CONFIG, 
        NVMF_TGT_INIT_CREATE_POLL_GROUPS, 
        NVMF_TGT_INIT_START_SUBSYSTEMS, 
        NVMF_TGT_INIT_START_ACCEPTOR,     
        NVMF_TGT_RUNNING,                              
        NVMF_TGT_FINI_STOP_SUBSYSTEMS,
        NVMF_TGT_FINI_DESTROY_POLL_GROUPS,
        NVMF_TGT_FINI_STOP_ACCEPTOR,
        NVMF_TGT_FINI_FREE_RESOURCES,
        NVMF_TGT_STOPPED,
        NVMF_TGT_ERROR,
};

首先在NVMF_TGT_INIT_PARSE_CONFIG状态中,nvmf_tgt会去解析启动时传入的配置文件,
当解析了[nvmf]这个label后,会调用spdk_nvmf_tgt_create这个方法,
这个方法将初始化了全局的g_nvmf_tgt变量,同时也将tgt注册成了一个io_device。

spdk_io_device_register(tgt,
  spdk_nvmf_tgt_create_poll_group,
  spdk_nvmf_tgt_destroy_poll_group,
  sizeof(struct spdk_nvmf_poll_group),
  "nvmf_tgt");

spdk_nvmf_tgt_create_poll_group和spdk_nvmf_tgt_destroy_poll_group是io_channel创建和销毁的回调方法。
第三个参数是io_channel_ctx的size,既然这里传入了spdk_nvmf_poll_group的大小,那么很明显说明在nvmf中io_channel_ctx对象就是spdk_nvmf_poll_group。
当config文件解析完了之后,nvmf_tgt状态到了NVMF_TGT_INIT_CREATE_POLL_GROUPS,这个状态下会为每一个线程都创建相应的poll group。

spdk_for_each_thread(nvmf_tgt_create_poll_group,NULL,nvmf_tgt_create_poll_group_done);
static void nvmf_tgt_create_poll_group(void *ctx)
{
        struct nvmf_tgt_poll_group *pg;.
        pg->thread = spdk_get_thread();
        pg->group = spdk_nvmf_poll_group_create(g_spdk_nvmf_tgt);.
}

再看spdk_nvmf_poll_group_create中,

struct spdk_nvmf_poll_group * spdk_nvmf_poll_group_create(struct spdk_nvmf_tgt *tgt)
{
        struct spdk_io_channel *ch;
        ch = spdk_get_io_channel(tgt);.
        return spdk_io_channel_get_ctx(ch);
}

在spdk_get_io_channel中,会先去检查传入的io_device是不是已经注册好了的,
如果已经注册了,将会创建一个新的io_channel返回,
创建的过程会回调在注册io_device时注册的io_channel创建方法(即方法spdk_nvmf_tgt_create_poll_group)。

static int spdk_nvmf_tgt_create_poll_group(void *io_device, void *ctx_buf)
{
        // ….. 
        // 初始化transport 、nvmf subsystem等
       // 注册一个poller
       group->poller = spdk_poller_register(spdk_nvmf_poll_group_poll, group, 0);
       group->thread = spdk_get_thread();
        return 0;
}

在spdk_nvmf_poll_group_poll中,因为spdk_nvmf_poll_group对象中有transport的poll group,所以它会调用对应的transport的poll_group_poll方法,
比如rdma的poll_group_poll就会轮询rdma注册的poller处理每个在相应的qpair来的请求,进入rdma的状态机将请求处理好。
然后这个状态就结束了,之后再初始化好了nvmf subsystem相关的东西之后,到了状态NVMF_TGT_INIT_START_ACCEPTOR。
在这个状态中,只注册了一个poller。

g_acceptor_poller = spdk_poller_register(acceptor_poll, g_spdk_nvmf_tgt,g_spdk_nvmf_tgt_conf->acceptor_poll_rate);

这个poller调用的transport的方法,不断的监听是不是有新的fd连接进来,如果有就调用new_qpair的回调。

P3

总结
spdk thread 模型是spdk无锁化的基础,在一个线程中,当分配一个任务后,一直会运行到任务结束为止,这确保了不需要进行线程之间的切换而带来额外的损耗。
同时,高效的spdk ring提供了不同线程之间的消息传递,这就使得任务结束的结果可以高效的传递给别的处理线程。
而io_device和io_channel的设计保证了资源的抽象访问以及独立的路径不去争抢资源池,并且块设备由于是对块进行操作的所以也十分适合抽象成io_device。
正是因为以上几点才让spdk线程模型能够达到无锁化且为多个target提供了基础线程框架的支持。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值