OpenSIPS 3.1 开发手册(三)--锁及定时器API

https://www.opensips.org/Documentation/Development-Manual

目录

8.  锁API

8.1  单锁API

8.2  套锁API

8.3  读写锁API

9.  定时器API


8.  锁API

        OpenSIPS 封装了自己的锁API,推荐用它替代系统提供的锁,因为它们提供更高的灵活性。根据案例和menuconfig提供的编译开关,OpenSIPS的锁可以切换:

  • 架构特定锁
    • 使用futexes
    • 使用自适应等待 (生产执行而不是等待)
    • 使用遇忙等待
  • SysV 锁
  • pthread mutexes
  • POSIX semaphores信号量
  • umutexes (FreeBSD)


        锁API提供两种不同的功能,一种使用单个的锁元素,另一种操作一组的锁。

8.1  单锁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);

        /* … */
}

8.2  套锁API

        在处理散列表之类的数据结构时,如果能对一组锁进行操作那将会非常方便,因为每个散列项都需要一把锁,挨个操作太繁琐了。

        引入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);

        /* … */
}

8.3  读写锁API

        读写锁与互斥锁相似,都用于保护临界资源,不同之外是它允许多线程同时读取,但同时只能有一个线程执行写操作,读和写也不能并行。

        如果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只有在以下所有条件全部满足时才有用:

  • 同时在很多进程是执行了很多lock_start_read()
  • 在数量非常有限的进程中,您必须执行一个lock_start_read() ,该操作无法使用 lock_stop_read() + lock_start_write())转换为写入者角色,因为暂时退出临界区域是不可接受的。 同时,由于需要写权限的概率较低,因此始终在这些进程中执行lock_start_write()会导致不必要的开销。

... 其它读写案例中,仅需要基础的读写锁API。

9.  定时器API

        OpenSIPS 为循环调度任务封装了API,精度可以是秒或微秒。

        OpenSIPS的定时器体系涉及以下进程(可以在运行时执行opensips-cli -x mi ps命令获取):

  • "time_keeper" - 启动后开始记录时间的进程,使用两个全局的计时器,精度分别为秒和微秒
  • "timer" - 负责定时器调度任务的进程。它本身并不执行任务,只是把任务分派给其它进程执行
  • "SIP receiver" 和"TCP receiver" - SIP 工作进程,负责处理SIP消息。然而,它们中的任何一个都可能接收到上述timer" 进程发出的,高优先级的任务,这类任务将立即执行,忽略任何未决的网络SIP 消息 
  • "Timer handler" - 用于SIP工作进程忙碌处理流量的场景(比如说,陷入缓慢的DB查询之中,不能返回),以至于定时器任务有被延迟的风险,此进程旨在节省时间并确保定时器任务及时执行

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模块是如何注册两个定时器功能的:

  • 一个秒级精度,负责清理空的profile结构
  • 一个微秒级精度,负责将profile 计数复制到集群中的其它节点

 

  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);

你可能感兴趣的:(OpenSIPS 3.1 开发手册(三)--锁及定时器API)