最近在项目中使用了chan_ss7进行中国7号信令的接入,由于在接入过程中,遇到一些问题,需要对源代码进行分析,以解决遇到的问题。下面先对chan_ss7进行逻辑分析。
与OSI7层模型类似,在SS7中,也是按照层次来进行组织的,从底到上依次是MTP1,MTP2,MTP3,ISUP/TUP/SCCP。MTP层主要保证用户层数据(TUP/ISUP)的可靠端到端传输。在chan_ss7中对ISUP进行了较好的支持,对于TUP目前还没有支持,国内的开源通信(openvox)对这部分进行了扩展,增加了支持。这里是以ISUP为例来说明。
chan_ss7编译后,是以一个channel模块加载到asterisk中的。在chan_ss7中采用了asterisk较为常见的do_monitor线程对收到的消息进行处理。另外,还有一个MTP线程来负责接收MTP消息。为了避免死锁问题,这两个线程之间使用了一个receivedbuf进行中转。
MTP线程接收到来自MTP层的数据后,将消息封装成evet,然后通过mtp_put函数,将event送入到receivedbuf中,然后再通过alert pipe方式通知do_monitor线程有新消息到来需要处理。
在do_monitor线程中,从receivedbuf中逐个取出消息,然后按照消息的类型不同,分别进行处理。
消息的处理过程,以ISUP消息为例,
对于ISUP消息,首先会通过chan_ss7.c中的process_event函数,根据消息类型,转到l4isup_event中。在l4isup_event中,首先对event进行解码,解析出isup消息,然后根据isup消息中的cic查找到对应的pvt结构,然后到process_isup_message根据消息的类型的不同,调用对应的handler进行处理。
在process_isup_message中,根据msg的typ取值不同,向process_circuit_message传入不同的函数指针,以完成handler的抽象,进行调用。
在process_circuit_message中,调用具体的handler之前,要完成几个锁的访问问题,一个是global锁,一个是pvt的锁,一个是channel的锁。这三个锁的访问顺序,是需要仔细注意的,加锁的顺序必须是global->channel->pvt。加锁完成后,是对应的handler的调用。处理结束后,释放锁,处理结束。
我的问题:在处理高并发、长呼叫的时候,有些呼入的长呼叫没有正常挂机。如果被叫不主动挂机,主叫会一直在。并且,此时,所有的呼入/呼出信令全部被阻塞,整个ISUP层不会再接续处理。
分析:通过上面的执行过程分析来看,消息的处理过程是在do_monitor线程中进行的。如果在处理某个消息的时候,出现死锁问题,该线程将不会返回,直到死锁条件解除。特别是在process_circuit_message函数中,需要对三个锁进行分别处理,极容易引入锁死。
跟踪:在实际测试中,对process_circuit_message进行了跟踪,发现死锁问题是在ast_channel_lock的时候引起的,此时,通过CHECK_BLOCKING这个宏判断看,原来是在ast_write。通道线程在往线程中写数据的时候。
进一步分析:作为asterisk中较为核心的channel.c,我坚信不会存在死锁的问题。既然问题是在ast_write函数中,那么产生的根源应该是在ss7_write函数中。在这个函数中,直接调用了write函数,对话路那个通道直接写入语音数据。经过做log测试,看出,发生死锁的时候,是从write函数中没有返回。此处的write函数是同步的写,必须等待写结束后,才进行返回。问题的根源应该是在这里。
解决方法:既然问题已经找到了,那么该如何解决?
1.为了避免整个ISUP层被堵塞,应该避免死锁的发生。起初的判断是将处理过程process_circuit_message放到一个独立的线程中去执行。由于这里有个global锁,多线程实际上被串行执行了,没有多少意义。在process_circuit_message进行channel锁的时候,使用trylock,多检测几次,如果仍然没有锁住channel,则对本次消息,不予处理。等待下次重传的消息。
2.即便是处理重传,但是在实际中会一直出现锁在ast_write中。除非被叫端挂机,否则该通道一直会被占用。为了避免这种情况的发生,在1基础之上,对于拆线等事件,如果发现trylock失败,转而让bridged通道去挂机。对于这种方案,目前还没有进行测试。
阅读(3991) | 评论(0) | 转发(1) |