Greenlet切換源碼分析

Greenlet

協(xié)程可以算是自定義控制切換的微線程。

棧切換的本質(zhì)

1.棧

  • 棧是從高地址向低地址
  • 棧幀(stack frame),機(jī)器用棧來傳遞過程參數(shù),存儲(chǔ)返回信息,保存寄存器用于以后恢復(fù),以及本地存儲(chǔ)。為單個(gè)過程(函數(shù)調(diào)用)分配的那部分棧稱為棧幀。棧幀其實(shí)是兩個(gè)指針寄存器,寄存器%ebp為幀指針,而寄存器%esp為棧指針

2.切換

  • 切換其實(shí)是切換的執(zhí)行位置(top_frame)。
  • 但是當(dāng)我切換執(zhí)行位置,同時(shí)要切換到目的棧,同時(shí)要保證棧內(nèi)數(shù)據(jù)沒有丟失,且沒有被無意修改。這就需要棧數(shù)據(jù)的保存與恢復(fù)(slp_switch)。

如何進(jìn)行切換?

1. C棧切換

其實(shí)協(xié)程的一個(gè)很特殊的例子,就是函數(shù)調(diào)用。下面這個(gè)例子在main中調(diào)用func

#include<stdio.h>
int func(int arg)
{
    int d=4;
    int e=5;
    int f;
    f=d+e+arg;
    return f;
}

int main()
{
    int a=1;
    int b=2;
    int c=3;
    func(c);
    c=a+b;
}

用gcc生成匯編code,建議在redhat或centos下

.file   "stackpointer.c"
.text
.globl func
.type   func, @function
func:
pushl   %ebp
movl    %esp, %ebp
subl    $16, %esp
movl    $4, -12(%ebp)
movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
movl    -12(%ebp), %edx
leal    (%edx,%eax), %eax
addl    8(%ebp), %eax
movl    %eax, -4(%ebp)
movl    -4(%ebp), %eax
leave
ret
.size   func, .-func
.globl main
.type   main, @function
main:
pushl   %ebp
movl    %esp, %ebp
subl    $20, %esp
movl    $1, -12(%ebp)
movl    $2, -8(%ebp)
movl    $3, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, (%esp)  
call    func
movl    -8(%ebp), %eax
movl    -12(%ebp), %edx
leal    (%edx,%eax), %eax
movl    %eax, -4(%ebp)
leave 
ret
.size   main, .-main
.ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-11)"
.section        .note.GNU-stack,"",@progbits

call調(diào)用完成保存ip寄存器以及jump的作用,進(jìn)入func后保存之前的bp,設(shè)置新的棧頂和棧底(在leave時(shí)恢復(fù))
。然后將臨時(shí)、本地?cái)?shù)據(jù)保存以新的bp進(jìn)行偏移保存,最后恢復(fù)ebp和esp,返回到caller繼續(xù)執(zhí)行。

call func作用
  • push ip,保存下一條指令的地址
  • jump func,修改ip跳轉(zhuǎn)到func執(zhí)行函數(shù)
func作用
  • push ebp,保存bp
  • mov esp,ebp,設(shè)置新的棧底。
  • 以新的bp進(jìn)行偏移,保存臨時(shí)、本地變量,完成函數(shù)功能
  • leave(等價(jià)與mov ebp,esp;pop ebp)恢復(fù)esp和ebp
  • ret 恢復(fù)ip,回到call的下一條指令繼續(xù)執(zhí)行。

2. Python棧切換

我們進(jìn)行的切換方式與此類似,但是python的棧和c棧不同,python棧建立在虛擬機(jī)上。
總體上說,就是先進(jìn)行c棧切換,關(guān)于ip設(shè)置跳轉(zhuǎn)到下條指令執(zhí)行(即執(zhí)行位置的切換,如何跳到函數(shù)位置開始執(zhí)行,如何從函數(shù)返回原來位置執(zhí)行),需要在python上實(shí)現(xiàn)top_frame的設(shè)置。
具體細(xì)節(jié)參考:

switch具體實(shí)現(xiàn)

幾個(gè)注意點(diǎn):

  • 導(dǎo)入greenlet會(huì)初始化一個(gè)main_greenlet,并設(shè)置current為main_greenlet
  • greenlet運(yùn)行結(jié)束,會(huì)返回到父greenlet執(zhí)行
from greenlet import greenlet  

def func1(arg):  
    print (arg)  
    gr2.switch()  
    print ("func1 end")  

