nginx是个多进程web容器,不同的配置下它的启动方式也是不同的,这里我只说说最典型的启动方式。

它有1个master进程,和多个worker进程(最优配置的数量与CPU核数相关)。那么,首先我们要找到main函数,它在src/core/nginx.c文件中。谈到源码了,这时我们先简单看下源码的目录结构吧。

nginx主要有下列目录:

src/core,这个目录存放了基础的数据结构像LIST、红黑树、nginx字符串,贯穿始终的一些逻辑结构如ngx_cycle_s、ngx_connection_s等,还有对一些底层操作的封装如log、文件操作、共享内存、内存池等,最后还有个nginx.c这个main启动函数了。

src/event,这个目录下存放与抽象事件相关的结构和钩子函数。nginx是以事件驱动处理流程的,事件自然是整个体系的核心了,这里定义了最核心的ngx_event_s结构。

src/event/modules目录存放了具体的种种事件驱动方式,例如epoll、kqueue、poll、aio、select等,它们通过ngx_event_actions_t结构体中的钩子挂在nginx中。nginx启动时会根据配置来决定使用哪种实现方式。

src/os/unix中存放了unix系统下许多函数调用的UNIX实现。

src/http目录存放到http module的相关实现,这个module负责处理http请求,包括协议的解析以及访问backend server的代码。

src/http/module目录存放http module类型的一些特定用途的module,比如gzip处理加密,图片压缩等。

有个初步了解后,回到main函数中,顺序看看我们感兴趣的事情。它先执行了ngx_time_init,为什么要初始化时间呢?nginx考虑的还是很周到的,取系统时间gettimeofday是系统调用,这意味着,需要发送中断给linux内核,内核需要做进程间切换来处理这个调用。这是一个不能忽视成本的函数。nginx封装了时间函数,这样,每次我们需要处理时间时,并不是调用gettimeofday,而是nginx自己缓存的时间,这样大量减少了系统调用,取当前时间这事可是谁都爱干的。

那么,nginx是怎么维护自己的这个时钟呢?如何保证用户取到的当前时间是有意义的?nginx设计者的出发点是,nginx是事件驱动机制,当一批事件发生时,也就是epoll_wait返回时,会取一次gettimeofday来更新自己的时间,然后调用各个事件对应的处理函数。这些函数都会保证自己是无阻塞的,也就是毫秒级的处理能力,所以,在任何一个事件处理函数中,取到的时间都是之前epoll_wait刚返回时取到的时间,这样,即使拿到的时间慢了几毫秒也无所谓。关键是,每个函数都是无阻塞的,都要迅速的把控制权交还给nginx,这是基本设计原则哈。

main函数初始化时间后,建立了最核心的数据结构ngx_cycle,之后无论是worker进程还是master进程都是围绕着它进行的。下面,我们要超级关注ngx_init_cycle这个函数,启动过程中大量的工作是在这完成的,代码就不列了,这个函数有800行,超大,也可见其之关键。ngx_init_cycle里做的第一件事就是调用所有nginx module里的create_conf方法。好,现在我们才来详细看下nginx module是什么。

nginx 抽象出一个ngx_module_s结构用来描述各个module,每个module处理它感兴起的事件。nginx里共有多少个module既是写死在代码中的,也是可以灵活配置的,呵呵,nginx式的玩法。回想下,下载nginx源码包后,我们也要执行它提供的configure操作,这个命令会生成makefile和ngx_modules文件,makefilel决定编译哪些module源文件,而生成的ngx_modules.c文件决定编译出的执行文件究竟使用哪些module。ngx_modules.c里面会生成一个数组ngx_modules,这是整个nginx工程都在使用的全局变量,它的形式如下:

[cpp] view plain copy
  1. ngx_module_t *ngx_modules[] = {
  2. &ngx_core_module,
  3. &ngx_errlog_module,
  4. &ngx_conf_module,
  5. ... ...
  6. }

这个通过configure生成的全局变量很关键,只有它才知道,一个请求可能会用哪些module处理。

接上文,ngx_init_cycle就是通过ngx_modules数组来调用所有module的create_conf方法的(每个module有权力决定是否实现这个方法,如果不实现的话,当然不会调用了)。然后,开始处理配置文件,这里我们需要重点关注ngx_conf_parse函数,因为它里面调用了ngx_conf_handler方法,ngx_conf_handler方法会调用每个module里自己实现的set钩子函数,让每个module处理自己感兴趣的配置项。所以,如果你在nginx.conf里没有配置某个module想要的东东,这个module虽然编译进去了,却会一直不执行的。这里我们要看下module的结构了,不能总是干说哈。

