なんでも作っちゃう、かも。

Arduino/Make/フィジカルコンピューティング/電子工作あたりで活動しています。スタバの空きカップを使ったスタバカップアンプなど製作。最近はもっぱらArduinoと3Dプリンタの自作に興味があります。

C言語でコルーチン(co-routine)(2)

Posted by arms22 on 2008年04月10日 0  0

C言語でコルーチンを実装してみる、その2。
前回のコルーチンマクロではローカル変数が使えない、外部から状態を制御できないという問題があった。今回はそれらの問題を解決してみる。まずはサンプルコードを見てほしい。

int hoge(co_routine_t coro){
co_begin(coro);
co_local_valiables{
int a;
int b;
};
co_local.a = 0;
co_local.b = 0;
printf("arg 0x%x\n",(int)co_get_addrword(coro));
while(1){
printf("a=%d b=%d\n",co_local.a++,co_local.b++);
co_yield();
}
co_end;
}

main{
co_routine_t coro;
int stack[32];
coro = co_create(hoge,0xaa,stack,sizeof(stack));
co_call(coro);
co_call(coro);
co_call(coro);
co_call(coro);
}
実行結果は次のとおり。
% a.exe
arg=0xaa
a=0 b=0
a=1 b=1
a=2 b=2
a=3 b=3
前回のコルーチンマクロから変わった所は、コルーチンの関数定義、co_beginマクロに引数を追加、co_local_variables/co_localマクロの追加、co_create/co_call関数の追加ぐらいだろうか。順番に見ていこう。
  • int hoge(co_routine_t coro)
    今回の実装ではコルーチン関数はco_routine_t型の引数とint型の値を返す必要がある。なもんで、呼び出す度に戻り値が変わる関数を作りたい場合には使えない。
  • co_begin,co_end
    毎度おなじみの"おまじない"。
  • co_local_valiables
    ローカル変数の宣言をここで行う。これらの変数はコルーチンが呼び出されている間は消えることはない。ローカル変数へのアクセスはco_localマクロを経由して行う。co_localは単純にスタックのアドレスをローカル変数用の構造体にキャストしているだけ。
  • co_yield()
    このマクロが呼ばれた時点でコルーチンは処理を中断し、関数を抜ける。次に呼ばれた時は、このマクロの次の行から処理が再開される。
  • coro = co_create(hoge,0xaa,stack,sizeof(stack))
    co_create関数を使ってコルーチンを作成する。第1引数にコルーチン関数のポインタ、第2引数にコルーチン関数に渡す引数、第3引数にスタックのアドレス、第4引数にスタックのサイズを渡す。スタックのサイズは最低、sizeof(関数ポインタ)+sizeof(コルーチン関数に渡す引数)+sizeof(int)分必要。作成に成功したらスタックのアドレスがco_routine_t型にキャストされて返される。失敗した場合は0が返る。
  • co_call(coro)
    コルーチン呼び出す。コルーチンでco_yieldマクロが呼ばれたら処理が返ってくる。
ソースコードは長いので続きに載せておくよ。

そうそう、マクロでちまちまやるのは嫌だという人にはPCL(Portable Coroutine Library)というポータブルなコルーチンライブラリがあるよ。PCLはcontext save/restoreでコルーチンを実現しているよ。setjmp、longjmpを駆使するみたい。

Portable Coroutine Library
http://www.xmailserver.org/libpcl.html

coroutine.h
#ifndef  __COROUTINE_H__
#define __COROUTINE_H__

typedef struct coroutine* coroutine_t;
typedef int(*co_entry_t)(coroutine_t);
typedef void* co_addrword_t;
typedef void* co_stack_t;
typedef int co_size_t;
typedef int co_resume_t;

struct coroutine {
co_entry_t entry;
co_addrword_t addrword;
co_resume_t resume;
};

#define CO_INITIALALIZE (0)
#define CO_INVALID (-1)

#define co_begin(coro) \
void *__stack_addr = (void*)((coro)+1); \
switch((coro)->resume){ \
case CO_INITIALALIZE:;

#define co_yield() \
do{ \
return(__LINE__); \
case __LINE__:; \
}while(0)

#define co_exit() \
return(CO_INVALID)

#define co_end \
} \
co_exit()

#define co_local_valiables \
typedef struct co_local_valiables* co_local_valiables_t; \
struct co_local_valiables

#define co_local \
(*((co_local_valiables_t)__stack_addr))

#define co_get_addrword(coro) \
((coro)->addrword)

coroutine_t co_create(co_entry_t entry,co_addrword_t addrword,co_stack_t stack,co_size_t size);
co_resume_t co_call(coroutine_t coro);
int co_is_valid(coroutine_t coro);

#endif

coroutine.c
#include "coroutine.h"

coroutine_t co_create(co_entry_t entry,co_addrword_t addrword,co_stack_t stack,co_size_t size)
{
struct coroutine *coro = (struct coroutine*)stack;
if(coro && (size>=sizeof(*coro))){
coro->entry = entry;
coro->addrword = addrword;
coro->resume = CO_INITIALALIZE;
return (coroutine_t)coro;
}
return 0;
}

co_resume_t co_call(coroutine_t coro)
{
co_resume_t ret = CO_INVALID;
if(co_is_valid(coro)){
ret = coro->entry(coro);
coro->resume = ret;
}
return ret;
}

int co_is_valid(coroutine_t coro)
{
return (coro && coro->entry && (coro->resume!=CO_INVALID));
}

Ads by Google

Leave a reply






管理者にだけ表示を許可する

該当の記事は見つかりませんでした。