https://www.opensips.org/Documentation/Development-Manual
目录
8. 锁API
8.1 单锁API
8.2 套锁API
8.3 读写锁API
9. 定时器API
OpenSIPS 封装了自己的锁API,推荐用它替代系统提供的锁,因为它们提供更高的灵活性。根据案例和menuconfig提供的编译开关,OpenSIPS的锁可以切换:
锁API提供两种不同的功能,一种使用单个的锁元素,另一种操作一组的锁。
引入头文件“locking.h”就可以使用API。OpenSIPS通用锁定义为gen_lock_t 结构。可以调用lock_alloc分配一个新锁:
/*
Returns :
A shared memory allocated lock, or NULL in case of an error.
*/
gen_lock_t *lock_alloc(void);
由于锁的作用通常是在所有进程间保护某种临界资源,因此,缺省情况下,锁分配在共享内存中。此外,没必要总是单独分配锁,如果锁是嵌入在一个共享内存中分配的结构体中,那么效果是一样的。
在对锁进行操作之前,必须先初始化它:
/*
Parameters :
lock - the lock instance to be initialized
Returns :
The initialized lock in case of success, or NULL in case of error.
*/
gen_lock_t* lock_init(gen_lock_t* lock);
要获取锁,必须调用lock_get函数:
/*
Parameters :
lock - the lock to be acquired
*/
void lock_get(gen_lock_t *lock);
如果其它进程占有锁,函数将会堵塞,直到当前进程占有锁时返回。
可以调用lock_release释放锁:
/*
Parameters :
lock - the lock to be released.
*/
void lock_release(gen_lock_t *lock);
如果不再需要锁,那么必须销毁它,锁资源才能安全释放。
/*
Parameters :
lock - the lock to be destroyed
*/
void lock_destroy(gen_lock_t *lock);
/*
Parameters :
lock - the lock to be deallocated
*/
void lock_dealloc(gen_lock_t *lock);
以下代码片段展示了单锁使用的典型场景:
gen_lock_t *my_lock;
int init_function(void)
{
/* … */
my_lock = lock_alloc();
if (my_lock == NULL) {
LM_ERR(“Failed to allocate lock \n”);
return -1;
}
if (lock_init(my_lock) == NULL) {
LM_ERR(“Failed to init lock \n”);
return -1;
}
/* … */
return 0;
}
int do_work(void)
{
/* … */
lock_get(my_lock)
/* critical region protected by our lock
generally recommended to keep critical regions short
I/O operations to be avoided in such critical regions */
lock_release(my_lock)
/* … */
}
void destroy_function(void) {
/* … */
lock_destroy(my_lock);
lock_dealloc(my_lock);
/* … */
}
在处理散列表之类的数据结构时,如果能对一组锁进行操作那将会非常方便,因为每个散列项都需要一把锁,挨个操作太繁琐了。
引入locking.h 头文件就可以使用套锁API。OpenSIPS 的套锁定义为gen_lock_set_t 结构,它的用法和单锁实例非常相似。
调用lock_set_alloc函数分配套锁:
/*
Returns :
A shared memory allocated lock set, or NULL in case of an error.
*/
gen_lock_set_t *lock_set_alloc(void);
由于锁的作用通常是在所有进程间保护某种临界资源,因此,缺省情况下,锁分配在共享内存中。此外,没必要总是单独分配锁,如果锁是嵌入在一个共享内存中分配的结构体中,那么效果是一样的。
在对套锁进行操作之前,必须先初始化它:
/*
Parameters :
lock - the lock set instance to be initialized
Returns :
The initialized lock in case of success, or NULL in case of error.
*/
gen_lock_set_t* lock_set_init(gen_lock_set_t* lock);
调用ock_set_get函数获取套锁:
/*
Parameters :
lock - the lock to be acquired
entry - the entry in the lock set that needs to be acquired
*/
void lock_set_get(gen_lock_set_t *lock,int entry);
如果其它进程占用锁,那么函数堵塞,直到当前里程占有锁时返回。
调用lock_set_release释放套锁:
/*
Parameters :
lock - the lock to be released.
entry - the entry in the lock set that needs to be released
*/
void lock_set_release(gen_lock_set_t *lock,int entry);
一旦不再需要锁,需要先销毁它,套锁资源才能安全地释放:
/*
Parameters :
lock - the lock set to be destroyed
*/
void lock_set_destroy(gen_lock_set_t *lock);
/*
Parameters :
lock - the lock set to be deallocated
*/
void lock_set_dealloc(gen_lock_set_t *lock);
以下代码片段是套锁使用的一个实例:
gen_lock_set_t *my_lock;
int init_function(void)
{
/* … */
/* allocate lock set with 32 entries */
my_lock = lock_set_alloc(32);
if (my_lock == NULL) {
LM_ERR(“Failed to allocate lock set \n”);
return -1;
}
if (lock_set_init(my_lock) == NULL) {
LM_ERR(“Failed to init lock set \n”);
return -1;
}
/* … */
return 0;
}
int do_work(void)
{
/* … */
/* acquire entry 5 in the lock set */
lock_set_get(my_lock,5)
/* also acquire entry 21 in the lock set */
lock_set_get(my_lock,21);
/* critical region protected by our lock
generally recommended to keep critical regions short
I/O operations to be avoided in such critical regions */
lock_set_release(my_lock,21);
lock_set_release(my_lock,5);
/* … */
}
void destroy_function(void)
{
/* … */
lock_set_destroy(my_lock);
lock_set_dealloc(my_lock);
/* … */
}
读写锁与互斥锁相似,都用于保护临界资源,不同之外是它允许多线程同时读取,但同时只能有一个线程执行写操作,读和写也不能并行。
如果OpenSIPS 的大部分进程对某个资源只需要只读权限,但是你需要执行一条MI命令对资源重载(比如说数据库同步),那么这时使用读写锁是非常方便的。在这个场景中,使用读写锁可以显著提高性能。
引用rw_locking.h头文件可以使用读写锁。OpenSIPS 的读写锁定义为rw_lock_t 结构。
调用lock_init_rw函数从共享内存分配一个新的读写锁并执行初始化:
/*
Returns :
A shared memory allocated rw lock, or NULL in case of an error.
*/
inline static rw_lock_t * lock_init_rw(void);
调用 lock_start_read 函数,获取读权限:
/*
Parameters :
lock - the lock to be acquired
*/
void lock_start_read(rw_lock_t * lock);
这时,如果当前资源正在写入,函数将会堵塞,直到写锁释放;否则,立刻获取资源。
资源读取完毕之后,你必须调用lock_stop_read:
/*
Parameters :
lock - the lock to be released
*/
void lock_stop_read(rw_lock_t * lock);
调用lock_start_write以获取读权限:
/*
Parameters :
lock - the lock to be acquired
*/
void lock_start_write(rw_lock_t * lock);
这时,如果资源正在写入,操作将会挂起,直到其它方写入完毕。否则等待其它所有读取方读取完毕。
写入结束之后,必须调用 lock_stop_write:
/*
Parameters :
lock - the lock to be release
*/
void lock_stop_write(rw_lock_t * lock);
在调用 lock_stop_write之后,其它方才能访问临界区。
调用 lock_destroy_rw以销毁读写锁资源:
/*
Parameters :
lock - the lock to be destroyed
*/
void lock_destroy_rw(rw_lock_t * lock);
把读写锁提升到一个新的层次,对于N 方同读取的场景,允许其中一方获取读权限锁后转变角色,我们可以避免强制某个不确定的一方成为写入方(并一直压制其他读取方),实现一种“可切换的读取方”,可以考虑下面的API:
/*
Parameters :
lock - the lock to acquire
*/
void lock_start_sw_read(rw_lock_t * lock);
某个不确定的读取方可以切换为写入方,这时调用:
/*
Parameters :
lock - the lock to acquire
write_status - the previous "write" status of the lock
*/
void lock_switch_write(rw_lock_t * lock, int write_status); # this is actually a macro, so "write_status" is an output variable :)
角色切换之后,必须切换回原角色之后才能释放锁:
/*
Parameters :
lock - the lock to acquire
write_status - the previous "write" status of the lock
*/
void lock_switch_read(rw_lock_t * lock, int previous_write_status);
最后,释放可切换的读取写:
/*
Parameters :
lock - the lock to acquire
write_status - the previous "write" status of the lock
*/
void lock_stop_sw_read(rw_lock_t * lock);
注意:“可切换读取锁”API只有在以下所有条件全部满足时才有用:
... 其它读写案例中,仅需要基础的读写锁API。
OpenSIPS 为循环调度任务封装了API,精度可以是秒或微秒。
OpenSIPS的定时器体系涉及以下进程(可以在运行时执行opensips-cli -x mi ps命令获取):
timer.h 声明了OpenSIPS里为定时调度任务定义的所有相关接口。如果要注册一个新的定时功能,请使用:
/*
Parameters:
label – opaque string containing a short function description (for displaying/logging purposes)
f – the actual function to be called
param – parameter to be provided to the timer function
interval – the interval, in seconds, that the function needs to be called at
flags – the nature of the job: is it real-time (must be skipped if it cannot run on time) or critical (must never skip a single run, despite delays)
Returns:
0 in case of success, negative code in case of internal error.
*/
int register_timer(char *label, timer_function f, void* param,
unsigned int interval, unsigned short flags);
/*
The timer job prototype:
Parameters:
ticks – represents the current number of seconds since OpenSIPS startup
param - the parameter provided at job registration
*/
typedef void (timer_function)(unsigned int ticks, void* param);
需要微秒级精度调度任务时,使用:
/*
Parameters:
label – opaque string containing a short function description (for displaying/logging purposes)
f – the actual function to be called
param – parameter to be provided to the timer function
interval – the interval, in microseconds, that the function needs to be called at
flags – the nature of the job: is it real-time (must be skipped if it cannot run on time) or critical (must never skip a single run, despite delays)
Returns:
0 in case of success, negative code in case of internal error
*/
int register_utimer(char *label, utimer_function f, void* param,
unsigned int interval, unsigned short flags);
重要注意事项:上述所有定时器相关函数调用必须在"attendant"进程的上下文中,fork工作进程之前(不论是在模块的mod_init()回调中,还是在核心直接调用,都必须在fork之前)。).
Below is a code snippet exemplifying how the dialog module registers two timer functions:
以下代码片段,展示了dialog模块是如何注册两个定时器功能的:
if (register_timer("dialog-repl-profiles-timer", clean_profiles, NULL,
repl_prof_timer_check, TIMER_FLAG_DELAY_ON_DELAY) < 0) {
LM_ERR("failed to register profiles utimer\n");
return -1;
}
...
if (register_utimer("dialog-repl-profiles-utimer", broadcast_profiles, NULL,
repl_prof_utimer * 1000, TIMER_FLAG_DELAY_ON_DELAY) < 0) {
LM_ERR("failed to register profiles utimer\n");
return -1;
}
此外,timer.h API还提供了获取自OpenSIPS 启动后逸逝时间的接口:
/*
Returns :
the number of seconds elapsed since OpenSIPS start
*/
unsigned int get_ticks(void);
/*
Returns:
the number of microseconds elapsed since OpenSIPS start
*/
utime_t get_uticks(void);