Linux高性能网络:协程系列04-协程实现之工作原理

目录

4.1.创建协程

  当我们需要异步调用的时候,我们会创建一个协程。比如accept返回一个新的sockfd,创建一个客户端处理的子过程。再比如需要监听多个端口的时候,创建一个
server的子过程,这样多个端口同时工作的,是符合微服务的架构的。
  创建协程的时候,进行了如何的工作?创建API如下:

int nty_coroutine_create(nty_coroutine \**new_co, proc_coroutine func, void *arg);
  • 参数1:new_co,需要传入空的协程的对象,这个对象是由内部创建的,并且在函数返回的时候,会返回一个内部创建的协程对象。
  • 参数2:func,协程的子过程。当协程被调度的时候,就会执行该函数。
  • 参数3:arg,需要传入到新协程中的参数。

协程不存在亲属关系,都是一致的调度关系,接受调度器的调度。调用create API就会创建一个新协程,新协程就会加入到调度器的就绪队列中。创建的协程具体
步骤会在《协程的实现之原语操作》来描述。

4.2.实现IO异步操作

  大部分的朋友会关心IO异步操作如何实现,在send与recv调用的时候,如何实现异步操作的。
  先来看一下一段代码:

while (1) {
    int nready = epoll_wait(epfd, events, EVENT_SIZE, -1);

    for (i = 0;i < nready;i ++) {

        int sockfd = events[i].data.fd;
        if (sockfd == listenfd) {
            int connfd = accept(listenfd, xxx, xxxx);

            setnonblock(connfd);

            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = connfd;
            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

        } else {

            epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
            recv(sockfd, buffer, length, 0);

            //parser_proto(buffer, length);

            send(sockfd, buffer, length, 0);
            epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, NULL);
        }
    }
}

  在进行IO操作(recv,send)之前,先执行了 epoll_ctl的del操作,将相应的sockfd从epfd中删除掉,在执行完IO操作(recv,send)再进行epoll_ctl的add的动作。这段代码看起来似乎好像没有什么作用。
  如果是在多个上下文中,这样的做法就很有意义了。能够保证sockfd只在一个上下文中能够操作IO的。不会出现在多个上下文同时对一个IO进行操作的。协程的IO异步操作正式是采用此模式进行的。
  把单一协程的工作与调度器的工作的划分清楚,先引入两个原语操作 resume,yield会在《协程的实现之原语操作》来讲解协程所有原语操作的实现,yield就是让出运行,resume就是恢复运行。调度器与协程的上下文切换如下图所示:

在协程的上下文IO异步操作(nty_recv,nty_send)函数,步骤如下:

  1. 将sockfd 添加到epoll管理中;
  2. 进行上下文环境切换,由协程上下文yield到调度器的上下文;
  3. 调度器获取下一个协程上下文。Resume新的协程。

IO异步操作的上下文切换的时序图如下:

4.3.回调协程的子过程

  在create协程后,何时回调子过程?何种方式回调子过程?
  首先来回顾一下x86_64寄存器的相关知识。汇编与寄存器相关知识还会在《协程的实现之切换》继续深入探讨的。x86_64 的寄存器有16个64位寄存器,分别是:

%rax, %rbx,%rcx, %esi, %edi, %rbp, %rsp, %r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15。

  %rax 作为函数返回值使用的;
  %rsp 栈指针寄存器,指向栈顶;
  %rdi, %rsi, %rdx, %rcx, %r8, %r9 用作函数参数,依次对应第1参数,第2参数...
  %rbx, %rbp, %r12, %r13, %r14, %r15 用作数据存储,遵循调用者使用规则,换句话说,就是随便用。调用子函数之前要备份它,以防它被修改;
  %r10, %r11 用作数据存储,就是使用前要先保存原值

  以NtyCo的实现为例,来分析这个过程。CPU有一个非常重要的寄存器叫做EIP,用来存储CPU运行下一条指令的地址。我们可以把回调函数的地址存储到EIP中,将相应的参数存储到相应的参数寄存器中。实现子过程调用的逻辑代码如下:

void _exec(nty_coroutine *co) {
    co->func(co->arg); //子过程的回调函数
}

void nty_coroutine_init(nty_coroutine *co) {
    //ctx 就是协程的上下文
    co->ctx.edi = (void*)co; //设置参数
    co->ctx.eip = (void*)_exec; //设置回调函数入口
    //当实现上下文切换的时候,就会执行入口函数_exec , _exec 调用子过程func
}

更多分享

email: [email protected]
email: [email protected]
email: [email protected]
协程技术交流群:829348971

原文地址:http://blog.51cto.com/240630/2306849

时间: 2024-10-10 06:17:52

