ST学习笔记

State Threads(ST)学习笔记

一、ST简介

ST是一个C语言微线程开源库:http://sourceforge.net/projects/state-threads/

微线程最大的好处就是将传统的EDSM(Event Drive State Machine)异步化多路复用编程(epoll/select等)串行化。通过在一个进程内模拟多个微线程并发,使得异步编程像同步一样简单。但是ST的本质仍然是基于EDSM模型,ST将请求抽象为thread概念,ST的调度器(scheduler)对于用户来说是透明的,不像dispatcher那种将执行状态(execute state)暴露给回调方式。

ST学习笔记_第1张图片

传统EDSM调度模型

ST学习笔记_第2张图片

ST调度模型

ST通过setjmp和longjmp来实现微线程调用栈上下文(仅包括寄存器状态)的保存与恢复。而调用栈是通过额外分配堆内存来保存的,并没有与物理进程共享调用栈。

二、ST的栈结构介绍

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;

ST学习笔记_第3张图片

微线程栈结构图

上图是栈分配后的结果,两边是REDZONE使用mprotect保护不被访问(仅debug模式开启),extra是用于开启随机栈地址空间使用的,随机栈地址开启后会调整bottom和top,就是随机的向右边移动一点。应该是用于防止栈溢出攻击总之,最后使用的,对外提供的接口就是[bottom, top]这个内存区域[bottom, top]区域又进一步分为

ptds:微线程的私有数据private_data,是12个指针(ST_KEYS_MAX指定),参考st_key_create()。

trdst_thread_t结构本身也是在这个stack中分配的。

pad+align:在trd之后是对齐和pad(_ST_STACK_PAD_SIZE指定)。

sp:微线程实际的栈指针。

st_thread_create函数创建微线程时会初始化sp。微线程的分配详情参考_st_stack_new函数

三、重要函数注释

1st_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_size0,设置为默认大小 */

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++;/* 活动线程数加1idle线程在做调度时会判断该技术,决定程序是否退出 */

64. _ST_ADD_RUNQ(thread);/* 将线程加入运行队列,等待被调度执行 */

65. 

66. return thread;

67. }

2st_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. }

3st_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_startIO 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_dispatchIO Ready线程设置为可运行状态,加入到RUNQ

8.  *ii)调用_st_vp_check_clocktimewait超时的线程设置为可运行状态,加入到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结构初始化,以及三个线程队列(RUNQIOQZOMBIEQ)的初始化

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_exitst_pollst_usleep

63. */

64. }

四、ST线程的生命周期

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

你可能感兴趣的:(ST)