[skynet 源码阅读系列] 02_skynet_start - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yiouejv
V2EX    C

[skynet 源码阅读系列] 02_skynet_start

  •  1
     
  •   yiouejv 2021-05-17 19:41:01 +08:00 1938 次点击
    这是一个创建于 1655 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上一节总结了 main 函数里的代码都做了些啥事。

    • skynet 启动需要指定一个配置文件,main 中会把配置文件里的 $var 替换成系统的环境变量,并且会把配置文件内的键值对设置到 skynet_env E E->L 的 lua 虚拟机的全局环境中。
    • 创建了一个全局节点 skynet_node G_NODE/code>, 并把当前工作线程的状态由 THREAD_MAIN 改为 THREAD_WORKER
    • 设置当前进程忽略 SIGPIPE 信号。
    • 初始化 codecache CC
    • 初始化 skynet_config config 配置信息,传到 skynet_start(&config) 内。

    这一节继续,skynet_start, 完整的函数先贴出来。

    void skynet_start(struct skynet_config * config) { // register SIGHUP for log file reopen struct sigaction sa; sa.sa_handler = &handle_hup; sa.sa_flags = SA_RESTART; sigfillset(&sa.sa_mask); sigaction(SIGHUP, &sa, NULL); if (config->daemon) { if (daemon_init(config->daemon)) { exit(1); } } skynet_harbor_init(config->harbor); skynet_handle_init(config->harbor); skynet_mq_init(); skynet_module_init(config->module_path); skynet_timer_init(); skynet_socket_init(); skynet_profile_enable(config->profile); struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger); if (ctx == NULL) { fprintf(stderr, "Can't launch %s service\n", config->logservice); exit(1); } skynet_handle_namehandle(skynet_context_handle(ctx), "logger"); bootstrap(ctx, config->bootstrap); start(config->thread); // harbor_exit may call socket send, so it should exit before socket_free skynet_harbor_exit(); skynet_socket_free(); if (config->daemon) { daemon_exit(config->daemon); } } 

    struct sigaction sa; sa.sa_handler = &handle_hup; sa.sa_flags = SA_RESTART; sigfillset(&sa.sa_mask); sigaction(SIGHUP, &sa, NULL); 

    SIGHUP 信号重新注册了一个 handle, 如果收到 SIGHUP 信号,将调用 handle_hup 函数,将把 SIG 置为 1,SIG 定义为 static volatile int SIG = 0;


    if (config->daemon) { if (daemon_init(config->daemon)) { exit(1); } } 

    如果函数 daemon_init 返回值判断为 true,则退出当前进程。

    我们看看 daemon_init 里做了些什么事。

    // skynet-src/skynet-deamon.c int daemon_init(const char *pidfile) { int pid = check_pid(pidfile); if (pid) { fprintf(stderr, "Skynet is already running, pid = %d.\n", pid); return 1; } #ifdef __APPLE__ fprintf(stderr, "'daemon' is deprecated: first deprecated in OS X 10.5 , use launchd instead.\n"); #else if (daemon(1,1)) { fprintf(stderr, "Can't daemonize.\n"); return 1; } #endif pid = write_pid(pidfile); if (pid == 0) { return 1; } if (redirect_fds()) { return 1; } return 0; } 

    首先是 check_pid()

    static int check_pid(const char *pidfile) { int pid = 0; FILE *f = fopen(pidfile,"r"); if (f == NULL) return 0; int n = fscanf(f,"%d", &pid); fclose(f); if (n !=1 || pid == 0 || pid == getpid()) { return 0; } if (kill(pid, 0) && errno == ESRCH) return 0; return pid; } 

    从文件中读取一个数字,赋值给 pid,

    kill(pid, 0) && errno == ESRCH 向进程号为 pid 的进程发送一个信号 0,用于检查进程是否存在,如果错误信息为 ESRCH,代表进程不存在,return 0, 否则返回 pid 。

    如果 pid 不为 0,说明 pid 的进程存在,输出 "Skynet is already running, pid = ", skynet 进程退出。

    说明,在配置文件里配置 deamon 文件路径,是为了防止 skynet 再启动同一个配置。

    下面回到 daemon_init

    #ifdef __APPLE__ fprintf(stderr, "'daemon' is deprecated: first deprecated in OS X 10.5 , use launchd instead.\n"); #else if (daemon(1,1)) { fprintf(stderr, "Can't daemonize.\n"); return 1; } #endif 

    如果是苹果设备,输出提示,守护进程在 OX 系统被弃用了,否则设置把当前进程设置为守护进行。

    下面是对 daemon 的一段介绍。

    #include <unistd.h> int daemon(int nochdir,int noclose); // nochdir 参数用于指定是否改变工作目录,如果给它传递 0,则工作目录将被设置为“/”(根目录),否则继续使用当前工作目录。 // noclose 参数为 0 时,标准输入、标准输出和标准错误输出都被重定向到 /dev/null 文件,否则依然使用原来的设备。该函数成功时返回 0,失败返回-1,并设置 errno 。 
    pid = write_pid(pidfile); if (pid == 0) { return 1; } 

    将当前进程的 pid 写入文件,理所应当嘛,配置文件配了 daemon 的话,第一次启动要把进程号给记下来,下一次尝试启动就检查不过了。

    daemon_init 最后还做了一件事,重定向文件描述符,把当前进程的文件描述符 0,1,2,也就是 标准输入、标准输出、标准错误输出全部重定向到 nfd, nfd 对应文件 "/dev/null"。

    维基百科 /dev/null /dev/null (或称空设备)在类 Unix 系统中是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个 EOF

    if (redirect_fds()) { return 1; } static int redirect_fds() { int nfd = open("/dev/null", O_RDWR); if (nfd == -1) { perror("Unable to open /dev/null: "); return -1; } if (dup2(nfd, 0) < 0) { perror("Unable to dup2 stdin(0): "); return -1; } if (dup2(nfd, 1) < 0) { perror("Unable to dup2 stdout(1): "); return -1; } if (dup2(nfd, 2) < 0) { perror("Unable to dup2 stderr(2): "); return -1; } close(nfd); return 0; } 

    daemon_init 到这里就结束了,总结一下 daemon_init 做了哪些事,检查文件里的 pid 进程号,如果存在说明这个配置文件已经被启动过了,则不允许被再次启动为 skynet 进程,

    将当前进程设置为守护进程。

    如果是第一次启动则把 pid 号写入文件,用做下次尝试启动时的检查,

    重定向文件描述符,丢弃标准输入,标准输出,标准错误文件的数据。

    如果这些都满足了,return 0


    继续回到 skynet_start 函数。

    skynet_harbor_init(config->harbor);

    // skynet-src/skynet_harbor.c static unsigned int HARBOR = ~0; void skynet_harbor_init(int harbor) { HARBOR = (unsigned int)harbor << HANDLE_REMOTE_SHIFT; } // skynet-src/skynet_handle.h // #define HANDLE_REMOTE_SHIFT 24 HANDLE_REMOTE_SHIFT 

    把配置文件内的 harbor << 24位 赋值给 HARBOR,暂时先不管有什么用。


    skynet_handle_init(config->harbor);

    void skynet_handle_init(int harbor) { assert(H==NULL); struct handle_storage * s = skynet_malloc(sizeof(*H)); s->slot_size = DEFAULT_SLOT_SIZE; s->slot = skynet_malloc(s->slot_size * sizeof(struct skynet_context *)); memset(s->slot, 0, s->slot_size * sizeof(struct skynet_context *)); rwlock_init(&s->lock); // reserve 0 for system s->harbor = (uint32_t) (harbor & 0xff) << HANDLE_REMOTE_SHIFT; s->handle_index = 1; s->name_cap = 2; s->name_count = 0; s->name = skynet_malloc(s->name_cap * sizeof(struct handle_name)); H = s; // Don't need to free H } 

    H 的定义:

    // skynet-scr/skynet_handle.c static struct handle_storage *H = NULL; 

    handle_storage 的定义

    // skynet-scr/skynet_handle.c struct handle_storage { struct rwlock lock; uint32_t harbor; uint32_t handle_index; int slot_size; struct skynet_context ** slot; int name_cap; int name_count; struct handle_name *name; }; struct handle_name { char * name; uint32_t handle; }; 
    // skynet-scr/skynet_handle.c #define DEFAULT_SLOT_SIZE 4 #define MAX_SLOT_SIZE 0x40000000 

    skynet_handle_init 函数声明了一个 handle_storage 结构体赋值给了 H,

    handle_storage 包含一个读写锁,一个 harbor,harbor_index,

    slot_size 默认设置为 4,指定 skynet_context 结构体指针的二级指针 slot

    name_cap, name_count, 指向 handle_name 结构体对象的指针。

    // reserve 0 for system s->harbor = (uint32_t) (harbor & 0xff) << HANDLE_REMOTE_SHIFT; 

    从这里可以看出,harbor 不能为 0,系统保留,harbor 最大为 0xff, 也就是 255, 之后再左移 HANDLE_REMOTE_SHIFT,24 位,和 skynet-src/skynet_harbor.c 里的 HARBOR 保持一致。

    rwlock_init(&s->lock); struct rwlock { ATOM_INT write; ATOM_INT read; }; static inline void rwlock_init(struct rwlock *lock) { ATOM_INIT(&lock->write, 0); ATOM_INIT(&lock->read, 0); } 

    原子操作的读写锁。


    skynet_mq_init();

    // skynet-src/skynet_mq.c void skynet_mq_init() { struct global_queue *q = skynet_malloc(sizeof(*q)); memset(q,0,sizeof(*q)); SPIN_INIT(q); Q=q; } 
    // skynet-src/skynet_mq.c struct global_queue { struct message_queue *head; struct message_queue *tail; struct spinlock lock; }; static struct global_queue *Q = NULL; 

    定义了一个全局的消息队列 Q,包含一个 head 指针,tail 指针,一个 spinlock 自旋锁 lock 。

    头尾指针指向的也是一个消息队列,我们暂时称之为子消息队列,子消息队列表示的是某个具体的 handle 所要处理的消息所组成的队列。

    struct message_queue { struct spinlock lock; uint32_t handle; int cap; int head; int tail; int release; int in_global; int overload; int overload_threshold; struct skynet_message *queue; struct message_queue *next; }; struct skynet_message { uint32_t source; int session; void * data; size_t sz; }; 

    示意图大概是这样:


    skynet_module_init(config->module_path);

    void skynet_module_init(const char *path) { struct modules *m = skynet_malloc(sizeof(*m)); m->count = 0; m->path = skynet_strdup(path); SPIN_INIT(m) M = m; } 
    struct skynet_module { const char * name; void * module; skynet_dl_create create; skynet_dl_init init; skynet_dl_release release; skynet_dl_signal signal; }; struct modules { int count; struct spinlock lock; const char * path; struct skynet_module m[MAX_MODULE_TYPE]; }; static struct modules * M = NULL; 

    skynet-src/skynet_module.c 内声明了一个 modules 的结构体对象 M, 对 M 进行了初始化。

    m->path = skynet_strdup(path); 这里的 path 也就是 config->module_path, 在 main 函数内赋值,config.module_path = optstring("cpath","./cservice/?.so");,就是配置文件里的 cpath 配置。

    char * skynet_strdup(const char *str) { size_t sz = strlen(str); char * ret = skynet_malloc(sz+1); memcpy(ret, str, sz+1); return ret; } 

    我们可以看到,skynet_strdup 的作用是将字符串 copy 了一份。

    这里插个题外话,为什么函数命名为 skynet_strdup,还记得上面的赋值文件描述符的系统调用函数名吗,dup 和 dup2,现在是不是理解了。

    modules 内还声明了一个 skynet_module 类型的数组,struct skynet_module m[MAX_MODULE_TYPE];


    skynet_timer_init();

    static struct timer * TI = NULL; void skynet_timer_init(void) { TI = timer_create_timer(); uint32_t current = 0; systime(&TI->starttime, &current); TI->current = current; TI->current_point = gettime(); } 

    创建了一个 timer, 把 TI->starttime 的值赋为系统现实时间的秒数,TI->current 的值赋为系统现实时间秒数的 100 倍(只是时间小数点后面的时间),TI->current_point 的值赋为当前系统启动时间的秒数的 100 倍。

    static void systime(uint32_t *sec, uint32_t *cs) { struct timespec ti; clock_gettime(CLOCK_REALTIME, &ti); *sec = (uint32_t)ti.tv_sec; *cs = (uint32_t)(ti.tv_nsec / 10000000); } static uint64_t gettime() { uint64_t t; struct timespec ti; clock_gettime(CLOCK_MONOTONIC, &ti); t = (uint64_t)ti.tv_sec * 100; t += ti.tv_nsec / 10000000; return t; } 

    这里主要是对 TI 进行了初始化,暂时不细究,以后出专题研究它。

    CLOCK_REALTIME,CLOCK_MONOTONIC 的 区别可以看看参考博客:

    https://www.cnblogs.com/book-gary/p/3716790.html
    https://blog.csdn.net/tangchenchan/article/details/47989473


    skynet_socket_init

    void skynet_socket_init() { SOCKET_SERVER = socket_server_create(skynet_now()); } 

    看函数名知道创建了一个 socket_server,暂时不细究,估计要出专题。


    skynet_profile_enable(config->profile);

    void skynet_profile_enable(int enable) { G_NODE.profile = (bool)enable; } 

    通过配置文件设置 profile 是开还是关。

    skynet_start() 上半段暂时看到这里,后面才是重头戏呀。

    4 条回复    2021-05-18 11:01:09 +08:00
    r6Vm94FFk9u3W6XI
        1
    r6Vm94FFk9u3W6XI  
       2021-05-17 20:55:54 +08:00
    写的挺认真的,支持一下
    yiouejv
        2
    yiouejv  
    OP
       2021-05-18 00:01:14 +08:00
    @nanjoyoshino 哈哈哈
    sirius4gnu
        3
    sirius4gnu  
       2021-05-18 10:49:51 +08:00
    支持一下楼主,只要你坚持写完,我一定坚持看完。
    yiouejv
        4
    yiouejv  
    OP
       2021-05-18 11:01:09 +08:00
    @sirius4gnu 你也是做游戏的?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1133 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 23:16 PVG 07:16 LAX 15:16 JFK 18:16
    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