ST是一个C语言微线程开源库:http://sourceforge.net/projects/state-threads/
微线程最大的好处就是将传统的EDSM(Event Drive State Machine)异步化多路复用编程(epoll/select等)串行化。通过在一个进程内模拟多个微线程并发,使得异步编程像同步一样简单。但是ST的本质仍然是基于EDSM模型,ST将请求抽象为thread概念,ST的调度器(scheduler)对于用户来说是透明的,不像dispatcher那种将执行状态(execute state)暴露给回调方式。
传统EDSM调度模型
ST调度模型
ST通过setjmp和longjmp来实现微线程调用栈上下文(仅包括寄存器状态)的保存与恢复。而调用栈是通过额外分配堆内存来保存的,并没有与物理进程共享调用栈。
1. // 栈结构
2. typedef struct _st_stack {
3. _st_clist_t links;
4. char *vaddr; /* Base of stack's allocated memory */
5. int vaddr_size; /* Size of stack's allocated memory */
6. int stk_size; /* Size of usable portion of the stack */
7. char *stk_bottom; /* Lowest address of stack's usable portion */
8. char *stk_top; /* Highest address of stack's usable portion */
9. void *sp; /* Stack pointer from C's point of view */
10. } _st_stack_t;
11. //线程结构
12. typedef struct _st_thread {
13. int state; /* Thread's state */
14. int flags; /* Thread's flags */
15. void *(*start)(void *arg); /* The start function of the thread */
16. void *arg; /* Argument of the start function */
17. void *retval; /* Return value of the start function */
18. _st_stack_t *stack; /* Info about thread's stack */
19. _st_clist_t links; /* For putting on run/sleep/zombie queue */
20. _st_clist_t wait_links; /* For putting on mutex/condvar wait queue */
21. st_utime_t due; /* Wakeup time when thread is sleeping */
22. _st_thread_t *left; /* For putting in timeout heap */
23. _st_thread_t *right; /* -- see docs/timeout_heap.txt for details */
24. int heap_index;
25. void **private_data; /* Per thread private data */
26. _st_cond_t *term; /* Termination condition variable for join */
27. jmp_buf context; /* Thread's context */
28. } st_thread_t;
微线程栈结构图
上图是栈分配后的结果,两边是REDZONE使用mprotect保护不被访问(仅debug模式开启),extra是用于开启随机栈地址空间使用的,随机栈地址开启后会调整bottom和top,就是随机的向右边移动一点。应该是用于防止栈溢出攻击。总之,最后使用的,对外提供的接口就是[bottom, top]这个内存区域,[bottom, top]区域又进一步分为:
ptds:微线程的私有数据(private_data),是12个指针(ST_KEYS_MAX指定),参考st_key_create()。
trd:st_thread_t结构本身也是在这个stack中分配的。
pad+align:在trd之后是对齐和pad(_ST_STACK_PAD_SIZE指定)。
sp:微线程实际的栈指针。
st_thread_create函数创建微线程时会初始化sp。微线程栈的分配详情参考_st_stack_new函数。
1)st_thread_create
1. /*
2. * 创建微线程
3. * @start微线程start函数
4. * @arg入口函数参数列表
5. * @joinablejoinable标记
6. * @stk_size微线程栈大小
7. */
8. _st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stk_size)
9. {
10. _st_thread_t *thread;
11. _st_stack_t *stack;
12. void **ptds;
13. char *sp;
14.
15. /* 调整线程栈大小 */
16. if (stk_size == 0)
17. /* 如果stk_size为0,设置为默认大小 */
18. stk_size = ST_DEFAULT_STACK_SIZE;
19. /* 否则调整栈大小为内存页的整数倍 */
20. stk_size = ((stk_size + _ST_PAGE_SIZE - 1) / _ST_PAGE_SIZE) * _ST_PAGE_SIZE;
21. /* 申请占空间并对栈空间结构初始化,参见微线程栈结构图 */
22. stack = _st_stack_new(stk_size);
23. if (!stack)
24. return NULL;
25.
26. sp = stack->stk_top;
27. /* 私有数据指针ptds初始化 */
28. sp = sp - (ST_KEYS_MAX * sizeof(void *));
29. ptds = (void **) sp;
30. /* 线程结构指针初始化初始化 */
31. sp = sp - sizeof(_st_thread_t);
32. thread = (_st_thread_t *) sp;
33.
34. /* 栈实际可用空间64B对齐 */
35. if ((unsigned long)sp & 0x3f)
36. sp = sp - ((unsigned long)sp & 0x3f);
37. /* 栈指针初始化 */
38. stack->sp = sp - _ST_STACK_PAD_SIZE;
39.
40. /* 线程结构初始化和赋值 */
41. memset(thread, 0, sizeof(_st_thread_t));
42. memset(ptds, 0, ST_KEYS_MAX * sizeof(void *));
43.
44. thread->private_data = ptds;
45. thread->stack = stack;
46. thread->start = start;
47. thread->arg = arg;
48.
49. /* 线程上下文初始化
50. *#define _ST_INIT_CONTEXT(_thread, _sp, _main) \
51. *if (MD_SETJMP((_thread)->context)) \
52. *_main(); \
53. *(_thread)->context[3] = (long) (_sp); \
54. * 初始化上下文是MD_SETJMP返回0,此时不会执行入口函数_main(_st_thread_main)
55. * 当下一次调度到该微线程时,从_main(_st_thread_main)继续执行
56. * _st_thread_main的注释见下文
57. */
58. _ST_INIT_CONTEXT(thread, stack->sp, _st_thread_main);
59.
60. ......
61.
62. thread->state = _ST_ST_RUNNABLE;/* 设置线程为可运行状态 */
63. _st_active_count++;/* 活动线程数加1,idle线程在做调度时会判断该技术,决定程序是否退出 */
64. _ST_ADD_RUNQ(thread);/* 将线程加入运行队列,等待被调度执行 */
65.
66. return thread;
67. }
2)st_thread_main
1. /*
2. * 微线程统一入口函数
3. */
4. void _st_thread_main(void)
5. {
6. _st_thread_t *thread = _ST_CURRENT_THREAD();
7. /*
8. * Cap the stack by zeroing out the saved return address register
9. * value. This allows some debugging/profiling tools to know when
10. * to stop unwinding the stack. It's a no-op on most platforms.
11. */
12. MD_CAP_STACK(&thread);
13.
14. /* 调用线程start函数
15. * 如果不想让微线程退出,可以将start函数死循环,
16. * 在IO等待时ST会调用_ST_SWITCH_CONTEXT切换至其他线程运行
17. * 等到IO READY后调度回该线程继续执行
18. */
19. thread->retval = (*thread->start)(thread->arg);
20.
21. /* 然后调用st_thread_exit函数退出微线程 */
22. st_thread_exit(thread->retval);
23. }
3)st_thread_exit微线退出函数
1. /*
2. * 微线程退出函数
3. */
4. void st_thread_exit(void *retval)
5. {
6. _st_thread_t *thread = _ST_CURRENT_THREAD();
7.
8. thread->retval = retval;
9. /* 释放线程私有数据 */
10. _st_thread_cleanup(thread);
11. /* 活跃线程计数减1 */
12. _st_active_count--;
13. /* 对于joinable线程,推出前需要通知其他线程 */
14. if (thread->term) {
15. /* Put thread on the zombie queue */
16. thread->state = _ST_ST_ZOMBIE;
17. _ST_ADD_ZOMBIEQ(thread);
18.
19. /* Notify on our termination condition variable */
20. st_cond_signal(thread->term);
21.
22. /* Switch context and come back later */
23. _ST_SWITCH_CONTEXT(thread);
24.
25. /* Continue the cleanup */
26. st_cond_destroy(thread->term);
27. thread->term = NULL;
28. }
29.
30. if (!(thread->flags & _ST_FL_PRIMORDIAL))
31. _st_stack_free(thread->stack);
32.
33. /* 切换上下文 */
34. _ST_SWITCH_CONTEXT(thread);
35. /* Not going to land here */
36. /* 此处不会执行到,详见_ST_SWITCH_CONTEXT注释 */
37. }
4)_ST_SWITCH_CONTEXT
1. /*
2. * 切换上下文,即正在执行的线程主动让出CPU
3. * 1)保存当前线程上下文
4. * 2)调用_st_vp_schedule选择下一个可执行的线程
5. */
6. #define _ST_SWITCH_CONTEXT(_thread) \
7. ST_BEGIN_MACRO \
8. ST_SWITCH_OUT_CB(_thread); \/* ST_SWITCH_OUT_CB线程切出回调函数,默认为NULL,用户可以通过st_set_switch_out_cb设置 */
9. if (!MD_SETJMP((_thread)->context)) { \/* 保存当前线程上下文 */
10. _st_vp_schedule(); \/* 1、如果是上下文切出,调用_st_vp_schedule
11. * 2、如果是上下文切入,不会调用。对于st_thread_exit,上下文切出后thread结构被释放
12. * 不会再次切入
13. */
14. } \
15. ST_DEBUG_ITERATE_THREADS(); \
16. ST_SWITCH_IN_CB(_thread); \ /* ST_SWITCH_IN_CB线程切入回调函数,默认为NULL,用户可以通过st_set_switch_in_cb设置 */
17. ST_END_MACRO
5)_st_vp_schedule
1. /*
2. * ST的调度程序
3. */
4. void _st_vp_schedule(void)
5. {
6. _st_thread_t *thread;
7.
8. /* 选择下一个运行的线程 */
9. if (_ST_RUNQ.next != &_ST_RUNQ) {
10. /* 从RUNQ选择(RR调度)待运行线程 */
11. thread = _ST_THREAD_PTR(_ST_RUNQ.next);
12. _ST_DEL_RUNQ(thread);
13. } else {
14. /* 如果没有待运行线程,切换到idle线程执行
15. * 实际上是调用_st_idle_thread_start将IO Ready或者wait超时的线程加入待运行队列
16. * 然后idle线程切换上下文,让渡CPU。参见_st_idle_thread_start注释
17. */
18. thread = _st_this_vp.idle_thread;
19. }
20. ST_ASSERT(thread->state == _ST_ST_RUNNABLE);
21.
22. /* 通过longjmp恢复线程的上下文
23. * #define _ST_RESTORE_CONTEXT(_thread) \
24. * ST_BEGIN_MACRO \
25. * _ST_SET_CURRENT_THREAD(_thread); \
26. * MD_LONGJMP((_thread)->context, 1); \
27. * ST_END_MACRO
28. */
29. thread->state = _ST_ST_RUNNING;
30. _ST_RESTORE_CONTEXT(thread);
31. }
6)_st_idle_thread_start
1. /*
2. * idle线程循环调度函数,
3. * 仅当RUNQ上无可运行线程时,idle线程才会被调度
4. * idle线程功能:
5. * 1)当无活跃线程时(活跃线程不包括idle线程),退出整个执行程序
6. * 2)当有活跃线程
7. i)调用_st_epoll_dispatch将IO Ready线程设置为可运行状态,加入到RUNQ
8. *ii)调用_st_vp_check_clock将timewait超时的线程设置为可运行状态,加入到RUNQ
9. * iii)切换上下文,将CPU让渡给活跃线程
10. */
11. void *_st_idle_thread_start(void *arg)
12. {
13. _st_thread_t *me = _ST_CURRENT_THREAD();
14. /*
15. * 检查活跃线程计数,如果无活跃线程退出整个程序
16. * 这也是为何创建微线程时活跃计数加一,线程退出时活跃计数减一的原因
17. */
18. while (_st_active_count > 0) {
19. /* 实际上是调用_st_epoll_dispatch(或者_st_select_dispatch等其他多路复用函数)将之前因为IO等待换出的线程再次加入运行队列 */
20. _ST_VP_IDLE();
21.
22. /* 唤醒timewait的线程 */
23. _st_vp_check_clock();
24.
25. me->state = _ST_ST_RUNNABLE;
26. _ST_SWITCH_CONTEXT(me);
27. }
28.
29. /* No more threads */
30. exit(0);
31.
32. /* NOTREACHED */
33. return NULL;
34. }
7)_st_idle_thread_start
1. /*
2. * ST初始化函数
3. * 1) 完成全局数据的初始化,包括:
4. i) 多路复用机制的选择(默认epoll)
5. ii)_st_io_init信号SIGPIPE的屏蔽,rlimit参数的设置
6. iii)_st_vp结构初始化,以及三个线程队列(RUNQ、IOQ、ZOMBIEQ)的初始化
7. 2) 通过st_thread_create创建idle线程
8. 3) 创建初始(primordial)线程,不是通过st_thread_create创建,初始线程仅有_st_thread_t结构,并没有单独分配线程栈,而是直接使用物理线程的线程栈
9. */
10. int st_init(void)
11. {
12. _st_thread_t *thread;
13.
14. if (_st_active_count) {
15. /* Already initialized */
16. return 0;
17. }
18.
19. /* We can ignore return value here */
20. st_set_eventsys(ST_EVENTSYS_DEFAULT);
21.
22. if (_st_io_init() < 0)
23. return -1;
24.
25. memset(&_st_this_vp, 0, sizeof(_st_vp_t));
26.
27. ST_INIT_CLIST(&_ST_RUNQ);
28. ST_INIT_CLIST(&_ST_IOQ);
29. ST_INIT_CLIST(&_ST_ZOMBIEQ);
30.
31. if ((*_st_eventsys->init)() < 0)
32. return -1;
33.
34. _st_this_vp.pagesize = getpagesize();
35. _st_this_vp.last_clock = st_utime();
36.
37. /*
38. * 创建idle线程,入口函数为_st_idle_thread_start
39. */
40. _st_this_vp.idle_thread = st_thread_create(_st_idle_thread_start, NULL, 0, 0);
41. if (!_st_this_vp.idle_thread)
42. return -1;
43. _st_this_vp.idle_thread->flags = _ST_FL_IDLE_THREAD;
44. _st_active_count--;/* idle线程不作为活跃线程 */
45. _ST_DEL_RUNQ(_st_this_vp.idle_thread);/* idle线程不放到RUNQ被调度,而是当RUNQ为空时由_st_vp_schedule调度 */
46.
47. /*
48. * 创建初始线程
49. */
50. thread = (_st_thread_t *) calloc(1, sizeof(_st_thread_t) + (ST_KEYS_MAX * sizeof(void *)));
51. if (!thread)
52. return -1;
53. thread->private_data = (void **) (thread + 1);
54. thread->state = _ST_ST_RUNNING;
55. thread->flags = _ST_FL_PRIMORDIAL;
56. _ST_SET_CURRENT_THREAD(thread);/* 将初始线程设置为current线程,此时初始线程控制CPU */
57. _st_active_count++;/* 初始线程作为活跃进程,当初始线程不退出时,_st_idle_thread_start不会退出 */
58.
59. return 0;
60. /*
61. * ST初始化后,CPU仍有物理线程(初始线程)控制,
62. * 如果想切换至其他ST线程,需要手动调用会执行上下文切换的函数。如:st_thread_exit、st_poll或st_usleep等
63. */
64. }
ST执行流程。
第一个阶段: st_init创建idle线程和创建初始(priordial)线程,此时_st_active_count是1,物理线程(即初始线程)控制CPU。
第二个阶段:用户创建线程。调用st_thread_create时,会把_st_active_count递增,并且加入RUNQ。
第三个阶段:初始线程切换,将CPU控制权交给ST。也就是初始线程,做完st_init和创建其他线程后,这个时候还没有任何的线程切换。初始线程需要将控制权切换给st,可以调用st_sleep循环和休眠,或者调用st_thread_exit(NULL)等待其他线程结束。如果该阶段物理线程不进行切换,ST将无法获取控制权,程序会直接返回。
下面通过简单的ST例子,说明编写ST程序的流程。复杂的例子见源代码中proxy和server的实现。
1. #include
2. #include "st.h"
3. void *do_print(void *str)
4. {
5. fprintf(stdout, "arg is %s\n", (char *)str);
6. return NULL;
7. }
8.
9. int main(int argc, char *argv[])
10. {
11. int i;
12. if (argc < 2) {
13. fprintf(stderr, "Usage: %s args ...\n", argv[0]);
14. exit(1);
15. }
16. // 初始化ST
17. if (st_init() < 0) {
18. exit(1);
19. }
20. //根据参数个数创建微线程
21. for (i = 1; i < argc; i++) {
22. if (st_thread_create(do_print, argv[i], 0, 0) == NULL) {
23. exit(1);
24. }
25. }
26. //退出初始线程,让ST调度到其他微线程
27. st_thread_exit(NULL);
28. // 此处不会被执行到,进程实际是在_st_idle_thread_start中退出
29. printf("exit...\n");
30. return 0;
31. }
http://state-threads.sourceforge.net/docs/index.html
http://blog.csdn.net/win_lin/article/details/40978665
http://blog.csdn.net/win_lin/article/details/41009137
http://blog.csdn.net/tao_627/article/details/45788013