libco——源码分析(一)

发布时间:2024年01月23日

co_enable_hook_sys()

作用:允许当前的函数称为协程,挂在到当前线程;

流程:

  1. 调用GetCurrThreadCo获取当前的stCoRoutine_t* 类型协程结构体co;

    1. 调用co_get_curr_thread_env()获取 当前stCoRoutineEnv_t*类型变量。

      1. 获取TLS变量gCoEnvPerThread;
    2. 调用GetCurrCo(stCoRoutineEnv_t*)获取类型stCoRoutine_t*变量;

      1. 调用gCoEnvPerThread->pCallStack[gCoEnvPerThread->iCallStackSize-1];
      struct stCoRoutineEnv_t
      {
      	stCoRoutine_t *pCallStack[ 128 ];
      	int iCallStackSize;
      	stCoEpoll_t *pEpoll;
      
      	//for copy stack log lastco and nextco
      	stCoRoutine_t* pending_co;
      	stCoRoutine_t* occupy_co;
      };
      
      1. stCoRoutineEnv_t用来存放协程实时运行时状态;
      2. pCallStack应该是目前全部的待运行的协程,iCallStackSize是协程的数量;
  2. 设置返回co->cEnableSysHook = 1;

co_eventloop

入参

  1. stCoEpoll_t* ctx
  2. pfn_co_eventloop_t pfn
  3. void* arg

步骤

N
N
N
Y
Y
开始
ctx->result
给ctx->result赋值
开始循环
co_epoll_wait(ctx->iEpollFd,result,stCoEpoll_t::_EPOLL_SIZE, 1)
取出stCoEpoll_t* ctx中的pstActiveList作为激活队列
取出ctx中的pstTimeoutList作为超时队列
将epoll_wati到的数据放到active的末尾
将所有ctx中直到目前时刻的所有数据加到当前timeout中
将所有timeout的超时值置为true
将timeout加到active末尾
遍历active
调用每个弹出的stTimeoutItem_t的pfnProcess函数
判断是否输入pfn
pfn(arg)==-1
结束

co_enable_hook_sys

获取当前协程栈的最后一个协程结构体,并置cEnableSysHook

coctx_swap

入参

coctx_t* p1

coctx_t* p2

功能

将p1协程切出,将p2协程切入。

索引字节寄存器
0[0,7]r15
1[8,15]r14
2[16,23]r13
3[24,31]r12
4[32,39]r9
5[40,47]r8
6[48,55]rbp
7[56,63]rdi
8[64,71]rsi
9[72,79]返回地址
10[80,87]rdx
11[88,95]rcx
12[96,103]rbx
13[104,112]rsp
.globl coctx_swap
#if !defined( __APPLE__ )
.type  coctx_swap, @function
#endif
coctx_swap:
    leaq (%rsp),%rax      ; rax=rsp
    movq %rax, 104(%rdi)  ; p1->regs[13] = rsp
    movq %rbx, 96(%rdi)   ; p1->regs[12] = rbx
    movq %rcx, 88(%rdi)   ; p1->regs[11] = rcx
    movq %rdx, 80(%rdi)   ; p1->regs[10] = rdx
	  movq 0(%rax), %rax    ; rsp寄存器存储位置的内存为返回指令地址,ret后会写进rip指令寄存器
	  movq %rax, 72(%rdi)   ; p1->regs[9] = ret
    movq %rsi, 64(%rdi)   ; p1->regs[8] = rsi
	  movq %rdi, 56(%rdi)   ; p1->regs[7] = rdi
    movq %rbp, 48(%rdi)   ; p1->regs[6] = rbp
    movq %r8, 40(%rdi)    ; p1->regs[5] = r8
    movq %r9, 32(%rdi)    ; p1->regs[4] = r9
    movq %r12, 24(%rdi)   ; p1->regs[3] = r12
    movq %r13, 16(%rdi)   ; p1->regs[2] = r13
    movq %r14, 8(%rdi)    ; p1->regs[1] = r14
    movq %r15, (%rdi)     ; p1->regs[0] = r15
	  xorq %rax, %rax       ; rax = null

    movq 48(%rsi), %rbp   ; rbp = p2->regs[6]
    movq 104(%rsi), %rsp  ; rsp = p2->regs[13]
    movq (%rsi), %r15     ; r15 = p2->regs[0]
    movq 8(%rsi), %r14    ; r14 = p2->regs[1]
    movq 16(%rsi), %r13   ; r13 = p2->regs[2]
    movq 24(%rsi), %r12   ; r12 = p2->regs[3]
    movq 32(%rsi), %r9    ; r9 = p2->regs[4]
    movq 40(%rsi), %r8    ; r8 = p2->regs[5]
    movq 56(%rsi), %rdi   ; rdi = p2->regs[7], 8是rsi地址,最后赋值,9是ret地址,最后入栈
    movq 80(%rsi), %rdx   ; rdx = p2->regs[10]
    movq 88(%rsi), %rcx   ; rcx = p2->regs[11]
    movq 96(%rsi), %rbx   ; rbx = p2->regs[12]
		leaq 8(%rsp), %rsp    ; rsp退栈8字节
		pushq 72(%rsi)        ; 将返回代码地址ret入栈,这样函数ret后会将该值加载进rip指令寄存器,从而执行原来的代码

    movq 64(%rsi), %rsi   ; rsi = p2->regs[8]
	ret

