上一节总结了 main 函数里的代码都做了些啥事。
$var
替换成系统的环境变量,并且会把配置文件内的键值对设置到 skynet_env E
E->L
的 lua 虚拟机的全局环境中。skynet_node G_NODE
, 并把当前工作线程的状态由 THREAD_MAIN
改为 THREAD_WORKER
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, ¤t);
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() 上半段暂时看到这里,后面才是重头戏呀。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.