Linux高性能网络:协程系列04-协程实现之工作原理的相关文章

Linux内核设计第一周 ——从汇编语言出发理解计算机工作原理

Linux内核设计第一周 ——从汇编语言出发理解计算机工作原理 作者:宋宸宁(20135315) 一.实验过程 图1 编写songchenning5315.c文件 图2 将c文件汇编成32位机器语言 图3 将.s文件中的链接语句删除,获得最后的汇编代码 二.分析堆栈变化情况 三.总结 阐明对“计算机是如何工作的”的理解. ①计算机是依据冯诺依曼体存储结构,依据其核心思想——存储程序计算机工作模型,按程序编排的顺序,一步一步地取出指令,自动地完成指令规定的操作. ②从硬件的角度看,是通过总线连接C

【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻处理该消息,否则就一直等待. Looper中有一个属性: static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 这也就解释了,前面我们所说的我们可以通过ThreadLocal实现Looper

Selenium私房菜系列7 -- 深入了解Selenium RC工作原理(2)【II】

继续前一篇的问题,为什么Selenium RC中的Selenium Server需要以这种代理服务器的形式存在?其实,这和浏览器的“同源策略”(The Same Origin Policy)有关. 一.什么是同源策略  同源策略,它是由Netscape提出的一个著名的安全策略,现在所有的可支持javascript的浏览器都会使用这个策略. 为什么需要同源策略,这里举个例子:    假设现在没有同源策略,会发生什么事情呢?大家知道,JavaScript可以做很多东西,比如:读取/修改网页中某个值.

Selenium私房菜系列6 -- 深入了解Selenium RC工作原理(1)【QQ】

前一篇已经比较详细讲述了如何使用Selenium RC进行Web测试,但到底Selenium RC是什么?或者它由哪几部分组成呢?? 一.Selenium RC的组成: 关于这个问题,我拿了官网上的一幅图来说明这个问题. Selenium RC主要由两部分组成: (1).Selenium Server: Selenium Server负责控制浏览器行为,总的来说,Selenium Server主要包括3个部分:Launcher,Http Proxy,Selenium Core.其中Seleniu

【原创】源码角度分析Android的消息机制系列(六)——Handler的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Handler的定义: /** * A Handler allows you to send and process {@link Message} and Runnable * objects associated with a thread's {@link MessageQueue}. Each Handler * instance is associated with a single thread and that thre

【原创】源码角度分析Android的消息机制系列(四)——MessageQueue的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. MessageQueue,主要包含2个操作:插入和读取.读取操作会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除.虽然MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势. 先看M

【原创】源码角度分析Android的消息机制系列(三)——ThreadLocal的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Android源码(API24)中对ThreadLocal的定义: public class ThreadLocal<T> 即ThreadLoca是一个泛型类,再看对该类的注释: /** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread th

Linux高性能网络:协程系列09-协程性能测试

目录 Linux高性能网络:协程系列01-前言 Linux高性能网络:协程系列02-协程的起源 Linux高性能网络:协程系列03-协程的案例 Linux高性能网络:协程系列04-协程实现之工作原理 Linux高性能网络:协程系列05-协程实现之原语操作 Linux高性能网络:协程系列06-协程实现之切换 Linux高性能网络:协程系列07-协程实现之定义 Linux高性能网络:协程系列08-协程实现之调度器 Linux高性能网络:协程系列09-协程性能测试 [Linux高性能网络:协程系列10

Linux高性能网络:协程系列05-协程实现之原语操作

目录 Linux高性能网络:协程系列01-前言 Linux高性能网络:协程系列02-协程的起源 Linux高性能网络:协程系列03-协程的案例 Linux高性能网络:协程系列04-协程实现之工作原理 Linux高性能网络:协程系列05-协程实现之原语操作 Linux高性能网络:协程系列06-协程实现之切换 Linux高性能网络:协程系列07-协程实现之定义 Linux高性能网络:协程系列08-协程实现之调度器 Linux高性能网络:协程系列09-协程性能测试 [Linux高性能网络:协程系列10

Linux高性能网络:协程系列01-前言

协程 我们只听说过进程和线程,没有听说过协程,协程是个什么东西呢?我们的go语言,node.js语言的实现都使用到了协程,go的高效就不再细说了,那为什么使用了协程会让我们的后台程序变得高效呢?如果我们心里有这些疑问,那么请继续阅读本书和了解我们实现的开源库ntyco. 目录 Linux高性能网络:协程系列01-前言 Linux高性能网络:协程系列02-协程的起源 Linux高性能网络:协程系列03-协程的案例 Linux高性能网络:协程系列04-协程实现之工作原理 Linux高性能网络:协程系