[cpp] view plain copy
  1. struct ngx_module_s {
  2. ngx_uint_t            ctx_index;
  3. ngx_uint_t            index;
  4. ... ...
  5. void                 *ctx;
  6. ngx_command_t        *commands;
  7. ngx_uint_t            type;
  8. ngx_int_t           (*init_master)(ngx_log_t *log);
  9. ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
  10. ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
  11. ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
  12. void                (*exit_thread)(ngx_cycle_t *cycle);
  13. void                (*exit_process)(ngx_cycle_t *cycle);
  14. void                (*exit_master)(ngx_cycle_t *cycle);
  15. ... ...
  16. };

ctx_index用来表示我们定义的一个module在上下文数组中的序号,index就表示在ngx_modules这个数组中的序号。

ctx这个指针指向module的上下文,type表示这个module的类型(module是分类的,每种type可以有多个module的),下面8个钩子函数,表示对应的事件发生时会调用这些方法(当然,module也可以不实现)。commands指向这个module所属的command结构,例如,http module是这么定义自己的command的:

[cpp] view plain copy
  1. static ngx_command_t  ngx_http_commands[] = {
  2. { ngx_string("http"),
  3. NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
  4. ngx_http_block,
  5. 0,
  6. 0,
  7. NULL },
  8. ngx_null_command
  9. };

我们再看看ngx_command_s的定义:

[cpp] view plain copy
  1. struct ngx_command_s {
  2. ngx_str_t             name;
  3. ngx_uint_t            type;
  4. char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  5. ngx_uint_t            conf;
  6. ngx_uint_t            offset;
  7. void                 *post;
  8. };

所以,上文我说过,ngx_conf_handler方法会调用每个module里自己实现的set钩子函数,如果我们编译了http module(默认都有),那么就会在ngx_conf_handler方法中调用上面的ngx_http_block函数。这个ngx_http_block函数值得详细说说,因为它这时读取配置文件,决定要监听哪些http端口,它会把这些信息通过传进来的ngx_conf_t指针塞给ngx_cycle这个核心变量。

ngx_event_core_module也是个核心module,之前说到的到底是由epoll、select、poll还是kqueue来实现IO多路复用,就是由这个module来搞定的。

继续向下。ngx_init_cycle函数再来调用所有module实现的init_conf钩子函数。之后,执行到现在nginx进程终于要开始监听端口了。这事很关键,刚刚调用过各个module的set钩子方法了,例如上面http module的ngx_http_block方法,这些方法已经给ngx_cycle的listening数组塞进了需要监听的端口。为什么要现在就开始listen呢?因为现在还没有fork出worker子进程哈。大家知道,linux系统下,fork出的子进程会共享父进程的地址空间,所以,需要在全部worker进程中做的事,就都放到ngx_init_cycle里来做吧。监听的句柄,会被所有nginx worker共享使用的。

监听完指定的端口后,开始调用所有module实现的init_module钩子函数。接下来,要准备进程间通讯的事了。一个master,多个worker,这些进程间通过什么方式通讯呢?这里不展开了,下次再细说。它们也通过共享内存交换数据,这时开始初始化共享内存。终于,ngx_init_cycle执行完了,松口气?

接着,main函数要初始化信号量,进程间的同步都是通过信号量来玩的。然后创建pidfile,这个文件用于启动完成以后通过nginx命令行,对nginx进程发送信号量来控制它。main函数的最后,开始执行ngx_master_process_cycle函数了,这个函数做master进程该做的事。它首先调用ngx_start_worker_processes去启动worker,按照配置文件中配置的worker数量,fork出许多子进程,每个子进程执行ngx_worker_process_cycle函数,这是个死循环函数,将开始处理真正的用户请求。

ngx_master_process_cycle函数再调用ngx_start_cache_manager_processes启动cache的管理进程,这块限于篇幅,下次有空再讲吧。最后,ngx_master_process_cycle进入死循环,开始准备接收信号量传来的命令,以及监控每一个worker的运行状态,如果有worker非正常死掉,还会重新拉起的。

最后声明下,我是以nginx-0.7.65版本做例子来说的,上面列到的源代码文件,都是针对这个版本的。当然,我说的这些都是核心函数,其实1.x版本与之差别非常小。

熟悉了nginx的启动过程,知道它干了哪些事,就可以研究worker进程如何处理用户请求了。下次再说吧。

转至:https://blog.csdn.net/russell_tao/article/details/7165129

