操作系统抽象的API可以屏蔽操作系统特有的操作,使业务代码方便在不同平台移植。这部分实现主要是os_开头的几个文件,其中最重要的是os_core_unix.c(这里选择Linux平台)。我把这些分拆3类:1、线程Thread和本地线程存储TLS Thread Local Storage;2、线程同步对象;3、时间和高精度定时
线程本地存储
线程共享进程的数据,访问需要同步,有时线程需要有自己的私有数据,所以需要线程特有的数据空间。一个进程可以创建一个pthread_key_t,各线程使用该key设置私有数据。具体看下面四个函数的实现,使用全局变量thread_tls_id来存储key。
pj_thread_local_alloc
pj_thread_local_free
pj_thread_local_set
pj_thread_local_get
线程结构体
struct pj_thread_t
{
char obj_name[PJ_MAX_OBJ_NAME]; //线程名字
pthread_t thread; //线程id
pj_thread_proc *proc; //线程函数
void *arg; //线程参数
pj_uint32_t signature1; //标记1,已注册线程会标记
pj_uint32_t signature2; //标记2
pj_mutex_t *suspended_mutex; //使用锁模拟线程挂起
#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0
pj_uint32_t stk_size; //当前栈使用大小
pj_uint32_t stk_max_usage; //线程栈最大限制
char *stk_start; //线程栈起始地址
const char *caller_file; //调用的源码文件__FILE__
int caller_line; //调用的行数__LINE__
#endif
};
线程创建pj_thread_create
1、申请pj_thread_t结构体
2、填充线程名字thread_name
3、根据编译选项是否自定义线程栈大小和自己申请线程栈空间
4、根据flag是否有PJ_THREAD_SUSPENDED,是否创建一个锁模拟线程挂起
5、pthread_create( &rec->thread, &thread_attr, &thread_main, rec);创建原生线程,并且执行函数是thread_main,而不是用户设置的函数
线程入口函数thread_main
1、设置本地存储对象,对象为pj_thread_create创建的pj_thread_t结构体
2、执行用户线程函数
也就说,所以pj创建的线程,都是先执行默认函数thread_main,然后在thread_main才回调用户线程。在用户线程中,需要注册线程。
注册线程pj_thread_register ( const char *cstr_thread_name, pj_thread_desc desc, pj_thread_t **ptr_thread)
注册线程传入一个数组参数pj_thread_desc,有128个long大小,该栈空间就是用来存储线程私有数据,大小足够存储pj_thread_t对象。注册线程比较完整地填充了新的pj_thread_t结构体,然后还是调用pj_thread_local_set设置线程本地私有数据。
线程的创建和注册流程介绍完毕,根据线程示例pjlib/src/pjlib-test/thread.c,这里有个地方我觉得不是很合理,即线程设置了两次私有数据,而且两次的对象并不一样。第一次是创建线程时动态申请了pj_thread_t,并在thread_main设置。第二次是根据注册函数通过栈参数空间desc进行设置,desc是栈局部变量,和创建线程时动态申请的pj_thread_t并不一致。所以第一次设置的pj_thread_t会别覆盖,而那些值也没有传递,比如pj_thread_t的proc arg,可能这些在线程跑起来以后就没用了吧。对这个地方有理解不对的地方,欢迎大家指正。
主线程
主线程比较特殊,和其它线程不一样的地方,主线程不需要调用pthread_create。主线程调用pj_init初始化pj库,里面调用pj_thread_init初始化主线程,在里面创建所有后续先线程需要用到的pthread_key_t,然后调用pj_thread_register,主线程传递的desc变量是全局变量static pj_thread_t main_thread(注意和线程入口函数thread_main不一样,命名颠倒,其实是两个互不相干的东西)。
Linux原生线程
如果是在Linux原生线程想要使用pjlib,则需要如下代码进行注册。
// PJSIP在线程中调用出现提示注册线程pj_thread_register的解决方案
pj_status_t pjcall_thread_register(void)
{
pj_thread_desc desc;
pj_thread_t* thread = 0;
if (!pj_thread_is_registered())
{
return pj_thread_register(NULL, desc, &thread);
}
return PJ_SUCCESS;
}
其它的线程函数比较简单,就不再一一描述
pj_thread_is_registered
pj_thread_get_prio
pj_thread_set_prio
pj_thread_get_prio_min
pj_thread_get_prio_max
pj_thread_get_os_handle
pj_thread_get_name
pj_thread_resume
pj_thread_this
pj_thread_join
pj_thread_destroy
pj_thread_sleep
pj_thread_check_stack
pj_thread_get_stack_max_usage
pj_thread_get_stack_info
线程同步对象
几个线程同步对象基本是对pthread的封装,没有太多可讲的,不再描述
os_core_unix.c
atomic:通话加锁来实现安全的加减
mutex:通话传入类型,可以创建一般的锁或者recursive递归锁
rwmutex:可以使用系统平台的读写锁,另外os_rwmutex.c文件使用mutex和sem实现仿真的读写锁
critical:也是使用mutex来模拟
sem:
event:经典的cond+mutex
另外,pjlib还封装了一个比较高层抽象的锁lock.h和lock.c,还实现了群组锁功能,暂不深入研究群组锁的实现。