您当前的位置:首页 > 电脑百科 > 站长技术 > 服务器

Linux服务器之协程技术点

时间:2020-06-09 16:36:27  来源:  作者:

Linux服务器之协程技术点篇

 

协程的实现之原语操作

问题:协程的内部原语操作有哪些?分别如何实现的?

协程的核心原语操作:create, resume, yield。协程的原语操作有create怎么没有exit?以NtyCo为例,协程一旦创建就不能有用户自己销毁,必须得以子过程执行结束,就会自动销毁协程的上下文数据。以_exec执行入口函数返回而销毁协程的上下文与相关信息。co->func(co->arg) 是子过程,若用户需要长久运行协程,就必须要在func函数里面写入循环等操作。所以NtyCo里面没有实现exit的原语操作。

create:创建一个协程。

1. 调度器是否存在,不存在也创建。调度器作为全局的单例。将调度器的实例存储在线程的私有空间pthread_setspecific。

2. 分配一个coroutine的内存空间,分别设置coroutine的数据项,栈空间,栈大小,初始状态,创建时间,子过程回调函数,子过程的调用参数。

3. 将新分配协程添加到就绪队列 ready_queue中

实现代码如下:

int nty_coroutine_create(nty_coroutine **new_co, proc_coroutine func, void *arg) {
 
    assert(pthread_once(&sched_key_once, nty_coroutine_sched_key_creator) == 0);
    nty_schedule *sched = nty_coroutine_get_sched();
 
    if (sched == NULL) {
        nty_schedule_create(0);
        
        sched = nty_coroutine_get_sched();
        if (sched == NULL) {
            printf("Failed to create schedulern");
            return -1;
        }
    }
 
    nty_coroutine *co = calloc(1, sizeof(nty_coroutine));
    if (co == NULL) {
        printf("Failed to allocate memory for new coroutinen");
        return -2;
    }
 
    //
    int ret = posix_memalign(&co->stack, getpagesize(), sched->stack_size);
    if (ret) {
        printf("Failed to allocate stack for new coroutinen");
        free(co);
        return -3;
    }
 
    co->sched = sched;
    co->stack_size = sched->stack_size;
    co->status = BIT(NTY_COROUTINE_STATUS_NEW); //
    co->id = sched->spawned_coroutines ++;
co->func = func;
 
    co->fd = -1;
co->events = 0;
 
    co->arg = arg;
    co->birth = nty_coroutine_usec_now();
    *new_co = co;
 
    TAILQ_INSERT_TAIL(&co->sched->ready, co, ready_next);
 
    return 0;
}

 

yield: 让出CPU。

void nty_coroutine_yield(nty_coroutine *co)

参数:当前运行的协程实例

调用后该函数不会立即返回,而是切换到最近执行resume的上下文。该函数返回是在执行resume的时候,会有调度器统一选择resume的,然后再次调用yield的。resume与yield是两个可逆过程的原子操作。

resume:恢复协程的运行权

int nty_coroutine_resume(nty_coroutine *co)

参数:需要恢复运行的协程实例

调用后该函数也不会立即返回,而是切换到运行协程实例的yield的位置。返回是在等协程相应事务处理完成后,主动yield会返回到resume的地方。

协程的实现之切换

问题:协程的上下文如何切换?切换代码如何实现?

 

首先来回顾一下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 用作数据存储,就是使用前要先保存原值。

 

上下文切换,就是将CPU的寄存器暂时保存,再将即将运行的协程的上下文寄存器,分别mov到相对应的寄存器上。此时上下文完成切换。如下图所示:

Linux服务器之协程技术点篇

 

切换_switch函数定义:

int _switch(nty_cpu_ctx *new_ctx, nty_cpu_ctx *cur_ctx);

参数1:即将运行协程的上下文,寄存器列表

参数2:正在运行协程的上下文,寄存器列表

我们nty_cpu_ctx结构体的定义,为了兼容x86,结构体项命令采用的是x86的寄存器名字命名。

