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服务列表
};

当要创建该类服务的实例时

  1. modules列表中取出该服务的skynet_module句柄, 然后调用m->create()创建实例。
  2. 将实例inst赋值到新的skynet_context中。
  3. 将新的skynet_context注册到skynet_contextlist中
  4. 初始化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)来处理,处理过程如下:

  1. 从全局消息队列中取出一个次级消息队列, skynet_globalmq_pop();;
  2. 通过次级消息队列中的handle变量来获取对应服务的上下文, struct skynet_context * ctx = skynet_handle_grab(handle);;
  3. 调用回调函数,dispatch_message(ctx, &msg);

总结

本文从数据结构的层面,讲述了一个C服务的启动,以及消息的处理流程。

--完--