Skynet源码赏析二 | 基础数据结构
module管理模块
我们所写的C服务编译成.so文件后放在cpath变量路径下,程序会加载路径cpath = root.."cservice/?.so"下的.so文件,通过static void * _try_open(struct modules *m, const char * name)函数第一次打开,然后被加载到全局变量static struct modules * M中。
module对应的数据结构如下:
//skynet_module.h
struct skynet_module {
	const char * name;          // C服务名称
	void * module;              // 访问so的dl句柄
	skynet_dl_create create;    // 绑定.so的 xxx_create 函数
	skynet_dl_init init;        // 绑定.so的 xxx_init 函数
	skynet_dl_release release;  // 绑定.so的 xxx_release 函数
	skynet_dl_signal signal;    // 绑定.so的 xxx_signal 函数
};
//skynet_modules.c
#define MAX_MODULE_TYPE 32
struct modules {
	int count;                  // C服务数量
	struct spinlock lock;       // 自旋锁
	const char * path;          // C服务.so文件目录,由cpath变量控制置
	struct skynet_module m[MAX_MODULE_TYPE]; // C服务列表
};
当要创建该类服务的实例时
- 从modules列表中取出该服务的skynet_module句柄, 然后调用m->create()创建实例。
- 将实例inst赋值到新的skynet_context中。
- 将新的skynet_context注册到skynet_contextlist中
- 初始化inst实例m->init()。 对应的代码如下
struct skynet_context * skynet_context_new(const char * name, const char *param) {
	struct skynet_module * mod = skynet_module_query(name); //1. 取出服务句柄
    ...
	void *inst = skynet_module_instance_create(mod);//m->create 创建实例
	struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));
	CHECKCALLING_INIT(ctx)
	ctx->mod = mod;
	ctx->instance = inst; //2. 将实例赋值懂啊 skynet_context中
    ...
	ctx->handle = 0;	
	ctx->handle = skynet_handle_register(ctx); //3. 注册到skynet_context list中
    ...
	// init function maybe use ctx->handle, so it must init at last
	context_inc();
	CHECKCALLING_BEGIN(ctx)
	int r = skynet_module_instance_init(mod, inst, ctx, param); //4.m->init()
	CHECKCALLING_END(ctx)
	if (r == 0) {
        ...
	} else {
        ...
	}
}
skynet_context服务管理模块
创建一个新服务,首先要找到对应服务的modules,在创建并初始化modules之后绑定一个上下文skynet_context,skynet通过全局变量static struct handle_storage *H来管理服务。
主要的数据结构如下:
//skynet_server.c
struct skynet_context {
	void * instance;                // m-create()创建的实例
	struct skynet_module * mod;     // 引用mod服务的指针,方便 m 的init(),create()等函数调用 
	void * cb_ud;                   // 调用callback函数时,回传给callback的userdata,一般是instance指针
    skynet_cb cb;                   // 服务的消息回调函数,一般在skynet_module的init函数里指定
	struct message_queue *queue;    // 服务专属的次级消息队列
	ATOM_POINTER logfile;           // 日志句柄
	uint64_t cpu_cost;	            // in microsec 
	uint64_t cpu_start;	            // in microsec 
	char result[32];                // 操作skynet_context的返回值,会写到这里
	uint32_t handle;                // 标识唯一context的服务id
	int session_id;                 // 在发出请求后,收到对方的返回消息时,通过session_id来匹配一个返回,对应哪个请求
	ATOM_INT ref;                   // 引用计数变量,当为0时,表示内存可以被释放
	int message_count;              // 消息数量
	bool init;                      // 是否完成初始化 
	bool endless;                   // 消息是否堵住 
	bool profile;                   // 配合 cpu_cost 和 cpu_start使用
	CHECKCALLING_DECL
};
//skynet_handle.c
struct handle_name {
	char * name;                    // 服务别名
	uint32_t handle;                // 标识唯一context的服务id
};
struct handle_storage {
	struct rwlock lock;             // 读写锁
	uint32_t harbor;                // harbor id
	uint32_t handle_index;          // 创建下一个服务时,该服务的slot idx,一般会先判断该slot是否被占用
	int slot_size;                  // slot的大小,一定是2^n,初始值是4
	struct skynet_context ** slot;  // skynet_context list
	
	int name_cap;                   // 别名列表大小,大小为2^n
	int name_count;                 // 别名数量
	struct handle_name *name;       // 别名列表
};
消息与队列
在创建C服务module和对应的上下文skynet_context时,有一个对应的消息队列message_queue与之绑定, 最后把该消息队列push到全局队列中。
数据结构如下:
//skynet_mq.c
struct message_queue {
	struct spinlock lock;           // 自旋锁 防止数据冲突
	uint32_t handle;                // 服务id 唯一
	int cap;                        // 队列容量
	int head;                       // 头部索引,当head=tail时会进行扩容。
	int tail;                       // 尾部索引
	int release;                    // 是否能释放消息
	int in_global;                  // 是否在全局消息队列中,0表示不是,1表示是
	int overload;                   // 是否过载
	int overload_threshold;
	struct skynet_message *queue;   // 消息内容
	struct message_queue *next;     // 下一个次级消息队列
};
struct global_queue {
	struct message_queue *head;     // 全局消息队列头部指针
	struct message_queue *tail;     // 全局消息队列尾部指针
	struct spinlock lock;           // 自旋锁
};
消息通过函数struct message_queue * skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight)来处理,处理过程如下:
- 从全局消息队列中取出一个次级消息队列, skynet_globalmq_pop();;
- 通过次级消息队列中的handle变量来获取对应服务的上下文, struct skynet_context * ctx = skynet_handle_grab(handle);;
- 调用回调函数,dispatch_message(ctx, &msg);
总结
本文从数据结构的层面,讲述了一个C服务的启动,以及消息的处理流程。
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/2021/10/skynettwo/
- 更新时间:2024-04-16 01:01:05
- 本文声明:转载请标记原文作者及链接