typedef struct _nty_cpu_ctx {
    void *esp; //
    void *ebp;
    void *eip;
    void *edi;
    void *esi;
    void *ebx;
    void *r1;
    void *r2;
    void *r3;
    void *r4;
    void *r5;
} nty_cpu_ctx;

 

_switch返回后,执行即将运行协程的上下文。是实现上下文的切换

 

_switch的实现代码:

0: __asm__ (
1: "    .text                                  n"
2: "       .p2align 4,,15                                   n"
3: ".globl _switch                                          n"
4: ".globl __switch                                         n"
5: "_switch:                                                n"
6: "__switch:                                               n"
7: "       movq %rsp, 0(%rsi)      # save stack_pointer     n"
8: "       movq %rbp, 8(%rsi)      # save frame_pointer     n"
9: "       movq (%rsp), %rax       # save insn_pointer      n"
10: "       movq %rax, 16(%rsi)                              n"
11: "       movq %rbx, 24(%rsi)     # save rbx,r12-r15       n"
12: "       movq %r12, 32(%rsi)                              n"
13: "       movq %r13, 40(%rsi)                              n"
14: "       movq %r14, 48(%rsi)                              n"
15: "       movq %r15, 56(%rsi)                              n"
16: "       movq 56(%rdi), %r15                              n"
17: "       movq 48(%rdi), %r14                              n"
18: "       movq 40(%rdi), %r13     # restore rbx,r12-r15    n"
19: "       movq 32(%rdi), %r12                              n"
20: "       movq 24(%rdi), %rbx                              n"
21: "       movq 8(%rdi), %rbp      # restore frame_pointer  n"
22: "       movq 0(%rdi), %rsp      # restore stack_pointer  n"
23: "       movq 16(%rdi), %rax     # restore insn_pointer   n"
24: "       movq %rax, (%rsp)                                n"
25: "       ret                                              n"
26: );
 

按照x86_64的寄存器定义,%rdi保存第一个参数的值,即new_ctx的值,%rsi保存第二个参数的值,即保存cur_ctx的值。X86_64每个寄存器是64bit,8byte。

Movq %rsp, 0(%rsi) 保存在栈指针到cur_ctx实例的rsp项

Movq %rbp, 8(%rsi)

Movq (%rsp), %rax #将栈顶地址里面的值存储到rax寄存器中。Ret后出栈,执行栈顶

Movq %rbp, 8(%rsi) #后续的指令都是用来保存CPU的寄存器到new_ctx的每一项中

Movq 8(%rdi), %rbp #将new_ctx的值

Movq 16(%rdi), %rax #将指令指针rip的值存储到rax中

Movq %rax, (%rsp) # 将存储的rip值的rax寄存器赋值给栈指针的地址的值。

Ret # 出栈,回到栈指针,执行rip指向的指令。

上下文环境的切换完成

 协程的实现之定义

问题:协程如何定义? 调度器如何定义?

先来一道设计题:

设计一个协程的运行体R与运行体调度器S的结构体

1. 运行体R:包含运行状态{就绪,睡眠,等待},运行体回调函数,回调参数,栈指针,栈大小,当前运行体

2. 调度器S:包含执行集合{就绪,睡眠,等待}

这道设计题拆分两个个问题,一个运行体如何高效地在多种状态集合更换。调度器与运行体的功能界限。

 运行体如何高效地在多种状态集合更换

新创建的协程,创建完成后,加入到就绪集合,等待调度器的调度;协程在运行完成后,进行IO操作,此时IO并未准备好,进入等待状态集合;IO准备就绪,协程开始运行,后续进行sleep操作,此时进入到睡眠状态集合。

就绪(ready),睡眠(sleep),等待(wait)集合该采用如何数据结构来存储?

就绪(ready)集合并不没有设置优先级的选型,所有在协程优先级一致,所以可以使用队列来存储就绪的协程,简称为就绪队列(ready_queue)。

睡眠(sleep)集合需要按照睡眠时长进行排序,采用红黑树来存储,简称睡眠树(sleep_tree)红黑树在工程实用为<key, value>, key为睡眠时长,value为对应的协程结点。

