skynet 源码分析-启动流程之初始化
Last updated
Last updated
skynet 启动, 相当于在 c 代码里注册一个lua 虚拟机, 在其中解释lua 代码, 并通过c 内存在lua 各个线程之间共享使用, 接下来开始看skynet 的启动流程
整个过程中, 主要是初始化一些全局数据, 加载lua lib, 启动service, 并监听, 接下来会逐一解释这些过程
G_NODE 是一个全局变量, 数据结构就是 skynet_node, total 是用来记录 skynet_node 个数的, 每当一个新的 skynet_context 被创建时, 他的计数就会+1, monitor_exit 用来表示 monitor 模块是否已经推出, init 表示是否已经初始化, handle_key 用来存储线程的局部存储, 在 skynet_initthread 会将这个handle 指向 0xFFFFFFFF 这个地址
函数中的E, 是一个全局的 skynet_env, 其中这个lua_State 是很重要的数据结构, 可以说 每个lua线程, 在c 中就是一个lua_State, 通过这个lua_state, 在 c 和 lua 之间传递数据
这两步创建一个新的lua_State, 在 openlibs 时, 绑定了lua 的一些基础操作, loadedlibs 包含了lua包, 协程, table, io...这些基础操作, 在 luaL_loadbufferx 时, 这个 load_config 实际上是一段lua 的代码, 用来解析我们传入的配置文件, 解析出来的配置, 通过 init_env 放在上面初始化的全局 skynet_env 中, 然后释放这个临时申请的lua虚拟机; 最后加载整个配置, 代码中是默认值, 如果config 里写了则加载config 的值
这部分比较重要的是解读这个全局的 handle_storage 作用, 方法本身对这些变量做了初始化,各个字段的含义如下:
harbor 这个字段, 0是单机模式, 大于0则是集群模式
slot_size 记录当前slot 数组大小
slot 中, 存放所有已注册的 skynet 服务
name_cap, name_count 表示name数组的最大可存储量和当前name数组个数
name则表示可以通过名称查找handle
所有的skynet 服务句柄都会记录在这个全局的 H 中
在这个操作中, 将harbor 左移24位, 如果是 0 就会得到0x00000000, 在skynet 中, 高八位(0x00)表示非集群模式, 如果结果就是 0x01000001, 这个代表远程第1个节点(0x01) 的第1个服务(000001)
skynet_mq_init初始化了全局的global_queue, 是个存储message_queue的链表, 每个 skynet 服务都会有一个message_queue, 而skynet 是用线程池来调度任务的, global_queue就充当了任务池的角色, worker 就可以公平的从中获取任务, 让每个worker 都可以跑起来
count: 加载模块数
path: 指向了配置文件里的 cpath
m: skynet_modules 数组, 存储了不同的skynet 模块
module 其实就是这个目录下的文件编译出的动态链接, 这一步出初始化了全局的 module, 但是还没有实际加载这些模块, 这里提前看一下这些模块, 后面启动的时候会用到
near: 存储近期定时任务的链表
t: 存储不同时间级别的定时任务链表
time: 当前时间
starttime: 定时器的起始时间
current: 当前的系统时间(毫秒)
current_point: 当前的时间点,用于精确的时间管理
这一步初始化了skynet 的全局定时器, 所有的定时任务, 都需要在这里存储调度, 他的使用逻辑是这样的, near 里存储了即将要执行的任务, t 里存储了各个时间级别的任务, 如果t 中的任务即将执行, 会被放在near链表里, 然后去执行
这一步做了两个比较重要的事情, sp_create() 创建了一个epoll 描述符(linux下), 并把 pipe 的读端 fd[0] 添加到epoll的事件池中监听 pipe, pipe在这里主要用作进程间通( IPC ), 然后赋给了 SOCKET_SERVER, 这部分会在 skynet 网络部分详细讲解, 这里只要明白做了什么事情就可以
到这一步, 所有的全局变量已经初始化完成, 接下来要实际创建skynet 的service了, 并且给全局变量赋值