coroutine協程

    若是你接觸過lua這種小巧的腳本語言,你就會常常接觸到一個叫作協程的神奇概念。大多數腳本語言都有對協程不一樣程度的支持。可是大多編譯語言,如C/C++,根本就不知道這樣的東西存在。固然也不少人研究如何在編譯語言實現協程的實現,輪子一個又一個的被髮明。酷殼 這篇文章《一個「蠅量級」 C 語言協程庫》說的很詳細,但對於文中介紹的協程庫protothread,很難看的懂。風雲大哥在搜索無滿意結果後也從新發明輪子,實現本身版本的一個協程庫《C 的 coroutine 庫》, 風雲版本的coroutine是常規的根據getcontext/swapcontext的很是傳統的方法,沒有用到其餘什麼奇淫技巧。同時他接口幾乎和lua協程的接口同樣,比較容易看的懂,可是貌似和CPU構架相關,下面詳細說。這篇文章就是主要寫看風雲的代碼的,代碼註釋放到github上面coroutine,固然由於代碼自己很精練和簡單,註釋也就很是少的。關於協程,可參考百度百科:協程html

  開始以前,看看unix/linux下面的context系列函數風雲大哥說,windows下面能夠用纖程實現,之前看《windows核心編程》的時候,瞭解過這個概念,可是實際上編程運用的只限於進程/線程,沒有用到過纖程這高級貨,如今接觸windows api也相對少,因此這個就不詳細展開)。context系列函數大約就如下函數:linux

#include <ucontext.h> int getcontext(ucontext_t *ucp); int setcontext(const ucontext_t *ucp); void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...); int swapcontext(ucontext_t *oucp, ucontext_t *ucp); 

  根據linux手冊頁,getcontext返回的是當前執行進程的上下文信息,包括信號掩碼,執行棧,寄存器等信息。比較值得注意的是makecontext,該函數除了傳遞ucp外,還傳遞一個通用的函數指針func和一個argc,傳遞給func的函數原型並不必定是void(*)()類型的,參數的個數由argc肯定。當調用makecontext成功後,若是對這個上下文進行切換(swapcontext)或用這個上下文設置(setcontext),內核就會調用該func函數。協程的實現就是用到這個重要的原理。linux手冊頁有個詳細的例子介紹makecontext怎麼用的,請參考之。git

  咱們來看風雲大哥的協程庫:github

// 協程調度大小 struct schedule { char stack[STACK_SIZE]; // 當前運行的協程的棧 ucontext_t main; // 下個協程切換的上下文狀態 int nco; // 當前協程 int cap; // 容量 int running; // 當前運行的協程 struct coroutine **co; // 協程數組 }; struct coroutine { coroutine_func func; // 調用函數 void *ud; // 用戶數據 ucontext_t ctx; // 保存的協程上下文狀態 struct schedule * sch; // 保存struct schedule指針 ptrdiff_t cap; // 上下文切換時保存的棧的容量 ptrdiff_t size; // 上下文切換時保存的棧的大小 size <= cap int status; // 協程狀態 char *stack; // 保存的協程棧大小 }; 

  定義的結構體夠簡潔的,除了struct coroutine裏面的capsize,感受做用不大。上下文信息是在coroutine_resume裏面設置的,這裏將棧設置爲本身定義的區域,大小也限定了,還設置了下個上下文切換的地址(在swapcontext裏爲他賦初始值):shell

getcontext(&C->ctx); C->ctx.uc_stack.ss_sp = S->stack; // 設置棧 C->ctx.uc_stack.ss_size = STACK_SIZE; // 設置棧大小 C->ctx.uc_link = &S->main; // 下個切換的上下文狀態,由swapcontext設置其值 S->running = id; C->status = COROUTINE_RUNNING; uintptr_t ptr = (uintptr_t)S; makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32)); swapcontext(&S->main, &C->ctx); 

  風雲大哥的協程庫有點很差的地方就是這個協程庫或許是與CPU的構架相關的,作成與CPU無關應該不難。看保存上下文信息的代碼:編程

static void _save_stack(struct coroutine *C, char *top) { char dummy = 0; //這裏作了一個特定的假設 // 棧由高地址向低地址方向增加,這種程序佈局的CPU通常是X86構架的 // 因此這個庫時與CPU結構相關的。 assert(top - &dummy <= STACK_SIZE); // stack大小 if (C->cap < top - &dummy) { free(C->stack); // 首次C->stack爲NULL,free(NULL)是OK的 C->cap = top-&dummy; C->stack = malloc(C->cap); } C->size = top - &dummy; memcpy(C->stack, &dummy, C->size); } 

  傳遞的參數top指向的是棧頂,棧頂與新定義的變量相減top - &dummy獲得棧的大小。這裏作了一個特定的假設,棧由高地址向低地址方向增加(這是很是典型的程序佈局方式,雖然我不知道那種CPU不是用這種佈局,可是以爲確定有有低地址向高地址增加的),這種程序佈局的CPU通常是X86構架的,因此這個庫時與CPU結構相關的。windows

  總的來講,這個協程庫寫的很是精簡。protothread這種高級貨,暫時不看了。粗略的介紹完畢,若有錯誤,請批評指正。api

  POST AT: http://luoguochun.cn/2014/08/21/coroutine/數組

相關文章
相關標籤/搜索