天天看點

6 VPP源碼分析 (VPP中的多線程)

VPP支援多線程模式,其中區分為main線程和worker線程,這兩種線程都運作vlib_main_or_worker_loop函數作為線程主函數,主要差別在于執行時的is_main參數。

主線程可以排程執行所有類型的node,工作線程隻能排程 VLIB_NODE_TYPE_INTERNAL和VLIB_NODE_TYPE_INPUT類型的結點。

在設定工作線程的時候,工作線程數最好和網卡的收包隊列數保持一緻。

6.1 多線程架構

所有的VLIB_NODE_TYPE_PROCESS結點登記的任務均被處理為使用jmp機制的協程。

而worker線程由pthread_create建立是傳統意義上的線程模型,每個worker線程都被綁定到相應的核上。

VPP程序
├── main線程
│      ├── process協程1
│      ├── process協程2
│      └── process協程3
├── worker線程1    
└── worker線程2
           

具體的工作線程初始化操作由

static clib_error_t *start_workers(vlib_main_t *vm)

函數負責

每個線程都有自己的線程堆棧vlib_thread_stacks,mheap,vlib_main_t,nodes,frames,next_frames,pengding frame等資料結構。

6.2. 線程間同步

VPP多線程之間同步采用的是類似于帶信号和逾時機制的自旋鎖,主要有check、sync、release操作。

總體上類似于pthread_cond_timedwait中的互斥體改成自旋鎖所提供的功能,超過BARRIER_SYNC_TIMEOUT時間的話說明可能發生死鎖故直接abort。

其中:

vlib_worker_thread_barrier_check類似于pthread_cond_wait操作,等待vlib_worker_threads->wait_at_barrier條件。

vlib_worker_thread_barrier_sync類似于spin_lock操作,置位vlib_worker_threads->workers_at_barrier。

vlib_worker_thread_barrier_release類似于spin_unlock操作,複位vlib_worker_threads->workers_at_barrier。

6.2.1. vlib_worker_thread_barrier_check

vlib_main_or_worker_loop函數在開始時會調用vlib_worker_thread_barrier_check函數用以檢查線程間的同步情況。

vlib_main_or_worker_loop代碼片段:

while (1) {
    vlib_node_runtime_t *n;

if (!is_main) {
    vlib_worker_thread_barrier_check();
        vec_foreach(fqm, tm->frame_queue_mains)
        vlib_frame_queue_dequeue (vm, fqm);
    }           

如果某個線程申請通路臨界區那麼本worker線程暫時不去處理資料,自旋等待。

vlib_worker_thread_barrier_check代碼片段:

if (PREDICT_FALSE(*vlib_worker_threads->wait_at_barrier)) {             // 某個線程申請通路臨界區
    clib_smp_atomic_add(vlib_worker_threads->workers_at_barrier, 1);    // 本線程将進入自旋狀态,登記workers_at_barrier

    while (*vlib_worker_threads->wait_at_barrier)                            // 本線程進入自旋狀态
        ;

    clib_smp_atomic_add (vlib_worker_threads->workers_at_barrier, -1);    // 本線程将退出自旋狀态,取消登記workers_at_barrier           

4.2.2. vlib_worker_thread_barrier_sync

count = vec_len(vlib_mains) - 1;                 // 這裡count數等于vlib_mains的vector長度 - 1 ,也就是worker線程總數 - 1
deadline = vlib_time_now(vm) + BARRIER_SYNC_TIMEOUT;

*vlib_worker_threads->wait_at_barrier = 1;    //    本線程獲得鎖,是以将該條件變量标志置為1
while (*vlib_worker_threads->workers_at_barrier != count) {
    // 自旋等待其他線程(在其他核上)将變量vlib_worker_threads->workers_at_barrier設為count
// 當count等于workers_at_barrier時,其他的worker線程均處于自旋狀态,也就是其他線程被鎖住操作,本線程可以開始通路臨界區

    if (vlib_time_now(vm) > deadline) {
      fformat(stderr, "%s: worker thread deadlock\n", __FUNCTION__);
      os_panic();
    }
}           

4.2.3. vlib_worker_thread_barrier_release

deadline = now + BARRIER_SYNC_TIMEOUT;

*vlib_worker_threads->wait_at_barrier = 0;    //    本線程釋放鎖,是以将該條件變量标志置為0
while (*vlib_worker_threads->workers_at_barrier > 0) {
    // 自旋等待其他線程(在其他核上)将變量vlib_worker_threads->workers_at_barrier設為0
// 當count等于0時,其他的worker線程均處于工作狀态,所有線程均退出臨界區

    if ((now = vlib_time_now (vm)) > deadline) {
        fformat (stderr, "%s: worker thread deadlock\n", __FUNCTION__);
        os_panic ();
    }
}           

繼續閱讀