nginx启动期做了哪些事相关推荐

  1. nginx启动初始化过程(二)

    前面提到nginx启动过程中,关键部分是ngx_init_cycle.ngx_init_cycle()函数究竟做了哪些工作.ngx_cycle_t结构定义在src/core/ngx_core.h文件中 ...

  2. Nginx动态、静态分离,Nginx配置中做适配

    Nginx[静态,动态] 页面是html,mp3,mp4,txt,doc,pdf 动态脚本语言:shell,PHP,java 一:       LNMP (Linux+Nginx+MariaDB==m ...

  3. 自己写了个nginx启动脚本,shell

    为什么80%的码农都做不了架构师?>>>    思路:nginx启动后会有nginx.pid文件在指定位置下,通过判断该文件是否存在.决定nginx是否已经启动. #!/bin/ba ...

  4. 35岁前必做10件事 让你少奋斗8年挣足钱

    35岁前必做10件事 让你少奋斗8年挣足钱(转)中财网 (2010-03-27 16:04:01) 转载 标签: 就业 求职 乘数 钱经 李彦宏 美国 杂谈 分类:转载 男人.女人都要过三十五岁这堵墙 ...

  5. 现在开始(译) 明确的知道你想要什麽是关键 每次 只做一件事的习惯 成功的人是因为屡次尝试经历失败保持激情 动手一试...

    现在开始 现在开始(译) 每次读这篇文章,总是能让我学到很多,充满激情. 作者:Steve Pavlina  译文地址:http://www.metaldudu.com/blog/index.php/ ...

  6. Nginx学习之十一-Nginx启动框架处理流程

    Nginx启动过程流程图 下面首先给出Nginx启动过程的流程图: ngx_cycle_t结构体 Nginx的启动初始化在src/core/nginx.c的main函数中完成,当然main函数是整个N ...

  7. 2019年,瑞云渲染做了这些事……

    2019年,瑞云渲染做了这些事-- Renderbus瑞云渲染 发布时间:01-0310:14Renderbus官方帐号 时光匆匆,2019年已悄然逝去,回顾这一年百感交集. 这一年,有人说是影视行业 ...

  8. 转:以独立之心,做合群之事

    个人理解:管理好自己,就是最好的管理:融入团队,保持独立. 如果不想浪费光阴的话,要么静下心来读点书,要么去赚点钱. 很多人花一辈子才明白的道理是,我们真正需要的东西实在太少. 什么是我们一生的选择与 ...

  9. 常用的计算机中开机键是什么,按下电脑开机键,电脑内部都做了哪些事

    观看哈工大操作系统视频课程,李志军老师讲解配套使用. 参考博客: https://blog.csdn.net/tang_love_yuan/article/details/79127043 https ...

最新文章

  1. 标题 相机标定(Camera calibration)原理和步骤
  2. 【robotframework】robotframework基本使用
  3. 数据中心基础设施及日常运维管理
  4. P4301 [CQOI2013] 新Nim游戏
  5. 搭建自己的博客(二十七):增加登录注册以及个人资料按钮
  6. 10-C++远征之模板篇-学习笔记
  7. 论文笔记_S2D.61_2019-CVPR-DeepLiDAR:基于稀疏激光雷达数据和单张彩色图像的户外场景的表面法线引导的深度预测
  8. webpack5学习与实战-(九)-区分开发和生产环境的配置
  9. 程序员接私活常用平台汇总!
  10. 计算机网络 CDN技术介绍
  11. YOLO V5 实现课堂行为检测
  12. Android error: “Apostrophe not preceded by \” 解决办法
  13. css中鼠标手,css各种鼠标手型集合
  14. 7-2 符号配对 (20 分) c语言版
  15. 使用spacedesk实现两台笔记本的双屏显示
  16. Bot 崛起:你的企业需要考虑这11个重要问题
  17. php7 根据日期算星座,js根据日期判断星座的示例代码
  18. java if打折怎么算_用java写出商品打折程序
  19. python创建excel文件报错_python-通过openpy操作excel
  20. 循序渐进,QAD ERP 为DHAC “赋能”未来

热门文章

  1. LINUX--特殊权限SUID,SGID,Sticky
  2. Spring学习之AOP(面向切面编程)
  3. 这样设计是否更好些~仓储接口是否应该设计成基础操作接口和扩展操作接口
  4. 2021牛客多校7 - xay loves monotonicity(线段树区间合并)
  5. 牛客多校4 - Harder Gcd Problem(构造+贪心)
  6. 牛客多校4 - Count New String(序列自动机+广义后缀自动机)
  7. CodeForces - 78E Evacuation(最大流)
  8. 洛谷 - P4012 深海机器人问题(最大费用最大流)
  9. POJ - 2104 K-th Number(主席树)
  10. java比较两个对象重写,不重写equals进行两个对象间的深度比较