协程Protothread

协程的定义

维基百科对协程定义:

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-邮箱通信;
实现协程的另一种思路;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值