等待(wait)集合,其功能是在等待IO准备就绪,等待IO也是有时长的,所以等待(wait)集合采用红黑树的来存储,简称等待树(wait_tree),此处借鉴Nginx的设计。

数据结构如下图所示:

Linux服务器之协程技术点篇

 

Coroutine就是协程的相应属性,status表示协程的运行状态。sleep与wait两颗红黑树,ready使用的队列,比如某协程调用sleep函数,加入睡眠树(sleep_tree),status |= S即可。比如某协程在等待树(wait_tree)中,而IO准备就绪放入ready队列中,只需要移出等待树(wait_tree),状态更改status &= ~W即可。有一个前提条件就是不管何种运行状态的协程,都在就绪队列中,只是同时包含有其他的运行状态。

 

调度器与协程的功能界限

每一协程都需要使用的而且可能会不同属性的,就是协程属性。每一协程都需要的而且数据一致的,就是调度器的属性。比如栈大小的数值,每个协程都一样的后不做更改可以作为调度器的属性,如果每个协程大小不一致,则可以作为协程的属性。

用来管理所有协程的属性,作为调度器的属性。比如epoll用来管理每一个协程对应的IO,是需要作为调度器属性。

按照前面几章的描述,定义一个协程结构体需要多少域,我们描述了每一个协程有自己的上下文环境,需要保存CPU的寄存器ctx;需要有子过程的回调函数func;需要有子过程回调函数的参数 arg;需要定义自己的栈空间 stack;需要有自己栈空间的大小 stack_size;需要定义协程的创建时间 birth;需要定义协程当前的运行状态 status;需要定当前运行状态的结点(ready_next, wait_node, sleep_node);需要定义协程id;需要定义调度器的全局对象 sched。

协程的核心结构体如下:

typedef struct _nty_coroutine {
 
    nty_cpu_ctx ctx;
    proc_coroutine func;
    void *arg;
    size_t stack_size;
 
    nty_coroutine_status status;
    nty_schedule *sched;
 
    uint64_t birth;
    uint64_t id;
 
    void *stack;
 
    RB_ENTRY(_nty_coroutine) sleep_node;
    RB_ENTRY(_nty_coroutine) wait_node;
 
    TAILQ_ENTRY(_nty_coroutine) ready_next;
    TAILQ_ENTRY(_nty_coroutine) defer_next;
 
} nty_coroutine;
 

调度器是管理所有协程运行的组件,协程与调度器的运行关系。

 

Linux服务器之协程技术点篇

 

调度器的属性,需要在保存CPU的寄存器上下文 ctx,可以从协程运行状态yield到调度器运行的。从协程到调度器用yield,从调度器到协程用resume

以下为协程的定义。

typedef struct _nty_coroutine_queue nty_coroutine_queue;
 
typedef struct _nty_coroutine_rbtree_sleep nty_coroutine_rbtree_sleep;
typedef struct _nty_coroutine_rbtree_wait nty_coroutine_rbtree_wait;
 
typedef struct _nty_schedule {
    uint64_t birth;
nty_cpu_ctx ctx;
 
    struct _nty_coroutine *curr_thread;
    int page_size;
 
    int poller_fd;
    int eventfd;
    struct epoll_event eventlist[NTY_CO_MAX_EVENTS];
    int nevents;
 
    int num_new_events;
 
    nty_coroutine_queue ready;
    nty_coroutine_rbtree_sleep sleeping;
    nty_coroutine_rbtree_wait waiting;
 
} nty_schedule;

协程的实现之调度器

问题:协程如何被调度?

调度器的实现,有两种方案,一种是生产者消费者模式,另一种多状态运行。

 生产者消费者模式

 

Linux服务器之协程技术点篇

 

逻辑代码如下:

while (1) {
 
        //遍历睡眠集合,将满足条件的加入到ready
        nty_coroutine *expired = NULL;
        while ((expired = sleep_tree_expired(sched)) != ) {
            TAILQ_ADD(&sched->ready, expired);
        }
 
        //遍历等待集合,将满足添加的加入到ready
        nty_coroutine *wait = NULL;
        int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1);
        for (i = 0;i < nready;i ++) {
            wait = wait_tree_search(events[i].data.fd);
            TAILQ_ADD(&sched->ready, wait);
        }
 
        // 使用resume回复ready的协程运行权
        while (!TAILQ_EMPTY(&sched->ready)) {
            nty_coroutine *ready = TAILQ_POP(sched->ready);
            resume(ready);
        }
    }
 

