文前导读
skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第二篇文章,探讨了 skynet 的模块加载与服务的启动功能,主要包含了以下内容:
- 基本概念:模块与服务是什么?
- 模块的加载
- 服务的启动
- 流程的回顾
1.基本概念:模块与服务
*模块(module)**:在skynet中,模块是指符合规范的 C 共享库文件。一个符合规范的 C 共享库应当具备 *_create
、*_signal
、*_release
以及 *_init
四个接口。其中 \ 代表模块名称。其中模块的接口及定义如下:
1 | //skynet_module.h |
**服务(service)**:相对于模块是静态的概念,服务则是动态的概念,指的是运行在独立上下文中的模块。
skynet 提供了这样的一种机制:用户可以将自定义的模块放置到 skynet 指定的目录下。当 skynet 使用到对应的服务时,会将该模块加载到 modules 当中,并为其创建一个独立的上下文环境(context)。这样不同的服务的运行环境相互透明,交互则通过消息队列来进行。
1 | //skynet_server.c: |
2.模块的加载
在 skynet 中,模块的加载主要通过 skynet_module_query
函数来完成。当 skynet 启动时会先执行 skynet_module_init
函数对全局模块列表 modules 进行初始化。当需要使用到某个服务时,skynet 会调用 skynet_context_new
函数为其创建上下文,这个过程当中会调用 skynet_module_query(name)
函数,该函数会根据 name 查找相应的模块。如果该模块尚未被加载,则将其加载到 modules 当中。具体代码如下
1 | //skynet_module.c |
从上述代码中可以看出,加载模块需要先调用 _try_open()
函数去打开对应的 .so 文件, 并通过 open_sym
函数来将对应的 api 存放到 module
结构体中相应的函数指针处。.so 文件中的 api 命名统一按照 “module_function” 的格式命名。
3.服务的启动
skynet 中服务的创建主要通过 skynet_context_new
来完成,其代码定义如下:
1 | //skynet_server.c |
从上述代码中我们可以看出 skynet_context_new
的主要工作为如下:
- 在 modules 中查找对应的模块名称,如果存在则直接返回模块的句柄,不存在则将模块加载进内存,并保存在 modules 当中
- 调用 module 的 create api 创建 module 的实例 inst
- 分配 skynet_context 结构体并为其赋上相应的值
- 调用 module 的 init api 为 inst 进行初始化
如果初始化成功,则将该 context 中的次级消息队列 queue 放入到全局消息队列当中,然后返回创建好的服务(context)
如果失败则释放分配的 skynet_context, 为服务分配的 handle 以及专属的次级消息队列, 然后返回 NULL。
上述代码中需要注意的,ctx->ref
的初始值为 2。这是因为当 skynet_context_new
执行完毕后,会有两个地方引用了新创建好的 context。一个是 skynet_context_new
的调用者,它会保存返回的 context 指针; 另一个则是 skynet_handle_register
函数,该函数会将新创建的 context 保存在 handle_storage
的 slot
字段中
接下来,我们来看看 skynet_context_new
中的几个模块相关的函数:skynet_module_instance_create
、 skynet_module_instance_init
1 | //skynet_module.c |
在上述代码中,skynet_module_instance_create
的返回值 (void *)(intptr_t)(~0)
引起了我的好奇。这个地址的值为 0xffffffff
, 代表的是内存地址的最底端的地址。它主要的作用就是为了和 NULL
作区分。当 skynet 调用对应模块的 _create
函数时, 如果此时内存耗尽,无法创建模块对象,则会返回 NULL
。如果用户在没有定义 _create
函数情况下也使用 NULL
做返回值,则无法区分这两种情况。
4.总结
简单地来讲,skynet 的模块加载与服务创建的整体过程为:
当 skynet 启动时会先执行 skynet_module_init
进行 modules 的创建,随后调用 skynet_context_new
创建新的服务。在这个过程当中, skynet 先会自动根据配置文件中指定的模块路径进行 module 的加载。完成加载后的 module 将被保存在全局的 modules 当中。随后,分配 skynet_context
结构体并进行相应赋值。在赋值的过程中会调用到 module 的 _create
, _init
等 api。如果分配成功则将 context
返回给调用者,失败返回 NULL
。创建好的服务彼此透明,运行在不同的 skynet_context
下,不同的服务之间的交互必须通过消息队列进行转发
- 本文作者: Phoenix
- 本文链接: http://hacker-cube.com/2020/11/04/skynet-源码阅读笔记-——-skynet-的模块与服务/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!