移植luaCoco

栏目: C · 发布时间: 9年前

内容简介:移植luaCoco

2017-05-30 13:38 PM

lua 语言最大的卖点之一就是他的协程(coroutine)了。但是在 lua 5.1 中有一个文档都没提到的一个坑:协程 只能 在 lua 中使用,当调用 yield 时,如果当前的调用栈上有 c 代码,则会报错 “attempt to yield across metamethod/C-call boundary” 。目前有个第三方的 patch 叫做 luaCoco 可以让 lua 支持 “真协程”。本文研究了 luaCoco 的内部实现,并把它移植到了 xtensa 处理器上。

移植luaCoco

setjmp 的实现

在文章的开头需要先讲解一下 c语言 标准库中 setjmp 的内部实现,因为之后的 luaCoco 的实现就是对 setjmp 的数据结构的一个 hack。

先看看 setjmp 的用法

#inlcude <stdio.h>
#include <stdlib.h>
#inlcude <setjmp.h>

int main() {
 jmp_buf buf;
 int code = setjmp(buf);
 if (code == 0) {
 print("before jmp\n");
 longjmp(buf, 1024);
 } else {
 printf("jmp here, code: %d\n", code);
 }
 return 0;
}

在调用 setjmp 的时候,会返回 0,从而会执行 if (code == 0) { 为 true 的 block,会打印出 before jmp 。当执行了 longjmp 之后,程序的执行会重新跳转到 setjmp 那一行(第七行)然而这次 setjmp 的返回值 code 不再是 0,而是 longjmp 的第二个参数(1024),这样就会打印出 jmp here, code: 1024 。

利用 setjmp 的这个功能能实现出很多有趣的东西,比如在c语言中做 Exception ,但是 setjmp 是怎么实现的呢?我们去读一读源码。

libc 的实现有好多种,常见的比如 glibc、uclibc 和 musl-libc,但是我们这次读一读 newlib 的源码。newlib 也是一个 libc 的实现,常用于嵌入式开发中。

打开 inlcude/machine/setjmp-dj.h 文件,可以看到 jmp_buf 的定义:

// from inlcude/machine/setjmp-dj.h
typedef struct {
 unsigned long eax;
 unsigned long ebx;
 unsigned long ecx;
 unsigned long edx;
 unsigned long esi;
 unsigned long edi;
 unsigned long ebp;
 unsigned long esp;
 unsigned long eip;
} jmp_buf[1];

发现这个结构体是用来存储 cpu 的寄存器的。这里很好理解,因为要实现 长跳转(longjmp),必须要首先把跳转的目的地的 现场 先保存下来。

setjmp 函数做的工作就是保存现场:

// from machine/i386/setjmp.S
 /*
 ** jmp_buf:
 ** eax ebx ecx edx esi edi ebp esp eip
 ** 0 4 8 12 16 20 24 28 32
 */

 #include "i386mach.h"

 .global SYM (setjmp)
 .global SYM (longjmp)
 SOTYPE_FUNCTION(setjmp)
 SOTYPE_FUNCTION(longjmp)

SYM (setjmp):

 pushl ebp
 movl esp,ebp

 pushl edi
 movl 8 (ebp),edi

 movl eax,0 (edi)
 movl ebx,4 (edi)
 movl ecx,8 (edi)
 movl edx,12 (edi)
 movl esi,16 (edi)

 movl -4 (ebp),eax
 movl eax,20 (edi) // edi

 movl 0 (ebp),eax
 movl eax,24 (edi) // ebp

 movl esp,eax
 addl $12,eax
 movl eax,28 (edi) // esp

 movl 4 (ebp),eax
 movl eax,32 (edi) // eip (PC)

 popl edi
 movl $0,eax
 leave
 ret
移植luaCoco setjmp 的栈帧

看图解释一下代码:

  • 17 行:push ebp 到栈上(esp 同时下移 4 字节)
  • 18 行:ebp 指向 esp
  • 20 行:push edi 到栈上(esp 同时再下移 4 字节)
  • 21 行:8(ebp) 保存的是 jmp_buf 的指针,先把它放到 edi 中
  • 23 - 27 行:分别把 eax、ebx、ecx、edx、esi 保存到 jmp_buf 里
  • 29 - 30 行:-4 (ebp) 是edi,如图
  • 32 - 33 行:(ebp) 就是之前的ebp,如图
  • 35 - 37 行:保存 esp
  • 39 - 40 行:如图,保存 return addr 到 jmp_buf->eip

longjmp 的实现是正好相反的:

SYM (longjmp):
 pushl ebp
 movl esp,ebp

 movl 8(ebp),edi /* get jmp_buf */
 movl 12(ebp),eax /* store retval in j->eax */
 testl eax,eax
 jne 0f
 incl eax
0:
 movl eax,0(edi)

 movl 24(edi),ebp

 __CLI
 movl 28(edi),esp

 pushl 32(edi) 

 movl 0(edi),eax
 movl 4(edi),ebx
 movl 8(edi),ecx
 movl 12(edi),edx
 movl 16(edi),esi
 movl 20(edi),edi
 __STI

 ret

代码就不赘述了,基本上就是恢复 现场 、把longjmp的第二个参数作为返回值返回(7 - 9 行还有一个判断:如果参数为0的话,会把它改成 1)。

luaCoco 的实现

// TODO: 写完它

xtensa 架构 ABI

// TODO: 写完它

移植 luaCoco

// TODO: 写完它

以上所述就是小编给大家介绍的《移植luaCoco》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Growth Hacker Marketing

Growth Hacker Marketing

Ryan Holiday / Portfolio / 2013-9-3 / USD 10.31

Dropbox, Facebook, AirBnb, Twitter. A new generation of multibillion dollar brands built without spending a dime on “traditional marketing.” No press releases, no PR firms, and no billboards in Times ......一起来看看 《Growth Hacker Marketing》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具