st的代码有一点难读, 主要是代理用了很多的宏实现, 这样gdb调试时断点不好设置, 而且库里面用到了一些偏底层的函数(对我来说, 很多函数都没有用到过), 比如mprotect, mmap之类的。 但是底层的原理还是比较简单的, 就是通过setjmp和longjmp实现缓存状态和切换。
apue上将goto语句不能跨越函数, 执行这类跳转功能的函数是setjmp和longjmp。
goto语句是在一个函数内实施跳转, 就是只能在当前的函数栈上跳转; 而setjmp和longjmp在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。 在希望返回的位置调用setjmp。setjmp的参数类型是一个特殊类型的jmp_buf. 这一数据类型是某种形式的数组, 其中存放在调用longjmp时能用来恢复栈状态的所有信息。
但是apue上也明确说了, 对于longjmp返回到setjmp的栈帧时,对于自动变量, 寄存器变量等能否回滚到原先值是不确定的。详细的请查看apue 上相关页.
直接通过例子分析st的流程:
看lookupdns程序:
st库的流程就是:
1. st_init()
2. st_thread_create()
3. st_thread_exit() 或 st_sleep
简单的流程:
1.0 st_init() 创建一个idle thread, _st_this_vp.idle_thread 指向该thread; 在调用st_thread_create的时候, 会设置setjmp点, 如果后面有longjmp了, 会执行传入st_thread_create的回调函数(这里传入的是 _st_idle_thread_start )
1.1 初始化一个primordial thread, 并设置全局变量_st_this_thread 指向该thread, 设置thread->state=_ST_ST_RUNNING
2.0 st_thread_create一个协程: 函数内部创建一个thread, 然后将其加入_st_this_vp.run_q链表
3.0 st_usleep一定时间的时候, 获取_st_this_thread指向的thread, 设置thread的状态为me->state = _ST_ST_SLEEPING, 将该thread加入sleep_q对列, 设置setjmp点 , 然后调用_st_vp_schedule时会取出run_q队列的thread, 然后longjmp该thread的context, 此时run_q只有2.0中创建的thread, 所以会跳到st_thread_create传入的回调函数中去执行.
详细分析代码:
st_init():
int st_init(void)
{
_st_thread_t *thread; // 创建一个st_thread_t类型
if (_st_active_count) { // _st_active_cout: global data, active thread count; st_init初始化后, 至少会有一个idle thread; 所以_st_active_cout 至少为1. 这里如果重复执行st_init会直接退出.
/* Already initialized */
return 0;
}
/* We can ignore return value here */
st_set_eventsys(ST_EVENTSYS_DEFAULT); // 根据系统选择是调用epoll还是select, kqueue. centos下,是通过判断是否存在/usr/include/sys/epoll.h, 从而定义宏MD_HAVE_EPOLL, 参看Makefile:188行, 然后会根据该宏, 设置_st_eventsys = &_st_epoll_eventsys;, _st_eventsys也是一个全局变量, _st_epoll_eventsys里包含了一系列的回调函数
if (_st_io_init() < 0) // ignore SIGPIPE信号, 并设置最大的文件描述符个数
return -1;
memset(&_st_this_vp, 0, sizeof(_st_vp_t)); // _st_this_vp 也是一个全局变量
ST_INIT_CLIST(&_ST_RUNQ); // 初始化 _st_this_vp 的run_q链表
ST_INIT_CLIST(&_ST_IOQ); // 初始化 _st_this_vp 的io_q链表
ST_INIT_CLIST(&_ST_ZOMBIEQ); // 初始化 _st_this_vp的zombie_q
#ifdef DEBUG
ST_INIT_CLIST(&_ST_THREADQ); // 如果执行的是make linux-debug, 会定义DEBUG宏, 还会初始化_st_this_vp的thread_q
#endif
if ((*_st_eventsys->init)() < 0) // 我这里其实是调用_st_epoll_init
return -1;
_st_this_vp.pagesize = getpagesize(); // 获取分页的大小
_st_this_vp.last_clock = st_utime(); // 返回当前的时间,st_utime() 内部调用的是gettimeofday
/*
* Create idle thread
*/
_st_this_vp.idle_thread = st_thread_create(_st_idle_thread_start,
NULL, 0, 0);
// st_thread_create函数讲解见下文
if (!_st_this_vp.idle_thread)
return -1;
_st_this_vp.idle_thread->flags = _ST_FL_IDLE_THREAD;
_st_active_count--;
_ST_DEL_RUNQ(_st_this_vp.idle_thread);
/*
* Initialize primordial thread
*/
thread = (_st_thread_t *) calloc(1, sizeof(_st_thread_t) +
(ST_KEYS_MAX * sizeof(void *)));
if (!thread)
return -1;
thread->private_data = (void **) (thread + 1);
thread->state = _ST_ST_RUNNING;
thread->flags = _ST_FL_PRIMORDIAL;
_ST_SET_CURRENT_THREAD(thread);
_st_active_count++;
#ifdef DEBUG
_ST_ADD_THREADQ(thread);
#endif
return 0;
}
st_thread_create
_st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg,
int joinable/*0*/, int stk_size/*0*/)
{
_st_thread_t *thread;
_st_stack_t *stack;
void **ptds;
char *sp;
#ifdef __ia64__
char *bsp;
#endif
/* Adjust stack size */
if (stk_size == 0)
stk_size = ST_DEFAULT_STACK_SIZE; //
stk_size = ((stk_size + _ST_PAGE_SIZE - 1) / _ST_PAGE_SIZE) * _ST_PAGE_SIZE;
stack = _st_stack_new(stk_size);
if (!stack)
return NULL;
/* Allocate thread object and per-thread data off the stack */
#if defined (MD_STACK_GROWS_DOWN)
sp = stack->stk_top;
#ifdef __ia64__
/*
* The stack segment is split in the middle. The upper half is used
* as backing store for the register stack which grows upward.
* The lower half is used for the traditional memory stack which
* grows downward. Both stacks start in the middle and grow outward
* from each other.
*/
sp -= (stk_size >> 1);
bsp = sp;
/* Make register stack 64-byte aligned */
if ((unsigned long)bsp & 0x3f)
bsp = bsp + (0x40 - ((unsigned long)bsp & 0x3f));
stack->bsp = bsp + _ST_STACK_PAD_SIZE;
#endif
sp = sp - (ST_KEYS_MAX * sizeof(void *));
ptds = (void **) sp;
sp = sp - sizeof(_st_thread_t);
thread = (_st_thread_t *) sp;
/* Make stack 64-byte aligned */
if ((unsigned long)sp & 0x3f)
sp = sp - ((unsigned long)sp & 0x3f);
stack->sp = sp - _ST_STACK_PAD_SIZE;
#elif defined (MD_STACK_GROWS_UP)
sp = stack->stk_bottom;
thread = (_st_thread_t *) sp;
sp = sp + sizeof(_st_thread_t);
ptds = (void **) sp;
sp = sp + (ST_KEYS_MAX * sizeof(void *));
/* Make stack 64-byte aligned */
if ((unsigned long)sp & 0x3f)
sp = sp + (0x40 - ((unsigned long)sp & 0x3f));
stack->sp = sp + _ST_STACK_PAD_SIZE;
#else
#error Unknown OS
#endif
memset(thread, 0, sizeof(_st_thread_t));
memset(ptds, 0, ST_KEYS_MAX * sizeof(void *));
/* Initialize thread */
thread->private_data = ptds;
thread->stack = stack;
thread->start = start;
thread->arg = arg;
#ifndef __ia64__ // cpu的宏,
_ST_INIT_CONTEXT(thread, stack->sp, _st_thread_main);
#else
_ST_INIT_CONTEXT(thread, stack->sp, stack->bsp, _st_thread_main);
#endif
/* If thread is joinable, allocate a termination condition variable */
if (joinable) {
thread->term = st_cond_new();
if (thread->term == NULL) {
_st_stack_free(thread->stack);
return NULL;
}
}
/* Make thread runnable */
thread->state = _ST_ST_RUNNABLE;
_st_active_count++;
_ST_ADD_RUNQ(thread);
#ifdef DEBUG
_ST_ADD_THREADQ(thread);
#endif
return thread;