def func2():  
    print ("fun2 come")  

#設(shè)置parent為main_greenlet
gr1 = greenlet(func1)  
gr2 = greenlet(func2)  
value = gr1.switch("fun1 come")  
print (value)  

首先:

gr1.switch("func1")

會(huì)調(diào)用g_switch函數(shù),其中target=gr1,args=('func1')

static PyObject *
g_switch(PyGreenlet* target, PyObject* args, PyObject* kwargs)
{
  ...
  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;
  }
  ...
}
  • gr1(new_greenlet),默認(rèn)stack_start = NULL(沒有運(yùn)行),stack_stop = NULL(沒有啟動(dòng)),因而執(zhí)行g(shù)_initialstub()
  • dummymarker設(shè)置為棧底
  • 為什么要將dummymarker棧底設(shè)置于此處?
    g_initialstub的棧中包含函數(shù)需要的參數(shù)等數(shù)據(jù),然而&dummymarker的位置恰為g_initialstub棧的ebp。

g_initialstub

代碼已簡(jiǎn)化

static int GREENLET_NOINLINE(g_initialstub)(void* mark))
{
  ...
  /* 設(shè)置stack_stop,表明start該greenlet */
  self->stack_start = NULL;
  self->stack_stop = (char*) mark;

  /* 設(shè)置target的上一個(gè)活動(dòng)棧 */
  /* Example:g1_greenlet.stack_prev=main_greenlet */
  if (ts_current->stack_start == NULL) {
    /* ts_current is dying */
    self->stack_prev = ts_current->stack_prev;
  }
  else {
    self->stack_prev = ts_current;
  }
  /* 核心代碼,進(jìn)行棧切換 */
  err = g_switchstack();

  /* 標(biāo)志greenlet正在運(yùn)行,將要運(yùn)行PyEval_CallObjectWithKeywords */
  self->stack_start = (char*) 1;  /* running

  /* 設(shè)置當(dāng)前運(yùn)行參數(shù)為parent參數(shù) */
  self->run_info = green_statedict(self->parent);
?
  /* 開始執(zhí)行函數(shù) */
  /* 注意:可能在該函數(shù)運(yùn)行過程中,存在switch其他的greenlet,否則運(yùn)行到函數(shù)結(jié)束 */
  result = PyEval_CallObjectWithKeywords(
    run, args, kwargs);

  /* 標(biāo)志函數(shù)結(jié)束 */
  self->stack_start = NULL;  /* dead */

  /* 函數(shù)結(jié)束切換到parent運(yùn)行 */
  for (parent = self->parent; parent != NULL; parent = parent->parent) {
    result = g_switch(parent, result, NULL);
}

  • 設(shè)置當(dāng)前greenlet的stack_prev為ts_current,即上一個(gè)正在運(yùn)行的棧
  • PyEval_CallObjectWithKeywords過程中可能會(huì)切換另一個(gè)greenlet,否則函數(shù)運(yùn)行到結(jié)束

g_switchstack

static int g_switchstack(void)
{
    int err;
    {   /* save state */
        /* 保存線程狀態(tài)或者說EIP */
        PyGreenlet* current = ts_current;
        PyThreadState* tstate = PyThreadState_GET();
        current->recursion_depth = tstate->recursion_depth;
        current->top_frame = tstate->frame;
        current->exc_type = tstate->exc_type;
        current->exc_value = tstate->exc_value;
        current->exc_traceback = tstate->exc_traceback;
    }
    /* 匯編實(shí)現(xiàn)棧切換,分不同平臺(tái) */
    err = slp_switch();
    if (err < 0) {   /* error */
        PyGreenlet* current = ts_current;
        current->top_frame = NULL;
        current->exc_type = NULL;
        current->exc_value = NULL;
        current->exc_traceback = NULL;

        assert(ts_origin == NULL);
        ts_target = NULL;
    }
    else {
        /* 恢復(fù)線程狀態(tài),或者說EIP,即跳轉(zhuǎn)執(zhí)行位置 */
        PyGreenlet* target = ts_target;
        PyGreenlet* origin = ts_current;
        PyThreadState* tstate = PyThreadState_GET();
        tstate->recursion_depth = target->recursion_depth;
        tstate->frame = target->top_frame;
        target->top_frame = NULL;
        tstate->exc_type = target->exc_type;
        target->exc_type = NULL;
        tstate->exc_value = target->exc_value;
        target->exc_value = NULL;
        tstate->exc_traceback = target->exc_traceback;
        target->exc_traceback = NULL;

        assert(ts_origin == NULL);
        Py_INCREF(target);
        ts_current = target;
        ts_origin = origin;
        ts_target = NULL;
    }
    return err;
}
  • 保存線程狀態(tài),即EIP
  • 進(jìn)行C棧切換,匯編實(shí)現(xiàn)
  • 恢復(fù)目標(biāo)線程狀態(tài),即跳轉(zhuǎn)執(zhí)行位置

slp_switch(核心代碼)

static int
slp_switch(void)
{
    /* 下面變量保存在棧(current)中 */
    int err;
    void* rbp;
    void* rbx;
    unsigned int csr;
    unsigned short cw;
    register long *stackref, stsizediff;
    /* 這里save的是current線程的狀態(tài),變量保存在棧中 */
    __asm__ volatile ("" : : : REGS_TO_SAVE);
    __asm__ volatile ("fstcw %0" : "=m" (cw));
    __asm__ volatile ("stmxcsr %0" : "=m" (csr));
    __asm__ volatile ("movq %%rbp, %0" : "=m" (rbp));
    __asm__ volatile ("movq %%rbx, %0" : "=m" (rbx));
    __asm__ ("movq %%rsp, %0" : "=g" (stackref));
    {
        /* 保存當(dāng)前線程的數(shù)據(jù),包括上面的那些寄存器等等數(shù)據(jù) */
        /* 當(dāng)為new_greenlet直接返回1,無??汕袚Q */
        SLP_SAVE_STATE(stackref, stsizediff);
        
        /* 重要!current在此暫停,target從此處繼續(xù)之前的狀態(tài)之前 */
        __asm__ volatile (
            "addq %0, %%rsp\n"
            "addq %0, %%rbp\n"
            :
            : "r" (stsizediff)
            );
        /* 恢復(fù)棧(target)中數(shù)據(jù) */
        SLP_RESTORE_STATE();
        __asm__ volatile ("xorq %%rax, %%rax" : "=a" (err));
    }
    /* 恢復(fù)寄存器變量,這里恢復(fù)的是之前保存在target棧中的變量 */
    /* 恢復(fù)了target的esp和ebp,因?yàn)樽兞康谋4媸且詄bp進(jìn)行偏移尋址中,所以當(dāng)進(jìn)行恢復(fù)時(shí),進(jìn)行相同偏移,但是因?yàn)閑bp為已變?yōu)橹暗膖arget棧,因而恢復(fù)的寄存器也仍為之前的狀態(tài)。 */
    __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx));
    __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp));
    __asm__ volatile ("ldmxcsr %0" : : "m" (csr));
    __asm__ volatile ("fldcw %0" : : "m" (cw));
    __asm__ volatile ("" : : : REGS_TO_SAVE);
    return err;
}
  • 很重要的一點(diǎn),當(dāng)從恢復(fù)ebp和esp開始,current暫停,target繼續(xù)之前運(yùn)行,恢復(fù)之前數(shù)據(jù),恢復(fù)的寄存器也仍為之前保存的狀態(tài),因?yàn)樗麄兪腔趀bp的偏移尋址,尋址方式不變,只受ebp的控制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文地址:C語言函數(shù)調(diào)用棧(一)C語言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過程可看作連續(xù)的函數(shù)調(diào)用。當(dāng)一個(gè)函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,975評(píng)論 1 19
  • 首先寄存器使用慣例:eip :指令地址寄存器,保存程序計(jì)數(shù)器的值,當(dāng)前執(zhí)行的指令的下一條指令的地址值,16位中為i...
    扎Zn了老Fe閱讀 2,106評(píng)論 0 0
  • 站在巨人的肩膀上——IDA PRO權(quán)威指南閱讀筆記 一,窗口 view->open subviews 打開/關(guān)閉各...
    SueLyon閱讀 14,792評(píng)論 0 6
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡(jiǎn)單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運(yùn)行的地址不確定 關(guān)于...
    SeanCST閱讀 8,142評(píng)論 0 27
  • 堆棧是連續(xù)的地址空間,且向低地址端生長(zhǎng)。 esp 是堆棧指針ebp 是基址指針那兩條指令的意思是將棧頂指向ebp的...
    wyrover閱讀 1,248評(píng)論 0 1

友情鏈接更多精彩內(nèi)容