SRS的信号处理模块SrsSignalManager—SRS源码分析
作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音
SrsSignalManager
就是 SRS 的信号处理模块,他的主要逻辑是把 信号事件 转换成 IO 事件,这样就能用协程来处理了。关于 信号 转 IO 可以阅读之前的文章《HTTP服务器server程序信号处理》
SrsSignalManager
模块使用的是 SRS 封装 StateThreads 协程之后的 API,也就是《SRS对StateThreads的封装》介绍的方法。
SrsSignalManager
实际上是一个 Handler
,因为它继承的是ISrsCoroutineHandler
。SRS 封装后的协程的用法是在构造函数里面把 Handler 注册进去 SrsSTCoroutine
协程,如下:
class SrsSignalManager : public ISrsCoroutineHandler
然后 SrsSTCoroutine
协程内部会调 _pfn_st_thread_create()
创建一个协程,最终会运行 Handler
的 cycle() 方
法,如下:
下面介绍一下 SrsSignalManager
类里面一些重点字段跟方法。
1,int sig_pipe[2]
这是用 pipe()
函数创建的两个文件描述符,关于 pipe
管道的用法,推荐阅读《Unix环境高级编程》。
2,srs_netfd_t signal_read_stfd
signal_read_stfd
是经过 StateThreads 协程库封装之后的 文件描述符,实际上就是对 sig_pipe[0]
的封装,如下:
if ((signal_read_stfd = srs_netfd_open(sig_pipe[0])) == NULL) {
return srs_error_new(ERROR_SYSTEM_CREATE_PIPE, "open pipe");
}
可能这里读者有点疑惑,前面 sig_pipe[]
数组里面明明有两个文件描述符,为什么只针对 read 描述符 进行 srs_netfd_open()
封装?为什么没有定义一个 signal_write_stfd
变量出来
答:因为这类似于 生产与消费者模式,写的时候直接写就行了,读的时候才需要阻塞事件监控。所以他写是直接往 sig_pipe[1]
里面写数据就行了,不需要用协程库对 write fd 进行监控。
3,SrsServer* server
SrsSignalManager
是绑定在 SrsServer
里面的,他们之间的关系如下:
4,SrsCoroutine* trd
trd
是信号模块对应的 协程实例,其他模块也是一样的用法,变量名都叫 trd
。
5,static SrsSignalManager* instance
注意 instance
是一个 static
静态变量,所以可以直接 SrsSignalManager::instance
调用的,他这样是把自己变成了一个带 命名空间的 全局变量,前缀 SrsSignalManager::
可以理解为 命名空间的用法。
instance
是在构造函数里面把自己赋值进去的,如下:
SrsSignalManager
类里面的重点的方法如下:
1,virtual srs_error_t initialize()
initialize()
主要是创建 管道描述符。
2,virtual srs_error_t start()
start()
函数主要设置 信号的处理函数,哪些信号 由 哪些函数 来处理。start()
是在 SrsServer::register_signal
里面被调用的,如下:
3,virtual srs_error_t cycle()
cycle()
就是协程里运行的函数,所有模块的协程函数 都是叫 cycle
的。在信号处理模块,cycle() 主要是不断阻塞读取 管道描述符的 数据,当产生信号的时候 管道描述符就会有数据到来,然后 cycle()
进行处理。
那是在哪里往管道描述符写入数据的呢?
答:在 SrsSignalManager::sig_catcher()
里,因为之前在 SrsSignalManager::start()
已经把所有信号的处理函数都设置成了 sig_catcher()
。
所以当信号产生的时候,代码的执行位置就会跳进去 sig_catcher()
函数,也可以理解为 rip 寄存器跳到 sig_catcher()
了。信号是可以在任何时候产生的,所以任何时候都有机会跳进去 sig_catcher()
函数。
而 sig_catcher()
就负责往 管道描述符 写入数据,如下: