state thread库阅读笔记(一)

    一直以来,读过的代码和做过的项目都没有什么笔记,以至于很多学过的看过的,到后面居然记不起来,所以好记性不如烂笔头,好好总结,画图记录下来,总是有好处的,而且有很多东西也是没有去深入研究。

    之前有幸在学习srs服务器中,接触到了state thread库,并且将srs改造成了支持多线程,多协程的版本,让srs不再只使用单一cpu,发挥多核优势的同时保持多协程的高效处理方式。

    想想多协程在高并发中还是很好的处理方式,对一个网络请求来说,只要能将请求正确处理并返回,中间能不切换线程尽量不切换线程,这样能省下大量CPU时间,线程切换虽然不需要切换页表,但是线程间经常需要做事件等待,阻塞等消耗CPU的操作,因此引入了协程的概念,在应用层面实现类似CPU的多线程,主要用到了setjmp及longjmp系统调用。

    setjmp及longjmp是C语言标准库中的函数,所以任何操作系统都支持。

    int setjmp(jmp_buf envbuf):它将当前程序的栈内容保存到jmp_buf中,对于一段代码而言,如果不切换页表,那么能让它还原需要保存的变量主要应该是一些CPU寄存器,比如X86 CPU包括:CS,IP,EAX,EBX,ECX,EDX,SS,ESP,DS,ES,FS等【以后有时间再研究此结构体具体定义】。

    定义:

    typedef size_t jmp_buf[6];

    int setjmp(size_t* buf);

    void longjmp(size_t* buf, int);

setjmp源码:

setjmp:

mov eax, [esp+4]

mov [eax], ebx

mov [eax+4], ebp

mov [eax+8], esi

mov [eax+12], edi

mov [eax+16], esp

mov ecx, [esp]

mov [eax+20], ecx

mov eax, 0

ret

其实也比较简单,如下图:


图1 setjmp调用栈

setjmp调用后,按照C语言的生成方法,先push 参数,也就是jmp_buf(其实是个指针)到stack中,也就是图1中的P1;然后再执行jmp汇编指令,该指令会将eip存入栈中(由于是短跳转,所以CS不必要入栈),所以ESP+4指向的就是P1,也就是jmp_buf指针存放地址;

mov eax, jmp_buf

push eax

call $setjmp

add esp,$4 #还原栈

#上面这段应该是C语言调用方法

setjmp:

mov eax, [esp+4]   #取到jmp_buf指针存到eax中

mov [eax], ebx    #ebx存到jmp_buf[0]中(假设jmp_buf是DWORD*类型);

mov [eax+4], ebp #ebp存到jmp_buf[1]中

mov [eax+8], esi #esi存到jmp_buf[2]中

mov [eax+12], edi #edi存到jmp_buf[3]中

mov [eax+16], esp #esp存到jmp_buf[4]中

mov ecx, [esp] #EIP存到ECX中

mov [eax+20], ecx  #EIP存到jmp_buf[5]中

mov eax, 0 #c语言规定函数返回值用eax返回,所以这里setjmp返回值为0

ret  #ret后,会将ESP指向的内容存入EIP,ESP会加4,将EIP出栈,所以上面要提前保存EIP到jmp_buf[5]中;

所以setmp就把上述的寄存器保存到jmp_buf结构中了,然后也不管,在返回0的判断后,该干嘛继续干嘛。

再来看看longjmp(longjmp顾名思义,就是长跳转):


图2 longjmp调用栈结构

mov eax, status  #这里应该是一个立即数地址,编译后的,这里只是示意

push eax

mov eax, jmp_buf 

push eax

call $longjmp

#上面这段应该是C语言函数调用实现,忘记X86汇编了,有错误请指正

longjmp:

mov eax, [esp+8]   #ESP指向EIP,ESP+4指向jmp_buf,ESP+8指向status

mov ecx, [esp+4]   #取出jmp_buf,存入ecx

mov esp, [ecx+16] #将jmp_buf[4]还原到esp中,此时esp就指回了原来setjmp时的栈

mov ebx, [ecx]       #将jmp_buf[0]还原到ebx中

mov ebp, [ecx+4]  #将jmp_buf[1]还原到ebp中

mov esi, [ecx+8]   #将jmp_buf[2]还原到esi中

mov edi, [ecx+12] #将jmp_buf[3]还原到edi中

mov edx, [ecx+20] #将jmp_buf[5]还原到edx中,也就是EIP存到edx中

mov [esp], edx      #将edx放到esp指向的地方,也就是图1的中ESP指向的地址,然后将EIP放回到栈中,至此,还原到了setjmp调用结束ret那一刻的状态,只是eax是longjmp自己设定的;

ret                        #返回到setjmp(这就是setjmp的第二次返回了,其返回值就是longjmp设置的status)

今天先写到这里吧,主要把协程机制中最主要的setjmp及longjmp函数分析了下,从这里还可以看出,一个协程最好有自己的一个栈空间,至于具体如何设计后面再看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容