协程的实现之切换

协程的实现之切换

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

首先来回顾一下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到相对应的寄存器上。此时上下文完成切换。如下图所示:
在这里插入图片描述
切换_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指向的指令。
上下文环境的切换完成。

未完待续~
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libevent是一个开源的网络IO框架,它使用了事件驱动的方式来实现高效的网络处理。协程是一种轻量级的线程,可以在单线程中实现多个任务的并发执行,可以用来实现libevent中的并发处理。 在使用协程实现libevent时,我们需要做以下步骤: 1. 定义事件和回调函数 我们需要定义事件和回调函数,用来处理网络连接和数据传输。在协程模型中,我们需要将回调函数封装为一个协程,用来实现并发处理。可以使用协程库(如libco)提供的API来创建协程。 2. 初始化事件循环 我们需要初始化事件循环,用来监听网络事件并调用回调函数。在协程模型中,我们可以使用一个无限循环来实现事件循环,并在每个循环中切换到不同的协程来处理事件。 3. 注册事件回调函数 我们需要注册事件回调函数,使它们可以被事件循环监听到。在协程模型中,我们可以将事件回调函数封装为一个协程,并将其注册到事件循环中。 4. 运行事件循环 最后,我们需要运行事件循环,开始监听和处理网络事件。在协程模型中,我们可以使用协程库提供的API来启动事件循环,并在其中调用切换协程的函数,以实现并发处理。 下面是一个简单的使用协程实现libevent的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "co.h" #include "event.h" // 回调函数1 void* handle_client(void* arg) { int client_fd = (int)arg; char buf[1024]; int n = recv(client_fd, buf, sizeof(buf), 0); if (n > 0) { buf[n] = '\0'; printf("Received data: %s\n", buf); send(client_fd, buf, n, 0); } close(client_fd); // 切换回事件循环协程 co_yield_ct(); } // 回调函数2 void* handle_accept(void* arg) { int server_fd = (int)arg; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("accept"); return NULL; } // 创建协程来处理客户端连接 int ret = co_create(handle_client, (void*)client_fd); if (ret < 0) { fprintf(stderr, "co_create error\n"); close(client_fd); return NULL; } // 切换回事件循环协程 co_yield_ct(); } // 初始化事件循环 event_base_t* init_event() { event_base_t* base = event_base_new(); if (!base) { return NULL; } return base; } // 注册事件回调函数 int register_events(event_base_t* base, int server_fd) { // 注册accept事件 event_t* ev_accept = event_new(base, server_fd, EV_READ|EV_PERSIST, handle_accept, (void*)server_fd); if (!ev_accept) { return -1; } event_add(ev_accept, NULL); return 0; } int main() { const char* ip = "0.0.0.0"; int port = 8888; // 创建服务器套接字 int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); exit(1); } // 绑定服务器地址和端口 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(ip); server_addr.sin_port = htons(port); int ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (ret < 0) { perror("bind"); exit(1); } // 开始监听客户端连接 ret = listen(server_fd, 128); if (ret < 0) { perror("listen"); exit(1); } // 初始化事件循环 event_base_t* base = init_event(); if (!base) { fprintf(stderr, "init_event error\n"); exit(1); } // 注册事件回调函数 ret = register_events(base, server_fd); if (ret < 0) { fprintf(stderr, "register_events error\n"); exit(1); } // 运行事件循环 event_base_loop(base, 0); return 0; } ``` 在上面的代码中,我们使用了libco和libevent两个库来实现协程和事件处理。在handle_accept函数中,当有客户端连接时,我们使用co_create函数创建一个新协程来处理客户端连接。在handle_client函数中,我们处理客户端发送的数据,并使用co_yield_ct函数切换回事件循环协程。在main函数中,我们初始化事件循环并注册事件回调函数,并使用event_base_loop函数开始事件循环。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值