greenlet代碼解讀

協程html

上次已經講解了協程的的實現方法,和我對協程的一些理解。這裏指我就先以代碼說明協程的運行。
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()是一個重要的方法,
它啓動協程、切換協程。python

協程實現linux

好了,開始咱們的協程代碼之旅。從協程的兩個步驟:
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作了幾件事情 保存當前協程的環境,將協程的棧空間保存到堆上,這個很簡單,切換回來的時候
把堆上的運行空間複製回來就能夠繼續運行。否則就沒有辦法進行切換)spa

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\n"
            "addl %0, %%ebp\n"
            :
            : "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

code

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的棧佈局以下:(圖片來源於網絡)

相關文章
相關標籤/搜索