内容目录
背景
不管什么协程库,最根本就是切换代码,类似c语言的关键词goto, 本质就是在汇编层切换eip,所以建议记住这句话,其他都是在这个前提写代码。
环境
windows 开发环境,我基本在windows开发,偶尔写linux 服务器写一下c++ 简单的代码,所以我的开发环境就是windows,我用vs2019进行开发。
技术点
- 使用SetThreadContext 和 GetThreadContext
简单需求1
我们使用SetThreadContext 和 GetTrheadContext 实现goto 跳转。
代码
void test(){
int i = 0;
HANDLE h = GetCurrentThread();
CONTEXT context;
GetThreadCOntext(h, context);
if(i > 0){
printf("i > 0")
}
i = 1;
SetThreadContext(h, context);
printf("end");
}
这个代码其实错误的,没有任何作用
可以跑的代码
这个是 __stdcall ,不然下一个地址不对,所以一定要这么设置,不然add esp,0x4 导致堆栈不对
void __stdcall getEip(int& eip_value) {
int r = 0;
__asm {
mov eax, [ebp + 0x4];
mov r, eax;
}
eip_value = r;
}
void testContext() {
int i = 0;
HANDLE h = GetCurrentThread();
CONTEXT context;
int eip_value = 0x0;
int esp_value = 0x0;
int ebp_value = 0x0;
context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(h, &context);
getEip(eip_value);
__asm {
mov esp_value, esp;
mov ebp_value, ebp;
}
if (i > 0) {
printf("i > 0");
return;
}
i = 1;
context.Eip = eip_value;
context.Esp = esp_value;
context.Ebp = ebp_value;
context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(h, &context);
printf("end");
}
void testContext2() {
int i = 0;
int eip_value = 0x0;
int esp_value = 0x0;
int ebp_value = 0x0;
getEip(eip_value);
if (i > 0) {
printf("i > 0");
return;
}
i = 1;
__asm {
jmp eip_value;
}
printf("end");
}
int main()
{
testContext2();
getchar();
return 0;
}
知识点
- GetTrheadContext和SetThreadContext 调用应该是要在别的线程里面,一般前提都是把别的线程暂停掉,然后获取当前运行的eip(rip)指针。如果当前线程获取,就是GetTrheadContext这个函数里面eip,其实它里面调用系统api,获取eip(rip)指向这个函数结尾ret.
- getEip 必须是stdcall 不然eip指向汇编代码是add esp,0x4,这个函数调用约定,你可以搜索stdcall
- testContext 使用调用SetThreadContext ,testContext2纯汇编实现。
- 这里函数里面,基本不用考虑栈的问题,但协程需要考虑栈的问题,因为c/c++ 这个是函数概念,函数就堆栈概念,汇编也有函数这个说法,栈的作用是用来传递参数(x86汇编,x64基本通过寄存器)和局部变量使用,协程相当于把函数继续切分,我感觉其实协程就当成函数看也是可以的。
- 对机器码来说没有函数概念,它只是执行0101而已,都是上层的规则,计算机语法就是跟堆积木一样,一层层堆积,上层越来越复杂,同时也越来越好用,这个本质跟我们分层开发差不多,我们开发模块的时候,不断分层,封装,不断组合功能和业务。业务组合再组合系统。
目的
- 这个只是为了自己学习协程本质东西,只是用一个很简单东西说明协程的原理,我最开始不明白它怎么切换的,后面找了一些资料和思考才明白,在我没有认识这个协程之前,我一直以为协程之是用线程封装一个东西而已。
- 为了跟好理解协程,我后面会写一个简单系列,用简单代码实现一个简单协程,最重要写清楚原理,并不是为了开发这个协程库,协程对我看来一种更加优雅的异步写法,c#是没有协程概念,但它有异步概念,用await实现确实非常方便,node js用await也非常方便。c# await+ Task(线程)实现效果感觉也不差,不过这个客户端实现,因为客户端对高并发没有要求,对CPU消耗不大,服务器高并发(要求高就不一样),对于用户量巨大的,考虑用协程实现减少线程切换,但大部分基本不用考虑这么多,同时IO复用+异步+线程池+任务队列性能也非常不错了,同时用协程,对调试代码也会代码不方便,如果crash,堆栈只能部分能看,因为堆栈已经别切换了,上一个真正堆栈在哪里,这个也是为什么开发规范为什么要用禁用goto原因,所以本文为什么要模拟实现goto,也是说明协程类似goto,只是可以跨函数调用而已