协程设计需要注意的两个小问题及解决

这两天我尝试仿照Golang正在设计一款协程库。
协程有什么用途呢?合理利用资源。通用的进程和线程模型都耗费了不应该耗费的资源,要想处理更多并发得自己管理任务切换。

我实现的协程比较简单。在设计过程中发现了两个小难点,分享给大家。

A 如何实现扩容

我们要压缩任务的内存空间,首要的措施就是在任务创建开始不给它过多的内存。但是任务需要更多的内存的时候呢?这就是涉及到了扩容:

  • Golang如何处理扩容的呢?
    它不允许直接的指针操作,所以它可以通过移动内存和其上的数据达成目的。
    C语言肯定是限制不了了。

  • 如何获得用户即将超过栈的消息?
    C语言编译会对 可能超过栈的操作 建立一个栈检查,防止其越过栅栏页,此外通过栅栏页来使我们能够侦测到超出栈但是不超出一个页面的行为。

我采用的是Windows系统提供的预订调拨内存操作。详细内容可参考:《Windows核心编程第五版》
预订不占用内存,只是占号码范围,我们知道虚拟内容是假的。
所以方法是这样:

  1. 给每个协程单元预订足够的内容(比如,像线程的默认大小一样1M)。
  2. 实际**调拨**4kb。

这样还顺带解决栅栏页的问题,因为未被调拨的空间访问,返回AV异常。我们接到这个异常然后判断范围后就去尝试调拨。

B 协程状态的保存与恢复

要给协程一个主动让出时间片机制,比较重要。

举个例子:
协程A执行甲乙两个阻塞任务。
但乙操作预计属于可等待操作,可以让出时间片。

  • Windows的做法是:Sleep(0),SwitchToThread()

怎么在R3实现这个操作呢?
通过触发异常我们可以获取完整的信息,但是这个操作太费时间,所以我设计了一段汇编来完成我们的任务:
我们只需要保存:

    1. `pushad`:保存除IP外的寄存器到栈
    2. `pushfd`:保护符号寄存器到栈
    3. `call 0`:保存ip到栈

OK。
下面是详细流程:

A. 如何实现协程扩容?√

通知机制

安置栅栏页

扩容

预订和调拨物理存储器//Windows核心编程第五版
预订地址空间区域,给区域调拨来自页交换文件的物理存储器。
根据自己的需要来设置页面的保护属性。

/*预订地址空间区域*/
VirtualAlloc(
/*想要的地址:分配粒度:64kb倍数,向下取整*/,
/*大小,   分配粒度按照CPU页面大小。一般是4kb?*/,
MEM_RESERVE/*预订*/,
PAGE_EXECUTE_READWRITE
)

/*调拨物理存储器*/
VirtualAlloc((PVOID)Address/*开始地址*/,/*调拨数量*/,
MEMCOMMIT,PAGE_READWRITE)

B. 协程状态保存与恢复√

状态有哪些?如何恢复?

esp ebp
eax ebx ecx edx
esi edi
eip
flags

漂亮的命令有:
pushad: 将所有的32位通用寄存器压入堆栈,只对ESP有影响
压入顺序(从低地址到高地址):
eax,ecx,edx,ebx,esp,ebp,esi,edi


pushfd:32位标志寄存器EFLAGS压入堆栈

什么形式探测这些状态?

  • 内联函数
    非常快速,但是要考虑如何安排数据

    1. pushad:保存除IP外的寄存器
    2. pushfd:保护符号寄存器
    3. call 0:保存ip并计算出下次执行应该开始的地方,然后push
    4. jmp到收集栈的流程。
  • 内联异常
    这是一种非常好的方式。但是涉及异常,影响速度?

猜你喜欢

转载自blog.csdn.net/dalerkd/article/details/78389521