【nginx流程分析】
自上而下
写在前面
如果没有看到前面的文章,推荐读者看一下前面的文章,方便有一些快速的理解。
传送门
【nginx源码分析系列】
【nginx 模块分析】
main文件
首先我们看一下main文件所在的地方,地址是在/root/nginx/nginx-1.14.2/src/core/nginx.c,因为我是在/root下面解压的,读者可能因为解压的目录不同,而有所不同。照常我们还是先截个图。
左边的这一栏,是vim中的tagList,也算是vim中的一个插件,可以很方便的展示当前目录下面的所有变量和方法。
变量分析
可以看到开头定义的变量 ngx_buf_t,ngx_log_t,ngx_uint_t 之前我们已经说过,其实本质就是 unsigned long 。然后就是 ngx_cycle_t,ngx_conf_dump_t,ngx_core_conf_t,可参考 nginx流程分析之变量篇. 好的经过一番曲折我们变量篇再试告一段落,接下来就是方法的开始。
ngx_debug_init
这个其实没什么作用哈,这边就不浪费笔墨了。
ngx_strerror_init
这个从名字可以看出来,初始化错误,我们可以先看一下方法
我们开始分析,先是根据NGX_SYS_NERR和sizeof(ngx_str_t)给ngx_sys_errlist申请了NGX_SYS_NERR * sizeof(ngx_str_t) 大的内存,然后从下标0开始到NGX_SYS_NERR(107)结束,给ngx_sys_errlist 复制错误,可以看出这边nginx一共有107错误,当然这个也是全局变量。我这边列几个,Undefined error,Operation not permitted,No such file or directory,No such process,Interrupted system call等等,有兴趣的读者可以自己断点或者打印看看。
简单概括一下,这个方法就是申明了107种错误,存在ngx_sys_errlist这个结构中。
ngx_get_options
这个方法从名称上面可以看出来,是获取相关的参数。我们来看一下代码:
static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{
u_char *p;
ngx_int_t i;
for (i = 1; i < argc; i++) { //直接用参数第二位开始
p = (u_char *) argv[i];
if (*p++ != '-') { // 判断是都是 -v -p 这样的用-开头的命令 移动指针
ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
return NGX_ERROR;
}
while (*p) { //判断
switch (*p++) { //判断当前值
case '?':
case 'h':
ngx_show_version = 1;
ngx_show_help = 1;
break;
case 'v':
ngx_show_version = 1;
break;
case 'V':
ngx_show_version = 1;
ngx_show_configure = 1;
break;
case 't':
ngx_test_config = 1;
break;
case 'T':
ngx_test_config = 1;
ngx_dump_config = 1;
break;
case 'q':
ngx_quiet_mode = 1;
break;
case 'p':
if (*p) {
ngx_prefix = p;
goto next;
}
if (argv[++i]) {
ngx_prefix = (u_char *) argv[i];
goto next;
}
ngx_log_stderr(0, "option \"-p\" requires directory name");
return NGX_ERROR;
case 'c':
if (*p) {
ngx_conf_file = p;
goto next;
}
if (argv[++i]) {
ngx_conf_file = (u_char *) argv[i];
goto next;
}
ngx_log_stderr(0, "option \"-c\" requires file name");
return NGX_ERROR;
case 'g':
if (*p) {
ngx_conf_params = p;
goto next;
}
if (argv[++i]) {
ngx_conf_params = (u_char *) argv[i];
goto next;
}
ngx_log_stderr(0, "option \"-g\" requires parameter");
return NGX_ERROR;
case 's':
if (*p) {
ngx_signal = (char *) p;
} else if (argv[++i]) {
ngx_signal = argv[i];
} else {
ngx_log_stderr(0, "option \"-s\" requires parameter");
return NGX_ERROR;
}
if (ngx_strcmp(ngx_signal, "stop") == 0
|| ngx_strcmp(ngx_signal, "quit") == 0
|| ngx_strcmp(ngx_signal, "reopen") == 0
|| ngx_strcmp(ngx_signal, "reload") == 0)
{
ngx_process = NGX_PROCESS_SIGNALLER;
goto next;
}
ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
return NGX_ERROR;
default:
ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
return NGX_ERROR;
}
}
next:
continue;
}
return NGX_OK;
}
此时我们再看一下nginx的常用命令:
对比图片和代码,是不是就是一目了然。在调用ngx_get_options的时候,循环判断./nginx后面的参数,首先就是 ./nginx -h 或者 ./nginx -v 这样的简单命令
首先判断是不是-开头,如果不是直接返回错误,然后就是判断-后的字符,是 h,v还是其他等。
然后分别设置对应的全局变量。
比如 nginx -c 指定配置文件,其实就是
设置了ngx_conf_file的全局变量。这样看下来是不是一目了然
ngx_show_version
其实就是nginx -h 后面的要走的,在上面可以看到在 nginx -h之后设置了
ngx_show_version = 1;
ngx_show_help = 1;
所以走到了这里,我们先看如下代码:
然后就是ngx_show_version_info这个方法了,我们先看看内容
再看看我们手动输入./nginx -h的命令,
相比是不是一目了然,就是在ngx_show_version_info里面写的。方法就是ngx_write_stderr,其实就是写到了控制台。
然后就是 判断!ngx_test_config了,这个在 nginx -t 或者 nginx -T 的时候进行设置,主要是判断是否为了配置文件是否可以运行,如果没有,那么就直接返回了,
ngx_time_init
接下来就到了分析nginx初始化时间的地方,也就是ngx_time_init这个方法
因为这个方法内容比较多设计到时间操作和锁,所以我们单独开一个篇章分析,详见
【nginx流程分析之时间和并发控制】
获取进程id和父进程id
还是一样我们先看一下代码
ngx_pid = ngx_getpid();
ngx_parent = ngx_getppid();
我们先看一下ngx_getpid和ngx_getppid的定义:
#define ngx_getpid getpid
#define ngx_getppid getppid
然后可以看出来就是c语言中,获取当前进程的进程id和父进程的进程id。
ngx_log_init
从名称可以看出这个是初始化nginx的日志方法,还是一样我们先看一下具体的实现逻辑:
ngx_log_t *
ngx_log_init(u_char *prefix)
{
u_char *p, *name;
size_t nlen, plen;
ngx_log.file = &ngx_log_file;
ngx_log.log_level = NGX_LOG_NOTICE;
name = (u_char *) NGX_ERROR_LOG_PATH;
/*
* we use ngx_strlen() here since BCC warns about
* condition is always false and unreachable code
*/
nlen = ngx_strlen(name);
if (nlen == 0) {
ngx_log_file.fd = ngx_stderr;
return &ngx_log;
}
p = NULL;
if (name[0] != '/') {
if (prefix) {
plen = ngx_strlen(prefix);
} else {
prefix = (u_char *) NGX_PREFIX;
plen = ngx_strlen(prefix);
}
if (plen) {
name = malloc(plen + nlen + 2);
if (name == NULL) {
return NULL;
}
p = ngx_cpymem(name, prefix, plen);
if (!ngx_path_separator(*(p - 1))) {
*p++ = '/';
}
ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1);
p = name;
}
}
ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND,
NGX_FILE_CREATE_OR_OPEN,
NGX_FILE_DEFAULT_ACCESS);
if (ngx_log_file.fd == NGX_INVALID_FILE) {
ngx_log_stderr(ngx_errno,
"[alert] could not open error log file: "
ngx_open_file_n " \"%s\" failed", name);
ngx_log_file.fd = ngx_stderr;
}
if (p) {
ngx_free(p);
}
return &ngx_log;
}
然后这边为了逻辑的流程,把代码中的针对win平台的宏定义去掉了。然后接下来我们简单分析一下这个流程:
首先就是把ngx_log_file的指针放到了ngx_log.file中,然后就是定义ngx_log.log_level的类型为notice。
然后看一下NGX_ERROR_LOG_PATH这个常量定义
#define NGX_ERROR_LOG_PATH "logs/error.log"
其实就是logs/error.log,然后就是判断name[0] != ‘/’,此时我们的name是logs/error.log,很明显不是的,然后进入方法。
因为我们在运行nginx的时候一般不指定前缀,所以一般都是空,所以系统会指定一个默认的前缀给我们,也就是NGX_PREFIX,然后我们点进去看一下就是/usr/local/nginx/,这个也是我们默认的安装目录。
然后接下来,通过ngx_cpymem把前缀也就是/usr/local/nginx/ 赋值给p,再把NGX_ERROR_LOG_PATH赋值给p,那么此时p就是/usr/local/nginx/logs/error.log,感兴趣的同学在运行nginx之后,看一下/usr/local/nginx/logs/error.log这个目录,是不是有很多日志。最后把p赋值给name。
然后就是通过ngx_open_file ,ngx_open_file其实就是c语言中的open,然后打开获取得到一个文件描述符,如果失败用系统的默认STDERR_FILENO描述符输出到控制台,然后返回ngx_log。
init_cycle 初始化
首先我们来看一下代码
其实代码也是比较简单,主要就是把init_cycle 初始化,然后把log赋值过去,然后把init_cycle赋值给ngx_cycle。
ngx_create_pool
然后这个就是创建nginx的内存池,详见 nginx内存初始化和操作
ngx_save_argv
首先看一下方法,因为比较简单所以就加了一些注释
static ngx_int_t
ngx_save_argv(ngx_cycle_t *cycle, int argc, char *const *argv)
{
size_t len;
ngx_int_t i;
ngx_os_argv = (char **) argv;
ngx_argc = argc;
//二级指针 char **ngx_argv;
ngx_argv = ngx_alloc((argc + 1) * sizeof(char *), cycle->log);
if (ngx_argv == NULL) {
return NGX_ERROR;
}
for (i = 0; i < argc; i++) {
len = ngx_strlen(argv[i]) + 1;
//每个指针进行分配内存
ngx_argv[i] = ngx_alloc(len, cycle->log);
if (ngx_argv[i] == NULL) {
return NGX_ERROR;
}
//将参数复制过去
(void) ngx_cpystrn((u_char *) ngx_argv[i], (u_char *) argv[i], len);
}
ngx_argv[i] = NULL;
//将系统的环境变量存到ngx_os_environ中
ngx_os_environ = environ;
return NGX_OK;
}
ngx_process_options
这个方法主要是初始化cycle的配置文件,因为我们就在代码中增加注释,
static ngx_int_t
ngx_process_options(ngx_cycle_t *cycle)
{
u_char *p;
size_t len;
//这里我们没有指定前缀 因此ngx_prefix为null
if (ngx_prefix) {
len = ngx_strlen(ngx_prefix);
p = ngx_prefix;
if (len && !ngx_path_separator(p[len - 1])) {
p = ngx_pnalloc(cycle->pool, len + 1);
if (p == NULL) {
return NGX_ERROR;
}
ngx_memcpy(p, ngx_prefix, len);
p[len++] = '/';
}
cycle->conf_prefix.len = len;
cycle->conf_prefix.data = p;
cycle->prefix.len = len;
cycle->prefix.data = p;
} else {
//设置cycle->conf_prefix为conf/
ngx_str_set(&cycle->conf_prefix, NGX_CONF_PREFIX);
//设置cycle->prefix 为/usr/local/nginx/
ngx_str_set(&cycle->prefix, NGX_PREFIX);
}
//如果没有指定nginx配置 ngx_conf_file为null
if (ngx_conf_file) {
cycle->conf_file.len = ngx_strlen(ngx_conf_file);
cycle->conf_file.data = ngx_conf_file;
} else {
//设置cycle->conf_file 为 conf/nginx.conf
ngx_str_set(&cycle->conf_file, NGX_CONF_PATH);
}
//检查配置文件是否正确 同时把cycle->conf_file组装为/usr/local/nginx/conf/nginx.conf
if (ngx_conf_full_name(cycle, &cycle->conf_file, 0) != NGX_OK) {
return NGX_ERROR;
}
//将cycle->conf_prefix从/nginx设置成/usr/local/nginx/conf/nginx.conf cycle->conf_prefix.len为strlen(/usr/local/nginx/conf) +
for (p = cycle->conf_file.data + cycle->conf_file.len - 1;
p > cycle->conf_file.data;
p--)
{
if (ngx_path_separator(*p)) {
cycle->conf_prefix.len = p - cycle->conf_file.data + 1;
cycle->conf_prefix.data = cycle->conf_file.data;
break;
}
}
if (ngx_conf_params) {
cycle->conf_param.len = ngx_strlen(ngx_conf_params);
cycle->conf_param.data = ngx_conf_params;
}
if (ngx_test_config) {
cycle->log->log_level = NGX_LOG_INFO;
}
return NGX_OK;
}
ngx_os_init
从名称可以看出来,是初始化nginx的系统配合,因为内容比较多,所以我们单独开一篇文章。
详见 nginx流程分析之系统初始化
ngx_crc32_table_init
这个是初始化nginx的 crc32_table,我们先看一下代码:
//ngx_crc32_table_short 是ngx_crc32_table16的首地址
uint32_t *ngx_crc32_table_short = ngx_crc32_table16;
ngx_int_t
ngx_crc32_table_init(void)
{
void *p;
//判断 ngx_crc32_table_short的地址是否是 按照ngx_cacheline_size的对齐的 详见
if (((uintptr_t) ngx_crc32_table_short & ~((uintptr_t) ngx_cacheline_size - 1))
== (uintptr_t) ngx_crc32_table_short)
{
return NGX_OK;
}
//分配 16*4 + 32/64 的内存 调用 malloc
p = ngx_alloc(16 * sizeof(uint32_t) + ngx_cacheline_size, ngx_cycle->log);
if (p == NULL) {
return NGX_ERROR;
}
//地址按照ngx_cacheline_size 进行对齐
p = ngx_align_ptr(p, ngx_cacheline_size);
//把ngx_crc32_table16 赋值给p
ngx_memcpy(p, ngx_crc32_table16, 16 * sizeof(uint32_t));
//再把 p的地址赋值给ngx_crc32_table_short
ngx_crc32_table_short = p;
//综上所述把ngx_crc32_table_short的地址变成了按照32/64 cpu对齐,这样cpu一次即可取出
return NGX_OK;
}
这个里面比较令人头疼的就是各种位操作 进行内存对齐的判断和操作,这个里面 【nginx流程分析只内存对齐】
ngx_slab_sizes_init
初始化一些变量,见注释。当然这个在linux 平台,不同的平台getPageSize的值可能会不同,道理是一样的
void
ngx_slab_sizes_init(void)
{
ngx_uint_t n;
//ngx_pagesize 4096
//ngx_slab_max_size 2048
ngx_slab_max_size = ngx_pagesize / 2;
//ngx_slab_exact_size 64
ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {
/* void */
}
//ngx_slab_exact_shift 6
}
ngx_add_inherited_sockets
从名称上可以看出来,是继承已经存在的sockets。然后是从NGINX这个全局变量中获取,当然因为这个功能很少用,这里我们不多说了,后面有用到再补上吧。
ngx_preinit_modules
初始化模块的一些信息
ngx_int_t
ngx_preinit_modules(void)
{
ngx_uint_t i;
//给ngx_modules的index和name赋值
for (i = 0; ngx_modules[i]; i++) {
ngx_modules[i]->index = i;
ngx_modules[i]->name = ngx_module_names[i];
}
//ngx_modules_n 是总共的模块数量 这里为50
ngx_modules_n = i;
//ngx_max_module 当前nginx的模块数量加上128 等于 178
ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;
return NGX_OK;
}
ngx_init_cycle
初始化nginx的cycle,详见 nginx流程分析只cycle初始化