协程的定义
维基百科对协程定义:
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking,by allowing execution to be suspended and resumed.
-
CoRoutine,其中 Co 是 Cooperative,意为平等、协作,指多段程序之间相互转移控制权;Routine 一词在高级语言中对应函数/方法等概念,即 Routine 与 Program、Subprogram、Function、Method、Subroutine、Procedure、Callable Unit 等词同义。故协程可以简称为协作的程序。
-
协程是一个能够支持协作式多任务调度,而且是通过代码执行的暂停和恢复的方式实现的。利用协程可以实现线程中多程序的并发执行,并且在协程上下文切换时,不需要像OS调度那样进行上下文环境的保存、恢复。这种协程也叫无栈协程(Stackless Coroutine),通常利用switch-case、goto等跳转方式配合程序代码结构实现,Protothread就属于这种。其他代表有是 JavaScript、Kotlin 等,共同点是关键字 async/await。
-
当然,也有协程的实现类似线程的方式,将程序切换处涉及的调用栈、 程序计数器PC、EBP、ESP 等寄存器数据保存一份“快照”,当协程需要从暂停点恢复执行时,只需恢复这个快照即可。利用这种方式实现的协程被称为有栈协程(Stackful Coroutine),像RTOS中的Task的一样。代表有 Go 和 Lua。有栈协程有时被称为 Fibers 或者用户态线程。
Protothread的实现
Protothread在contiki os 的 process中有广泛的应用!contiki 的作者Adam Dunkels,同时也是uip,lwip的作者。
Contiki Protothread宏
// contiki OS protothread
// path: contiki\core\sys\pt.h
struct pt {
lc_t lc; // 定义了一个存放行号的变量, lc_t只是一个短整形的类型
};
#define PT_WAITING 0
#define PT_YIELDED 1
#define PT_EXITED 2
#define PT_ENDED 3
PT_INIT(pt) // 宏语句展开后的实际代码如下
(pt)->lc = 0;
/// 实际contiki中每个process在每次被调度时,都是从头开始执行的(函数被调用,从头执行)
/// 在PT_BEGIN处,利用switch()-case实现跳转功能,切换到上次Wait或Yield处。
PT_BEGIN(pt); // 宏语句展开后的实际代码如下
{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} switch((pt)->lc) { case 0: ;
PT_EXIT(pt); // 宏语句展开后的实际代码如下
do { \
(pt)->lc = 0; \
return PT_EXITED; \
} while(0);
/// PT_WAIT_UNTIL宏里在当前行放置一个case, 这样就跟前面的switch组合起来了.
/// 然后判断condition是否满足, 不满足就return.
/// 下一次进这个函数的时候, 直接从PT_BEGIN的地方, 跳转到这里.
PT_WAIT_UNTIL(pt, condition); // 宏语句展开后的实际代码如下
do { \
(pt)->lc = __LINE__; case __LINE__: ; \
if(!(condition)) { \
return PT_WAITING; \
} \
} while(0);
PT_RESTART(pt); // 宏语句展开后的实际代码如下
do { \
(pt)->lc = 0; \
return PT_WAITING; \
} while(0);
PT_SPAWN(pt, child, thread); // 宏语句展开后的实际代码如下
do { \
((child))->lc = 0;; \
PT_WAIT_WHILE((pt), ((thread) < PT_EXITED)); \ // similar to PT_WAIT_UNTIL
} while(0);
PT_YIELD(pt); // 宏语句展开后的实际代码如下
do { \
PT_YIELD_FLAG = 0; \
(pt)->lc = __LINE__; case __LINE__: ; \
if(PT_YIELD_FLAG == 0) { \
return PT_YIELDED; \
} \
} while(0)
PT_YIELD_UNTIL(pt, cond); // 宏语句展开后的实际代码如下
do { \
PT_YIELD_FLAG = 0; \
(pt)->lc = __LINE__; case __LINE__: ; \
if((PT_YIELD_FLAG == 0) || !(cond)) { \
return PT_YIELDED; \
} \
} while(0);
PT_END(pt); // 宏语句展开后的实际代码如下
}; PT_YIELD_FLAG = 0; (pt)->lc = 0; return PT_ENDED; };
Contiki process
-
process.h
// contiki OS process interface // path: contiki\core\sys\process. // A process in Contiki consists of a single pt "protothread". typedef unsigned char process_event_t; typedef void * process_data_t; typedef unsigned char process_num_events_t; #define PROCESS_ERR_OK 0 #define PROCESS_ERR_FULL 1 /* @} */ #define PROCESS_NONE NULL #ifndef PROCESS_CONF_NUMEVENTS #define PROCESS_CONF_NUMEVENTS 32 #endif /* PROCESS_CONF_NUMEVENTS */ #define PROCESS_EVENT_NONE 0x80 #define PROCESS_EVENT_INIT 0x81 #define PROCESS_EVENT_POLL 0x82 #define PROCESS_EVENT_EXIT 0x83 #define PROCESS_EVENT_SERVICE_REMOVED 0x84 #define PROCESS_EVENT_CONTINUE 0x85 #define PROCESS_EVENT_MSG 0x86 #define PROCESS_EVENT_EXITED 0x87 #define PROCESS_EVENT_TIMER 0x88 #define PROCESS_EVENT_COM 0x89 #define PROCESS_EVENT_MAX 0x8a #define PROCESS_BROADCAST NULL #define PROCESS_ZOMBIE ((struct process *)0x1) /** * Define the beginning of a process. * * This macro defines the beginning of a process, and must always * appear in a PROCESS_THREAD() definition. The PROCESS_END() macro * must come at the end of the process. */ #define PROCESS_BEGIN() PT_BEGIN(process_pt) /** * Define the end of a process. * * This macro defines the end of a process. It must appear in a * PROCESS_THREAD() definition and must always be included. The * process exits when the PROCESS_END() macro is reached. */ #define PROCESS_END() PT_END(process_pt) /** * Wait for an event to be posted to the process. * * This macro blocks the currently running process until the process * receives an event. */ #define PROCESS_WAIT_EVENT() PROCESS_YIELD() /** * Wait for an event to be posted to the process, with an extra * condition. * * This macro is similar to PROCESS_WAIT_EVENT() in that it blocks the * currently running process until the process receives an event. But * PROCESS_WAIT_EVENT_UNTIL() takes an extra condition which must be * true for the process to continue. * * \param c The condition that must be true for the process to continue. * \sa PT_WAIT_UNTIL() */ #define PROCESS_WAIT_EVENT_UNTIL(c) PROCESS_YIELD_UNTIL(c) /** * Yield the currently running process. */ #define PROCESS_YIELD() PT_YIELD(process_pt) /** * Yield the currently running process until a condition occurs. * * This macro is different from PROCESS_WAIT_UNTIL() in that * PROCESS_YIELD_UNTIL() is guaranteed to always yield at least * once. This ensures that the process does not end up in an infinite * loop and monopolizing the CPU. * * \param c The condition to wait for. */ #define PROCESS_YIELD_UNTIL(c) PT_YIELD_UNTIL(process_pt, c) /** * Wait for a condition to occur. * * This macro does not guarantee that the process yields, and should * therefore be used with care. In most cases, PROCESS_WAIT_EVENT(), * PROCESS_WAIT_EVENT_UNTIL(), PROCESS_YIELD() or * PROCESS_YIELD_UNTIL() should be used instead. * * \param c The condition to wait for. */ #define PROCESS_WAIT_UNTIL(c) PT_WAIT_UNTIL(process_pt, c) #define PROCESS_WAIT_WHILE(c) PT_WAIT_WHILE(process_pt, c) /** * Exit the currently running process. */ #define PROCESS_EXIT() PT_EXIT(process_pt) /** * Spawn a protothread from the process. * * \param pt The protothread state (struct pt) for the new protothread * \param thread The call to the protothread function. * \sa PT_SPAWN() */ #define PROCESS_PT_SPAWN(pt, thread) PT_SPAWN(process_pt, pt, thread) /** * Yield the process for a short while. * * This macro yields the currently running process for a short while, * thus letting other processes run before the process continues. */ #define PROCESS_PAUSE() do { \ process_post(PROCESS_CURRENT(), PROCESS_EVENT_CONTINUE, NULL); \ PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_CONTINUE); \ } while(0) /** * Specify an action when a process is polled. * * \note This declaration must come immediately before the * PROCESS_BEGIN() macro. * * \param handler The action to be performed. */ #define PROCESS_POLLHANDLER(handler) if(ev == PROCESS_EVENT_POLL) { handler; } /** * Specify an action when a process exits. * * \note This declaration must come immediately before the * PROCESS_BEGIN() macro. * * \param handler The action to be performed. */ #define PROCESS_EXITHANDLER(handler) if(ev == PROCESS_EVENT_EXIT) { handler; } /** * Define the body of a process. * * This macro is used to define the body (protothread) of a * process. The process is called whenever an event occurs in the * system, A process always start with the PROCESS_BEGIN() macro and * end with the PROCESS_END() macro. */ #define PROCESS_THREAD(name, ev, data) \ static PT_THREAD(process_thread_##name(struct pt *process_pt, \ process_event_t ev, \ process_data_t data)) /** * Declare the name of a process. * * This macro is typically used in header files to declare the name of * a process that is implemented in the C file. */ #define PROCESS_NAME(name) extern struct process name /** * Declare a process. * * This macro declares a process. The process has two names: the * variable of the process structure, which is used by the C program, * and a human readable string name, which is used when debugging. * A configuration option allows removal of the readable name to save RAM. * * \param name The variable name of the process structure. * \param strname The string representation of the process' name. */ #if PROCESS_CONF_NO_PROCESS_NAMES #define PROCESS(name, strname) \ PROCESS_THREAD(name, ev, data); \ struct process name = { NULL, \ process_thread_##name } #else #define PROCESS(name, strname) \ PROCESS_THREAD(name, ev, data); \ struct process name = { NULL, strname, \ process_thread_##name } #endif /** @} */ struct process { struct process *next; #if PROCESS_CONF_NO_PROCESS_NAMES #define PROCESS_NAME_STRING(process) "" #else const char *name; #define PROCESS_NAME_STRING(process) (process)->name #endif PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t)); struct pt pt; unsigned char state, needspoll; }; /** * Start a process. * * \param p A pointer to a process structure. * * \param data An argument pointer that can be passed to the new * process * */ CCIF void process_start(struct process *p, process_data_t data); /** * Post an asynchronous event. * * This function posts an asynchronous event to one or more * processes. The handing of the event is deferred until the target * process is scheduled by the kernel. An event can be broadcast to * all processes, in which case all processes in the system will be * scheduled to handle the event. * * \param ev The event to be posted. * \param data The auxiliary data to be sent with the event * \param p The process to which the event should be posted, or * PROCESS_BROADCAST if the event should be posted to all processes. * * \retval PROCESS_ERR_OK The event could be posted. * * \retval PROCESS_ERR_FULL The event queue was full and the event could * not be posted. */ CCIF int process_post(struct process *p, process_event_t ev, process_data_t data); /** * Post a synchronous event to a process. * * \param p A pointer to the process' process structure. * * \param ev The event to be posted. * * \param data A pointer to additional data that is posted together * with the event. */ CCIF void process_post_synch(struct process *p, process_event_t ev, process_data_t data); /** * \brief Cause a process to exit * \param p The process that is to be exited * * This function causes a process to exit. The process can * either be the currently executing process, or another * process that is currently running. * * \sa PROCESS_CURRENT() */ CCIF void process_exit(struct process *p); /** * Get a pointer to the currently running process. * * This macro get a pointer to the currently running * process. Typically, this macro is used to post an event to the * current process with process_post(). * * \hideinitializer */ #define PROCESS_CURRENT() process_current CCIF extern struct process *process_current; /** * Switch context to another process * * This function switch context to the specified process and executes * the code as if run by that process. Typical use of this function is * to switch context in services, called by other processes. Each * PROCESS_CONTEXT_BEGIN() must be followed by the * PROCESS_CONTEXT_END() macro to end the context switch. * * Example: \code PROCESS_CONTEXT_BEGIN(&test_process); etimer_set(&timer, CLOCK_SECOND); PROCESS_CONTEXT_END(&test_process); \endcode * * \param p The process to use as context * * \sa PROCESS_CONTEXT_END() * \sa PROCESS_CURRENT() */ #define PROCESS_CONTEXT_BEGIN(p) {\ struct process *tmp_current = PROCESS_CURRENT();\ process_current = p /** * End a context switch * * This function ends a context switch and changes back to the * previous process. * * \param p The process used in the context switch * * \sa PROCESS_CONTEXT_START() */ #define PROCESS_CONTEXT_END(p) process_current = tmp_current; } /** * \brief Allocate a global event number. * \return The allocated event number * * In Contiki, event numbers above 128 are global and may * be posted from one process to another. This function * allocates one such event number. * * \note There currently is no way to deallocate an allocated event * number. */ CCIF process_event_t process_alloc_event(void); /** * Request a process to be polled. * * This function typically is called from an interrupt handler to * cause a process to be polled. * * \param p A pointer to the process' process structure. */ CCIF void process_poll(struct process *p); /** * \brief Initialize the process module. * * This function initializes the process module and should * be called by the system boot-up code. */ void process_init(void); /** * Run the system once - call poll handlers and process one event. * * This function should be called repeatedly from the main() program * to actually run the Contiki system. It calls the necessary poll * handlers, and processes one event. The function returns the number * of events that are waiting in the event queue so that the caller * may choose to put the CPU to sleep when there are no pending * events. * * \return The number of events that are currently waiting in the * event queue. */ int process_run(void); /** * Check if a process is running. * * This function checks if a specific process is running. * * \param p The process. * \retval Non-zero if the process is running. * \retval Zero if the process is not running. */ CCIF int process_is_running(struct process *p); /** * Number of events waiting to be processed. * * \return The number of events that are currently waiting to be * processed. */ int process_nevents(void); CCIF extern struct process *process_list; #define PROCESS_LIST() process_list
-
process.c
/* * Pointer to the currently running process structure. */ struct process *process_list = NULL; struct process *process_current = NULL; static process_event_t lastevent; /* * Structure used for keeping the queue of active events. */ struct event_data { process_event_t ev; process_data_t data; struct process *p; }; static process_num_events_t nevents, fevent; static struct event_data events[PROCESS_CONF_NUMEVENTS]; #if PROCESS_CONF_STATS process_num_events_t process_maxevents; #endif static volatile unsigned char poll_requested; #define PROCESS_STATE_NONE 0 #define PROCESS_STATE_RUNNING 1 #define PROCESS_STATE_CALLED 2 static void call_process(struct process *p, process_event_t ev, process_data_t data); #define DEBUG 0 #if DEBUG #include <stdio.h> #define PRINTF(...) printf(__VA_ARGS__) #else #define PRINTF(...) #endif /*---------------------------------------------------------------------------*/ process_event_t process_alloc_event(void) { return lastevent++; } /*---------------------------------------------------------------------------*/ void process_start(struct process *p, process_data_t data) { struct process *q; /* First make sure that we don't try to start a process that is already running. */ for(q = process_list; q != p && q != NULL; q = q->next); /* If we found the process on the process list, we bail out. */ if(q == p) { return; } /* Put on the procs list.*/ p->next = process_list; process_list = p; p->state = PROCESS_STATE_RUNNING; PT_INIT(&p->pt); PRINTF("process: starting '%s'\n", PROCESS_NAME_STRING(p)); /* Post a synchronous initialization event to the process. */ process_post_synch(p, PROCESS_EVENT_INIT, data); } /*---------------------------------------------------------------------------*/ static void exit_process(struct process *p, struct process *fromprocess) { register struct process *q; struct process *old_current = process_current; PRINTF("process: exit_process '%s'\n", PROCESS_NAME_STRING(p)); /* Make sure the process is in the process list before we try to exit it. */ for(q = process_list; q != p && q != NULL; q = q->next); if(q == NULL) { return; } if(process_is_running(p)) { /* Process was running */ p->state = PROCESS_STATE_NONE; /* * Post a synchronous event to all processes to inform them that * this process is about to exit. This will allow services to * deallocate state associated with this process. */ for(q = process_list; q != NULL; q = q->next) { if(p != q) { call_process(q, PROCESS_EVENT_EXITED, (process_data_t)p); } } if(p->thread != NULL && p != fromprocess) { /* Post the exit event to the process that is about to exit. */ process_current = p; p->thread(&p->pt, PROCESS_EVENT_EXIT, NULL); } } if(p == process_list) { process_list = process_list->next; } else { for(q = process_list; q != NULL; q = q->next) { if(q->next == p) { q->next = p->next; break; } } } process_current = old_current; } /*---------------------------------------------------------------------------*/ static void call_process(struct process *p, process_event_t ev, process_data_t data) { int ret; #if DEBUG if(p->state == PROCESS_STATE_CALLED) { printf("process: process '%s' called again with event %d\n", PROCESS_NAME_STRING(p), ev); } #endif /* DEBUG */ if((p->state & PROCESS_STATE_RUNNING) && p->thread != NULL) { PRINTF("process: calling process '%s' with event %d\n", PROCESS_NAME_STRING(p), ev); process_current = p; p->state = PROCESS_STATE_CALLED; ret = p->thread(&p->pt, ev, data); if(ret == PT_EXITED || ret == PT_ENDED || ev == PROCESS_EVENT_EXIT) { exit_process(p, p); } else { p->state = PROCESS_STATE_RUNNING; } } } /*---------------------------------------------------------------------------*/ void process_exit(struct process *p) { exit_process(p, PROCESS_CURRENT()); } /*---------------------------------------------------------------------------*/ void process_init(void) { lastevent = PROCESS_EVENT_MAX; nevents = fevent = 0; #if PROCESS_CONF_STATS process_maxevents = 0; #endif /* PROCESS_CONF_STATS */ process_current = process_list = NULL; } /*---------------------------------------------------------------------------*/ /* * Call each process' poll handler. */ /*---------------------------------------------------------------------------*/ static void do_poll(void) { struct process *p; poll_requested = 0; /* Call the processes that needs to be polled. */ for(p = process_list; p != NULL; p = p->next) { if(p->needspoll) { p->state = PROCESS_STATE_RUNNING; p->needspoll = 0; call_process(p, PROCESS_EVENT_POLL, NULL); } } } /*---------------------------------------------------------------------------*/ /* * Process the next event in the event queue and deliver it to * listening processes. */ /*---------------------------------------------------------------------------*/ static void do_event(void) { process_event_t ev; process_data_t data; struct process *receiver; struct process *p; /* * If there are any events in the queue, take the first one and walk * through the list of processes to see if the event should be * delivered to any of them. If so, we call the event handler * function for the process. We only process one event at a time and * call the poll handlers inbetween. */ if(nevents > 0) { /* There are events that we should deliver. */ ev = events[fevent].ev; data = events[fevent].data; receiver = events[fevent].p; /* Since we have seen the new event, we move pointer upwards and decrease the number of events. */ fevent = (fevent + 1) % PROCESS_CONF_NUMEVENTS; --nevents; /* If this is a broadcast event, we deliver it to all events, in order of their priority. */ if(receiver == PROCESS_BROADCAST) { for(p = process_list; p != NULL; p = p->next) { /* If we have been requested to poll a process, we do this in between processing the broadcast event. */ if(poll_requested) { do_poll(); } call_process(p, ev, data); } } else { /* This is not a broadcast event, so we deliver it to the specified process. */ /* If the event was an INIT event, we should also update the state of the process. */ if(ev == PROCESS_EVENT_INIT) { receiver->state = PROCESS_STATE_RUNNING; } /* Make sure that the process actually is running. */ call_process(receiver, ev, data); } } } /*---------------------------------------------------------------------------*/ int process_run(void) { /* Process poll events. */ if(poll_requested) { do_poll(); } /* Process one event from the queue */ do_event(); return nevents + poll_requested; } /*---------------------------------------------------------------------------*/ int process_nevents(void) { return nevents + poll_requested; } /*---------------------------------------------------------------------------*/ int process_post(struct process *p, process_event_t ev, process_data_t data) { process_num_events_t snum; if(PROCESS_CURRENT() == NULL) { PRINTF("process_post: NULL process posts event %d to process '%s', nevents %d\n", ev,PROCESS_NAME_STRING(p), nevents); } else { PRINTF("process_post: Process '%s' posts event %d to process '%s', nevents %d\n", PROCESS_NAME_STRING(PROCESS_CURRENT()), ev, p == PROCESS_BROADCAST? "<broadcast>": PROCESS_NAME_STRING(p), nevents); } if(nevents == PROCESS_CONF_NUMEVENTS) { #if DEBUG if(p == PROCESS_BROADCAST) { printf("soft panic: event queue is full when broadcast event %d was posted from %s\n", ev, PROCESS_NAME_STRING(process_current)); } else { printf("soft panic: event queue is full when event %d was posted to %s from %s\n", ev, PROCESS_NAME_STRING(p), PROCESS_NAME_STRING(process_current)); } #endif /* DEBUG */ return PROCESS_ERR_FULL; } snum = (process_num_events_t)(fevent + nevents) % PROCESS_CONF_NUMEVENTS; events[snum].ev = ev; events[snum].data = data; events[snum].p = p; ++nevents; #if PROCESS_CONF_STATS if(nevents > process_maxevents) { process_maxevents = nevents; } #endif /* PROCESS_CONF_STATS */ return PROCESS_ERR_OK; } /*---------------------------------------------------------------------------*/ void process_post_synch(struct process *p, process_event_t ev, process_data_t data) { struct process *caller = process_current; call_process(p, ev, data); process_current = caller; } /*---------------------------------------------------------------------------*/ void process_poll(struct process *p) { if(p != NULL) { if(p->state == PROCESS_STATE_RUNNING || p->state == PROCESS_STATE_CALLED) { p->needspoll = 1; poll_requested = 1; } } } /*---------------------------------------------------------------------------*/ int process_is_running(struct process *p) { return p->state != PROCESS_STATE_NONE; }
-
etimer.h
/** * A timer. * * This structure is used for declaring a timer. The timer must be set * with etimer_set() before it can be used. * * \hideinitializer */ struct etimer { struct timer timer; struct etimer *next; struct process *p; }; /** * \brief Set an event timer. * \param et A pointer to the event timer * \param interval The interval before the timer expires. * * This function is used to set an event timer for a time * sometime in the future. When the event timer expires, * the event PROCESS_EVENT_TIMER will be posted to the * process that called the etimer_set() function. * */ CCIF void etimer_set(struct etimer *et, clock_time_t interval); /** * \brief Reset an event timer with the same interval as was * previously set. * \param et A pointer to the event timer. * * This function resets the event timer with the same * interval that was given to the event timer with the * etimer_set() function. The start point of the interval * is the exact time that the event timer last * expired. Therefore, this function will cause the timer * to be stable over time, unlike the etimer_restart() * function. * * \sa etimer_restart() */ CCIF void etimer_reset(struct etimer *et); /** * \brief Reset an event timer with a new interval. * \param et A pointer to the event timer. * \param interval The interval before the timer expires. * * This function very similar to etimer_reset. Opposed to * etimer_reset it is possible to change the timout. * This allows accurate, non-periodic timers without drift. * * \sa etimer_reset() */ void etimer_reset_with_new_interval(struct etimer *et, clock_time_t interval); /** * \brief Restart an event timer from the current point in time * \param et A pointer to the event timer. * * This function restarts the event timer with the same * interval that was given to the etimer_set() * function. The event timer will start at the current * time. * * \note A periodic timer will drift if this function is * used to reset it. For periodic timers, use the * etimer_reset() function instead. * * \sa etimer_reset() */ void etimer_restart(struct etimer *et); /** * \brief Adjust the expiration time for an event timer * \param et A pointer to the event timer. * \param td The time difference to adjust the expiration time with. * * This function is used to adjust the time the event * timer will expire. It can be used to synchronize * periodic timers without the need to restart the timer * or change the timer interval. * * \note This function should only be used for small * adjustments. For large adjustments use etimer_set() * instead. * * \note A periodic timer will drift unless the * etimer_reset() function is used. * * \sa etimer_set() * \sa etimer_reset() */ void etimer_adjust(struct etimer *et, int td); /** * \brief Get the expiration time for the event timer. * \param et A pointer to the event timer * \return The expiration time for the event timer. * * This function returns the expiration time for an event timer. */ clock_time_t etimer_expiration_time(struct etimer *et); /** * \brief Get the start time for the event timer. * \param et A pointer to the event timer * \return The start time for the event timer. * * This function returns the start time (when the timer * was last set) for an event timer. */ clock_time_t etimer_start_time(struct etimer *et); /** * \brief Check if an event timer has expired. * \param et A pointer to the event timer * \return Non-zero if the timer has expired, zero otherwise. * * This function tests if an event timer has expired and * returns true or false depending on its status. */ CCIF int etimer_expired(struct etimer *et); /** * \brief Stop a pending event timer. * \param et A pointer to the pending event timer. * * This function stops an event timer that has previously * been set with etimer_set() or etimer_reset(). After * this function has been called, the event timer will not * emit any event when it expires. * */ void etimer_stop(struct etimer *et); /** * \brief Make the event timer aware that the clock has changed * * This function is used to inform the event timer module * that the system clock has been updated. Typically, this * function would be called from the timer interrupt * handler when the clock has ticked. */ void etimer_request_poll(void); /** * \brief Check if there are any non-expired event timers. * \return True if there are active event timers, false if there are * no active timers. * * This function checks if there are any active event * timers that have not expired. */ int etimer_pending(void); /** * \brief Get next event timer expiration time. * \return Next expiration time of all pending event timers. * If there are no pending event timers this function * returns 0. * * This functions returns next expiration time of all * pending event timers. */ clock_time_t etimer_next_expiration_time(void); PROCESS_NAME(etimer_process);
-
etimer.c
static struct etimer *timerlist; static clock_time_t next_expiration; PROCESS(etimer_process, "Event timer"); /*---------------------------------------------------------------------------*/ static void update_time(void) { clock_time_t tdist; clock_time_t now; struct etimer *t; if (timerlist == NULL) { next_expiration = 0; } else { now = clock_time(); t = timerlist; /* Must calculate distance to next time into account due to wraps */ tdist = t->timer.start + t->timer.interval - now; for(t = t->next; t != NULL; t = t->next) { if(t->timer.start + t->timer.interval - now < tdist) { tdist = t->timer.start + t->timer.interval - now; } } next_expiration = now + tdist; } } /*---------------------------------------------------------------------------*/ PROCESS_THREAD(etimer_process, ev, data) { struct etimer *t, *u; PROCESS_BEGIN(); timerlist = NULL; while(1) { PROCESS_YIELD(); if(ev == PROCESS_EVENT_EXITED) { struct process *p = data; while(timerlist != NULL && timerlist->p == p) { timerlist = timerlist->next; } if(timerlist != NULL) { t = timerlist; while(t->next != NULL) { if(t->next->p == p) { t->next = t->next->next; } else t = t->next; } } continue; } else if(ev != PROCESS_EVENT_POLL) { continue; } again: u = NULL; for(t = timerlist; t != NULL; t = t->next) { if(timer_expired(&t->timer)) { if(process_post(t->p, PROCESS_EVENT_TIMER, t) == PROCESS_ERR_OK) { /* Reset the process ID of the event timer, to signal that the etimer has expired. This is later checked in the etimer_expired() function. */ t->p = PROCESS_NONE; if(u != NULL) { u->next = t->next; } else { timerlist = t->next; } t->next = NULL; update_time(); goto again; } else { etimer_request_poll(); } } u = t; } } PROCESS_END(); } /*---------------------------------------------------------------------------*/ void etimer_request_poll(void) { process_poll(&etimer_process); } /*---------------------------------------------------------------------------*/ static void add_timer(struct etimer *timer) { struct etimer *t; etimer_request_poll(); if(timer->p != PROCESS_NONE) { for(t = timerlist; t != NULL; t = t->next) { if(t == timer) { /* Timer already on list, bail out. */ timer->p = PROCESS_CURRENT(); update_time(); return; } } } /* Timer not on list. */ timer->p = PROCESS_CURRENT(); timer->next = timerlist; timerlist = timer; update_time(); } /*---------------------------------------------------------------------------*/ void etimer_set(struct etimer *et, clock_time_t interval) { timer_set(&et->timer, interval); add_timer(et); } /*---------------------------------------------------------------------------*/ void etimer_reset_with_new_interval(struct etimer *et, clock_time_t interval) { timer_reset(&et->timer); et->timer.interval = interval; add_timer(et); } /*---------------------------------------------------------------------------*/ void etimer_reset(struct etimer *et) { timer_reset(&et->timer); add_timer(et); } /*---------------------------------------------------------------------------*/ void etimer_restart(struct etimer *et) { timer_restart(&et->timer); add_timer(et); } /*---------------------------------------------------------------------------*/ void etimer_adjust(struct etimer *et, int timediff) { et->timer.start += timediff; update_time(); } /*---------------------------------------------------------------------------*/ int etimer_expired(struct etimer *et) { return et->p == PROCESS_NONE; } /*---------------------------------------------------------------------------*/ clock_time_t etimer_expiration_time(struct etimer *et) { return et->timer.start + et->timer.interval; } /*---------------------------------------------------------------------------*/ clock_time_t etimer_start_time(struct etimer *et) { return et->timer.start; } /*---------------------------------------------------------------------------*/ int etimer_pending(void) { return timerlist != NULL; } /*---------------------------------------------------------------------------*/ clock_time_t etimer_next_expiration_time(void) { return etimer_pending() ? next_expiration : 0; } /*---------------------------------------------------------------------------*/ void etimer_stop(struct etimer *et) { struct etimer *t; /* First check if et is the first event timer on the list. */ if(et == timerlist) { timerlist = timerlist->next; update_time(); } else { /* Else walk through the list and try to find the item before the et timer. */ for(t = timerlist; t != NULL && t->next != et; t = t->next); if(t != NULL) { /* We've found the item before the event timer that we are about to remove. We point the items next pointer to the event after the removed item. */ t->next = et->next; update_time(); } } /* Remove the next pointer from the item to be removed. */ et->next = NULL; /* Set the timer as expired */ et->p = PROCESS_NONE; }
-
contiki-main.c
unsigned int idle_count = 0; int main() { dbg_setup_uart(); printf("Initialising\n"); clock_init(); process_init(); process_start(&etimer_process, NULL); autostart_start(autostart_processes); printf("Processes running\n"); while(1) { do { } while(process_run() > 0); idle_count++; /* Idle! */ /* Stop processor clock */ /* asm("wfi"::); */ } return 0; }
Protothread 使用例子
// example
/**
* Send data.
* PT_THREAD这个宏只是定义了一个带返回char型的函数
* This macro sends data over a protosocket. The protosocket protothread blocks
* until all data has been sent and is known to have been received by
* the remote end of the TCP connection.
*
* \param psock (struct psock *) A pointer to the protosocket over which
* data is to be sent.
*
* \param data (uint8_t *) A pointer to the data that is to be sent.
*
* \param datalen (unsigned int) The length of the data that is to be
* sent.
*
* \hideinitializer
*/
PT_THREAD(psock_send(CC_REGISTER_ARG struct psock *s, const uint8_t *buf, unsigned int len))
{
PT_BEGIN(&s->psockpt);
/* If there is no data to send, we exit immediately. */
if(len == 0) {
PT_EXIT(&s->psockpt);
}
/* Save the length of and a pointer to the data that is to be sent. */
s->sendptr = buf;
s->sendlen = len;
s->state = STATE_NONE;
/* We loop here until all data is sent. The s->sendlen variable is
updated by the data_sent() function. */
while(s->sendlen > 0) {
/*
* The protothread will wait here until all data has been
* acknowledged and sent (data_is_acked_and_send() returns 1).
*/
PT_WAIT_UNTIL(&s->psockpt, data_is_sent_and_acked(s));
}
s->state = STATE_NONE;
PT_END(&s->psockpt);
}
static int run_trickle(struct trickle_conn *c)
{
clock_time_t interval;
PT_BEGIN(&c->pt);
while(1) {
interval = c->interval << c->interval_scaling;
set_timer(c, &c->interval_timer, interval);
set_timer(c, &c->t, interval / 2 + (random_rand() % (interval / 2)));
c->duplicates = 0;
PT_YIELD(&c->pt); /* Wait until listen timeout */
if(c->duplicates < DUPLICATE_THRESHOLD) {
send(c);
}
PT_YIELD(&c->pt); /* Wait until interval timer expired. */
if(c->interval_scaling < INTERVAL_MAX) {
c->interval_scaling++;
}
}
PT_END(&c->pt);
}
/// timer_set将当前的tick值记录起来, PT_WAIT_UNTIL会检测timer_expired是否超时,
/// 超时即可继续执行下面的程序, 这样就可以实现非阻塞延时.
static int timer_expired(struct timer *t)
{
// clock_time()会获取当前的tick值, 如果当前值-开始值超过了延时的tick数, 那么就是超时了
return (int)(clock_time() - t->start) >= (int)t->interval; // 思考:溢出会如何呢?
}
struct timer input_timer;
PT_THREAD(input_thread(struct pt *pt))
{
PT_BEGIN(pt);
timer_set(&input_timer, 1000); // 延时1000个tick
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
timer_set(&input_timer, 100); // 延时100个tick
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
timer_set(&input_timer, 300);
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
timer_set(&input_timer, 2000);
PT_WAIT_UNTIL(pt, timer_expired(&input_timer));
PT_END(pt);
}
参考:
协程简史,一文讲清楚协程的起源、发展和实现;
【对比Java学Kotlin】协程简史;
浅谈协程;
Protothread入门;
状态机——protothreads;
Contiki 非阻塞延时的原理;
Contiki IPC-邮箱通信;
实现协程的另一种思路;