既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
for(int i=0;i<cnt;i++) {
task_t * task = (task_t*)calloc( 1,sizeof(task_t) );
task->fd = -1;
co_create( &(task->co),NULL,readwrite_routine,task );
co_resume( task->co );
}
主进程执行cnt次循环,来启动cnt个协程。每次调用co_resume时,主进程便会挂起,直到被激活运行的协程执行到某个步骤后,它主动放弃CPU,把执行权再次交给主进程。
那么在每个task(协程)内,到底做了哪些动作呢?
static void *readwrite_routine( void *arg )
{
co_enable_hook_sys();
task_t *co = (task_t*)arg;
char buf[ 1024 * 16 ];
for(;;)
{
// 初始化阶段,fd为-1,表示当前没有就绪的任务需要处理
if( -1 == co->fd )
{
// 当前协程入队列。这个动作的意义在于,当有任务要处理时,
// 从g\_readwrite里依次取出,并分配任务给他们去执行。
g_readwrite.push( co );
// 挂起当前协程,让出执行权给其他协程。
// 原则很简单,就是让上次挂起的协程执行,可以认为是返回到上次执行的运行点。
co_yield_ct();
continue;
}
...
}
回到主进程,在启动了cnt个task之后的处理:
for(int k=0;k<proccnt;k++) {
....
stCoRoutine_t *accept_co = NULL;
// 启动一个协程专门做accept
co_create( &accept_co,NULL,accept_routine,0 );
// accept协程会一直接受新连接,直到它交出执行权,才会重新回到主进程
co_resume( accept_co );
co_eventloop( co_get_epoll_ct(),0,0 );
exit(0);
}
看看accept_routine的内部:
static void *accept_routine( void * )
{
for(;;) {
// 如果工作协程队列为空,就等待1秒或者等再来事件,重试
if( g_readwrite.empty() ) {
printf("empty\n"); //sleep
struct pollfd pf = { 0 };
pf.fd = -1;
poll( &pf,1,1000);
continue;
}
struct sockaddr_in addr;
memset( &addr,0,sizeof(addr) );
socklen_t len = sizeof(addr);
int fd = co_accept(g_listen_fd, (struct sockaddr *)&addr, &len);
// 未就绪,等待下次事件继续处理
if( fd < 0 ) {
struct pollfd pf = { 0 };
pf.fd = g_listen_fd;
pf.events = (POLLIN|POLLERR|POLLHUP);
// 当前运行在accept协程,co\_poll会在等待事件的时候交出cpu,回到主进程
co_poll( co_get_epoll_ct(),&pf,1,1000 );
continue;
}
// Fun!这里工作协程用尽,直接关闭当前连接...
if( g_readwrite.empty() ) {
close( fd );
continue;
}
// 弹出一个协程,去处理新连接
SetNonBlock( fd );
task_t *co = g_readwrite.top();
co->fd = fd;
g_readwrite.pop();
// 此时执行权会转移到某个线程,知道它交出cpu,当前协程才会再次执行
co_resume( co->co );
}
return 0;
}
当readwrite_routine和accept_routine都会调用co_poll,但是accept会将执行权交给主进程,而task协程挂起后,执行权则会交给accept协程。这里交给accept协程,是为了进行后续的新连接的接收。那么前面由于执行碰到EAGAIN而挂起的task协程,则通过co_eventloop来驱动继续执行。
主进程中通过co_eventloop来调度事件来驱动各个协程的处理。具体的是通过stTimeoutItem_t结构中的pfnProcess来处理的。代码比较直观,就不细说了。
说完了上层的核心逻辑,我们关注下底层。先注意下co_resume函数:
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;
coctx_swap( &(lpCurrRoutine->ctx),&(co->ctx) );
}
其中的调用coctx_make的一些实现涉及到x86_64的架构细节:
16个64位寄存器,分别是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,
%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。
其中:%rax 作为函数返回值使用。%rsp 栈指针寄存器,指向栈顶。%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
有了上面的说明,我们再看coctx_make函数中,regs[RIP]是执行入口点,它的原型:
static int CoRoutineFunc( stCoRoutine_t *co,void * );
而regs[ RDI ]和regs[ RSI ]就是它的两个参数。而运行的栈帧,通过regs[ RBX ]和regs[ RSP ]来指定。
int coctx_make( coctx_t *ctx,coctx_pfn_t pfn,const void *s,const void *s1 )
{
...
ctx->regs[ RBX ] = stack + ctx->ss_size - 1;
ctx->regs[ RSP ] = (char*)(ctx->param) + 8;
ctx->regs[ RIP ] = (char*)pfn;
ctx->regs[ RDI ] = (char*)s;
ctx->regs[ RSI ] = (char*)s1;
return 0;
}
coctx_swap.S:
#define \_esp 0
#define \_eip 4
#define \_rsp 0
#define \_rip 8
#define \_rbx 16
#define \_rdi 24
#define \_rsi 32
.globl coctx_swap
.type coctx_swap, @function
coctx_swap:
leaq -8(%rsp),%rsp
pushq %rbp
pushq %r12
pushq %r13
pushq %r14
pushq %r15
pushq %rdx
pushq %rcx
pushq %r8
pushq %r9
leaq 80(%rsp),%rsp
// rdi是coctx_swap的第一个参数,需要挂起的协程对应的coctx_t结构。
// 最开头的,是5个指针(void \*regs[ 5 ]),用来保存一些寄存器信息
movq %rbx,_rbx(%rdi) // +16 Bytes
movq %rdi,_rdi(%rdi) // +24Bytes
movq %rsi,_rsi(%rdi) // +32Bytes
/* 保存coctx_swap第一个参数的运行状态 \*/
movq (%rsp), %rcx // +8Bytes 保存返回地址
movq %rcx, _rip(%rdi)
leaq 8(%rsp), %rcx // +0Bytes 来保存之前的栈顶
movq %rcx, _rsp(%rdi)
/* 将当前的环境设置为 coctx_swap第二个参数提供的值 \*/
movq _rip(%rsi), %rcx
movq _rsp(%rsi), %rsp
pushq %rcx // 将执行入口点入栈,这样在ret执行之后,会取栈顶指针作为执行点
movq _rbx(%rsi),%rbx
movq _rdi(%rsi),%rdi
movq _rsi(%rsi),%rsi
leaq -80(%rsp),%rsp
popq %r9
popq %r8
popq %rcx
![img](https://img-blog.csdnimg.cn/img_convert/3178ac6c13f1d81d3fd197ff7dbf6f68.png)
![img](https://img-blog.csdnimg.cn/img_convert/915ae4a380f96f5d12189b53d655a3b9.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
)]
[外链图片转存中...(img-AWXFcg3Z-1715673683247)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**