1. VPP中process 协程节点示意
VPP进程
├── main线程
│ ├── process协程1
│ ├── process协程2
│ └── process协程3
│ └── process协程...
├── worker线程1
└── worker线程2
└── worker线程...
所有的VLIB_NODE_TYPE_PROCESS结点登记的任务均被处理为使用jmp机制的协程。worker线程由 pthread_create 新建是传统意义上的线程模型,每个 worker线程都被绑定到相应的核上。
2. jmp 原理
2.1 jmp栈保存的数据结构定义
struct clib_longjmp_t
X64寄存器
存放 rbx, rbp, r12, r13, r14, r15, eip, rsp 寄存器
源码备注的 eip 寄存器应该为 rdx寄存器
2.2 寄存器简介
clib_longjmp_t 结构体里面有8个寄存器,利用8个寄存器实现跳转功能
编号 | 寄存器 | 作用 |
1 | %rbx | 被调用者保存 |
2 | %rbp | 被调用者保存 |
3 | %r12 | 被调用者保存 |
4 | %r13 | 被调用者保存 |
5 | %r14 | 被调用者保存 |
6 | %r15 | 被调用者保存 |
7 | %rdx | 第三个参数 实际过程中利用它进行中转 |
8 | %rsp | 栈指针 |
•%rax
作为函数返回值使用。
•%rsp
栈指针寄存器,指向栈顶
•%rdi,%rsi,%rdx,%rcx,%r8,%r9
用作函数参数,依次对应第1参数,第2参数...
•%rbx,%rbp,%r12,%r13,%14,%15
用作数据存储,遵循被调用者使用规则,
调用子函数之前要备份它,以防他被修改
•%r10,%r11
用作数据存储,遵循调用者使用规则,使用之前要先保存原值
2.3 重要指令
mov 与 lea区别
lea: load effective address, 将一个内存地址直接赋给目的操作数,例如:lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。而mov指令则恰恰相反,例如:mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。
xchgl 交换寄存器
push :将一个寄存器中的数据入栈
pop :出栈用一个寄存器接收数据
2.4 利用TEST实例说明跳转设计
VPP拽出来的三个文件 longjmp.S,longjmp.h,test_longjmp.c,经过简单修改,点击原文链接下载:
longjmp.S 中三个重要函数:
clib_setjmp
clib_longjmp
clib_calljmp
详细内容注解见示例,并利用GDB查看相关寄存器
示例中如果 f2 设置为0 则会无限循环
3. VPP中 利用jmp 原理实现协程的应用
需要恢复执行的 process 有两种原因:
1.等待的时钟已经到时(即定时器到期)
2.等待的事件已经发生。
process 等待时钟的时候利用epoll机制,让定时器到时后或event发生调度它重新执行
执行的调用代码在 main线程主循环里
一般process 节点都是这样 结构
static uword xxx_process ( ...){ 初始化... while(1) { vlib_process_wait_for_event_or_clock(...); 或 vlib_process_wait_for_event(...); 逻辑执行操作... } return xxx;}
例如:nat_ha_process 节点
3.1 process 节点startup 流程调用栈
-->> vlib_main
-->> -->> vlib_main_loop
-->> -->> -->> vlib_main_or_worker_loop
-->> -->> -->> -->> dispatch_process (while1前)
-->> -->> -->> -->> -->> vlib_process_startup
startup过程 四次 jmp 操作
① clib_setjmp 设置return_longjmp -> ② clib_calljmp 调用 vlib_process_bootstrap 进入相应的process节点(出入点vlib_process_wait_for_event_or_clock/vlib_process_wait_for_event) -> ③ clib_setjmp 设置return_resumejmp -> ④ clib_longjmp 跳到 ①的return_longjmp位置
1. clibsetjmp :设置 p->returnlongjmp返回: VLIB_PROCESS_RETURN_LONGJMP_RETURN位置: vlib_process_startup line 1525
设置 process sarttup 刚进入时候 的jmp 保存到对应process 节点的 p->return_longjmp 位置,开始进入的时候 返回值为 VLIB_PROCESS_RETURN_LONGJMP_RETURN ,调用后
依赖 第四步 longjmp 参数2 (n)返回值
2. clib_calljmp 调用 vlib_process_bootstrap返回: VLIB_PROCESS_RESUME_LONGJMP_SUSPEND 取决于步骤4 clib_longjmp 返回值位置: vlib_process_startup line 1527
调用 vlib_process_bootstrap 函数,进一步调用 process_node 的func (process节点真实运行逻辑)如:vrrp_periodic_process / nat_ha_process 等这些节点循环在开头处都会 调用 vlib_process_wait_forevent/vlib_process_wait_for_event_or_clock
设置逻辑节点 的 栈信息,为以后调用做准备,(步骤3)
3. clib_setjmp :设置 p->resume_longjmp 返回: VLIB_PROCESS_RESUME_LONGJMP_SUSPEND位置: vlib_process_wait_for_event line 604
设置 p->resume_longjmp 即 不同process 节点的调用栈,记录到对应的p(vlib_process_t)结构体中。
4. clib_longjmp 跳转到 p->return_longjmp 位置返回: VLIB_PROCESS_RETURN_LONGJMP_SUSPEND位置: vlib_process_wai_tfor_event line 606
返回最开始处(步骤1)设置的 process 节点返回处调用栈
状态VLIB_PROCESS_RETURN_LONGJMP_SUSPEND 往下继续走:如图
最终process节点初始化完的状态
resume 状态 为 susped
return 状态 为 susped
flags标记VLIB_PROCESS_RESUME_LONGJMP_SUSPEND
VLIB_PROCESS_RETURN_LONGJMP_SUSPEND
3.2 process 节点主循环 流程调用栈
主线程 thread0
-->> vlib_main
-->>-->> vlib_main_loop
-->>-->>-->> vlib_main_or_worker_loop
-->>-->>-->>-->> dispatch_suspended_process (while1中)
-->>-->>-->>-->>-->>-->> vlib_process_resume
startup过程中 四次 jmp 操作
① clib_setjmp 设置return_longjmp -> ② clib_longjmp 跳到startup 步骤3 设置的resume_longjmp位置 -> ③ 执行process 逻辑(出入点vlib_process_wait_for_event_or_clock/vlib_process_wait_for_event)-> ④ clib_setjmp 设置return_resumejmp -> ⑤ clib_longjmp 跳到 ①的return_longjmp位置
1. clibsetjmp :设置 p->returnlongjmp返回: VLIB_PROCESS_RETURN_LONGJMP_RETURN位置: vlib_process_resume line 1540
2. clib_longjmp :跳转到 p->resume_longjmp返回: VLIB_PROCESS_RESUME_LONGJMP_RESUME位置: vlib_process_resume line 1542
跳转到 startup时候 步骤3 设置 p->resume_longjmp 的位置设置 返回值为VLIB_PROCESS_RESUME_LONGJMP_RESUME
3. vlib_process_wait_for_event_or_clock
process 节点 继续从 vlib_process_wait_for_event_or_clock 后继续执行
所有process 节点已进入 while(1)循环 都调用clib_process_wait_for_event_or_clock
转一圈后又调用vlib_process_wait_for_event_or_clock。
4. clibsetjmp :设置 p->resumelongjmp 返回: VLIB_PROCESS_RESUME_LONGJMP_SUSPEND位置:vlib_process_wait_for_event line 604
与startup 时步骤3 一样
设置 p->resume_longjmp 即 不同process 节点的调用栈,记录到对应的p(vlib_process_t)结构体中。
5. cliblongjmp 跳转到 p->returnlongjmp 位置返回:VLIB_PROCESS_RETURN_LONGJMP_SUSPEND位置:vlib_process_wait_for_event line 606
与startup 时步骤4 一样
只不过此时设置 的return_longjmp 不一样,是步骤1中设置的return_longjmp
4. 节点实例
以unix_cli_process 为例
该函数为while (1)的形式,说明该node函数将一直执行直到应用进程退出为止。
而在开始的时候这里先调用了vlib_process_wait_for_event函数,检查是否由事件需要处理。
•检查non_empty_event_type_bitmap是否置位,
•如有说明有需要处理的事件则立刻返回进行处理,不走if里面的流程
•否则说明该process node任然需要等待事件,
•设置标志位 VLIB_PROCESS_IS_SUSPENDED_WAITING_FOR_EVENT
•暂不需要分配CPU时间,可以进入suspend状态。
•所以,这里先记录下当前的位置(记为resume_longjmp),
•然后再跳回return_longjmp所记录的位置,完成一次结点调度过程。
备注:event触发调度 相关使用的是1t_3w_1024sl_ov类型时间轮,另作分析。
原文链接提取码:zsul