• greenlet代码解读


    协程

    上次已经讲解了协程的的实现方法,和我对协程的一些理解。这里指我就先以代码说明协程的运行。
    def test1():
        print 12         (2)
        gr2.switch()     (3)
        print 34         (6)

    def test2():
        print 56         (4)
        gr1.switch()     (5)
        print 78         (8)

    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()          (1)
    gr2.switch()          (7)

    输出结果为:
    12
    56
    34
    78
    可以看到声明了两个协程gr1,gr2。通过协程的switch,进行协程切换。在代码后边,已经表明了协程代码执行过程。
    可以比较明确的看到协程之间交互执行,这也是协程的名称的由来。他们协作进行进行工作。switch()是一个重要的方法,
    它启动协程、切换协程。

    协程实现

    好了,开始我们的协程代码之旅。从协程的两个步骤:
    Note: 如果你对Python的C extensions不是非常熟悉,建议你先看(https://docs.python.org/2/extending/extending.html)
    1.创建协程(greenlet)
    首先:初始化协程环境,将greenlet模块的各种状态(GreenMethods,模块变量ts_curkey等,各项Exception,当前运行ts_current,最终将这些都形成模块的
    属性)请看initgreenlet。
    一直到现在我们可以创建协程了,因为我们有了协程的这些Type。
    创建协程,这个过程非常简单,greenlet(func)就创建了一个协程。PyGreenlet_Type(greenlet.c)已经说明了这两个方法(green_new,green_init)
    green_new,创建了一个PyGreenlet,他们的parent设置为ts_current.(如果是协程A内有创建协程B,则ts_current就是协程A, B.parent就是A)

    static PyObject* green_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        PyObject* o = PyBaseObject_Type.tp_new(type, ts_empty_tuple, ts_empty_dict);
        if (o != NULL) {
            if (!STATE_OK) {
                Py_DECREF(o);
                return NULL;
            }
            Py_INCREF(ts_current);
            ((PyGreenlet*) o)->parent = ts_current;
        }
        return o;
    }


    green_init,将参数进行分解,赋予self对应的PyGreenlet。

    static int green_init(PyGreenlet *self, PyObject *args, PyObject *kwargs)
    {
        PyObject *run = NULL;
        PyObject* nparent = NULL;
        static char *kwlist[] = {"run", "parent", 0};
        if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO:green", kwlist,
                                         &run, &nparent))
            return -1;
            
        if (run != NULL) {
            if (green_setrun(self, run, NULL))
                return -1;
        }
        if (nparent != NULL && nparent != Py_None)
            return green_setparent(self, nparent, NULL);
        return 0;
    }

    是的很容易明白他们的意图。

    2.协程切换(switch)
    从green_switch->g_switch非常直接,进入协程的核心。协程切换(不需要关注协程运行环境,因为CPU整体,运行时状态的寄存器进行了切换和保存。)

    g_switch(PyGreenlet* target, PyObject* args, PyObject* kwargs)
    {
           ......(省略)
        /* find the real target by ignoring dead greenlets,
           and if necessary starting a greenlet. */
        while (target) {
            if (PyGreenlet_ACTIVE(target)) {
                ts_target = target;
                err = g_switchstack();
                break;
            }
            if (!PyGreenlet_STARTED(target)) {
                void* dummymarker;
                ts_target = target;
                err = g_initialstub(&dummymarker);
                if (err == 1) {
                    continue; /* retry the switch */
                }
                break;
            }
            target = target->parent;
        }
            ................(省略)
    }


    首先就是获取真正执行的target,当然target分为两种情况,活跃的协程,没有开始的协程。按照我们的例子,gr1.switch()这个时候,
    是为开始的协程,下边执行的是g_initialstub。(g_initialstu做了几件事情 保存当前协程的环境,将协程的栈空间保存到堆上,这个很简单,切换回来的时候
    把堆上的运行空间复制回来就可以继续运行。不然就没有办法进行切换)

    static int GREENLET_NOINLINE(g_initialstub)(void* mark)
    {
            ........(省略)
            //将协程的前后关系处理好,形成一个链
            if (ts_current->stack_start == NULL) {
            /* ts_current is dying */
            self->stack_prev = ts_current->stack_prev;
        }
        else {
            self->stack_prev = ts_current;
        }
            .....(省略)
            //在g_switchstack.进行
        /* perform the initial switch */
        err = g_switchstack();
            //纠正,这个返回twice是不正确的,返回两次,只有fork系统调用,因为完成之后,有两个进程执行。
            //这个方法并没有返回两次。因为执行完成之后,就再次进行了切换。不应该认为返回两次
        /* returns twice!
           The 1st time with err=1: we are in the new greenlet
           The 2nd time with err=0: back in the caller's greenlet
        */
        if (err == 1) {
            /* in the new greenlet */
            PyGreenlet* origin;
                    .........(省略)
    
            if (args == NULL) {
                /* pending exception */
                result = NULL;
            } else {
                            //这个方法协程进行了真正的执行。
                /* call g.run(*args, **kwargs) */
                result = PyEval_CallObjectWithKeywords(
                    run, args, kwargs);
                Py_DECREF(args);
                Py_XDECREF(kwargs);
            }
            Py_DECREF(run);
            result = g_handle_exit(result);
            /* jump back to parent */
            self->stack_start = NULL;  /* dead */
            }
                    // 完成之后,在进行切换    
                    /* jump back to parent */
            self->stack_start = NULL;  /* dead */
            for (parent = self->parent; parent != NULL; parent = parent->parent) {
                result = g_switch(parent, result, NULL);
                /* Return here means switch to parent failed,
                 * in which case we throw *current* exception
                 * to the next parent in chain.
                 */
                assert(result == NULL);
            }
          }
    }

    现在开始关心g_switchstack如何进行切换了。
    g_switchstack执行了汇编slp_switch(),在这里我之查看x86汇编。

    slp_switch(void)
    {
        /*将寄存器内容保存在协程的内存中,当恢复了协程内存之后,将内容恢复到寄存器中。新旧协程不会冲突。
        协程切换,也就是将寄存器进行内容进行保存,将栈地址进行保存和恢复*/
        int err;
        void *ebp, *ebx;
        unsigned short cw;
        register int *stackref, stsizediff;
        __asm__ volatile ("" : : : "esi", "edi");
        __asm__ volatile ("fstcw %0" : "=m" (cw));
        __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp));
        __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx));
        __asm__ ("movl %%esp, %0" : "=g" (stackref));
        {
            SLP_SAVE_STATE(stackref, stsizediff);
            __asm__ volatile (
                "addl %0, %%esp
    "
                "addl %0, %%ebp
    "
                :
                : "r" (stsizediff)
                );
            SLP_RESTORE_STATE();
            __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err));
        }
        __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx));
        __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp));
        __asm__ volatile ("fldcw %0" : : "m" (cw));
        __asm__ volatile ("" : : : "esi", "edi");
        return err;
    }

    在汇编代码中,看到的内容就是保存、恢复寄存器和保存协程状态,恢复协程状态。


    下边来看以下如何保存状态的SLP_SAVE_STATE-> slp_save-state

    static int GREENLET_NOINLINE(slp_save_state)(char* stackref)
    {
        /* must free all the C stack up to target_stop */
        char* target_stop = ts_target->stack_stop;
        PyGreenlet* owner = ts_current;
        assert(owner->stack_saved == 0);
        if (owner->stack_start == NULL)
            owner = owner->stack_prev;  /* not saved if dying */
        else
            owner->stack_start = stackref;

        while (owner->stack_stop < target_stop) {
            //owner的结束地址小于target结束地址,产生了栈覆盖
            /* ts_current is entierely within the area to free */
            if (g_save(owner, owner->stack_stop))
                return -1;  /* XXX */
            owner = owner->stack_prev;
        }
        if (owner != ts_target) {
            if (g_save(owner, target_stop))
                return -1;  /* XXX */
        }
        return 0;
    }

    static
    int g_save(PyGreenlet* g, char* stop) { /* Save more of g's stack into the heap -- at least up to 'stop' g->stack_stop |________| | | |_ _ _ _ stop . . . . . | | ==> . . |________| _______ | | | | | | | | g->stack_start |________| |_______| g->stack_copy intptr_t sz1 = g->stack_saved; intptr_t sz2 = stop - g->stack_start; assert(g->stack_start != NULL); if (sz2 > sz1) { char* c = (char*)PyMem_Realloc(g->stack_copy, sz2); if (!c) { PyErr_NoMemory(); return -1; } memcpy(c+sz1, g->stack_start+sz1, sz2-sz1); g->stack_copy = c; g->stack_saved = sz2; } return 0; }


    这就是协程栈的保存,申请内存,然后将栈memcpy进入去。

    恢复,也是类似,将保存进内存,拷贝到栈上。然后将寄存器进行修改

    不过有点特殊的事情是:python看不到eip(指令指针,执行下一条运行指令),而python对应的内容,在frameobject中,
    有PyCodeObject,而执行的时候,在ceval.c可以看到是通过next_instr来获取python运行指令。
    所以协程有top_frame,就是为了切换协程时候,对指令集合进行切换

    代码中有使用PyThreadState,但是并没有用thread线程,对于PyThreadState的使用只是为了,方便的让run_info,这个函数指针,
    进行处理成为各项frameobject,recursion_depth等内容。


    注:
    1.对于中间的汇编,还是建议好好的看一下。我刚好看了中国科技大学-孟宁出的一个视频教程《linux 内核解读》刚好对汇编有非常好的讲解。

    2.而协程运行之后,greenlet的栈布局如下:(图片来源于网络)

  • 相关阅读:
    Java实现 LeetCode 416 分割等和子集
    Java实现 LeetCode 416 分割等和子集
    在Linux运行期间升级Linux系统(Uboot+kernel+Rootfs)
    【详解】嵌入式开发中固件的烧录方式
    嵌入式系统烧写uboot/bootloader/kernel的一般方法
    Linux下USB烧写uImage kernel
    Xmanager连接CentOS的远程桌面
    命令行利器Tmux
    u-boot中分区和内核MTD分区关系
    uboot环境变量与内核MTD分区关系
  • 原文地址:https://www.cnblogs.com/tom-zhao/p/4695859.html
Copyright © 2020-2023  润新知