skynet中动态库的处理
skynet中的.so动态库由service-src中的c文件编译完后生成,其中最重要的是snlua.c.
源码地址:https://github.com/cloudwu/skynet/service-src
这里不介绍如何生成动态库,而是介绍当编译成动态库后,skynet是如何利用里边的函数的.
源码:
#include "skynet.h"
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
struct snlua {
lua_State * L;
struct skynet_context * ctx;
};
// LUA_CACHELIB may defined in patched lua for shared proto
#ifdef LUA_CACHELIB
#define codecache luaopen_cache
#else
static int
cleardummy(lua_State *L) {
return 0;
}
static int
codecache(lua_State *L) { //该函数由_init中luaL_requiref(L, /modname/ "skynet.codecache", /openf/ codecache , 0)
luaL_Reg l[] = {
{ "clear", cleardummy },
{ "mode", cleardummy },
{ NULL, NULL },
};
luaL_newlib(L,l); //向lua中注册“clear”和“mode”函数
lua_getglobal(L, "loadfile");//把全局变量 name 里的值压栈,返回该值的类型,目前尚不知道loadfile是在哪里注册到lua中的?
lua_setfield(L, -2, "loadfile");//做一个等价于 t[k] = v 的操作, 这里 t 是给出的索引处的值, 而 v 是栈顶的那个值。
return 1;
}
#endif
static int
traceback (lua_State *L) {
const char *msg = lua_tostring(L, 1);
if (msg)
luaL_traceback(L, L, msg, 1);
else {
lua_pushliteral(L, "(no error message)");
}
return 1;
}
static void
_report_launcher_error(struct skynet_context *ctx) {
// sizeof "ERROR" == 5
skynet_sendname(ctx, 0, ".launcher", PTYPE_TEXT, 0, "ERROR", 5);
}
static const char *
optstring(struct skynet_context *ctx, const char *key, const char * str) {
const char * ret = skynet_command(ctx, "GETENV", key);
if (ret == NULL) {
return str;
}
return ret;
}
static int
_init(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
lua_State *L = l->L;
l->ctx = ctx;
lua_gc(L, LUA_GCSTOP, 0); //LUA_GCSTOP: 停止垃圾收集器
lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ //放个nil值到栈上
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); //LUA_REGISTRYINDEX?,"LUA_NOENV"?
luaL_openlibs(L);
lua_pushlightuserdata(L, ctx); //lua中的lightuserdata存放的是c语言中的变量,这里为结构体指针
lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
luaL_requiref(L, /modname/ "skynet.codecache", /openf/ codecache , 0);
//如果 modname 不在 package.loaded 中, 则调用函数 openf ,并传入字符串 modname。 将其返回值置入 package.loaded[modname]。 这个行 为好似该函数通过 require 调用过一样。
//如果glb 为真, 同时也讲模块设到全局变量 modname 里。
//在栈上留下该模块的副本。
lua_pop(L,1);
const char *path = optstring(ctx, "lua_path","./lualib/?.lua;./lualib/?/init.lua");
lua_pushstring(L, path);
lua_setglobal(L, "LUA_PATH");
const char *cpath = optstring(ctx, "lua_cpath","./luaclib/?.so");
lua_pushstring(L, cpath);
lua_setglobal(L, "LUA_CPATH");
const char *service = optstring(ctx, "luaservice", "./service/?.lua");
lua_pushstring(L, service);
lua_setglobal(L, "LUA_SERVICE");
const char *preload = skynet_command(ctx, "GETENV", "preload");
lua_pushstring(L, preload);
lua_setglobal(L, "LUA_PRELOAD"); //
lua_pushcfunction(L, traceback); //以上将各种path注册到lua的全局变量中,变量名字即为 "LUA_xxxx"
assert(lua_gettop(L) == 1);
const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");
int r = luaL_loadfile(L,loader); //加载loader.lua
if (r != LUA_OK) {
skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));
_report_launcher_error(ctx);
return 1;
}
lua_pushlstring(L, args, sz); //将bootstart传入
r = lua_pcall(L,1,0,1); //load bootstart 服务
if (r != LUA_OK) {
skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));
_report_launcher_error(ctx);
return 1;
}
lua_settop(L,0);
lua_gc(L, LUA_GCRESTART, 0);
return 0;
}
static int
_launch(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) { //ud就是服务中的结构体
assert(type == 0 && session == 0); //确保_launch服务在一个进程中只调用一次
struct snlua *l = ud;
skynet_callback(context, NULL, NULL);
int err = _init(l, context, msg, sz);
if (err) {
skynet_command(context, "EXIT", NULL);
}
return 0;
}
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) { //BOOTSTART
int sz = strlen(args);
char * tmp = skynet_malloc(sz);
memcpy(tmp, args, sz);
skynet_callback(ctx, l , _launch); //注册回掉函数为_launch,当工作线程轮询到的时候调用的回掉函数即为"_launch"
const char * self = skynet_command(ctx, "REG", NULL); //服务有handle和名字,这里为服务注册名字,若名字为NULL,则返回handle的字符串 “:handle”
uint32_t handle_id = strtoul(self+1, NULL, 16);
// it must be first message
skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz); //给他自己发送一个信息,目的地为刚注册的地址,最终将该消息push到对应的mq上
return 0;
}
struct snlua *
snlua_create(void) {
struct snlua * l = skynet_malloc(sizeof(*l));
memset(l,0,sizeof(*l));
l->L = lua_newstate(skynet_lalloc, NULL);
return l;
}
void
snlua_release(struct snlua *l) {
lua_close(l->L);
skynet_free(l);
}
void
snlua_signal(struct snlua *l, int signal) {
skynet_error(l->ctx, "recv a signal %d", signal);
#ifdef lua_checksig
// If our lua support signal (modified lua version by skynet), trigger it.
skynet_sig_L = l->L;
#endif
}
以上为service_snlua.c中的内容,初看起来一头雾水,我们来看一下整个过程。
skynet.main.c中的main函数最后调用skynet_start.c中的skynet_start函数,其中启动snlua服务代码部分如下:
bootstrap(ctx, config->bootstrap); //创建snlua服务模块,及ctx
这句话是启动snlua模块,具体bootstrap代码:
static void
bootstrap(struct skynet_context * logger, const char * cmdline) {
int sz = strlen(cmdline);
char name[sz+1];
char args[sz+1];
sscanf(cmdline, "%s %s", name, args); //最终name = "snlua" , args = "bootstrap"
struct skynet_context *ctx = skynet_context_new(name, args); //启动snlua , bootstarp为参数
if (ctx == NULL) {
skynet_error(NULL, "Bootstrap error : %s\n", cmdline);
skynet_context_dispatchall(logger);
exit(1);
}
}
函数参数 : logger,忽略
cmdline : " snlua bootstrap”
让我们看一下skynet_contex_new( "snlua" , "bootstrap" )都做了些什么,该函数在skynet_server.c中
struct skynet_module {
const char * name;
void * module; 动态链接库的指针.so
skynet_dl_create create;
skynet_dl_init init;
skynet_dl_release release;
skynet_dl_signal signal; //skynet.module.c
}; //结构体中的create init release signal 分别对应着service_snlua.c中的create init release signal的函数地址,skynet_dl_XX是宏定义
struct skynet_context *
skynet_context_new(const char * name, const char *param) { //name为服务名,param为需要的参数
struct skynet_module * mod = skynet_module_query(name); //根据name即"snlua"来得到对应名字的mod地址,有就直接查到,没有先打开动态库,获取里边的函数地址,并注册该服务,并返回
//module里实际上是改动态库中的内容
if (mod == NULL) //正常情况下mod不会为NULL
return NULL;
void *inst = skynet_module_instance_create(mod); //调用mod中的create函数,实际上就是service_snlua.c中的create,即调用dlopen来获得动态库
if (inst == NULL)
return NULL;
struct skynet_context * ctx = skynet_malloc(sizeof(*ctx)); //为每个服务创建一个ctx
CHECKCALLING_INIT(ctx)
ctx->mod = mod; //里边有动态库指针和动态库中的函数指针及该库的名字,根据名字已经注册到了skynet
ctx->instance = inst; //每个服务都有一个结构体,如logger的指针
ctx->ref = 2; //why
ctx->cb = NULL;
ctx->cb_ud = NULL;
ctx->session_id = 0;
ctx->logfile = NULL;
ctx->init = false;
ctx->endless = false;
// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
ctx->handle = 0;
ctx->handle = skynet_handle_register(ctx); //将该ctx注册到handle中
struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
// 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); //执行mod中的init函数,若为0执行成功
CHECKCALLING_END(ctx)
if (r == 0) {
struct skynet_context * ret = skynet_context_release(ctx); //减少ctx的引用计数,若为0,删除ctx资源
if (ret) {
ctx->init = true;
}
skynet_globalmq_push(queue); //将创建的服务对应的队列push到全局队列
if (ret) {
skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");
}
return ret;
} else {
skynet_error(ctx, "FAILED launch %s", name);
uint32_t handle = ctx->handle;
skynet_context_release(ctx);
skynet_handle_retire(handle);
struct drop_t d = { handle };
skynet_mq_release(queue, drop_message, &d);
return NULL;
}
}
接下来我们看一下skynet_module.c中的关于动态库看起
应该从skynet_module_query(const char * name)看起 , 此处name为“snlua”
struct skynet_module *
skynet_module_query(const char * name) {
struct skynet_module * result = _query(name);//首先查找snlua module是否存在
if (result)
return result; //存在则返回
SPIN_LOCK(M)
result = _query(name); // double check
if (result == NULL && M->count < MAX_MODULE_TYPE) { //不存在,接下来就是重点
int index = M->count; //M中记录共有多少个c module创建了,->count 为下一个module的索引
void * dl = _try_open(M,name); //调用dlopen来打开动态库,返回动态库指针
if (dl) {
M->m[index].name = name;
M->m[index].module = dl;
if (_open_sym(&M->m[index]) == 0) { //调用_open_sym来获取动态库中的函数地址,具体看下面实现
M->m[index].name = skynet_strdup(name);
M->count ++;
result = &M->m[index];
}
}
}
SPIN_UNLOCK(M)
return result;
}
struct modules {
int count;
struct spinlock lock;
const char * path;
struct skynet_module m[MAX_MODULE_TYPE];
};
static struct modules * M = NULL;
static void *
_try_open(struct modules *m, const char * name) { M , “snlua”
const char *l;
const char * path = m->path; //path就是初始化M时传入的动态库所在的路径
size_t path_size = strlen(path);
size_t name_size = strlen(name); //“snlua”
int sz = path_size + name_size;
//search path
void * dl = NULL;
char tmp[sz];
do
{
memset(tmp,0,sz);
while (*path == ';') path++;
if (*path == '\0') break;
l = strchr(path, ';');
if (l == NULL) l = path + strlen(path);
int len = l - path;
int i;
for (i=0;path[i]!='?' && i < len ;i++) {
tmp[i] = path[i];
}
memcpy(tmp+i,name,name_size);
if (path[i] == '?') {
strncpy(tmp+i+name_size,path+i+1,len - i - 1);
} else {
fprintf(stderr,"Invalid C service path\n");
exit(1);
} //以上循环为拼凑出完成的动态库路径,用真是动态库名字替代 “?”
dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL); //打开动态库,返回动态库指针
path = l;
}while(dl == NULL);
if (dl == NULL) {
fprintf(stderr, "try open %s failed : %s\n",name,dlerror());
}
return dl;
}
static struct skynet_module *
_query(const char * name) {
int i;
for (i=0;i<M->count;i++) {
if (strcmp(M->m[i].name,name)==0) {
return &M->m[i];
}
}
return NULL;
}
static int
_open_sym(struct skynet_module *mod) {
size_t name_size = strlen(mod->name);
char tmp[name_size + 9]; // create/init/release/signal , longest name is release (7)
memcpy(tmp, mod->name, name_size);
strcpy(tmp+name_size, "_create");
mod->create = dlsym(mod->module, tmp);//tmp为snlua_create
strcpy(tmp+name_size, "_init");
mod->init = dlsym(mod->module, tmp);//snlua_init
strcpy(tmp+name_size, "_release");//snlua_release
mod->release = dlsym(mod->module, tmp);
strcpy(tmp+name_size, "_signal");
mod->signal = dlsym(mod->module, tmp); //snlua_signal
return mod->init == NULL;
}
总结:snlua相当于lua服务的入口,在snlua的 _init函数中根据参数名称如(bootstrap)等启动对应的lua服务。
即首先到module中查找该c服务(例如snlua)是否存在,若不存在则调用动态库函数打开该库,获取该库的必要函数。
然后为该服务创建队列,并放到全局消息队列中。
skyney中每个动态库,都是扩展的服务,有自己的消息队列,回掉函数
下面介绍下标题内容即动态库操作的函数,这里用到:
#include <dlfcn.h> void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle);
具体使用参考:http://www.cnblogs.com/Anker/p/3746802.html
未完,待续。
skynet中动态库的处理的更多相关文章
- Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf 动态库的后缀为*.so 静态库的后缀为 libxxx.a ldconfig 目录名
Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf 动态库的后缀为*.so 静态库的后缀为 libxxx.a ldconfig 目录名 转载自:http://b ...
- ios 开发中 动态库 与静态库的区别
使用静态库的好处 1,模块化,分工合作 2,避免少量改动经常导致大量的重复编译连接 3,也可以重用,注意不是共享使用 动态库使用有如下好处: 1使用动态库,可以将最终可执行文件体积缩小 2使用动态库, ...
- Linux系统中“动态库”和“静态库”那点事儿【转】
转自:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻. ...
- gcc中动态库和静态库的链接顺序
so文件:动态库a文件: 静态库exe文件:可执行程序(linux下以文件属性来标示是否是可执行文件,与后缀名无关) 经过自己写的一些测试程序,大致了解了下gcc中链接顺序问题,总结出以下几点:1,动 ...
- Linux系统中“动态库”和“静态库”那点事儿
摘自http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在 ...
- 024-linux中动态库libXXX.so
1.动态库的概念.动态链接库与普通的程序相比而言,没有main函数,是一系列函数的实现.通过shared和fPIC编译参数生产so动态链接库文件.程序在调用库函数时,只需要连接上这个库即可. 2.动态 ...
- Linux中的静态库与动态库
什么是库文件? 库文件是事先编译好的方法的合集.比如:我们提前写好一些数据公式的实现,将其打包成库文件,以后使用只需要库文件就可以,不需要重新编写. Linux系统中: 1.静态库的扩展名为.a:2. ...
- Android 5.0 到 Android 6.0 + 的深坑之一 之 .so 动态库的适配
(原创:http://www.cnblogs.com/linguanh) 目录: 前序 一,问题描述 二,为何会如此"无情"? 三,目前存在该问题的知名SDK 四,解决方案,1 对 ...
- iOS 静态库和动态库的区别&静态库的生成
linux中静态库和动态库的区别 一.不同 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 1. 静态函数库 这类库的名字一般是libxxx.a:利用静态函 ...
随机推荐
- python发送邮件(yagmail模块)
import yagmail user = 'xxxx@qq.com' passwd = 'xxxx' # 授权码,不是密码,需要在邮箱中设置,看邮箱类型,有的需要设置 res = yagmail.S ...
- WPF中DPI的问题
先搞清楚一下几个概念: DPI:dots per inch ,每英寸的点数.我们常说的鼠标DPI,是指鼠标移动一英寸的距离滑过的点数:打印DPI,每英寸的长度打印的点数:扫描DPI,每英寸扫描了多 ...
- POJ 1125 Stockbroker Grapevine【floyd简单应用】
链接: http://poj.org/problem?id=1125 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=22010#probl ...
- UTF-8, UTF-16, UTF-32 & BOM
FAQ - UTF-8, UTF-16, UTF-32 & BOM http://www.unicode.org/faq/utf_bom.html General questions, rel ...
- 客户端-服务器通信安全 sign key
API接口签名校验,如何安全保存appsecret? - 知乎 https://www.zhihu.com/question/40855191 要保证一般的客户端-服务器通信安全,可以使用3个密钥. ...
- 博文分类索引--Python
Python 基础 [python]-- 初识python [python]-- 基本语法.循环 [python]-- 列表 [python]-- 元组.字典 [python]-- 字符串.字符编码与 ...
- 数据库之MySQL(一)
概述 1.什么是数据库 ? 数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 2.什么是 MySQL.Oracle.SQLite.Access.MS SQL Server等 ...
- 理解java注解
@是java注解,即annotation. 注解功能可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件. Java注解是附加在代码中的一些元信息,用于一些工具在编译 ...
- JDBC注册驱动程序的三种方式
1. Class.forName("com.mysql.jdbc.Driver");//加载数据库驱动 Class.forName("com.mysql.jdbc.Dri ...
- Binary Search in Java
关于折半查找中的几个注意点. Version 1: public static <T extends Comparable<? super T>> int binSearch( ...