多状态运行

 

Linux服务器之协程技术点篇

 

实现逻辑代码如下:

while (1) {
 
        //遍历睡眠集合,使用resume恢复expired的协程运行权
        nty_coroutine *expired = NULL;
        while ((expired = sleep_tree_expired(sched)) != ) {
            resume(expired);
        }
 
        //遍历等待集合,使用resume恢复wait的协程运行权
        nty_coroutine *wait = NULL;
        int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1);
        for (i = 0;i < nready;i ++) {
            wait = wait_tree_search(events[i].data.fd);
            resume(wait);
        }
 
        // 使用resume恢复ready的协程运行权
        while (!TAILQ_EMPTY(sched->ready)) {
            nty_coroutine *ready = TAILQ_POP(sched->ready);
            resume(ready);
        }
    }


Tags:Linux服务器   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
今日总监说32 服务器 磁盘占用率 超过80%多了 。不对啊 之前才清理过df -h 查看了一番 果然40g 用了33g。看了下历史会爆目录 也就5.3G 之前是服务生成临时下载的附件多。...【详细内容】
2021-12-10  Tags: Linux服务器  点击:(17)  评论:(0)  加入收藏
在Linux系统下如何分享文件呢,你可能会想到用scp、rsync此类的命令,但都需要给出服务器密码,不安全。或者搭建一个ftp、nfs或samba的服务,分配个账号或划分个权限给其它人共享文...【详细内容】
2021-12-08  Tags: Linux服务器  点击:(17)  评论:(0)  加入收藏
远程登录介绍目前是虚拟化、云计算的时代,今后的运维工作大概率接触不到物理服务器,因为大部分中小型互联网公司出于节省成本、便捷性等因素考虑会直接购买阿里云、腾讯云等云...【详细内容】
2021-10-21  Tags: Linux服务器  点击:(96)  评论:(0)  加入收藏
Linux目录结构说明谈到Linux文件管理,首先我们需要了解的是要对文件做些什么事情,其实无非就是对一个文件进行创建、复制、移动、查看、编辑、压缩、查找、删除等等。 例如我...【详细内容】
2021-10-12  Tags: Linux服务器  点击:(50)  评论:(0)  加入收藏
案例:100W 的客户端,每三分钟上传一次数据。数据库如何设计?能够查询所有记录? 官方 MySQL 集群方案MySQL ReplicationMySQL Replication 是 mysql 自带的功能,主从复制是通过重...【详细内容】
2021-08-05  Tags: Linux服务器  点击:(68)  评论:(0)  加入收藏
在我们平常使用的开发或者测试环境,由于系统日志通常是debug级别,难免会碰到磁盘满的情况,这时候怎么办呢? 01&mdash;查找大文件一般来说,我们的系统或者应用通常部署在/opt目录...【详细内容】
2021-03-26  Tags: Linux服务器  点击:(246)  评论:(0)  加入收藏
本文在分析服务器集群实现虚拟网络服务的相关技术上,详细描述了LVS集群中实现的三种IP负载均衡技术(VS/NAT、VS/TUN和VS/DR)的工作原理,以及它们的优缺点。1.前言我们先分析实现...【详细内容】
2021-03-11  Tags: Linux服务器  点击:(104)  评论:(0)  加入收藏
重装服务器必备安装-精Linux查看版本当前操作系统内核信息 uname -a cat /proc/versionLinux查看当前操作系统版本信息3、 cat /etc/issue 或cat /etc/redhat-release(看CentO...【详细内容】
2020-09-01  Tags: Linux服务器  点击:(98)  评论:(0)  加入收藏
1、账号安全基本使用:1 )用 户 信 息 文 件 /etc/passwd root:x:0:0:root:/root:/bin/bashaccount:password:UID:GID:GECOS:directory:shell用户名:密码:用户ID:组ID:用户说明:家目...【详细内容】
2020-08-15  Tags: Linux服务器  点击:(157)  评论:(0)  加入收藏
一、RedHat/CentOS包管理1. 包管理工具RPM(1) 简介最早用在 RedHat的包管理器,现在已经成为Linux常见的包管理系统。RPM包有两种类型: 二进制RPM包 源码RPM包可以在www.rpmfi...【详细内容】
2020-08-12  Tags: Linux服务器  点击:(90)  评论:(0)  加入收藏
▌简易百科推荐
阿里云镜像源地址及安装网站地址https://developer.aliyun.com/mirror/centos?spm=a2c6h.13651102.0.0.3e221b111kK44P更新源之前把之前的国外的镜像先备份一下 切换到yumcd...【详细内容】
2021-12-27  干程序那些事    Tags:CentOS7镜像   点击:(1)  评论:(0)  加入收藏
前言在实现TCP长连接功能中,客户端断线重连是一个很常见的问题,当我们使用netty实现断线重连时,是否考虑过如下几个问题: 如何监听到客户端和服务端连接断开 ? 如何实现断线后重...【详细内容】
2021-12-24  程序猿阿嘴  CSDN  Tags:Netty   点击:(12)  评论:(0)  加入收藏
一. 配置yum源在目录 /etc/yum.repos.d/ 下新建文件 google-chrome.repovim /etc/yum.repos.d/google-chrome.repo按i进入编辑模式写入如下内容:[google-chrome]name=googl...【详细内容】
2021-12-23  有云转晴    Tags:chrome   点击:(7)  评论:(0)  加入收藏
一. HTTP gzip压缩,概述 request header中声明Accept-Encoding : gzip,告知服务器客户端接受gzip的数据 response body,同时加入以下header:Content-Encoding: gzip:表明bo...【详细内容】
2021-12-22  java乐园    Tags:gzip压缩   点击:(9)  评论:(0)  加入收藏
yum -y install gcc automake autoconf libtool makeadduser testpasswd testmkdir /tmp/exploitln -s /usr/bin/ping /tmp/exploit/targetexec 3< /tmp/exploit/targetls -...【详细内容】
2021-12-22  SofM    Tags:Centos7   点击:(7)  评论:(0)  加入收藏
Windows操作系统和Linux操作系统有何区别?Windows操作系统:需支付版权费用,(华为云已购买正版版权,在华为云购买云服务器的用户安装系统时无需额外付费),界面化的操作系统对用户使...【详细内容】
2021-12-21  卷毛琴姨    Tags:云服务器   点击:(6)  评论:(0)  加入收藏
参考资料:Hive3.1.2安装指南_厦大数据库实验室博客Hive学习(一) 安装 环境:CentOS 7 + Hadoop3.2 + Hive3.1 - 一个人、一座城 - 博客园1.安装hive1.1下载地址hive镜像路径 ht...【详细内容】
2021-12-20  zebra-08    Tags:Hive   点击:(9)  评论:(0)  加入收藏
以下是服务器安全加固的步骤,本文以腾讯云的CentOS7.7版本为例来介绍,如果你使用的是秘钥登录服务器1-5步骤可以跳过。1、设置复杂密码服务器设置大写、小写、特殊字符、数字...【详细内容】
2021-12-20  网安人    Tags:服务器   点击:(7)  评论:(0)  加入收藏
项目中,遇到了一个问题,就是PDF等文档不能够在线预览,预览时会报错。错误描述浏览器的console中,显示如下错误:nginx代理服务报Mixed Content: The page at ******** was loaded...【详细内容】
2021-12-17  mdong    Tags:Nginx   点击:(7)  评论:(0)  加入收藏
转自: https://kermsite.com/p/wt-ssh/由于格式问题,部分链接、表格可能会失效,若失效请访问原文密码登录 以及 通过密钥实现免密码登录Dec 15, 2021阅读时长: 6 分钟简介Windo...【详细内容】
2021-12-17  LaLiLi    Tags:SSH连接   点击:(16)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条