coctx_make

参数

coctx_t* ctx 新创建的上下文对象

coctx_pfn_t pfn 上下文要执行的函数

const void* s 执行函数参数1

const void* s1 执行函数参数2

作用

初始化协程

int coctx_make(coctx_t* ctx, coctx_pfn_t pfn, const void* s, const void* s1) {
  char* sp = ctx->ss_sp + ctx->ss_size - sizeof(void*);
  sp = (char*)((unsigned long)sp & -16LL);

  memset(ctx->regs, 0, sizeof(ctx->regs));
  void** ret_addr = (void**)(sp);
  *ret_addr = (void*)pfn;

  ctx->regs[kRSP] = sp;                 // 每个co给他一个独立的栈空间

  ctx->regs[kRETAddr] = (char*)pfn;     // 切换协程后的rip地址,这样即可做到切换协程后执行函数

  ctx->regs[kRDI] = (char*)s;           // 入参1地址放入rdi
  ctx->regs[kRSI] = (char*)s1;          // 入参2地址放入rsi
  return 0;
}

co_resume

参数

stCoRoutine_t* co 需要唤醒的协程

作用

void co_resume( stCoRoutine_t *co )
{
	stCoRoutineEnv_t *env = co->env;
	stCoRoutine_t *lpCurrRoutine = env->pCallStack[ env->iCallStackSize - 1 ];		// 取出当前运行的协程
	if( !co->cStart )							// 协程未经初始化则进行协程初始化
	{
		coctx_make( &co->ctx,(coctx_pfn_t)CoRoutineFunc,co,0 );
		co->cStart = 1;
	}
	env->pCallStack[ env->iCallStackSize++ ] = co;	// 新协程压入
	co_swap( lpCurrRoutine, co );				// 切入新协程
}

CoRoutineFunc函数

static int CoRoutineFunc( stCoRoutine_t *co,void * )
{
	if( co->pfn )
	{
		co->pfn( co->arg );				// 此处可能调用co_yield切出
	}
	co->cEnd = 1;						// 执行完毕

	stCoRoutineEnv_t *env = co->env;

	co_yield_env( env );				// 切出

	return 0;
}

co_poll/co_poll_inner

参数

stCoEpoll_t ctx 协程上下文,包含所有当前线程的协程信息

struct pollfd fds[] 检测的文件描述符

nfds_t nfds 文件描述符数量

int timeout_ms 超时时长,毫秒

int	co_poll( stCoEpoll_t *ctx,struct pollfd fds[], nfds_t nfds, int timeout_ms )
{
	return co_poll_inner(ctx, fds, nfds, timeout_ms, NULL);
}

作用

graph TD
	start([开始]) --> judge_timeout_0{"timeout==0"} --Y--> call_pollfunc["pollfunc(fds, nfds, timeout)"] --> finish([结束])
	judge_timeout_0--N-->create_arg["创建stPoll_t类型变量arg"]-->set_epollfd["设置arg.iEpollfd为ctx->iEpollFd"]-->add_epoll["在ctx->iEpollFd上增加监听事件,事件数据为栈中的pPollItem[i]对象,处理函数为OnPollPreparePfn"]-->调用AddTimeout将arg加到计时循环队列中-->add_timeout_ok{增加是否成功}--Y-->yield_env["co_yield_env( co_get_curr_thread_env() )切出当前协程,切入下一个协程..."]-->timeout_occur[\超时检查触发resume,恢复当前协程/]-->rasecnt["记录raisecnt数"]
	subgraph 清除操作
		event_come["从超时队列中取出arg"]-->rewrite_event["回写事件信息到返回数组中"]-->clean_memory["清除分配的内存"]
	end
	rasecnt-->event_come;
	clean_memory-->finish

总结

总体的网络服务器操作流程是这样的:

co_poll co_event_loop other 增加epoll_fd监听文件描述符 将自己的协程增加到计时队列中 发送网络消息或者其他 被激活的文件描述符放到激活队列 loop [check_data] 取出超时队列中超时的任务放到超时队列 将超时队列黏贴到激活队列末尾 激活元素的回调函数处理 resume挂起的协程 loop [check_activated] 写入收到消息的数据并返回上层 loop [epoll_wait] co_poll co_event_loop other
文章来源:https://blog.csdn.net/Dr_Jack/article/details/135778193
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。