[skynet 源码阅读系列] 01_从 main 函数开始 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
yiouejv
V2EX    C

[skynet 源码阅读系列] 01_从 main 函数开始

  •  
  •   yiouejv May 12, 2021 3015 views
    This topic created in 1810 days ago, the information mentioned may be changed or developed.

    skynet 是 C 语言写的框架,我们采用学习过程中最基本的方式去阅读 skynet,从 C 语言的 main 函数开始。

    首先我们找到框架的入口main函数,在 skynet/skynet-src/skynet_main.c 文件内。

    main 函数的代码如下:

    int main(int argc, char *argv[]) { const char * config_file = NULL ; if (argc > 1) { config_file = argv[1]; } else { fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n" "usage: skynet configfilename\n"); return 1; } skynet_globalinit(); skynet_env_init(); sigign(); struct skynet_config config; #ifdef LUA_CACHELIB // init the lock of code cache luaL_initcodecache(); #endif struct lua_State *L = luaL_newstate(); luaL_openlibs(L); // link lua lib int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t"); assert(err == LUA_OK); lua_pushstring(L, config_file); err = lua_pcall(L, 1, 1, 0); if (err) { fprintf(stderr,"%s\n",lua_tostring(L,-1)); lua_close(L); return 1; } _init_env(L); config.thread = optint("thread",8); config.module_path = optstring("cpath","./cservice/?.so"); config.harbor = optint("harbor", 1); config.bootstrap = optstring("bootstrap","snlua bootstrap"); config.daemon = optstring("daemon", NULL); config.logger = optstring("logger", NULL); config.logservice = optstring("logservice", "logger"); config.profile = optboolean("profile", 1); lua_close(L); skynet_start(&config); skynet_globalexit(); return 0; } 

    我们一段一段查看


    int main(int argc, char *argv[]) { const char * config_file = NULL ; if (argc > 1) { config_file = argv[1]; } else { fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n" "usage: skynet configfilename\n"); return 1; } //... } 

    定义了一个指针, 指针指向常量, const char* config_file, config_file 赋值为启动时的第二个参数,也就是配置文件的路径。


    skynet_globalinit();

    // skynet/skynet-src/skynet_server.c struct skynet_node { ATOM_INT total; int init; uint32_t monitor_exit; pthread_key_t handle_key; bool profile; // default is off }; static struct skynet_node G_NODE; void skynet_globalinit(void) { ATOM_INIT(&G_NODE.total , 0); G_NODE.monitor_exit = 0; G_NODE.init = 1; if (pthread_key_create(&G_NODE.handle_key, NULL)) { fprintf(stderr, "pthread_key_create failed"); exit(1); } // skynet/skynet-src/skynet_imp.h /* #define THREAD_WORKER 0 #define THREAD_MAIN 1 #define THREAD_SOCKET 2 #define THREAD_TIMER 3 #define THREAD_MONITOR 4 */ skynet_initthread(THREAD_MAIN); } skynet_initthread(int m) { // skynet/skynet-src/atomic.h // #define ATOM_POINTER volatile uintptr_t uintptr_t v = (uint32_t)(-m); pthread_setspecific(G_NODE.handle_key, (void *)v); } 

    初始化全局节点信息,total 为 0,monitor_exit 为 0,init 1,

    pthread_key_create(&G_NODE.handle_key, NULL) 创建了一个多线程私有数据 handle_key,可参考文章: https://www.jianshu.com/p/d78d93d46fc2

    skynet_initthread(THREAD_MAIN); 将当前线程状态由 THREAD_MAIN 切换为 THREAD_WORKER 状态并记录在 handle_key 。


    skynet_env_init();

    // skynet/skynet-src/skynet_env.c struct skynet_env { struct spinlock lock; lua_State *L; }; static struct skynet_env *E = NULL; void skynet_env_init() { E = skynet_malloc(sizeof(*E)); SPIN_INIT(E) E->L = luaL_newstate(); } 

    E 一个skynet_env结构体,结构体包含一个 spinlock 自旋锁,一个 lua 虚拟机指针。

    skynet_malloc 为结构体 E 分配内存,skynet_malloc内部暂时不细究。

    SPIN_INIT(E)

    通过查找代码得知, 这是在 skynet/skynet-src/spinlick.h 中定义的一个宏。
    #define SPIN_INIT(q) spinlock_init(&(q)->lock);
    对 E 中的 lock 进行初始化。

    E->L = luaL_newstate(); L 绑定了一个 lua 虚拟机。


    sigign();

    #include <signal.h> int sigign() { struct sigaction sa; sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGPIPE, &sa, 0); return 0; } 

    main 函数同文件下的 sigign() 函数。

    定义了一个 sigaction 结构体,将 sa_handler 设置为 SIG_IGN,表示要忽略信号的产生的动作。

    sigaction(SIGPIPE, &sa, 0); 将 SIGPIPE 的行为替换为 sa 结构体定义的形式,表示当前进程忽略 SIGPIPE 信号。

    这里简单记录了一下 sigaction 的资料。 01ext_sigaction


    struct skynet_config config;

    定义了结构体 config

    struct skynet_config { int thread; int harbor; int profile; const char * daemon; const char * module_path; const char * bootstrap; const char * logger; const char * logservice; }; 

    luaL_initcodecache();

    // skynet/skynet-src/skynet_main.c #ifdef LUA_CACHELIB luaL_initcodecache(); #endif // skynet/3rd/lauxlib.c static struct codecache CC; struct codecache { struct spinlock lock; lua_State *L; }; LUALIB_API void luaL_initcodecache(void) { SPIN_INIT(&CC); } 

    static const char * load_cOnfig= "\ local result = {}\n\ local function getenv(name) return assert(os.getenv(name), [[os.getenv() failed: ]] .. name) end\n\ local sep = package.config:sub(1,1)\n\ local current_path = [[.]]..sep\n\ local function include(filename)\n\ local last_path = current_path\n\ local path, name = filename:match([[(.*]]..sep..[[)(.*)$]])\n\ if path then\n\ if path:sub(1,1) == sep then -- root\n\ current_path = path\n\ else\n\ current_path = current_path .. path\n\ end\n\ else\n\ name = filename\n\ end\n\ local f = assert(io.open(current_path .. name))\n\ local code = assert(f:read [[*a]])\n\ code = string.gsub(code, [[%$([%w_%d]+)]], getenv)\n\ f:close()\n\ assert(load(code,[[@]]..filename,[[t]],result))()\n\ current_path = last_path\n\ end\n\ setmetatable(result, { __index = { include = include } })\n\ local config_name = ...\n\ include(config_name)\n\ setmetatable(result, nil)\n\ return result\n\ "; struct lua_State *L = luaL_newstate(); luaL_openlibs(L); // link lua lib int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t"); assert(err == LUA_OK); lua_pushstring(L, config_file); err = lua_pcall(L, 1, 1, 0); if (err) { fprintf(stderr,"%s\n",lua_tostring(L,-1)); lua_close(L); return 1; } 

    luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");

    加载了一段 lua 代码到内存里,并压入 lua 栈内。

    load_config 这段代码实现的功能: 将配置文件内的 $var 替换成了环境变量的内容, 并返回了一个 result 表。

    lua_pcall(L, 1, 1, 0);

    执行压入的 load_config 代码块,第二个参数 1 表示压入的栈的个数为 1,lua_pushstring(L, config_file); 被压栈的配置文件名。 执行完函数之后,函数和参数自动出栈,此时栈为空。 函数的返回值被压栈,此时栈内只有一个表 result, result 内包含了配置在 config_file 内的键值对。


    _init_env(L);

    static void _init_env(lua_State *L) { lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) { int keyt = lua_type(L, -2); if (keyt != LUA_TSTRING) { fprintf(stderr, "Invalid config table\n"); exit(1); } const char * key = lua_tostring(L,-2); if (lua_type(L,-1) == LUA_TBOOLEAN) { int b = lua_toboolean(L,-1); skynet_setenv(key,b ? "true" : "false" ); } else { const char * value = lua_tostring(L,-1); if (value == NULL) { fprintf(stderr, "Invalid config table key = %s\n", key); exit(1); } skynet_setenv(key,value); } lua_pop(L,1); } lua_pop(L,1); } // skynet/skynet-src/skynet_env.c void skynet_setenv(const char *key, const char *value) { SPIN_LOCK(E) lua_State *L = E->L; lua_getglobal(L, key); assert(lua_isnil(L, -1)); lua_pop(L,1); lua_pushstring(L,value); lua_setglobal(L,key); SPIN_UNLOCK(E) } // 从堆栈上弹出一个值,并将其设为全局变量 name 的新值。 void lua_setglobal (lua_State *L, const char *name); // 把全局变量 name 里的值压栈,返回该值的类型。 int lua_getglobal (lua_State *L, const char *name); 

    将 lua 栈表内的键值对设置到 &E->L 的全局环境中。


    config.thread = optint("thread",8); config.module_path = optstring("cpath","./cservice/?.so"); config.harbor = optint("harbor", 1); config.bootstrap = optstring("bootstrap","snlua bootstrap"); config.daemon = optstring("daemon", NULL); config.logger = optstring("logger", NULL); config.logservice = optstring("logservice", "logger"); config.profile = optboolean("profile", 1); static int optint(const char *key, int opt) { const char * str = skynet_getenv(key); if (str == NULL) { char tmp[20]; sprintf(tmp,"%d",opt); skynet_setenv(key, tmp); return opt; } return strtol(str, NULL, 10); } // skynet/skynet-src/skynet_env.c const char * skynet_getenv(const char *key) { SPIN_LOCK(E) lua_State *L = E->L; lua_getglobal(L, key); const char * result = lua_tostring(L, -1); lua_pop(L, 1); SPIN_UNLOCK(E) return result; } 

    optint, optstring, optboolean 从 &E->L 的全局环境中取得对应键的值,如果全局环境内未定义,则第二个参数 opt 设为 key 的默认值。


    lua_close(L);

    关闭 main 函数内创建的 lua 虚拟机。


    skynet_start(&config);

    下一节的内容。


    skynet_globalexit();

    void skynet_globalexit(void) { pthread_key_delete(G_NODE.handle_key); } 

    删除在 skynet_initthread 中定义的特殊的线程数据。

    13 replies    2021-05-13 20:59:17 +08:00
    orange
        1
    orange  
       May 12, 2021
    支持一下
    yiouejv
        2
    yiouejv  
    OP
       May 12, 2021
    @orange 谢谢
    join
        3
    join  
       May 12, 2021 via iPhone
    挺优美的,我也喜欢读。不搞游戏从来没用过。
    zhengxiaowai
        4
    zhengxiaowai  
       May 12, 2021
    源码阅读文章不是那么写的,大段的贴代码,没啥意义

    应该抽取核心流程,删除防御检查类代码,5-10 行最好,配置详细的文字说明,实在简单就略过
    yiouejv
        5
    yiouejv  
    OP
       May 12, 2021
    @zhengxiaowai 我就是想要一句句详细解读啊
    zhengxiaowai
        6
    zhengxiaowai  
       May 12, 2021
    @yiouejv 诉我直言,那你直接在 code 上注释就好了。

    这样文章四不像,无法阅读
    yiouejv
        7
    yiouejv  
    OP
       May 12, 2021
    @zhengxiaowai 我倒是觉得很清晰哦,阅读源码没有具体的写法公式吧
    orange
        8
    orange  
       May 13, 2021
    建议画一些图,对整体有更清晰的描述
    yiouejv
        9
    yiouejv  
    OP
       May 13, 2021
    @orange 看这个确实需要自己细细的究细节点,下次注意画图
    b00tyhunt3r
        10
    b00tyhunt3r  
       May 13, 2021
    感谢楼主!同时赞同画图可以加深理解 我看源码肯定要备一个笔记本的

    其实蛮好奇云风为什么选择 c 语言写这个架构
    paoqi2048
        11
    paoqi2048  
       May 13, 2021
    @b00tyhunt3r 他是 C 厨
    helllllloworld
        12
    helllllloworld  
       May 13, 2021
    @b00tyhunt3r 可以看看云风早些年的 blog 就明白了
    luoqeng
        13
    luoqeng  
       May 13, 2021
    skynet 这种单机服务不怎么值得研究,现在的发展方向 微服务 service-mesh 之类的技术
    About     Help     Advertise     Blog     API     FAQ     Solana     4924 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 71ms UTC 09:56 PVG 17:56 LAX 02:56 JFK 05:56
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86