V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yiouejv
V2EX  ›  C

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

  •  
  •   yiouejv · 256 天前 · 1063 次点击
    这是一个创建于 256 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    其实蛮好奇云风为什么选择 c 语言写这个架构
    paoqi2048
        11
    paoqi2048  
       255 天前
    @b00tyhunt3r 他是 C 厨
    helllllloworld
        12
    helllllloworld  
       255 天前
    @b00tyhunt3r 可以看看云风早些年的 blog 就明白了
    luoqeng
        13
    luoqeng  
       254 天前
    skynet 这种单机服务不怎么值得研究,现在的发展方向 微服务 service-mesh 之类的技术
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2476 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:51 · PVG 20:51 · LAX 04:51 · JFK 07:51
    ♥ Do have faith in what you're doing.