雲風協程庫儲存和恢復協程執行棧原理講解
首先, linux
下的程序地址空間佈局是這樣子的:
可以看到 整個程序地址空間從上到下地址變化是從高地址到低地址的! 32 位系統有 4G 的地址空間,其中0x08048000
-> 0xbfffffff
是使用者空間,0xc0000000
~0xffffffff
是核心空間,包括核心程式碼和資料、與程序相關的資料結構(如頁表、核心棧)等。其實就是大體上就是下面這樣:
另外, %esp 執行棧頂,往低地址方向變化; brk/sbrk 函式控制堆頂往高地址方向變化
。也就是說棧是向下生長的.(堆向上生長,這樣可以充分利用空間)
知道了這些,我們就可以開心的來看程式碼了^-^
儲存現場:
協程在從 RUNNING
到 SUSPEND
狀態時需要儲存執行棧,即呼叫 coroutine_yield
之後掛起協程,讓出CPU的過程。
原理:就是將該協程執行時候的所有資料轉而儲存到每個協程所對應的結構中,為了在下次回到這個協程時能回到之前的執行點繼續執行
,那麼你會想問,那這個協程執行的時候的棧在哪?我們來看程式碼:
C->ctx.uc_stack.ss_sp = S->stack;
C->ctx.uc_stack.ss_size = STACK_SIZE;
makecontext(&C->ctx, (void (*)(void)) mainfunc, 2,
(uint32_t)ptr, (uint32_t)(ptr>>32));
這是協程初始化時進行的設定.我們可以看到schedule 的 stack[STACK_SIZE] 是子協程執行的公共棧空間!
該協程棧的棧頂設定為 S->stack,mainfunc 的執行使用 S->stack 作為棧頂,大小為 STACK_SIZE.ok,一切搞定,那麼轉儲的時候其實就是將這個棧中該協程所用到的資料copy到它對應的結構中就行了
.
static void
_save_stack(struct coroutine *C, char *top)
{
printf ("top:%p \n", top);
char dummy = 0;
printf("dummy:%p \n", &dummy);
// top-&dummy 即該協程的執行時所佔的空間大小
assert(top - &dummy <= STACK_SIZE);
printf("STACK_ZISE:%d \n", STACK_SIZE);
printf("top - &dummy:%ld \n", top - &dummy);
//如果協程私有棧空間大小(C->cap)不夠放下執行時的棧空間,則要重新擴容
if (C->cap < top - &dummy)
{
free(C->stack);
C->cap = top - &dummy;
C->stack = malloc(C->cap);
}
C->size = top - &dummy; /*c->stack 是在堆上的*/
memcpy(C->stack, &dummy, C->size);
//將(dummy->top) 的資料copy到該協程對應的結構中就行了
}
void coroutine_yield(struct schedule *S)
{
int id = S->running;
assert(id >= 0);
struct coroutine *C = S->co[id];
assert((char *)&C > S->stack);
printf("S->stack ==%p\n", S->stack);
printf("S->stack+STACK_SIZE ==%p\n", S->stack + STACK_SIZE);
//為了在下次回到這個協程時能回到之前的執行點繼續執行,必須要儲存其執行的上下文
_save_stack(C, S->stack + STACK_SIZE);
C->status = COROUTINE_SUSPEND;
S->running = -1;
swapcontext(&C->ctx, &S->main);
}
關於_save_stack()
函式,我畫了這樣的一幅圖,相信你一看就能懂
程式執行結果:
1. 可以看到S->stack+STACK_SIZE
地址值大於S->stack
,所以他在高地址.
2. dummy
介於兩者之間,> S->stack
,< S->stack+STACK_SIZE
.因為我們沒有用到什麼資料,所以dummy
的地址很靠近< S->stack+STACK_SIZE
.
3. 證明上面畫得圖是正確的.
恢復現場:
協程從 SUSPEND
到 RUNNING
狀態時,是要恢復當時的執行棧,回到執行點再執行的.
原理:將各自私有的C->stack 空間中的資料恢復到S->stack中
// C->size 表示協程似有棧的實際大小
memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);
至此,該原理就講解到這兒了,你理解了嗎??