2.2.2. VLIB_NODE_TYPE_PROCESS结点
每个process结点是由jump机制构成的一个协程,协程主要用于等待、处理事件。
使用longjmp/setjmp的轻量级多任务协程,由应用进程自行进行调度,不受操作系统调度机制的影响,上下文切换只损耗调用longjmp/setjmp的时间。
协程中运行的函数类似于线程函数,区别在于协程函数Can be suspended, wait for events, be resumed… (based on setjump/longjump).
- Wait for an event
always_inline f64 vlib_process_wait_for_event_or_clock(vlib_main_t *vm, f64 dt) - Send an event
always_inline void vlib_process_signal_event(vlib_main_t *vm, uword node_index, uword type_opaque, uword data)
2.2.2.1. vlib_process_t结构体
协程间的切换最重要的是要保护CPU寄存器和私有栈。
这里clib_longjmp_t中保存上下文切换时CPU寄存器的信息,而相应的栈信息保存在process结构体中stack后续的内存空间中。
2.2.2.2. process node的初始化
在vlib_main_or_worker_loop开始包处理循环之前,main线程会对所有的process结点都调度一次,执行相应的node函数,类似于初始化操作。
在dispatch_process函数中讲调用vlib_process_startup函数,处理所有process结点的初始化工作。
vlib_process_startup先记录每个process结点正常返回时的jmp信息(return_longjmp),之后跳到vlib_process_bootstrap函数中执行process结点函数。
2.2.2.3. process node函数
以下为某个典型的process结点函数:
该函数为while (1)的形式,说明该node函数将一直执行直到应用进程退出为止。
而在开始的时候这里先调用了vlib_process_wait_for_event函数,检查是否由事件需要处理。
该函数先去检查non_empty_event_type_bitmap是否置位,如有说明有需要处理的事件则立刻返回进行处理,否则说明该process node任然需要等待事件,暂不需要分配CPU时间,可以进入suspend状态。
所以,这里先记录下当前的位置(记为resume_longjmp),然后再跳回return_longjmp所记录的位置,完成一次结点调度过程。
这样,初始化时调用的dispatch_process可以先初始化所有的process node,而各process node暂时处于suspend状态,等待某个条件的到来进入resume状态并执行业务逻辑,执行完后退出释放CPU资源。
2.2.3. VLIB_NODE_TYPE_INTERNAL结点
internal结点主要用于处理数据包业务逻辑,结点通过函数流程上与input结点大致相同。static uword sample_node_fn(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
2.2.3.1. 从本node frame中取包
vlib_frame_t *frame -> 由node函数入口传入
U32 *from -> 当前frame中vector的起始数据
from = vlib_frame_vector_args(frame);
调用vlib_frame_vector_args函数返回本node vlib_frame_t中数据包的起始位置,对包进行处理使用from[0],from[1]…
2.2.3.2. to_next指针的获得
vlib_main_t *vm = vlib_get_main ();
u32 next_index = VNET_DEVICE_INPUT_NEXT_ETHERNET_INPUT;
vlib_node_runtime_t *node;
u32 n_left_to_next;
u32 *to_next;
vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);
调用vlib_get_next_frame函数,找到下一node对应的vlib_frame_t中合适的存放数据包的起始位置并保存到to_next
如果要发送数据包,则可以修改to_next[0],to_next[1]……中的数据包索引值
2.2.3.3. 验证处理结果
vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, n_left_to_next, pi0, next0);
next_index -> 默认的下一结点的index
next0 -> 实际的下一个结点的index
当next0 != next_index时,说明该包被正确处理,该宏将do nothing
否则,说明本来该包应去next_index但是经过包处理需要去往其他结点
使得next0 != next_index,该宏会将该包索引pi0发往到next0实际的下一个结点
2.2.3.4. 登记调度
vlib_put_next_frame(vm, node, next_index, n_left_to_next);
所有流程都正确处理完毕后,下一结点的frame上已经有本结点处理过后的数据索引
执行该函数,将相关信息登记到vlib_pending_frame_t中,准备开始调度处理