当前位置: 首页 > news >正文

Nginx开发框架

文章目录

  • 服务器程序目录规划、makefile编写
    • 信号高级认识范例
    • 服务器架构初步规划——目录
    • 编译工具make的使用概述
    • makefile脚本用法
    • makefile的$
    • 项目所有文件
      • ifeq
      • makefile赋值
        • =
        • :=
        • ?=
      • @的作用
      • %.o表示匹配所有的.o文件,注意是用于匹配的
      • .PHONY: clean的作用
      • SRC = $(wildcard *.cpp)的意思
      • OBJ = $(patsubst %.cpp, %.o, $(SRC))
      • make -C
      • addprefix函数
      • mkdir的参数的作用
      • 立即展开和延迟展开
      • makefile常见自动变量
      • echo的用法
      • > 和 >> 的区别
      • gcc -I$(INCLUDE_PATH) -MM $^ >> $@
      • \$(CC) -I$(INCLUDE_PATH) -o $@ -c \$(filter %.c,$^)
  • 读配置文件、查泄漏、设置标题实战
    • 配置文件读取
      • telnet工具
      • nginx特点之热升级
    • 配置文件读取功能实战代码
      • _include的ngx_global.h
      • _include的ngx_signal.h
      • app的makefile
      • app的nginx.cxx
      • app的ngx_c_conf.cxx
      • app的ngx_conf.cxx
      • app的ngx_setproctitle.cxx
      • app的ngx_string.cxx
      • signal的makefile
      • signal的ngx_signal.cxx
      • common.mk
      • config.mk
      • makefile
      • nginx.conf
    • 单例模式
      • 饿汉模式(线程安全)
      • gpt给的饿汉模式版本
      • 懒汉模式(线程安全需要加锁)
      • PTHREAD_MUTEX_INITIALIZER
      • gpt给的懒汉模式
      • std::thread
      • std::once_flag 和 std::call_once
    • feof函数
    • fgets函数
    • strchr函数
    • strncpy函数
    • 注意
    • strcasecmp函数
    • 内存泄漏的检查工具memcheck
      • 总结
    • 设置进程名称(标题)
      • environ全局变量
    • 设置进程名称实战代码
      • ngx_setproctitle.cxx
      • delete []gp_envmem
      • ngx_setproctitle.cxx
      • nginx.cxx
  • 日志打印实战,优化main函数调用顺序
    • 日志打印实战
      • ngx_global.h
      • va_start、va_arg、va_end
      • memcpy函数
      • ngx_printf.cxx
      • 优化代码
      • sprintf函数
      • strerror
      • write函数
      • ngx_log.cxx
      • nginx.cxx
      • nginx.cxx
    • 设置时区
    • 完善日志打印
      • ngx_macro.h
      • nginx.conf
      • gettimeofday函数
      • localtime_r函数
      • nginx.cxx
      • ngx_log.cxx
  • 信号功能实战
    • signal和sigaction区别
    • sigaction
      • ngx_signal.cxx
      • nginx.cxx
    • Nginx中创建worker子进程
      • SIG_BLOCK和SIG_SETMASK的区别
      • ngx_process_cycle.cxx
      • config.mk
      • nginx.cxx
    • sigsuspend函数
      • ngx_process_cycle.cxx
      • ngx_process_cycle.cxx
    • sigsuspend函数执行步骤
    • pause函数用法
    • 日志输出细节
      • watch -n 可以实时监视文件大小变化
      • printf函数不加“\n”无法及时输出的解释
      • ngx_process_cycle.cxx
      • 加入fflush
      • ngx_process_cycle.cxx
    • write函数细节点
      • ngx_process_cycle.cxx
      • 总结
    • 掉电导致write写入的数据丢失破解法
      • 1.直接I/O, 直接访问物理磁盘
      • 2.open文件时的O_SYNC选项
      • 3.缓存同步
    • 标准I/O库
      • fgets函数
      • fwrite函数精细解读
  • 守护进程功能的实现
    • nginx.conf
    • ngx_daemon.cxx
    • nginx.cxx
    • ngx_process_cycle.cxx
    • ngx_process_cycle.cxx
    • 信号处理函数的进一步完善(避免僵尸子进程)
      • sig_atomic_t类型
      • siginfo_t 结构
      • errno
      • WTERMSIG 和 WEXITSTATUS

服务器程序目录规划、makefile编写

信号高级认识范例

nginx4_1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>//信号处理函数
void sig_usr(int signo)
{         if(signo == SIGUSR1) {printf("收到了SIGUSR1信号, 我休息10秒......!\n");sleep(10);printf("收到了SIGUSR1信号, 我休息10秒完毕, 苏醒了......!\n");} else if(signo == SIGUSR2) {printf("收到了SIGUSR2信号, 我休息10秒......!\n");sleep(10);printf("收到了SIGUSR2信号, 我休息10秒完毕, 苏醒了......!\n");} else {printf("收到了未捕捉的信号%d!\n", signo);}
}int main(int argc, char const *argv[])
{/* code *///系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数if(signal(SIGUSR1, sig_usr) == SIG_ERR) {printf("无法捕捉SIGUSR1信号!\n");}if(signal(SIGUSR2, sig_usr) == SIG_ERR) {printf("无法捕捉SIGUSR2信号!\n");}for(;;){sleep(1); //休息1秒    printf("休息1秒~~~~!\n");}printf("再见!\n");return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • (1) 执行信号处理函数时因为sleep(10);语句导致被卡住10s,这时程序执行流程回不到main中(程序执行流程在sig_usr信号处理函数中),所以main中的语句无法得到执行了
  • (2) 在触发SIGUSR1信号并因此休眠10s期间,就算多次触发SIGUSR1信号,也不会重新执行SIGUSR1信号对应的信号处理函数sig_usr,而是会等待上个SIGUSR1信号处理函数执行完毕才第2次执行SIGUSR1信号对应的信号处理函数sig_usr。换句话说,在信号处理函数被调用时,操作系统建立的新信号屏蔽字(就是之前谈sigprocmask 函数时所说的进程对应的信号集)自动包括了正在被递送的信号,保证了在处理一个给定的信号时,如果再次收到同种信号,会将其阻塞到对前一个信号的处理结束为止
  • (3) 不管发送多少次SIGUSR1信号,在该信号处理函数执行期间,后续收到的所有SIGUSR1信号统统被归结为一次。比如当前正在执行SIGUSR1信号的处理程序但没执行完,此时又收到4次SIGUSR1信号,当SIGUSR1信号处理函数执行完毕(解除阻塞后),SIGUSR1的信号处理函数只会被调用一次(而不会分别调用4次SIGUSR1的信号处理函数)

继续试验。先发送一个SIGUSR1信号,在SIGUSR1信号处理函数没执行完毕时,接着发送SIGUSR2信号

在这里插入图片描述
在这里插入图片描述

  • (1) 执行SIGUSR1信号处理函数,但没有执行完时,是可以执行SIGUSR2信号处理函数的,此时SIGUSR2和SIGUSR1信号处理函数都没执行完毕,再发送SIGUSR1或SIGUSR2信号都不会有任何响应
  • (2) 既然是在执行SIGUSR1信号处理函数的时候收到SIGUSR2信号导致又去执行了SIGUSR2信号处理函数,就意味着只有SIGUSR2信号处理函数执行完毕,才会返回SIGUSR1信号处理函数;只有SIGUSR1信号处理函数执行完毕,才会最终返回main函数主流程中继续执行

服务器架构初步规划——目录

主目录名为nginx, 建立主目录的主要目的是把整个项目的扩展名为.c的代码按照功能划分到不同的子目录中。在主目录下,有若干子目录和文件

  • (1) _include目录。专门存放各种头文件,希望头文件集中一点,如果分散到各个子目录中,写代码时#include语句中就要包含各种路径,不太方便。
  • (2) app目录。放主应用程序扩展名为.c的文件及一些比较核心的文件。在该目录下有如下内容。
    • link_obj目录。临时目录,会存放临时的扩展名为.o的文件(编译之中产生的目标文件)。该目录不手动创建,后续将使用makefile脚本(编译项目用的脚本)来创建。
    • dep目录。临时目录,会存放临时的.d开头的依赖关系文件(make的时候要用到),后续也是用makefile脚本来创建。依赖关系文件能够追踪修改的文件,编译器gcc发现某个所依赖的文件被修改时,能够根据这种依赖关系自动重新编译改动的部分。比如修改了一个扩展名为.h的文件,编译器就知道跟这个.h文件关联的那些扩展名为.c的文件需要重新编译,这就是依赖关系文件存在的意义。
    • ③ nginx.c:主文件,main入口函数就放在这里。
    • ④ngx_conf.c:普通文件,和主文件关系密切一些,但不值得单独放到一个目录里去的文件。
  • (3) signal目录:专门用于存放和信号处理有关的1到多个扩展名为.c的文件。目前只有ngx_signal.c这一个文件,这里代码简单,主要供测试使用。
  • (4) proc目录:专门用于存放和进程处理有关的1到多个扩展名为.c的文件,暂时为空目录。
  • (5) misc目录:专门用于存放各种杂合不好归类的1到多个扩展名为.c的文件,暂时为空目录。
  • (6) net目录:专门用于存放和网络处理有关的1到多个扩展名为.c的文件,暂时为空目录。

上面这些目录有些先保持为空,以后增加功能的时候再向里面增加文件。其中app 和signal目录中有一些文件,这些文件里的代码都是一些测试代码(还没开始写正式代码),这些测试代码的主要目的在于验证通过编译脚本(makefile文件)能够成功编译整个项目。

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ tree

在这里插入图片描述

编译工具make的使用概述

可执行程序需要针对每个扩展名为.c的文件进行编译,每个扩展名为.c的文件编译后都会生成一个扩展名为.o 的目标文件,然后系统对多个扩展名为.o的文件进行链接,最终生成可执行程序

在这里插入图片描述

make命令的工作原理就是读取当前目录下的makefile文件(文本文件,没有扩展名,是一个纯粹的主文件名makefile),然后根据makefile文件中定义的规则把项目源代码进行编译、链接直至生成可执行程序。程序员的任务就是编写makefile文件,然后用make 命令执行。makefile文件里定义了如何编译整个项目,也就是描述了整个项目的编译、链接规则(实际上makefile文件就是一个编译项目时要用到的各种源文件等的依赖关系描述文件)

当然也有Autotools、Cmake等工具能够自动帮助程序员生成makefile文件,但我不想介绍这些工具,一来这些工具本身的使用步骤比较复杂,二来我希望借助现在的内容让你们对写makefile文件有一定的了解。借助工具生成makefile文件,就失去了学习makefile 文件语法的机会

makefile文件是一个文本文件,没有扩展名,一般放在根目录下(这里放在nginx目录下)

makefile文件的编写需要有个规划, 下面简单介绍

  • (1) nginx根目录下会放3个文件
    • ①makefile。编译整个项目的入口脚本,编译项目从这里开始,固定不动,起总体控制作用。
    • ②config.mk。配置脚本,被makefile包含。当增加一些新的项目目录时,一般只需要修改这个脚本。单独分离出来的目的就是应付一些可变的内容,所以,一般变动的内容都放到这里
    • ③common.mk。最重要、最核心的编译脚本,定义makefile的编译规则、依赖规则等,通用性强,各子目录都用到这个脚本来实现子目录中扩展名为.c的C语言编程文件的编译。
  • (2) 每个子目录下(app、signal等)都有一个名为makefile的文件,这些makefile文件中,都会包含根目录(nginx)下的common.mk文件,从而实现每个子目录下的扩展名为.c 的C语言编程文件的编译。
    但是,请注意,当前的makefile脚本文件,不支持在子目录下再创建子目录的源码组织方式(只支持1级子目录)。请遵照这个规则,除非读者能够很好地修改makefile文件,来支持子目录下再创建子目录。本书的重点是搭建服务器程序框架和编写服务器程序代码,而不是编写makefile文件,所以这里对makefile文件的学习以适度和够用为原则,满足日常(本项目)的开发即可。
  • (3) 其他规划。前面提过,app目录下在编译过程中会有临时目录产生。
    • ①link_obj目录。临时目录,存放临时的对象文件,makefile脚本中会创建该目录。
    • ②dep目录。临时目录,存放临时的扩展名为.d的依赖关系文件(make时要用),makefile脚本中会创建该目录。

makefile脚本用法

ngx_signal2.h

#ifndef __NGX_SIGNAL2_H__
#define __NGX_SIGNAL2_H__//函数声明
void mysignal2();   #endif

ngx_signal.h

#ifndef __NGX_FUNC_H__
#define __NGX_FUNC_H__//函数声明
void myconf();    
#define  MYVER  "1.2"
#endif  

在这里插入图片描述
ngx_func.h

#ifndef __NGX_FUNC_H__
#define __NGX_FUNC_H__//函数声明
void myconf();    
#define  MYVER  "1.2"
#endif  

在SecureCRT终端窗口的nginx项目主目录中,先输入如下命令:

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ make clean

这行命令的作用是删除以往项目编译时残留的中间目录、文件等。其中make是一个命令,后面的clean实际是一个标记(目标),读者可以在nginx项目主目录的makefile文件中找到该标记。执行make clean命令实际就是执行该makefile文件中clean标记下面的命令行(rm命令用于删除目录、文件等)

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ make

在这里插入图片描述

在这里插入图片描述
可以看到,app目录下多出了dep和link_obj 2个子目录及子目录中的文件,根目录下生成了可执行程序nginx。执行nginx

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ ./nginx 

在这里插入图片描述

编译成功后,也生成了许多残留的扩展名为.o的目标文件和扩展名为.d的记录依赖关系的纯文本文件(也可以把生成的可执行程序看成是残留的文件),如果不希望这些文件影响对项目的观察,可以把它们删除。直接执行如下命令

在这里插入图片描述
在这里插入图片描述

makefile的$

在这里插入图片描述

项目所有文件

config.mk

# 定义项目编译的根目录, 通过export把某个变量声明为全局的[其他文件中可以用], 这里获取当前这个文件所在的路径作为根目录;
#BUILD_ROOT = /home/ubuntu/myProj/nginx
export BUILD_ROOT = $(shell pwd)#定义头文件的路径变量
export INCLUDE_PATH = $(BUILD_ROOT)/_include#定义我们要编译的目录
BUILD_DIRS = $(BUILD_ROOT)/signal/ \$(BUILD_ROOT)/app/ #编译时是否生成调试信息。GNU调试器可以利用该信息
export DEBUG = true

makefile

include config.mk
all:
#-C是指定目录
#make -C signal   #可执行文件应该放最后
#make -C app      #用shell命令for搞,shell里边的变量用两个$@for dir in $(BUILD_DIRS); \do \make -C $$dir; \done.PHONY: clean
clean:
#-rf:删除文件夹,强制删除rm -rf app/link_obj app/dep nginxrm -rf signal/*.gch app/*.gch

在这里插入图片描述

ifeq

对于Makefile来说,也有两组条件判断结构。第一组是ifeq和ifneq,其功能是用来判断是否相等,它们使用的一般格式如下

在这里插入图片描述

上述用法都是用来比较“参数1”和“参数2”是否相同,ifeq关键字的含义是:如果相同则为真。“参数1”和“参数2”可以为函数返回值。ifneq的用法类似,只不过ifneq是用来比较“参数1”和“参数2”是否不相等,如果不相等的话就为真

makefile赋值

=

对于Makefile文件中常使用的赋值规则,第一种是在C语言赋值过程中最为常见的“=”操作,在Makefile文件中称作递归式变量赋值操作。这种赋值操作的特点在于左侧为变量名、右侧为变量的值,优点是右侧变量的值可以定义在文件的任何一处,也就是说,右侧的变量不一定非要是已定义好的值,因为只要在Makefile中定义了,就可以在文件的任何位置

在这里插入图片描述
在这里插入图片描述

:=

第二种称为直接展开式变量赋值操作,其符号标识为“:=”。这个操作的特点在于,定义变量时,变量右侧的值会立即替换到变量值中。所以变量被定义后就是一个实际需要的文本值,其中不再包含任何变量的引用。这种方法前面的变量不能使用后面的变量,只能使用前面已定义好的变量,换句话说,即使后面再赋值,也不能覆盖前面定义的变量值

在这里插入图片描述
在这里插入图片描述

?=

第三种就是条件赋值操作,其符号是“?=”。这个赋值条件和条件判断语句差不多,其意义是用于判断左侧变量值是否被赋值操作过,如果是,就不会执行右侧值替换左侧变量值的操作;如果不是,则执行右侧值替换左侧变量值的操作
在这里插入图片描述
在这里插入图片描述

common.mk


#.PHONY:all clean ifeq ($(DEBUG),true)
#-g是生成调试信息。GNU调试器可以利用该信息
CC = gcc -g
VERSION = debug
else
CC = gcc
VERSION = release
endif#CC = gcc# $(wildcard *.c)表示扫描当前目录下所有.c文件
#SRCS = nginx.c ngx_conf.c
SRCS = $(wildcard *.c)#OBJS = nginx.o ngx_conf.o  这么一个一个增加.o太麻烦,下行换一种写法:把字符串中的.c替换为.o
OBJS = $(SRCS:.c=.o)#把字符串中的.c替换为.d
#DEPS = nginx.d ngx_conf.d
DEPS = $(SRCS:.c=.d)#可以指定BIN文件的位置,addprefix是增加前缀函数
#BIN = /mnt/hgfs/linux/nginx
BIN := $(addprefix $(BUILD_ROOT)/,$(BIN))#定义存放ojb文件的目录,目录统一到一个位置才方便后续链接,不然整到各个子目录去,不好链接
#注意下边这个字符串,末尾不要有空格等否则会语法错误 
LINK_OBJ_DIR = $(BUILD_ROOT)/app/link_obj
DEP_DIR = $(BUILD_ROOT)/app/dep#-p是递归创建目录,没有就创建,有就不需要创建了
$(shell mkdir -p $(LINK_OBJ_DIR))
$(shell mkdir -p $(DEP_DIR))#我们要把目标文件生成到上述目标文件目录去,利用函数addprefix增加个前缀
#处理后形如 /mnt/hgfs/linux/nginx/app/link_obj/ngx_signal2.o /mnt/hgfs/linux/nginx/app/link_obj/ngx_signal.o
# := 在解析阶段直接赋值常量字符串【立即展开】,而 = 在运行阶段,实际使用变量时再进行求值【延迟展开】
# /mnt/hgfs/linux/nginx/app/link_obj/nginx.o   /mnt/hgfs/linux/nginx/app/link_obj/ngx_conf.o 
OBJS := $(addprefix $(LINK_OBJ_DIR)/,$(OBJS))
DEPS := $(addprefix $(DEP_DIR)/,$(DEPS))#找到目录中的所有.o文件(编译出来的)
LINK_OBJ = $(wildcard $(LINK_OBJ_DIR)/*.o)
#因为构建依赖关系时app目录下这个.o文件还没构建出来,所以LINK_OBJ是缺少这个.o的,我们 要把这个.o文件加进来
LINK_OBJ += $(OBJS)#-------------------------------------------------------------------------------------------------------
#make找第一个目标开始执行[每个目标[就是我们要生成的东西],其实都是定义一种依赖关系],目标的格式为:
#目标:目标依赖【可以省略】
#	要执行的命令【可以省略】
#如下这行会是开始执行的入口,执行就找到依赖项$(BIN)去执行了,同时,这里也依赖了$(DEPS),这样就会生成很多.d文件了
all:$(DEPS) $(OBJS) $(BIN)#这里是诸多.d文件被包含进来,每个.d文件里都记录着一个.o文件所依赖哪些.c和.h文件。内容诸如 nginx.o: nginx.c ngx_func.h
#我们做这个的最终目的说白了是,即便.h被修改了,也要让make重新编译我们的工程,否则,你修改了.h,make不会重新编译,那不行的
#有必要先判断这些文件是否存在,不然make可能会报一些.d文件找不到
ifneq ("$(wildcard $(DEPS))","")   #如果不为空,$(wildcard)是函数【获取匹配模式文件名】,这里 用于比较是否为""
include $(DEPS)  
endif#----------------------------------------------------------------1begin------------------
#$(BIN):$(OBJS)
$(BIN):$(LINK_OBJ)@echo "------------------------build $(VERSION) mode--------------------------------!!!"#一些变量:$@:目标,     $^:所有目标依赖
# gcc -o 是生成可执行文件$(CC) -o $@ $^#----------------------------------------------------------------1end-------------------#----------------------------------------------------------------2begin-----------------
#%.o:%.c
$(LINK_OBJ_DIR)/%.o:%.c
# gcc -c是生成.o目标文件   -I可以指定头文件的路径
#如下不排除有其他字符串,所以从其中专门把.c过滤出来 
#$(CC) -o $@ -c $^$(CC) -I$(INCLUDE_PATH) -o $@ -c $(filter %.c,$^)
#----------------------------------------------------------------2end-------------------#----------------------------------------------------------------3begin-----------------
#我们现在希望当修改一个.h时,也能够让make自动重新编译我们的项目,所以,我们需要指明让.o依赖于.h文件
#那一个.o依赖于哪些.h文件,我们可以用“gcc -MM c程序文件名” 来获得这些依赖信息并重定向保存到.d文件中
#.d文件中的内容可能形如:nginx.o: nginx.c ngx_func.h
#%.d:%.c
$(DEP_DIR)/%.d:%.c
#gcc -MM $^ > $@
#.d文件中的内容形如:nginx.o: nginx.c ngx_func.h ../signal/ngx_signal.h,但现在的问题是我们的.o文件已经放到了专门的目录
# 所以我们要正确指明.o文件路径这样,对应的.h,.c修改后,make时才能发现,这里需要用到sed文本处理工具和一些正则表达式语法,不必深究
#gcc -MM $^ | sed 's,\(.*\)\.o[ :]*,$(LINK_OBJ_DIR)/\1.o:,g' > $@
#echo 中 -n表示后续追加不换行echo -n $(LINK_OBJ_DIR)/ > $@
#	gcc -MM $^ | sed 's/^/$(LINK_OBJ_DIR)&/g' > $@
#  >>表示追加gcc -I$(INCLUDE_PATH) -MM $^ >> $@#上行处理后,.d文件中内容应该就如:/mnt/hgfs/linux/nginx/app/link_obj/nginx.o: nginx.c ngx_func.h ../signal/ngx_signal.h#----------------------------------------------------------------4begin-----------------#----------------------------------------------------------------nbegin-----------------
#clean:			
#rm 的-f参数是不提示强制删除
#可能gcc会产生.gch这个优化编译速度文件
#	rm -f $(BIN) $(OBJS) $(DEPS) *.gch
#----------------------------------------------------------------nend------------------

在这里插入图片描述

在这里插入图片描述

ngx_func.h

#ifndef __NGX_FUNC_H__
#define __NGX_FUNC_H__//函数声明
void myconf();    
#define  MYVER  "1.2"
#endif  

ngx_signal.h

#ifndef __NGX_SIGNAL_H__
#define __NGX_SIGNAL_H__#include "ngx_signal2.h"  
//函数声明
void mysignal();  #endif

ngx_signal2.h

#ifndef __NGX_SIGNAL2_H__
#define __NGX_SIGNAL2_H__//函数声明
void mysignal2();   #endif

nginx.c

#include <stdio.h>
#include <unistd.h>#include "ngx_func.h"
#include "ngx_signal.h"int main(int argc, char const *argv[])
{/* code */printf("Coming for ya!!!\n");    myconf();mysignal();/*for(;;){sleep(1); //休息1秒printf("休息1秒\n");}*/printf("程序退出,再见!\n");    return 0;
}

ngx_conf.c

#include <stdio.h>#include "ngx_func.h"void myconf()
{      printf("执行了myconf()函数,MYVER=%s!\n",MYVER);  return ;
}

makefile

#只生成 .d,.o即可 
BIN = nginx   #这个可以保证生成nginx可执行文件
include $(BUILD_ROOT)/common.mk

在这里插入图片描述

ngx_signal.c

#include <stdio.h>
#include <unistd.h>#include "ngx_signal.h"void mysignal()
{      printf("执行了mysignal()函数!\n");return ;
}

在这里插入图片描述

makefile

#只生成 .d,.o即可 
BIN = 
include $(BUILD_ROOT)/common.mk

@的作用

如果规则的命令行以字符“@”开始,则make在执行这个命令时就不会回显这个将要被执行的命令

在这里插入图片描述

%.o表示匹配所有的.o文件,注意是用于匹配的

.PHONY: clean的作用

Makefile中存在一种伪目标,一般不需要依赖任何文件。使用伪目标主要是为了避免Makefile中文件命名的冲突。有时候需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的

输入make clean,此时假设该目录下面不存在名为clean的文件,由于没有文件存在,make将要调用相应的规则“生成”该文件(伪目标)。这里只是一个形象的比喻,实际上只是执行目标clean的相应规则而已,规则中一般不会执行生成clean文件的命令,所以使用该伪目标时,可以强制规则的执行。如果此时有好奇的程序员非要尝试在当前目录下创建一个clean文件,那么系统就会发出异常提示make:‘clean’ is up to data

在这里插入图片描述
要想解决这个棘手的问题,可以通过使用伪目标关键字.PHONY来声明。使用伪目标关键字声明以后,即使在当前目录下创建了和伪目标文件名相同的文件,依旧能够正常执行make命令。

使用伪目标关键字.PHONY声明clean以后,就算当前目录下有重名的文件clean,也能够正常使用make clean命令
在这里插入图片描述

在这里插入图片描述

SRC = $(wildcard *.cpp)的意思

在这里插入图片描述

OBJ = $(patsubst %.cpp, %.o, $(SRC))

在这里插入图片描述

make -C

在这里插入图片描述

addprefix函数

在这里插入图片描述

mkdir的参数的作用

在这里插入图片描述

立即展开和延迟展开

在这里插入图片描述

makefile常见自动变量

在这里插入图片描述

echo的用法

在这里插入图片描述

> 和 >> 的区别

在这里插入图片描述

gcc -I$(INCLUDE_PATH) -MM $^ >> $@

ubuntu@VM-20-6-ubuntu:~/myProj/nginx/app$ gcc -I/home/ubuntu/myProj/nginx/_include -MM nginx.c

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

$(CC) -I$(INCLUDE_PATH) -o $@ -c $(filter %.c,$^)

在这里插入图片描述

读配置文件、查泄漏、设置标题实战

配置文件读取

cxx是比较通用的C++源码文件扩展名(同时支持C和C++语法)

telnet工具

在SecureCRT终端窗口中输入telnet(这是个工具,专门用于连接远程主机(服务器)的某个端口并发送简单信息),连接淘宝服务器的80端口

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ telnet www.taobao.com 80

在这里插入图片描述

nginx特点之热升级

在这里插入图片描述

配置文件读取功能实战代码

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

_include的ngx_global.h

#ifndef __NGX_GBLDEF_H__
#define __NGX_GBLDEF_H__//一些比较通用的定义放在这里
void Rtrim(char *string);
void Ltrim(char *string);void ngx_init_setproctitle();
void ngx_setproctitle(const char *title);//结构定义
typedef struct
{char ItemName[50];char ItemContent[500];
}CConfItem, *LPCConfItem;//外部全局量声明
extern char  **g_os_argv;
extern char  *gp_envmem; 
extern int   g_environlen; #endif

_include的ngx_signal.h

#ifndef __NGX_SIGNAL_H__
#define __NGX_SIGNAL_H__//函数声明
void mysignal();  #endif

app的makefile

#只生成 .d,.o即可 
BIN = nginx   #这个可以保证生成nginx可执行文件
include $(BUILD_ROOT)/common.mk

app的nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int  g_environlen = 0;       //环境变量所占内存大小int main(int argc, char *const *argv)
{             g_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出//if(true)//{//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;//}//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);//strcpy(argv[0],"ce");//strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存//printf("environ[0]=%s\n" , environ[0]);//printf("environ[1]=%s\n" , environ[1]);//printf("environ[2]=%s\n" , environ[2]);//printf("environ[3]=%s\n" , environ[3]);//printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉ngx_setproctitle("nginx: master process");//myconf();//mysignal();//for(;;)//{//    sleep(1); //休息1秒//    printf("休息1秒\n");//}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

app的ngx_c_conf.cxx

//系统头文件放上边
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>//自定义头文件放下边,因为g++中用了-I参数,所以这里用<>也可以
#include "ngx_func.h"     //函数声明
#include "ngx_c_conf.h"   //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_global.h"//静态成员赋值
CConfig *CConfig::m_instance = NULL;//构造函数
CConfig::CConfig()
{		
}//析构函数
CConfig::~CConfig()
{    std::vector<LPCConfItem>::iterator pos;	for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos){		delete (*pos);}//end form_ConfigItemList.clear(); 
}//装载配置文件
bool CConfig::Load(const char *pconfName) 
{   FILE *fp;fp = fopen(pconfName, "r");if(fp == NULL)return false;//每一行配置文件读出来都放这里char linebuf[501];   //每行配置都不要太长,保持<500字符内,防止出现问题//走到这里,文件打开成功 while(!feof(fp))  //检查文件是否结束 ,没有结束则条件成立{    if(fgets(linebuf, 500, fp) == NULL) //从文件中读数据,每次读一行,一行最多不要超过500个字符 continue;if(linebuf[0] == 0) //跳过空行continue;//处理注释行if(*linebuf==';' || *linebuf==' ' || *linebuf=='#' || *linebuf=='\t'|| *linebuf=='\n')continue;lblprocstring://屁股后边若有换行,回车,空格等都截取掉if(strlen(linebuf) > 0){if(linebuf[strlen(linebuf)-1] == 10 || linebuf[strlen(linebuf)-1] == 13 || linebuf[strlen(linebuf)-1] == 32) {linebuf[strlen(linebuf)-1] = 0;goto lblprocstring;}		}if(linebuf[0] == 0)continue;if(*linebuf=='[') //[开头的也不处理continue;//这种 “ListenPort = 5678”走下来;char *ptmp = strchr(linebuf, '=');if(ptmp != NULL) {//LPCConfItem相当于“CConfItem *”,//所以千万不要写成LPCConfItem *p_confitem  = new CConfItem;LPCConfItem p_confitem = new CConfItem;     memset(p_confitem, 0, sizeof(CConfItem));strncpy(p_confitem->ItemName, linebuf, (int)(ptmp-linebuf)); //等号左侧的拷贝到p_confitem->ItemNamestrcpy(p_confitem->ItemContent,ptmp+1);                    //等号右侧的拷贝到p_confitem->ItemContentRtrim(p_confitem->ItemName); //自定义函数 截取字符串尾部空格Ltrim(p_confitem->ItemName); //自定义函数 截取字符串首部空格Rtrim(p_confitem->ItemContent);Ltrim(p_confitem->ItemContent);//printf("itemname=%s | itemcontent=%s\n",p_confitem->ItemName,p_confitem->ItemContent);            m_ConfigItemList.push_back(p_confitem);  //内存要释放,因为这里是new出来的 } //end if} //end while(!feof(fp)) fclose(fp); //这步不可忘记return true;
}//根据ItemName获取配置信息字符串,不修改不用互斥
const char *CConfig::GetString(const char *p_itemname)
{std::vector<LPCConfItem>::iterator pos;	for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos){	if(strcasecmp((*pos)->ItemName, p_itemname) == 0)return (*pos)->ItemContent;}//end forreturn NULL;
}//根据ItemName获取数字类型配置信息,不修改不用互斥
int CConfig::GetIntDefault(const char *p_itemname, const int def)
{std::vector<LPCConfItem>::iterator pos;	for(pos = m_ConfigItemList.begin(); pos !=m_ConfigItemList.end(); ++pos){	if(strcasecmp((*pos)->ItemName, p_itemname) == 0)return atoi((*pos)->ItemContent);}//end forreturn def;
}

app的ngx_conf.cxx

#include <stdio.h>#include "ngx_func.h"void myconf()
{      printf("执行了myconf()函数,MYVER=%s!\n",MYVER);  return ;
}

app的ngx_setproctitle.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  //env
#include <string.h>#include "ngx_global.h"//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来
void ngx_init_setproctitle()
{    int i;//统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for (i = 0; environ[i]; i++) {g_environlen += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end for//这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好; gp_envmem = new char[g_environlen]; memset(gp_envmem,0,g_environlen);  //内存要清空防止出现问题char *ptmp = gp_envmem;//把原来的内存内容搬到新地方来for (i = 0; environ[i]; i++) {size_t size = strlen(environ[i])+1 ; //不要拉下+1,否则内存全乱套了,因为strlen是不包括字符串末尾的\0的strcpy(ptmp,environ[i]);      //把原环境变量内容拷贝到新地方【新内存】environ[i] = ptmp;            //然后还要让环境变量指针指向这段新内存ptmp += size;}return;
}//设置可执行程序标题
void ngx_setproctitle(const char *title)
{//我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;//注意:我们的标题长度,不会长到原始标题和原始环境变量都装不下,否则怕出问题,不处理//(1)计算新标题长度size_t ititlelen = strlen(title); //(2)计算总的原始的argv那块内存的总长度【包括各种参数】size_t e_environlen = 0;     //e表示局部变量吧for (int i = 0; g_os_argv[i]; i++)  {e_environlen += strlen(g_os_argv[i]) + 1;}size_t esy = e_environlen + g_environlen; //argv和environ内存总和if( esy <= ititlelen){//你标题多长啊,我argv和environ总和都存不下?注意字符串末尾多了个 \0,所以这块判断是 <=【也就是=都算存不下】return;}//空间够保存标题的,够长,存得下,继续走下来    //(3)设置后续的命令行参数为空,表示只有argv[]中只有一个元素了,这是好习惯;防止后续argv被滥用,因为很多判断是用argv[] == NULL来做结束标记判断的;g_os_argv[1] = NULL;  //(4)把标题弄进来,注意原来的命令行参数都会被覆盖掉,不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了char *ptmp = g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存strcpy(ptmp,title);ptmp += ititlelen; //跳过标题//(5)把剩余的原argv以及environ所占的内存全部清0,否则会出现在ps的cmd列可能还会残余一些没有被覆盖的内容;size_t cha = esy - ititlelen;  //内存总和减去标题字符串长度(不含字符串末尾的\0),剩余的大小,就是要memset的;memset(ptmp,0,cha);  return;
}

app的ngx_string.cxx

#include <stdio.h>
#include <string.h>#include "ngx_global.h"//一些和字符串处理相关的函数,准备放这里//截取字符串尾部空格
void Rtrim(char *string)   
{   size_t len = 0;   if(string == NULL)   return;   len = strlen(string);   while(len > 0 && string[len-1] == ' ')   //位置换一下   string[--len] = 0;   return;   
}//截取字符串首部空格
void Ltrim(char *string)
{ char *p_tmp = string;if( (*p_tmp) != ' ') //不是以空格开头return;//找第一个不为空格的while((*p_tmp) != '\0'){if( (*p_tmp) == ' ')p_tmp++;elsebreak;}if((*p_tmp) == '\0') //全是空格{*string = '\0';return;}char *p_tmp2 = string; while((*p_tmp) != '\0'){(*p_tmp2) = (*p_tmp);p_tmp++;p_tmp2++;}(*p_tmp2) = '\0';return;
}

signal的makefile

#只生成 .d,.o即可 
BIN = 
include $(BUILD_ROOT)/common.mk

signal的ngx_signal.cxx

#include <stdio.h>
#include <unistd.h>#include "ngx_signal.h"void mysignal()
{      printf("执行了mysignal()函数!\n");return ;
}

common.mk


#.PHONY:all clean ifeq ($(DEBUG),true)
#-g是生成调试信息。GNU调试器可以利用该信息
CC = g++ -g
VERSION = debug
else
CC = g++
VERSION = release
endif#CC = gcc# $(wildcard *.c)表示扫描当前目录下所有.c文件
#SRCS = nginx.c ngx_conf.c
SRCS = $(wildcard *.cxx)#OBJS = nginx.o ngx_conf.o  这么一个一个增加.o太麻烦,下行换一种写法:把字符串中的.c替换为.o
OBJS = $(SRCS:.cxx=.o)#把字符串中的.c替换为.d
#DEPS = nginx.d ngx_conf.d
DEPS = $(SRCS:.cxx=.d)#可以指定BIN文件的位置,addprefix是增加前缀函数
#BIN = /home/ubuntu/myProj/nginx
BIN := $(addprefix $(BUILD_ROOT)/,$(BIN))#定义存放ojb文件的目录,目录统一到一个位置才方便后续链接,不然整到各个子目录去,不好链接
#注意下边这个字符串,末尾不要有空格等否则会语法错误 
LINK_OBJ_DIR = $(BUILD_ROOT)/app/link_obj
DEP_DIR = $(BUILD_ROOT)/app/dep#-p是递归创建目录,没有就创建,有就不需要创建了
$(shell mkdir -p $(LINK_OBJ_DIR))
$(shell mkdir -p $(DEP_DIR))#我们要把目标文件生成到上述目标文件目录去,利用函数addprefix增加个前缀
#处理后形如 /home/ubuntu/myProj/nginx/app/link_obj/ngx_signal2.o /home/ubuntu/myProj/nginx/app/link_obj/ngx_signal.o
# := 在解析阶段直接赋值常量字符串【立即展开】,而 = 在运行阶段,实际使用变量时再进行求值【延迟展开】
# /home/ubuntu/myProj/nginx/app/link_obj/nginx.o   /home/ubuntu/myProj/nginx/app/link_obj/ngx_conf.o 
OBJS := $(addprefix $(LINK_OBJ_DIR)/,$(OBJS))
DEPS := $(addprefix $(DEP_DIR)/,$(DEPS))#找到目录中的所有.o文件(编译出来的)
LINK_OBJ = $(wildcard $(LINK_OBJ_DIR)/*.o)
#因为构建依赖关系时app目录下这个.o文件还没构建出来,所以LINK_OBJ是缺少这个.o的,我们要把这个.o文件加进来
LINK_OBJ += $(OBJS)#-------------------------------------------------------------------------------------------------------
#make找第一个目标开始执行[每个目标[就是我们要生成的东西],其实都是定义一种依赖关系],目标的格式为:
#目标:目标依赖【可以省略】
#	要执行的命令【可以省略】
#如下这行会是开始执行的入口,执行就找到依赖项$(BIN)去执行了,同时,这里也依赖了$(DEPS),这样就会生成很多.d文件了
all:$(DEPS) $(OBJS) $(BIN)#这里是诸多.d文件被包含进来,每个.d文件里都记录着一个.o文件所依赖哪些.c和.h文件。内容诸如 nginx.o: nginx.c ngx_func.h
#我们做这个的最终目的说白了是,即便.h被修改了,也要让make重新编译我们的工程,否则,你修改了.h,make不会重新编译,那不行的
#有必要先判断这些文件是否存在,不然make可能会报一些.d文件找不到
ifneq ("$(wildcard $(DEPS))","")   #如果不为空,$(wildcard)是函数【获取匹配模式文件名】,这里 用于比较是否为""
include $(DEPS)  
endif#----------------------------------------------------------------1begin------------------
#$(BIN):$(OBJS)
$(BIN):$(LINK_OBJ)@echo "------------------------build $(VERSION) mode--------------------------------!!!"#一些变量:$@:目标,     $^:所有目标依赖
# gcc -o 是生成可执行文件$(CC) -o $@ $^#----------------------------------------------------------------1end-------------------#----------------------------------------------------------------2begin-----------------
#%.o:%.c
$(LINK_OBJ_DIR)/%.o:%.cxx
# gcc -c是生成.o目标文件   -I可以指定头文件的路径
#如下不排除有其他字符串,所以从其中专门把.c过滤出来 
#$(CC) -o $@ -c $^$(CC) -I$(INCLUDE_PATH) -o $@ -c $(filter %.cxx,$^)
#----------------------------------------------------------------2end-------------------#----------------------------------------------------------------3begin-----------------
#我们现在希望当修改一个.h时,也能够让make自动重新编译我们的项目,所以,我们需要指明让.o依赖于.h文件
#那一个.o依赖于哪些.h文件,我们可以用“gcc -MM c程序文件名” 来获得这些依赖信息并重定向保存到.d文件中
#.d文件中的内容可能形如:nginx.o: nginx.c ngx_func.h
#%.d:%.c
$(DEP_DIR)/%.d:%.cxx
#gcc -MM $^ > $@
#.d文件中的内容形如:nginx.o: nginx.c ngx_func.h ../signal/ngx_signal.h,但现在的问题是我们的.o文件已经放到了专门的目录
# 所以我们要正确指明.o文件路径这样,对应的.h,.c修改后,make时才能发现,这里需要用到sed文本处理工具和一些正则表达式语法,不必深究
#gcc -MM $^ | sed 's,\(.*\)\.o[ :]*,$(LINK_OBJ_DIR)/\1.o:,g' > $@
#echo 中 -n表示后续追加不换行echo -n $(LINK_OBJ_DIR)/ > $@
#	gcc -MM $^ | sed 's/^/$(LINK_OBJ_DIR)&/g' > $@
#  >>表示追加gcc -I$(INCLUDE_PATH) -MM $^ >> $@#上行处理后,.d文件中内容应该就如:/mnt/hgfs/linux/nginx/app/link_obj/nginx.o: nginx.c ngx_func.h ../signal/ngx_signal.h#----------------------------------------------------------------4begin-----------------#----------------------------------------------------------------nbegin-----------------
#clean:			
#rm 的-f参数是不提示强制删除
#可能gcc会产生.gch这个优化编译速度文件
#	rm -f $(BIN) $(OBJS) $(DEPS) *.gch
#----------------------------------------------------------------nend------------------

config.mk

# 定义项目编译的根目录, 通过export把某个变量声明为全局的[其他文件中可以用], 这里获取当前这个文件所在的路径作为根目录;
#BUILD_ROOT = /home/ubuntu/myProj/nginx
export BUILD_ROOT = $(shell pwd)#定义头文件的路径变量
export INCLUDE_PATH = $(BUILD_ROOT)/_include#定义我们要编译的目录
BUILD_DIRS = $(BUILD_ROOT)/signal/ \$(BUILD_ROOT)/app/ #编译时是否生成调试信息。GNU调试器可以利用该信息
export DEBUG = true

makefile

include config.mk
all:
#-C是指定目录
#make -C signal   #可执行文件应该放最后
#make -C app      #用shell命令for搞,shell里边的变量用两个$@for dir in $(BUILD_DIRS); \do \make -C $$dir; \done.PHONY: clean
clean:
#-rf:删除文件夹,强制删除rm -rf app/link_obj app/dep nginxrm -rf signal/*.gch app/*.gch

nginx.conf

#是注释行,
#每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;#[开头的表示组信息,也等价于注释行
[Socket]
ListenPort = 5678    DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g

单例模式

有两种懒汉和饿汉:

饿汉:饿了就饥不择⻝了,所以在单例类定义的时候就进⾏实例化

懒汉:顾名思义,不到万不得已就不会去实例化类,也就是在第⼀次⽤到的类实例的时候才会去实例化。

饿汉模式(线程安全)

在最开始的时候静态对象就已经创建完成,设计⽅法是类中包含⼀个静态成员指针,该指针指向该类的⼀个对象,提供⼀个公有的静态成员⽅法,返回该对象指针,为了使得对象唯⼀,构造函数设为私有。

#include <iostream>
#include <algorithm>
using namespace std;class SingleInstance {
public:// 获取单例实例的静态方法static SingleInstance* GetInstance() {static SingleInstance ins;  // 静态局部变量,单例实例return &ins;}// 析构函数~SingleInstance(){};private:// 构造函数SingleInstance() { std::cout << "SingleInstance() 饿汉" << std::endl; }// 拷贝构造函数,设置为私有以防止复制SingleInstance(const SingleInstance& other) {};// 赋值操作符,设置为私有以防止赋值SingleInstance& operator=(const SingleInstance& other) {return *this;}
};int main() {// 获取单例实例SingleInstance* ins = SingleInstance::GetInstance();return 0;
}

在这里插入图片描述

gpt给的饿汉模式版本

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

懒汉模式(线程安全需要加锁)

尽可能的晚的创建这个对象的实例,即在单例类第⼀次被引⽤的时候就将⾃⼰初始化,C++很多地⽅都有类型的思想,⽐如写时拷⻉,晚绑定等。

这段代码展示了一个使用懒汉式单例模式和线程锁来确保线程安全的C++程序

用到了双重锁定机制

#include <pthread.h>
#include <iostream>class SingleInstance {
public:static SingleInstance* GetInstance() {if (ins == nullptr) {pthread_mutex_lock(&mutex);if (ins == nullptr) {ins = new SingleInstance();}pthread_mutex_unlock(&mutex);}return ins;}static void DestroyInstance() {pthread_mutex_lock(&mutex);if (ins != nullptr) {delete ins;ins = nullptr;}pthread_mutex_unlock(&mutex);}~SingleInstance() {std::cout << "SingleInstance() 被销毁" << std::endl;}private:SingleInstance() {std::cout << "SingleInstance() 懒汉式初始化" << std::endl;}SingleInstance(const SingleInstance& other) = delete;SingleInstance& operator=(const SingleInstance& other) = delete;static SingleInstance* ins;static pthread_mutex_t mutex;
};// 静态成员初始化
SingleInstance* SingleInstance::ins = nullptr;
pthread_mutex_t SingleInstance::mutex = PTHREAD_MUTEX_INITIALIZER;int main() {// 获取单例实例SingleInstance* ins = SingleInstance::GetInstance();// 销毁单例实例SingleInstance::DestroyInstance();return 0;
}

在这里插入图片描述

PTHREAD_MUTEX_INITIALIZER

PTHREAD_MUTEX_INITIALIZER 是一个宏,用于静态初始化互斥锁。它把互斥锁初始化为默认属性,表示该互斥锁可以直接使用,而不需要显式调用 pthread_mutex_init 函数进行初始化。

在多线程环境中,互斥锁用于保护共享资源,以防止同时访问造成的数据不一致问题。通过使用 PTHREAD_MUTEX_INITIALIZER,可以在声明互斥锁时直接对其进行初始化,而不是在后续代码中调用初始化函数。这种静态初始化方式简单且高效,适用于静态或全局互斥锁。

gpt给的懒汉模式

在这里插入图片描述

std::thread

在这里插入图片描述

std::once_flag 和 std::call_once

在这里插入图片描述

feof函数

在这里插入图片描述

fgets函数

在这里插入图片描述

strchr函数

在这里插入图片描述

strncpy函数

在这里插入图片描述

注意

在这里插入图片描述

strcasecmp函数

在这里插入图片描述

内存泄漏的检查工具memcheck

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ sudo apt install valgrind

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

输入make命令重新编译、链接并生成nginx可执行程序

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int  g_environlen = 0;       //环境变量所占内存大小int main(int argc, char *const *argv)
{             g_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出if(true){printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");return 0;}//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);//strcpy(argv[0],"ce");//strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存//printf("environ[0]=%s\n" , environ[0]);//printf("environ[1]=%s\n" , environ[1]);//printf("environ[2]=%s\n" , environ[2]);//printf("environ[3]=%s\n" , environ[3]);//printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉ngx_setproctitle("nginx: master process");//myconf();//mysignal();//for(;;)//{//    sleep(1); //休息1秒//    printf("休息1秒\n");//}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}
ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ valgrind --tool=memcheck ./nginx 

在这里插入图片描述
可以看到“10 allocs,9 frees”,这似乎代表分配了10次,释放了9次。正常情况下分配了10次应该释放10次,但这里释放少了1次,估计与系统内部运作机制有关,可以先不理会(这里认为释放少了1次是正常的)。

然后看看LEAKSUMMARY(泄漏汇总),definitelylost(确定的泄漏)这一行显示的是0bytesin0block,意思是泄漏0字节,也就是没有内存泄漏。

没有内存泄漏,这里就制造一些内存泄漏。在ngx_c_conf.cxx文件的CConfig类的析构函数中,把代码注释掉。因为这部分代码是释放内存的,注释掉后,肯定会造成程序执行完毕后的内存泄漏

ngx_c_conf.cxx
在这里插入图片描述

在这里插入图片描述

可看到10 allocs,7 frees,与之前相比少了2个frees,这证明有2块内存没有释放。再看definitely lost部分,正好显示泄漏了2块(blocks),一共1100字节(bytes)

为什么是泄漏了1100字节呢?因为结构CConfltem的大小是550字节。文件nginx.conf中的配置项有2项,所以泄漏的字节正好是550×2=1100字节。

如果在输入上述valgrind命令的时候增加一个–leak-check=full开关,就可以完全检查内存泄漏

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ valgrind --tool=memcheck --leak-check=full ./nginx

在这里插入图片描述

不难看到,这一次系统指出了内存泄漏的详细信息:泄漏内存的文件是ngx_c_conf.cxx,泄漏的行是该文件的第75行。也就是如下这行分配的内存没有被释放:

LPCConfItem p_confitem = new CConfItem;     

此外,还有一些Valgrind命令开关可能对显示内存泄漏也有所帮助,所以,建议最终检查内存泄漏的命令可以这样使用(以免漏掉一些重要的内存泄漏信息):

valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./nginx

总结

  • (1)最上方HEAP SUMMARY中的“total heap usage:10 allocs, 7 frees”,这2个数字对比,目前观察到的是这2个数字差1没问题,如果超过1就说明有内存泄漏。
  • (2)中间的by 0x1093CC: CConfig::Load(char const*) (ngx_c_conf.cxx:75)明确指出内存泄漏的位置。其中还有by 0x109074: main (nginx.cxx:39)字样,表示nginx.cxx 的第39行调用了ngx_c_conf.cxx的第75行,导致产生内存泄漏(这几行信息看起来是一个函数调用栈,用以显示内存泄漏有关的代码行所在函数的调用关系)。
  • (3)LEAK SUMMARY中的definitely lost:1,100 bytes in 2 blocks字样,这里只要数字不是0一般就表示内存泄漏

取消CConfig::~CConfig析构函数中的注释,以确保这里不再产生内存泄漏。
另外演示一个CConfig::Load中fclose忘记调用的泄漏情形。将fclose所在代码行注释掉,保存文件,用make重新编译,然后输入上面的valgrind命令查看内存泄漏情形

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./nginx 

在这里插入图片描述
10 allocs, 8 frees 表示有一个泄露,“by 0x10927D: CConfig::Load(char const*) (ngx_c_conf.cxx:35)”表示在ngx_c_conf.cxx的第35行产生了泄漏,而第35行正好是对fopen函数的调用。当然,这个泄漏从LEAKSUMMARY中看不出来。

取消对CConfig::Load中fclose的注释。对内存泄漏检查的示范就到这里。建议偶尔使用,但一定不要过分依赖该工具。

设置进程名称(标题)

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int  g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char const *argv[]) {/* code *///测试代码:printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);//strcpy(argv[0],"ce");//strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存//printf("environ[0]=%s\n" , environ[0]);//printf("environ[1]=%s\n" , environ[1]);//printf("environ[2]=%s\n" , environ[2]);//printf("environ[3]=%s\n" , environ[3]);//printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ ./nginx -v -s 5

在这里插入图片描述

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int  g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char const *argv[]) {/* code *///测试代码:printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      // ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);//strcpy(argv[0],"ce");//strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存//printf("environ[0]=%s\n" , environ[0]);//printf("environ[1]=%s\n" , environ[1]);//printf("environ[2]=%s\n" , environ[2]);//printf("environ[3]=%s\n" , environ[3]);//printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

进程名称实际上保存在argv[0]所指向的内存中

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int  g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      // ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);strcpy(argv[0], "ce");//strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存//printf("environ[0]=%s\n" , environ[0]);//printf("environ[1]=%s\n" , environ[1]);//printf("environ[2]=%s\n" , environ[2]);//printf("environ[3]=%s\n" , environ[3]);//printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}    

在这里插入图片描述

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      // ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存//printf("environ[0]=%s\n" , environ[0]);//printf("environ[1]=%s\n" , environ[1]);//printf("environ[2]=%s\n" , environ[2]);//printf("environ[3]=%s\n" , environ[3]);//printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

在这里插入图片描述

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      // ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存printf("environ[0]=%s\n" , environ[0]);printf("environ[1]=%s\n" , environ[1]);printf("environ[2]=%s\n" , environ[2]);printf("environ[3]=%s\n" , environ[3]);printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着//for(int i = 0; i < argc; ++i)//{        //    printf("argv[%d]地址=%x    " ,i,(unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n",i,argv[i]);//}//下面环境变量随便输出2个//for(int i = 0; i < 2; ++i)//{//    printf("evriron[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" ,i,environ[i]);//}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;/*for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");
*/      // ngx_init_setproctitle();    //把环境变量搬家/*    for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}
*/    //我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着for(int i = 0; i < argc; ++i) {        printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));printf("argv[%d]内容=%s\n", i, argv[i]);}//下面环境变量随便输出2个for(int i = 0; i < 2; ++i) {printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));printf("evriron[%d]内容=%s\n" , i, environ[i]);}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}
ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ ./nginx -12 -v 568 -q gess

在这里插入图片描述

可以清楚地看到,environ指向的内存紧邻argv指向的内存

在这里插入图片描述

理解了argv和environ之后,如果设置的进程名称太长,不但会覆盖命令行参数,而且很有可能覆盖环境变量所指向的部分或全部内存内容,从而造成程序运行隐患。所以,在无法提前预估进程名称长度的情况下,有如下解决问题的思路,这个思路源于官方Nginx源码

  • (1) 重新分配一块内存,足够容纳新的environ所指向的内容,把environ内容搬到这块内存中来
  • (2) 将以往argv[0]指向的内容替换成实际要修改的新进程名称

在这里插入图片描述

在这里插入图片描述

environ全局变量

在这里插入图片描述

设置进程名称实战代码

首先看ngx_init_setproctitle函数。该函数的作用就是重新分配一块内存,保存environ所指向的内存中的内容。该函数的代码非常简单,做了如下3件事:①统计环境变量的长度(所需要的内存大小);②使用new来分配所需大小的内存;③逐个把环境变量的内容复制到这块内存,并让environi指向新的内存位置

ngx_setproctitle.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  //env
#include <string.h>#include "ngx_global.h"//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来
void ngx_init_setproctitle() {    int i;//统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for (i = 0; environ[i]; i++) {g_environlen += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end for//这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好; gp_envmem = new char[g_environlen]; memset(gp_envmem, 0, g_environlen);  //内存要清空防止出现问题char *ptmp = gp_envmem;//把原来的内存内容搬到新地方来for (i = 0; environ[i]; i++) {size_t size = strlen(environ[i]) + 1 ; //不要落下+1,否则内存全乱套了,因为strlen是不包括字符串末尾的\0的strcpy(ptmp, environ[i]);      //把原环境变量内容拷贝到新地方【新内存】environ[i] = ptmp;            //然后还要让环境变量指针指向这段新内存ptmp += size;}return;
}//设置可执行程序标题
void ngx_setproctitle(const char *title) {//我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;//注意:我们的标题长度,不会长到原始标题和原始环境变量都装不下,否则怕出问题,不处理//(1)计算新标题长度size_t ititlelen = strlen(title); //(2)计算总的原始的argv那块内存的总长度【包括各种参数】size_t e_environlen = 0;     //e表示局部变量吧for (int i = 0; g_os_argv[i]; i++) {e_environlen += strlen(g_os_argv[i]) + 1;}size_t esy = e_environlen + g_environlen; //argv和environ内存总和if( esy <= ititlelen) {//你标题多长啊,我argv和environ总和都存不下?注意字符串末尾多了个 \0,所以这块判断是 <=【也就是=都算存不下】return;}//空间够保存标题的,够长,存得下,继续走下来    //(3)设置后续的命令行参数为空,表示只有argv[]中只有一个元素了,这是好习惯;防止后续argv被滥用,因为很多判断是用argv[] == NULL来做结束标记判断的;g_os_argv[1] = NULL;  //(4)把标题弄进来,注意原来的命令行参数都会被覆盖掉,不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了char *ptmp = g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存strcpy(ptmp,title);ptmp += ititlelen; //跳过标题//(5)把剩余的原argv以及environ所占的内存全部清0,否则会出现在ps的cmd列可能还会残余一些没有被覆盖的内容;size_t cha = esy - ititlelen;  //内存总和减去标题字符串长度(不含字符串末尾的\0),剩余的大小,就是要memset的;memset(ptmp,0,cha);  return;
}

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;// 测试代码for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}printf("--------------------------------------------------------");ngx_init_setproctitle();    //把环境变量搬家// 测试代码for (int i = 0; environ[i]; i++){printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));printf("environ[%d]内容=%s\n" ,i,environ[i]);}//我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着for(int i = 0; i < argc; ++i) {        printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));printf("argv[%d]内容=%s\n", i, argv[i]);}//下面环境变量随便输出2个for(int i = 0; i < 2; ++i) {printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));printf("evriron[%d]内容=%s\n" , i, environ[i]);}//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();for(;;){sleep(1); //休息1秒printf("休息1秒\n");}//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

做了上述修改后,可以写一段测试代码测试所做的更改是否有效。测试代码的工作原理是在调用ngx_init_setproctitle函数之前输出一次environ的地址和内容,在调用ngx_init_setproctitle函数之后再输出一次,只要看到environ的地址发生了改变,但内容没有变化,就表示ngx_init_setproctitle函数正常工作了
在这里插入图片描述

delete []gp_envmem

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }printf("--------------------------------------------------------");ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();// for(;;)// {//    sleep(1); //休息1秒//    printf("休息1秒\n");// }//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

在这里插入图片描述

ngx_setproctitle函数正常工作需要2个前提条件

(1) 假设所有命令行参数都不需要,可以被任意覆盖。在前面可以清晰地看到,进程名称如果比较长,肯定会覆盖命令行参数。所以,如果程序中要使用命令行参数,就在调用ngx_setproctitle函数之前使用,因为此时这些命令行参数还没有被进程名称覆盖(当然进程名称足够长才会涉及覆盖命令行参数问题)。

(2) 假设新设置的进程名称长度不会大到原始的命令行参数所占内存与环境变量所占内存的总和,这样才可以保证保存进程名称的这部分内存是合法的,能够被程序员使用的。

本项目的ngx_setproctitle函数代码比较简单,做了如下几件事:①计算新进程名称(标题)的长度。②计算命令行参数所占内存与环境变量所占内存的总和,确保新进程名称小于该总和(确保内存足够保存新的进程名称)。③设置新的进程名称

ngx_setproctitle.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  //env
#include <string.h>#include "ngx_global.h"//设置可执行程序标题相关函数:分配内存,并且把环境变量拷贝到新内存中来
void ngx_init_setproctitle() {    int i;//统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for (i = 0; environ[i]; i++) {g_environlen += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end for//这里无需判断penvmen == NULL,有些编译器new会返回NULL,有些会报异常,但不管怎样,如果在重要的地方new失败了,你无法收场,让程序失控崩溃,助你发现问题为好; gp_envmem = new char[g_environlen]; memset(gp_envmem, 0, g_environlen);  //内存要清空防止出现问题char *ptmp = gp_envmem;//把原来的内存内容搬到新地方来for (i = 0; environ[i]; i++) {size_t size = strlen(environ[i]) + 1 ; //不要落下+1,否则内存全乱套了,因为strlen是不包括字符串末尾的\0的strcpy(ptmp, environ[i]);      //把原环境变量内容拷贝到新地方【新内存】environ[i] = ptmp;            //然后还要让环境变量指针指向这段新内存ptmp += size;}return;
}//设置可执行程序标题
void ngx_setproctitle(const char *title) {//我们假设,所有的命令 行参数我们都不需要用到了,可以被随意覆盖了;//注意:我们的标题长度,不会长到原始标题和原始环境变量都装不下,否则怕出问题,不处理//(1)计算新标题长度size_t ititlelen = strlen(title); //(2)计算总的原始的argv那块内存的总长度【包括各种参数】size_t e_environlen = 0;     //e表示局部变量吧for (int i = 0; g_os_argv[i]; i++) {e_environlen += strlen(g_os_argv[i]) + 1;}size_t esy = e_environlen + g_environlen; //argv和environ内存总和if( esy <= ititlelen) {//你标题多长啊,我argv和environ总和都存不下?注意字符串末尾多了个 \0,所以这块判断是 <=【也就是=都算存不下】return;}//空间够保存标题的,够长,存得下,继续走下来    //(3)设置后续的命令行参数为空,表示只有argv[]中只有一个元素了,这是好习惯;防止后续argv被滥用,因为很多判断是用argv[] == NULL来做结束标记判断的;g_os_argv[1] = NULL;  //(4)把标题弄进来,注意原来的命令行参数都会被覆盖掉,不要再使用这些命令行参数,而且g_os_argv[1]已经被设置为NULL了char *ptmp = g_os_argv[0]; //让ptmp指向g_os_argv所指向的内存strcpy(ptmp,title);ptmp += ititlelen; //跳过标题//(5)把剩余的原argv以及environ所占的内存全部清0,否则会出现在ps的cmd列可能还会残余一些没有被覆盖的内容;size_t cha = esy - ititlelen;  //内存总和减去标题字符串长度(不含字符串末尾的\0),剩余的大小,就是要memset的;memset(ptmp,0,cha);  return;
}

在main主函数中,找一个合适的位置(要在ngx_init_setproctitle();代码行之后),增加对ngx_setproctitle函数函数的调用:

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }printf("--------------------------------------------------------");ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{printf("配置文件载入失败,退出!\n");exit(1);}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉ngx_setproctitle("nginx: master process");//myconf();//mysignal();// for(;;)// {//    sleep(1); //休息1秒//    printf("休息1秒\n");// }//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}

在这里插入图片描述

在这里插入图片描述

日志打印实战,优化main函数调用顺序

日志打印实战

参考nginx官方源码/home/ubuntu/nginxsourcecode/nginx-1.26.0/src/core

ngx_global.h

#ifndef __NGX_GBLDEF_H__
#define __NGX_GBLDEF_H__
#include <sys/types.h>//一些比较通用的定义放在这里
void Rtrim(char *string);
void Ltrim(char *string);void ngx_init_setproctitle();
void ngx_setproctitle(const char *title);void ngx_log_stderr(int err, const char *fmt, ...);
void ngx_log_init();
void ngx_log_error_core(int level, int err, const char *fmt, ...);u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);
u_char *ngx_vslprintf(u_char *buf, u_char *last,const char *fmt,va_list args);//结构定义
typedef struct {char ItemName[50];char ItemContent[500];
} CConfItem, *LPCConfItem;//和运行日志相关 
typedef struct
{int    log_level;   //日志级别 或者日志类型,ngx_macro.h里分0-8共9个级别int    fd;          //日志文件描述符} ngx_log_t;//外部全局量声明
extern size_t      g_argvneedmem;
extern size_t      g_envneedmem; 
extern int         g_os_argc; 
extern char        **g_os_argv;
extern char        *gp_envmem; 
extern int 		   g_environlen;extern pid_t       ngx_pid;
extern pid_t       ngx_parent;
extern ngx_log_t   ngx_log;#endif

va_start、va_arg、va_end

在这里插入图片描述

memcpy函数

在这里插入图片描述

在这里插入图片描述

ngx_printf.cxx

//和打印格式相关的函数放这里#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>   //类型相关头文件#include "ngx_global.h"
#include "ngx_macro.h"
#include "ngx_func.h"//只用于本文件的一些函数声明就放在本文件中
static u_char *ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64,u_char zero, uintptr_t hexadecimal, uintptr_t width);//----------------------------------------------------------------------------------------------------------------------
//对于 nginx 自定义的数据结构进行标准格式化输出,就像 printf,vprintf 一样,我们顺道学习写这类函数到底内部是怎么实现的
//该函数只不过相当于针对ngx_vslprintf()函数包装了一下,所以,直接研究ngx_vslprintf()即可
u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...) 
{va_list   args;u_char   *p;va_start(args, fmt); //使args指向起始的参数p = ngx_vslprintf(buf, last, fmt, args);va_end(args);        //释放args   return p;
}//----------------------------------------------------------------------------------------------------------------------
//对于 nginx 自定义的数据结构进行标准格式化输出,就像 printf,vprintf 一样,我们顺道学习写这类函数到底内部是怎么实现的
//例如,给进来一个 "abc = %d",13   ,最终buf里得到的应该是   abc=13 这种结果
//buf:往这里放数据
//last:放的数据不要超过这里
//fmt:以这个为首的一系列可变参数
//支持的格式: %d【%Xd/%xd】:数字,    %s:字符串      %f:浮点,  %P:pid_t//对于:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123);//fmt = "invalid option: \"%s\",%d"//args = "testinfo",123
u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args) {//比如说你要调用ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);,那么这里的fmt就应该是:   invalid option: "%s"//printf("fmt = %s\n",fmt);u_char     zero;/*#ifdef _WIN64typedef unsigned __int64  uintptr_t;#elsetypedef unsigned int uintptr_t;#endif*/uintptr_t  width, sign, hex, frac_width, scale, n;  //临时用到的一些变量int64_t    i64;   //保存%d对应的可变参uint64_t   ui64;  //保存%ud对应的可变参,临时作为%f可变参的整数部分也是可以的 u_char     *p;    //保存%s对应的可变参double     f;     //保存%f对应的可变参uint64_t   frac;  //%f可变参数,根据%.2f等,取得小数部分的2位后的内容;//每次处理一个字符,处理的是  "invalid option: \"%s\",%d" 中的字符while (*fmt && buf < last) {//%开头的一般都是需要被可变参数 取代的if (*fmt == '%') {//-----------------变量初始化工作开始-----------------//++fmt是先加后用,也就是fmt先往后走一个字节位置,然后再判断该位置的内容zero  = (u_char) ((*++fmt == '0') ? '0' : ' ');  //判断%后边接的是否是个'0',如果是zero = '0',否则zero = ' ',一般比如你想显示10位,而实际数字7位,前头填充三个字符,就是这里的zero用于填充//ngx_log_stderr(0, "数字是%010d", 12); width = 0;                                       //格式字符% 后边如果是个数字,这个数字最终会弄到width里边来 ,这东西目前只对数字格式有效,比如%d,%f这种sign  = 1;                                       //显示的是否是有符号数,这里给1,表示是有符号数,除非你 用%u,这个u表示无符号数 hex   = 0;                                       //是否以16进制形式显示(比如显示一些地址),0:不是,1:是,并以小写字母显示a-f,2:是,并以大写字母显示A-Ffrac_width = 0;                                  //小数点后位数字,一般需要和%.10f配合使用,这里10就是frac_width;i64 = 0;                                         //一般用%d对应的可变参中的实际数字,会保存在这里ui64 = 0;                                        //一般用%ud对应的可变参中的实际数字,会保存在这里    //-----------------变量初始化工作结束-----------------//这个while就是判断%后边是否是个数字,如果是个数字,就把这个数字取出来,比如%16,最终这个循环就能够把16取出来弄到width里边去//%16d 这里最终width = 16;while (*fmt >= '0' && *fmt <= '9')  //如果%后边接的字符是 '0' --'9'之间的内容   ,比如  %16这种;   {//第一次 :width = 1;  第二次 width = 16,所以整个width = 16;width = width * 10 + (*fmt++ - '0');  //(*fmt++ - '0') 是什么,比如 '8' - '0' 的结果是8(字符对应的ASCII码相减), 所以(*fmt++ - '0') 的作用是能把字符8转成数字8}//一些特殊的格式,我们做一些特殊的标记【给一些变量特殊值等等】for ( ;; ) {//处理一些%之后的特殊字符switch (*fmt) {case 'u':       //%u,这个u表示无符号sign = 0;   //标记这是个无符号数fmt++;      //往后走一个字符continue;   //回到for继续判断case 'X':       //%X,X表示十六进制,并且十六进制中的A-F以大写字母显示,不要单独使用,一般是%Xdhex = 2;    //标记以大写字母显示十六进制中的A-Fsign = 0;fmt++;continue;case 'x':       //%x,x表示十六进制,并且十六进制中的a-f以小写字母显示,不要单独使用,一般是%xdhex = 1;    //标记以小写字母显示十六进制中的a-fsign = 0;fmt++;continue;case '.':       //其后边必须跟个数字,必须与%f配合使用,形如 %.10f:表示转换浮点数时小数部分的位数,比如%.10f表示转换浮点数时,小数点后必须保证10位数字,不足10位则用0来填补;fmt++;      //往后走一个字符,后边这个字符肯定是0-9之间,因为%.要求接个数字先 while(*fmt >= '0' && *fmt <= '9')  //如果是数字,一直循环,这个循环最终就能把诸如%.10f中的10提取出来{frac_width = frac_width * 10 + (*fmt++ - '0'); } //end while(*fmt >= '0' && *fmt <= '9') break;default:break;                } //end switch (*fmt) break;} //end for ( ;; )switch (*fmt) {case '%': //只有%%时才会遇到这个情形,本意是打印一个%,所以*buf++ = '%';fmt++;continue;case 'd': //显示整型数据,如果和u配合使用,也就是%ud,则是显示无符号整型数据if (sign)  //如果是有符号数{i64 = (int64_t) va_arg(args, int);  //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型} else //如果是和 %ud配合使用,则本条件就成立{ui64 = (uint64_t) va_arg(args, u_int);    }break;  //这break掉,直接跳到switch后边的代码去执行,这种凡是break的,都不做fmt++;  *********************【switch后仍旧需要进一步处理】case 's': //一般用于显示字符串p = va_arg(args, u_char *); //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型while (*p && buf < last)  //没遇到字符串结束标记,并且buf值够装得下这个参数{*buf++ = *p++;  //那就装,比如  "%s"    ,   "abcdefg",那abcdefg都被装进来}fmt++;continue; //重新从while开始执行 case 'P':  //转换一个pid_t类型i64 = (int64_t) va_arg(args, pid_t);sign = 1;break;case 'f': //一般 用于显示double类型数据,如果要显示小数部分,则要形如 %.5f  f = va_arg(args, double);  //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型if (f < 0)  //负数的处理{*buf++ = '-'; //单独搞个负号出来f = -f; //那这里f应该是正数了!}//走到这里保证f肯定 >= 0【不为负数】ui64 = (int64_t) f; //正整数部分给到ui64里frac = 0;//如果要求小数点后显示多少位小数if (frac_width) //如果是%d.2f,那么frac_width就会是这里的2{scale = 1;  //缩放从1开始for (n = frac_width; n; n--) {scale *= 10; //这可能溢出哦}//把小数部分取出来 ,比如如果是格式    %.2f   ,对应的参数是12.537// (uint64_t) ((12.537 - (double) 12) * 100 + 0.5);  //= (uint64_t) (0.537 * 100 + 0.5)  = (uint64_t) (53.7 + 0.5) = (uint64_t) (54.2) = 54frac = (uint64_t) ((f - (double) ui64) * scale + 0.5);   //取得保留的那些小数位数,【比如  %.2f   ,对应的参数是12.537,取得的就是小数点后的2位四舍五入,也就是54】//如果是"%.6f", 21.378,那么这里frac = 378000if (frac == scale)   //进位,比如    %.2f ,对应的参数是12.999,那么  = (uint64_t) (0.999 * 100 + 0.5)  = (uint64_t) (99.9 + 0.5) = (uint64_t) (100.4) = 100//而此时scale == 100,两者正好相等{ui64++;    //正整数部分进位frac = 0;  //小数部分归0}} //end if (frac_width)//正整数部分,先显示出来buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width); //把一个数字 比如“1234567”弄到buffer中显示if (frac_width) //指定了显示多少位小数{if (buf < last) {*buf++ = '.'; //因为指定显示多少位小数,先把小数点增加进来}buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width); //frac这里是小数部分,显示出来,不够的,前边填充'0'字符}fmt++;continue;  //重新从while开始执行//..................................//................其他格式符,逐步完善//..................................default:*buf++ = *fmt++; //往下移动一个字符continue; //注意这里不break,而是continue;而这个continue其实是continue到外层的while去了,也就是流程重新从while开头开始执行;} //end switch (*fmt) //显示%d的,会走下来,其他走下来的格式日后逐步完善......//统一把显示的数字都保存到 ui64 里去;if (sign) //显示的是有符号数{if (i64 < 0)  //这可能是和%d格式对应的要显示的数字{*buf++ = '-';  //小于0,自然要把负号先显示出来ui64 = (uint64_t) -i64; //变成无符号数(正数)}else //显示正数{ui64 = (uint64_t) i64;}} //end if (sign) //把一个数字 比如“1234567”弄到buffer中显示,如果是要求10位,则前边会填充3个空格比如“   1234567”//注意第5个参数hex,是否以16进制显示,比如如果你是想以16进制显示一个数字则可以%Xd或者%xd,此时hex = 2或者1buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width); fmt++;}else  //当成正常字符,源【fmt】拷贝到目标【buf】里{//用fmt当前指向的字符赋给buf当前指向的位置,然后buf往前走一个字符位置,fmt当前走一个字符位置*buf++ = *fmt++;   //*和++优先级相同,结合性从右到左,所以先求的是buf++以及fmt++,但++是先用后加;} //end if (*fmt == '%') }  //end while (*fmt && buf < last) return buf;
}//----------------------------------------------------------------------------------------------------------------------
//以一个指定的宽度把一个数字显示在buf对应的内存中, 如果实际显示的数字位数 比指定的宽度要小 ,比如指定显示10位,而你实际要显示的只有“1234567”,那结果可能是会显示“   1234567”//当然如果你不指定宽度【参数width=0】,则按实际宽度显示//你给进来一个%Xd之类的,还能以十六进制数字格式显示出来
//buf:往这里放数据
//last:放的数据不要超过这里
//ui64:显示的数字         
//zero:显示内容时,格式字符%后边接的是否是个'0',如果是zero = '0',否则zero = ' ' 【一般显示的数字位数不足要求的,则用这个字符填充】,比如要显示10位,而实际只有7位,则后边填充3个这个字符;
//hexadecimal:是否显示成十六进制数字 0:不
//width:显示内容时,格式化字符%后接的如果是个数字比如%16,那么width=16,所以这个是希望显示的宽度值【如果实际显示的内容不够,则后头用0填充】
static u_char * ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, uintptr_t hexadecimal, uintptr_t width)
{//temp[21]u_char      *p, temp[NGX_INT64_LEN + 1];   //#define NGX_INT64_LEN   (sizeof("-9223372036854775808") - 1)     = 20   ,注意这里是sizeof是包括末尾的\0,不是strlen;             size_t      len;uint32_t    ui32;static u_char   hex[] = "0123456789abcdef";  //跟把一个10进制数显示成16进制有关,换句话说和  %xd格式符有关,显示的16进制数中a-f小写static u_char   HEX[] = "0123456789ABCDEF";  //跟把一个10进制数显示成16进制有关,换句话说和  %Xd格式符有关,显示的16进制数中A-F大写p = temp + NGX_INT64_LEN; //NGX_INT64_LEN = 20,所以 p指向的是temp[20]那个位置,也就是数组最后一个元素位置// 需要进行十进制转换if (hexadecimal == 0) {//NGX_MAX_UINT32_VALUE :最大的32位无符号数:十进制是‭4294967295‬if (ui64 <= (uint64_t) NGX_MAX_UINT32_VALUE) {ui32 = (uint32_t) ui64; //能保存下//这个循环能够把诸如 7654321这个数字保存成:temp[13]=7,temp[14]=6,temp[15]=5,temp[16]=4,temp[17]=3,temp[18]=2,temp[19]=1//而且的包括temp[0..12]以及temp[20]都是不确定的值do {//把屁股后边这个数字拿出来往数组里装,并且是倒着装:屁股后的也往数组下标大的位置装;*--p = (u_char) (ui32 % 10 + '0');  } while (ui32 /= 10); //每次缩小10倍等于去掉屁股后边这个数字} else {do {*--p = (u_char) (ui64 % 10 + '0');} while (ui64 /= 10); //每次缩小10倍等于去掉屁股后边这个数字}} else if (hexadecimal == 1)  //如果显示一个十六进制数字,格式符为:%xd,则这个条件成立,要以16进制数字形式显示出来这个十进制数,a-f小写{//比如我显示一个1,234,567【十进制数】,他对应的二进制数实际是 12 D687 ,那怎么显示出这个12D687来呢?do {            //0xf就是二进制的1111,大家都学习过位运算,ui64 & 0xf,就等于把 一个数的最末尾的4个二进制位拿出来;//ui64 & 0xf  其实就能分别得到 这个16进制数也就是 7,8,6,D,2,1这个数字,转成 (uint32_t) ,然后以这个为hex的下标,找到这几个数字的对应的能够显示的字符;*--p = hex[(uint32_t) (ui64 & 0xf)];    } while (ui64 >>= 4);    //ui64 >>= 4     --->   ui64 = ui64 >> 4 ,而ui64 >> 4是啥,实际上就是右移4位,就是除以16,因为右移4位就等于移动了1111;//相当于把该16进制数的最末尾一位干掉,原来是 12 D687, >> 4后是 12 D68,如此反复,最终肯定有=0时导致while不成立退出循环//比如 1234567 / 16 = 77160(0x12D68) // 77160 / 16 = 4822(0x12D6)} else // hexadecimal == 2    //如果显示一个十六进制数字,格式符为:%Xd,则这个条件成立,要以16进制数字形式显示出来这个十进制数,A-F大写{ //参考else if (hexadecimal == 1),非常类似do { *--p = HEX[(uint32_t) (ui64 & 0xf)];} while (ui64 >>= 4);}len = (temp + NGX_INT64_LEN) - p;  //得到这个数字的宽度,比如 “7654321”这个数字 ,len = 7while (len++ < width && buf < last)  //如果你希望显示的宽度是10个宽度【%12f】,而实际想显示的是7654321,只有7个宽度,那么这里要填充5个0进去到末尾,凑够要求的宽度{*buf++ = zero;  //填充0进去到buffer中(往末尾增加),比如你用格式  //ngx_log_stderr(0, "invalid option: %10d\n", 21); //显示的结果是:nginx: invalid option:         21  ---21前面有8个空格,这8个弄个,就是在这里添加进去的;}len = (temp + NGX_INT64_LEN) - p; //还原这个len,也就是要显示的数字的实际宽度【因为上边这个while循环改变了len的值】//现在还没把实际的数字比如“7654321”往buf里拷贝呢,要准备拷贝//如下这个等号是我加的【我认为应该加等号】,nginx源码里并没有加;***********************************************if((buf + len) >= last)   //发现如果往buf里拷贝“7654321”后,会导致buf不够长【剩余的空间不够拷贝整个数字】{len = last - buf; //剩余的buf有多少我就拷贝多少}return ngx_cpymem(buf, p, len); //把最新buf返回去;
}

优化代码

在这里插入图片描述

sprintf函数

在这里插入图片描述

strerror

在这里插入图片描述

write函数

在这里插入图片描述

ngx_log.cxx

//和日志相关的函数放之类#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>    //uintptr_t
#include <stdarg.h>    //va_start....
#include <unistd.h>    //STDERR_FILENO等
#include <sys/time.h>  //gettimeofday
#include <time.h>      //localtime_r
#include <fcntl.h>     //open
#include <errno.h>     //errno
#include <sys/types.h>#include "ngx_global.h"
#include "ngx_macro.h"
#include "ngx_func.h"
#include "ngx_c_conf.h"//全局量---------------------
//错误等级, 和ngx_macro.h里定义的日志等级宏是一一对应关系
static u_char err_levels[][20]  = {{"stderr"},    //0:控制台错误{"emerg"},     //1:紧急{"alert"},     //2:警戒{"crit"},      //3:严重{"error"},     //4:错误{"warn"},      //5:警告{"notice"},    //6:注意{"info"},      //7:信息{"debug"}      //8:调试
};ngx_log_t ngx_log;//----------------------------------------------------------------------------------------------------------------------
//描述:给一段内存, 一个错误编号, 我要组合出一个字符串, 形如:   (错误编号: 错误原因), 放到给的这段内存中去
//     这个函数我改造的比较多, 和原始的nginx代码多有不同
//buf:是个内存, 要往这里保存数据
//last:放的数据不要超过这里
//err:错误编号, 我们是要取得这个错误编号对应的错误字符串, 保存到buffer中
u_char *ngx_log_errno(u_char *buf, u_char *last, int err)
{//根据资料不会返回NULL;char *perrorinfo = strerror(err); size_t len = strlen(perrorinfo);//然后我还要插入一些字符串: (%d:)  char leftstr[10] = {0}; sprintf(leftstr," (%d: ",err);size_t leftlen = strlen(leftstr);char rightstr[] = ") "; size_t rightlen = strlen(rightstr);size_t extralen = leftlen + rightlen; //左右的额外宽度if ((buf + len + extralen) < last){//保证整个我装得下, 我就装, 否则我全部抛弃 ,nginx的做法是 如果位置不够, 就硬留出50个位置【哪怕覆盖掉以往的有效内容】, 也要硬往后边塞, 这样当然也可以;buf = ngx_cpymem(buf, leftstr, leftlen);buf = ngx_cpymem(buf, perrorinfo, len);buf = ngx_cpymem(buf, rightstr, rightlen);}return buf;
}//----------------------------------------------------------------------------------------------------------------------
//描述:通过可变参数组合出字符串【支持...省略号形参】, 自动往字符串最末尾增加换行符【所以调用者不用加\n】,  往标准错误上输出这个字符串;
//如果err不为0, 表示有错误, 会将该错误编号以及对应的错误信息一并放到组合出的字符串中一起显示;//比较典型的C语言中的写法, 就是这种va_start,va_end
//fmt:通过这第一个普通参数来寻址后续的所有可变参数的类型及其值
//调用格式比如:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123);/* ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效, 往末尾填充0ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68Engx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68Engx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326ngx_log_stderr(0, "invalid option: %d", 1678); */
void ngx_log_stderr(int err, const char *fmt, ...) {    va_list args;                        //创建一个va_list类型变量u_char  errstr[NGX_MAX_ERROR_STR+1]; //2048  -- ************  +1是我自己填的, 感觉官方写法有点小瑕疵, 所以动手调整一下u_char  *p, *last;memset(errstr, 0, sizeof(errstr));     //我个人加的, 这块有必要加, 至少在va_end处理之前有必要, 否则字符串没有结束标记不行的;***************************last = errstr + NGX_MAX_ERROR_STR;        //last指向整个buffer最后去了【指向最后一个有效位置的后面也就是非有效位】, 作为一个标记, 防止输出内容超过这么长,//其实我认为这有问题, 所以我才在上边errstr[NGX_MAX_ERROR_STR+1]; 给加了1//比如你定义 char tmp[2]; 你如果last = tmp+2, 那么last实际指向了tmp[2], 而tmp[2]在使用中是无效的p = ngx_cpymem(errstr, "nginx: ", 7);     //p指向"nginx: "之后    va_start(args, fmt); //使args指向起始的参数p = ngx_vslprintf(p, last, fmt, args); //组合出这个字符串保存在errstr里va_end(args);        //释放args//如果错误代码不是0, 表示有错误发生if (err) {//错误代码和错误信息也要显示出来p = ngx_log_errno(p, last, err);}//若位置不够, 那换行也要硬插入到末尾, 哪怕覆盖到其他内容if (p >= (last - 1)){p = (last - 1) - 1; //把尾部空格留出来, 这里感觉nginx处理的似乎就不对 //我觉得, last-1, 才是最后 一个而有效的内存, 而这个位置要保存\0, 所以我认为再减1, 这个位置, 才适合保存\n}*p++ = '\n'; //增加个换行符    //往标准错误【一般是屏幕】输出信息    write(STDERR_FILENO, errstr, p - errstr); //这个叫标准错误, 一般指屏幕//测试代码://printf("ngx_log_stderr()处理结果=%s\n",errstr);//printf("ngx_log_stderr()处理结果=%s",errstr);return;
}//----------------------------------------------------------------------------------------------------------------------
//往日志文件中写日志, 代码中有自动加换行符, 所以调用时字符串不用刻意加\n;
//    日过定向为标准错误, 则直接往屏幕上写日志【比如日志文件打不开, 则会直接定位到标准错误, 此时日志就打印到屏幕上, 参考ngx_log_init()】
//level:一个等级数字, 我们把日志分成一些等级, 以方便管理、显示、过滤等等, 如果这个等级数字比配置文件中的等级数字"LogLevel"大, 那么该条信息不被写到日志文件中
//err:是个错误代码, 如果不是0, 就应该转换成显示对应的错误信息,一起写到日志文件中, 
//ngx_log_error_core(5,8,"这个XXX工作的有问题,显示的结果是=%s","YYYY");
void ngx_log_error_core(int level,  int err, const char *fmt, ...)
{u_char  *last;u_char  errstr[NGX_MAX_ERROR_STR+1];   //这个+1也是我放入进来的, 本函数可以参考ngx_log_stderr()函数的写法;memset(errstr,0,sizeof(errstr));  last = errstr + NGX_MAX_ERROR_STR;   struct timeval   tv;struct tm        tm;time_t           sec;   //秒u_char           *p;    //指向当前要拷贝数据到其中的内存位置va_list          args;memset(&tv,0,sizeof(struct timeval));    memset(&tm,0,sizeof(struct tm));gettimeofday(&tv, NULL);     //获取当前时间, 返回自1970-01-01 00:00:00到现在经历的秒数【第二个参数是时区, 一般不关心】        sec = tv.tv_sec;             //秒localtime_r(&sec, &tm);      //把参数1的time_t转换为本地时间, 保存到参数2中去, 带_r的是线程安全的版本, 尽量使用tm.tm_mon++;                 //月份要调整下正常tm.tm_year += 1900;          //年份要调整下才正常u_char strcurrtime[40]={0};  //先组合出一个当前时间字符串, 格式形如:2019/01/08 19:57:11ngx_slprintf(strcurrtime,  (u_char *)-1,                       //若用一个u_char *接一个 (u_char *)-1,则 得到的结果是 0xffffffff...., 这个值足够大"%4d/%02d/%02d %02d:%02d:%02d",     //格式是 年/月/日 时:分:秒tm.tm_year, tm.tm_mon,tm.tm_mday, tm.tm_hour,tm.tm_min, tm.tm_sec);p = ngx_cpymem(errstr,strcurrtime,strlen((const char *)strcurrtime));  //日期增加进来, 得到形如:     2019/01/08 20:26:07p = ngx_slprintf(p, last, " [%s] ", err_levels[level]);                //日志级别增加进来, 得到形如:  2019/01/08 20:26:07 [crit] p = ngx_slprintf(p, last, "%P: ",ngx_pid);                             //支持%P格式, 进程id增加进来, 得到形如:   2019/01/08 20:50:15 [crit] 2037:va_start(args, fmt);                     //使args指向起始的参数p = ngx_vslprintf(p, last, fmt, args);   //把fmt和args参数弄进去, 组合出来这个字符串va_end(args);                            //释放args if (err)  //如果错误代码不是0, 表示有错误发生{//错误代码和错误信息也要显示出来p = ngx_log_errno(p, last, err);}//若位置不够, 那换行也要硬插入到末尾, 哪怕覆盖到其他内容if (p >= (last - 1)){p = (last - 1) - 1; //把尾部空格留出来, 这里感觉nginx处理的似乎就不对 //我觉得, last-1, 才是最后 一个而有效的内存, 而这个位置要保存\0, 所以我认为再减1, 这个位置, 才适合保存\n}*p++ = '\n'; //增加个换行符       //这么写代码是图方便:随时可以把流程弄到while后边去;大家可以借鉴一下这种写法ssize_t   n;while(1) {        if (level > ngx_log.log_level) {//要打印的这个日志的等级太落后(等级数字太大, 比配置文件中的数字大)//这种日志就不打印了break;}//磁盘是否满了的判断, 先算了吧, 还是由管理员保证这个事情吧; //写日志文件        n = write(ngx_log.fd,errstr,p - errstr);  //文件写入成功后, 如果中途if (n == -1) {//写失败有问题if(errno == ENOSPC) //写失败, 且原因是磁盘没空间了{//磁盘没空间了//没空间还写个毛线啊//先do nothing吧;}else{//这是有其他错误, 那么我考虑把这个错误显示到标准错误设备吧;if(ngx_log.fd != STDERR_FILENO) //当前是定位到文件的, 则条件成立{n = write(STDERR_FILENO,errstr,p - errstr);}}}break;} //end while    return;
}//----------------------------------------------------------------------------------------------------------------------
//描述:日志初始化, 就是把日志文件打开 , 注意这里边涉及到释放的问题, 如何解决?
void ngx_log_init()
{u_char *plogname = NULL;size_t nlen;//从配置文件中读取和日志相关的配置信息CConfig *p_config = CConfig::GetInstance();plogname = (u_char *)p_config->GetString("Log");if(plogname == NULL) {//没读到, 就要给个缺省的路径文件名了plogname = (u_char *) NGX_ERROR_LOG_PATH; //"logs/error.log" ,logs目录需要提前建立出来}//缺省日志等级为6【注意】, 如果读失败, 就给缺省日志等级ngx_log.log_level = p_config->GetIntDefault("LogLevel", NGX_LOG_NOTICE);//nlen = strlen((const char *)plogname);//只写打开|追加到末尾|文件不存在则创建【这个需要跟第三参数指定文件访问权限】//mode = 0644:文件访问权限,  6: 110    , 4: 100:     【用户:读写,  用户所在组:读, 其他:读】 ngx_log.fd = open((const char *)plogname, O_WRONLY|O_APPEND|O_CREAT, 0644);  if (ngx_log.fd == -1)  //如果有错误, 则直接定位到 标准错误上去 {ngx_log_stderr(errno,"[alert] could not open error log file: open() \"%s\" failed", plogname);ngx_log.fd = STDERR_FILENO; //直接定位到标准错误去了        } return;
}

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }printf("--------------------------------------------------------");ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf");//exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件exitcode = 2; //标记找不到文件goto lblexit;}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();//(3)一些初始化函数,准备放这里ngx_log_init();             //日志初始化(创建/打开日志文件)//(4)一些不好归类的其他类别的代码,准备放这里// ngx_init_setproctitle();    //把环境变量搬家//ngx_log_stderr调用演示代码:// ngx_log_stderr(0, "invalid option: %s, %d", "testInfo", 326);// ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"// ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格// ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0// ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000// ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00// ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68e// ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E// ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326 (15: Block device required) // ngx_log_stderr(0, "invalid option: %d", 1678);         //nginx: invalid option: 1678//测试ngx_log_error_core函数的调用//ngx_log_error_core(5,8,"这个XXX工作的有问题,显示的结果=%s","YYYY");for(;;){sleep(1); //休息1秒printf("休息1秒\n");}lblexit://(5)该释放的资源要释放掉freeresource();  //一系列的main返回前的释放动作函数printf("程序退出,再见!\n");return exitcode;//对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}printf("程序退出,再见!\n");return 0;
}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】
void freeresource()
{//(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}//(2)关闭日志文件if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1)  {        close(ngx_log.fd); //不用判断结果了ngx_log.fd = -1; //标记下,防止被再次close吧        }
}

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }// printf("--------------------------------------------------------");ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//我们在main中,先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,配置文件与可执行程序在同一个目录,所以可以直接载入配置文件{ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf");//exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件exitcode = 2; //标记找不到文件goto lblexit;}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();//(3)一些初始化函数,准备放这里ngx_log_init();             //日志初始化(创建/打开日志文件)//(4)一些不好归类的其他类别的代码,准备放这里// ngx_init_setproctitle();    //把环境变量搬家//ngx_log_stderr调用演示代码:// ngx_log_stderr(0, "invalid option: %s, %d", "testInfo", 326);// ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"// ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格// ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0// ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000// ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00// ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68e// ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E// ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326 (15: Block device required) // ngx_log_stderr(0, "invalid option: %d", 1678);         //nginx: invalid option: 1678//测试ngx_log_error_core函数的调用//ngx_log_error_core(5,8,"这个XXX工作的有问题,显示的结果=%s","YYYY");for(;;){sleep(1); //休息1秒printf("休息1秒\n");}lblexit://(5)该释放的资源要释放掉freeresource();  //一系列的main返回前的释放动作函数printf("程序退出,再见!\n");return exitcode;
}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】
void freeresource()
{//(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem){delete []gp_envmem;gp_envmem = NULL;}//(2)关闭日志文件if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1)  {        close(ngx_log.fd); //不用判断结果了ngx_log.fd = -1; //标记下,防止被再次close吧        }
}

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ man errno

在这里插入图片描述

设置时区

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~/myProj/nginx$ tzselect

在这里插入图片描述

在这里插入图片描述

完善日志打印

ngx_macro.h


#ifndef __NGX_MACRO_H__
#define __NGX_MACRO_H__//各种#define宏定义相关的定义放这里#define NGX_MAX_ERROR_STR   2048   //显示的错误信息最大数组长度//简单功能函数--------------------
//类似memcpy,但常规memcpy返回的是指向目标dst的指针,而这个ngx_cpymem返回的是目标【拷贝数据后】的终点位置,连续复制多段数据时方便
#define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))  //注意#define写法,n这里用()包着,防止出现什么错误
#define ngx_min(val1, val2)  ((val1 > val2) ? (val2) : (val1))              //比较大小,返回小值,注意,参数都用()包着//数字相关--------------------
#define NGX_MAX_UINT32_VALUE   (uint32_t) 0xffffffff              //最大的32位无符号数:十进制是‭4294967295‬
#define NGX_INT64_LEN          (sizeof("-9223372036854775808") - 1)     //日志相关--------------------
//我们把日志一共分成八个等级【级别从高到低,数字最小的级别最高,数字大的级别最低】,以方便管理、显示、过滤等等
#define NGX_LOG_STDERR            0    //控制台错误【stderr】:最高级别日志,日志的内容不再写入log参数指定的文件,而是会直接将日志输出到标准错误设备比如控制台屏幕
#define NGX_LOG_EMERG             1    //紧急 【emerg】
#define NGX_LOG_ALERT             2    //警戒 【alert】
#define NGX_LOG_CRIT              3    //严重 【crit】
#define NGX_LOG_ERR               4    //错误 【error】:属于常用级别
#define NGX_LOG_WARN              5    //警告 【warn】:属于常用级别
#define NGX_LOG_NOTICE            6    //注意 【notice】
#define NGX_LOG_INFO              7    //信息 【info】
#define NGX_LOG_DEBUG             8    //调试 【debug】:最低级别#define NGX_ERROR_LOG_PATH       "logs/error1.log"   //定义日志存放的路径和文件名 #endif

nginx.conf

#是注释行,
#每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;#[开头的表示组信息,也等价于注释行
[Socket]
ListenPort = 5678    
DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g#日志相关
[Log]
#日志文件输出目录和文件名
Log=logs/error.log
# Log=error.log#只打印日志等级<= 数字 的日志到日志文件中 ,日志等级0-8,0级别最高,8级别最低。
LogLevel = 8

gettimeofday函数

在这里插入图片描述

localtime_r函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_signal.h"
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char *gp_envmem = NULL;      //指向自己分配的env环境变量的内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pidint main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出//(1)无伤大雅也不需要释放的放最上边    ngx_pid = getpid();         //取得进程pidg_os_argv = (char **) argv;// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }// printf("--------------------------------------------------------");// ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//(2)初始化失败,就要直接退出的//配置文件必须最先要,后边初始化啥的都用,所以先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存        {        ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf");//exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件exitcode = 2; //标记找不到文件goto lblexit;}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();//(3)一些初始化函数,准备放这里ngx_log_init();             //日志初始化(创建/打开日志文件)//(4)一些不好归类的其他类别的代码,准备放这里ngx_init_setproctitle();    //把环境变量搬家//ngx_log_stderr调用演示代码:// ngx_log_stderr(0, "invalid option: %s, %d", "testInfo", 326);// ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"// ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格// ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0// ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000// ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00// ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68e// ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E// ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326 (15: Block device required) // ngx_log_stderr(0, "invalid option: %d", 1678);         //nginx: invalid option: 1678//测试ngx_log_error_core函数的调用ngx_log_error_core(5, 8, "这个XXX工作的有问题, 显示的结果=%s", "YYYY");for(;;){sleep(1); //休息1秒printf("休息1秒\n");}lblexit://(5)该释放的资源要释放掉freeresource();  //一系列的main返回前的释放动作函数printf("程序退出,再见!\n");return exitcode;
}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】
void freeresource() {//(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem) {delete []gp_envmem;gp_envmem = NULL;}//(2)关闭日志文件if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1) {        close(ngx_log.fd); //不用判断结果了ngx_log.fd = -1; //标记下,防止被再次close    }
}

ngx_log.cxx

//和日志相关的函数放之类#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>    //uintptr_t
#include <stdarg.h>    //va_start....
#include <unistd.h>    //STDERR_FILENO等
#include <sys/time.h>  //gettimeofday
#include <time.h>      //localtime_r
#include <fcntl.h>     //open
#include <errno.h>     //errno
#include <sys/types.h>#include "ngx_global.h"
#include "ngx_macro.h"
#include "ngx_func.h"
#include "ngx_c_conf.h"//全局量---------------------
//错误等级, 和ngx_macro.h里定义的日志等级宏是一一对应关系
static u_char err_levels[][20]  = {{"stderr"},    //0:控制台错误{"emerg"},     //1:紧急{"alert"},     //2:警戒{"crit"},      //3:严重{"error"},     //4:错误{"warn"},      //5:警告{"notice"},    //6:注意{"info"},      //7:信息{"debug"}      //8:调试
};ngx_log_t ngx_log;//----------------------------------------------------------------------------------------------------------------------
//描述:给一段内存, 一个错误编号, 我要组合出一个字符串, 形如:   (错误编号: 错误原因), 放到给的这段内存中去
//     这个函数我改造的比较多, 和原始的nginx代码多有不同
//buf:是个内存, 要往这里保存数据
//last:放的数据不要超过这里
//err:错误编号, 我们是要取得这个错误编号对应的错误字符串, 保存到buffer中
u_char *ngx_log_errno(u_char *buf, u_char *last, int err)
{//根据资料不会返回NULL;char *perrorinfo = strerror(err); size_t len = strlen(perrorinfo);//然后我还要插入一些字符串: (%d:)  char leftstr[10] = {0}; sprintf(leftstr," (%d: ",err);size_t leftlen = strlen(leftstr);char rightstr[] = ") "; size_t rightlen = strlen(rightstr);size_t extralen = leftlen + rightlen; //左右的额外宽度if ((buf + len + extralen) < last){//保证整个我装得下, 我就装, 否则我全部抛弃 ,nginx的做法是 如果位置不够, 就硬留出50个位置【哪怕覆盖掉以往的有效内容】, 也要硬往后边塞, 这样当然也可以;buf = ngx_cpymem(buf, leftstr, leftlen);buf = ngx_cpymem(buf, perrorinfo, len);buf = ngx_cpymem(buf, rightstr, rightlen);}return buf;
}//----------------------------------------------------------------------------------------------------------------------
//描述:通过可变参数组合出字符串【支持...省略号形参】, 自动往字符串最末尾增加换行符【所以调用者不用加\n】,  往标准错误上输出这个字符串;
//如果err不为0, 表示有错误, 会将该错误编号以及对应的错误信息一并放到组合出的字符串中一起显示;//比较典型的C语言中的写法, 就是这种va_start,va_end
//fmt:通过这第一个普通参数来寻址后续的所有可变参数的类型及其值
//调用格式比如:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123);/* ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效, 往末尾填充0ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68Engx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68Engx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326ngx_log_stderr(0, "invalid option: %d", 1678); */
void ngx_log_stderr(int err, const char *fmt, ...) {    va_list args;                        //创建一个va_list类型变量u_char  errstr[NGX_MAX_ERROR_STR+1]; //2048  -- ************  +1是我自己填的, 感觉官方写法有点小瑕疵, 所以动手调整一下u_char  *p, *last;memset(errstr, 0, sizeof(errstr));     //我个人加的, 这块有必要加, 至少在va_end处理之前有必要, 否则字符串没有结束标记不行的;***************************last = errstr + NGX_MAX_ERROR_STR;        //last指向整个buffer最后去了【指向最后一个有效位置的后面也就是非有效位】, 作为一个标记, 防止输出内容超过这么长,//其实我认为这有问题, 所以我才在上边errstr[NGX_MAX_ERROR_STR+1]; 给加了1//比如你定义 char tmp[2]; 你如果last = tmp+2, 那么last实际指向了tmp[2], 而tmp[2]在使用中是无效的p = ngx_cpymem(errstr, "nginx: ", 7);     //p指向"nginx: "之后    va_start(args, fmt); //使args指向起始的参数p = ngx_vslprintf(p, last, fmt, args); //组合出这个字符串保存在errstr里va_end(args);        //释放args//如果错误代码不是0, 表示有错误发生if (err) {//错误代码和错误信息也要显示出来p = ngx_log_errno(p, last, err);}//若位置不够, 那换行也要硬插入到末尾, 哪怕覆盖到其他内容if (p >= (last - 1)){p = (last - 1) - 1; //把尾部空格留出来, 这里感觉nginx处理的似乎就不对 //我觉得, last-1, 才是最后 一个而有效的内存, 而这个位置要保存\0, 所以我认为再减1, 这个位置, 才适合保存\n}*p++ = '\n'; //增加个换行符    //往标准错误【一般是屏幕】输出信息    write(STDERR_FILENO, errstr, p - errstr); //这个叫标准错误, 一般指屏幕//测试代码://printf("ngx_log_stderr()处理结果=%s\n",errstr);//printf("ngx_log_stderr()处理结果=%s",errstr);return;
}//----------------------------------------------------------------------------------------------------------------------
//往日志文件中写日志, 代码中有自动加换行符, 所以调用时字符串不用刻意加\n;
//    日过定向为标准错误, 则直接往屏幕上写日志【比如日志文件打不开, 则会直接定位到标准错误, 此时日志就打印到屏幕上, 参考ngx_log_init()】
//level:一个等级数字, 我们把日志分成一些等级, 以方便管理、显示、过滤等等, 如果这个等级数字比配置文件中的等级数字"LogLevel"大, 那么该条信息不被写到日志文件中
//err:是个错误代码, 如果不是0, 就应该转换成显示对应的错误信息,一起写到日志文件中, 
//ngx_log_error_core(5,8,"这个XXX工作的有问题,显示的结果是=%s","YYYY");
void ngx_log_error_core(int level, int err, const char *fmt, ...) {u_char  *last;u_char  errstr[NGX_MAX_ERROR_STR + 1];   //这个+1也是我放入进来的, 本函数可以参考ngx_log_stderr()函数的写法;memset(errstr, 0, sizeof(errstr));  last = errstr + NGX_MAX_ERROR_STR;   struct timeval   tv;struct tm        tm;time_t           sec;   //秒u_char           *p;    //指向当前要拷贝数据到其中的内存位置va_list          args;memset(&tv, 0, sizeof(struct timeval));    memset(&tm, 0, sizeof(struct tm));//获取当前时间, 返回自1970-01-01 00:00:00到现在经历的秒数【第二个参数是时区, 一般不关心】gettimeofday(&tv, NULL);             sec = tv.tv_sec;             //秒localtime_r(&sec, &tm);      //把参数1的time_t转换为本地时间, 保存到参数2中去, 带_r的是线程安全的版本, 尽量使用tm.tm_mon++;                 //月份要调整下正常tm.tm_year += 1900;          //年份要调整下才正常u_char strcurrtime[40]={0};  //先组合出一个当前时间字符串, 格式形如:2019/01/08 19:57:11ngx_slprintf(strcurrtime,  (u_char *)-1,                       //若用一个u_char *接一个 (u_char *)-1,则 得到的结果是 0xffffffff...., 这个值足够大"%4d/%02d/%02d %02d:%02d:%02d",     //格式是 年/月/日 时:分:秒tm.tm_year, tm.tm_mon,tm.tm_mday, tm.tm_hour,tm.tm_min, tm.tm_sec);p = ngx_cpymem(errstr, strcurrtime, strlen((const char *)strcurrtime));  //日期增加进来, 得到形如:     2019/01/08 20:26:07p = ngx_slprintf(p, last, " [%s] ", err_levels[level]);                //日志级别增加进来, 得到形如:  2019/01/08 20:26:07 [crit] p = ngx_slprintf(p, last, "%P: ", ngx_pid);                             //支持%P格式, 进程id增加进来, 得到形如:   2019/01/08 20:50:15 [crit] 2037:va_start(args, fmt);                     //使args指向起始的参数p = ngx_vslprintf(p, last, fmt, args);   //把fmt和args参数弄进去, 组合出来这个字符串va_end(args);                            //释放args if (err)  //如果错误代码不是0, 表示有错误发生{//错误代码和错误信息也要显示出来p = ngx_log_errno(p, last, err);}//若位置不够, 那换行也要硬插入到末尾, 哪怕覆盖到其他内容if (p >= (last - 1)){p = (last - 1) - 1; //把尾部空格留出来, 这里感觉nginx处理的似乎就不对 //我觉得, last-1, 才是最后一个而有效的内存, 而这个位置要保存\0, 所以我认为再减1, 这个位置, 才适合保存\n}*p++ = '\n'; //增加个换行符       //这么写代码是图方便:随时可以把流程弄到while后边去;大家可以借鉴一下这种写法ssize_t   n;while(1) {        if (level > ngx_log.log_level) {//要打印的这个日志的等级太落后(等级数字太大, 比配置文件中的数字大)//这种日志就不打印了break;}//磁盘是否满了的判断, 先算了吧, 还是由管理员保证这个事情吧; //写日志文件        n = write(ngx_log.fd, errstr, p - errstr);  //文件写入成功后, 如果中途if (n == -1) {//写失败有问题if(errno == ENOSPC) //写失败, 且原因是磁盘没空间了{//磁盘没空间了//没空间还写个毛线啊//先do nothing吧;}else{//这是有其他错误, 那么我考虑把这个错误显示到标准错误设备吧;if(ngx_log.fd != STDERR_FILENO) //当前是定位到文件的, 则条件成立{n = write(STDERR_FILENO, errstr, p - errstr);}}}break;} //end while    return;
}//----------------------------------------------------------------------------------------------------------------------
//描述:日志初始化, 就是把日志文件打开 , 注意这里边涉及到释放的问题, 如何解决?
void ngx_log_init()
{u_char *plogname = NULL;size_t nlen;//从配置文件中读取和日志相关的配置信息CConfig *p_config = CConfig::GetInstance();plogname = (u_char *)p_config->GetString("Log");if(plogname == NULL) {//没读到, 就要给个缺省的路径文件名了plogname = (u_char *) NGX_ERROR_LOG_PATH; //"logs/error.log" ,logs目录需要提前建立出来}//缺省日志等级为6【注意】, 如果读失败, 就给缺省日志等级ngx_log.log_level = p_config->GetIntDefault("LogLevel", NGX_LOG_NOTICE);//nlen = strlen((const char *)plogname);//只写打开|追加到末尾|文件不存在则创建【这个需要跟第三参数指定文件访问权限】//mode = 0644:文件访问权限,  6: 110    , 4: 100:     【用户:读写,  用户所在组:读, 其他:读】 ngx_log.fd = open((const char *)plogname, O_WRONLY|O_APPEND|O_CREAT, 0644);  if (ngx_log.fd == -1)  //如果有错误, 则直接定位到 标准错误上去 {ngx_log_stderr(errno,"[alert] could not open error log file: open() \"%s\" failed", plogname);ngx_log.fd = STDERR_FILENO; //直接定位到标准错误去了        } return;
}

在这里插入图片描述

在这里插入图片描述

信号功能实战

signal和sigaction区别

在这里插入图片描述

sigaction

man sigaction

在这里插入图片描述

ngx_signal.cxx

//和信号有关的函数放这里
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>    //信号相关头文件 
#include <errno.h>     //errno
#include <unistd.h>#include "ngx_macro.h"
#include "ngx_func.h" 
#include "ngx_global.h"//一个信号有关的结构 ngx_signal_t
typedef struct {int           signo;       //信号对应的数字编号 ,每个信号都有对应的#define const  char   *signame;    //信号对应的中文名字 ,比如SIGHUP //信号处理函数,这个函数由我们自己来提供,但是它的参数和返回值是固定的【操作系统就这样要求】,大家写的时候就先这么写,也不用思考这么多;void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); //函数指针,   siginfo_t:系统定义的结构
} ngx_signal_t;//声明一个信号处理函数
//static表示该函数只在当前文件内可见
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); //数组 ,定义本系统处理的各种信号,我们取一小部分nginx中的信号,并没有全部搬移到这里,日后若有需要根据具体情况再增加
//在实际商业代码中,你能想到的要处理的信号,都弄进来
ngx_signal_t  signals[] = {// signo      signame             handler{ SIGHUP,    "SIGHUP",           ngx_signal_handler },        //终端断开信号,对于守护进程常用于reload重载配置文件通知--标识1{ SIGINT,    "SIGINT",           ngx_signal_handler },        //标识2   { SIGTERM,   "SIGTERM",          ngx_signal_handler },        //标识15{ SIGCHLD,   "SIGCHLD",          ngx_signal_handler },        //子进程退出时,父进程会收到这个信号--标识17{ SIGQUIT,   "SIGQUIT",          ngx_signal_handler },        //标识3{ SIGIO,     "SIGIO",            ngx_signal_handler },        //指示一个异步I/O事件【通用异步I/O信号】{ SIGSYS,    "SIGSYS, SIG_IGN",  NULL               },        //我们想忽略这个信号,SIGSYS表示收到了一个无效系统调用,如果我们不忽略,进程会被操作系统杀死,--标识31//所以我们把handler设置为NULL,代表 我要求忽略这个信号,请求操作系统不要执行缺省的该信号处理动作(杀掉我)//...日后根据需要再继续增加{ 0,         NULL,               NULL               }         //信号对应的数字至少是1,所以可以用0作为一个特殊标记
};//初始化信号的函数,用于注册信号处理程序
//返回值:0成功  ,-1失败
int ngx_init_signals() {//指向自定义结构数组的指针 ngx_signal_t      *sig;  //sigaction:系统定义的跟信号有关的一个结构,我们后续调用系统的sigaction()函数时要用到这个同名的结构struct sigaction   sa;   //将signo == 0作为一个标记,因为信号的编号都不为0;for (sig = signals; sig->signo != 0; sig++) {        //我们注意,现在要把一堆信息往 变量sa对应的结构里弄 ......memset(&sa, 0, sizeof(struct sigaction));if (sig->handler)  //如果信号处理函数不为空,这当然表示我要定义自己的信号处理函数{sa.sa_sigaction = sig->handler;  //sa_sigaction:指定信号处理程序(函数),注意sa_sigaction也是函数指针,是这个系统定义的结构sigaction中的一个成员(函数指针成员);sa.sa_flags = SA_SIGINFO;        //sa_flags:int型,指定信号的一些选项,设置了该标记(SA_SIGINFO),就表示信号附带的参数可以被传递到信号处理函数中//说白了就是你要想让sa.sa_sigaction指定的信号处理程序(函数)生效,你就把sa_flags设定为SA_SIGINFO} else {sa.sa_handler = SIG_IGN; //sa_handler:这个标记SIG_IGN给到sa_handler成员,表示忽略信号的处理程序,否则操作系统的缺省信号处理程序很可能把这个进程杀掉;//其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针他们参数不一样, sa_sigaction带的参数多,信息量大,//而sa_handler带的参数少,信息量少;如果你想用sa_sigaction,那么你就需要把sa_flags设置为SA_SIGINFO;                                       } //end ifsigemptyset(&sa.sa_mask);  //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号,那咱们就可以用诸如sigaddset(&sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理 //这里.sa_mask是个信号集(描述信号的集合),用于表示要阻塞的信号,sigemptyset():把信号集中的所有信号清0,本意就是不准备阻塞任何信号;//设置信号处理动作(信号处理函数),说白了这里就是让这个信号来了后调用我的处理程序,有个老的同类函数叫signal,不过signal这个函数被认为是不可靠信号语义,不建议使用,大家统一用sigaction//参数1:要操作的信号//参数2:主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容//参数3:返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】,跟参数2同一个类型,我们这里不需要这个东西,所以直接设置为NULL;if (sigaction(sig->signo, &sa, NULL) == -1) {   ngx_log_error_core(NGX_LOG_EMERG, errno, "sigaction(%s) failed", sig->signame); //显示到日志文件中去的 return -1; //有失败就直接返回} else {            //ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) succed!",sig->signame);     //成功不用写日志 ngx_log_stderr(0, "sigaction(%s) succeed!", sig->signame); //直接往屏幕上打印看看 ,不需要时可以去掉}} //end forreturn 0; //成功    
}//信号处理函数
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{     printf("来信号了\n");//sleep(10); //休息10秒        
}

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>#include "ngx_macro.h"   //各种宏定义
#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
size_t  g_argvneedmem=0;        //保存下这些argv参数所需要的内存大小
size_t  g_envneedmem=0;         //环境变量所占内存大小
int     g_os_argc;              //参数个数 char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char  *gp_envmem=NULL;        //指向自己分配的env环境变量的内存,在ngx_init_setproctitle()函数中会被分配内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pid
pid_t   ngx_parent;             //父进程的pid//标记子进程状态变化[一般是子进程发来SIGCHLD信号表示退出],sig_atomic_t:系统定义的类型:访问或改变这些变量需要在计算机的一条指令内完成
//一般等价于int【通常情况下,int类型的变量通常是原子访问的,也可以认为 sig_atomic_t就是int类型的数据】int main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出int i;                      //临时用//(1)无伤大雅也不需要释放的放最上边    ngx_pid    = getpid();      //取得进程pidngx_parent = getppid();     //取得父进程的id //统计argv所占的内存g_argvneedmem = 0;for(i = 0; i < argc; i++)  //argv =  ./nginx -a -b -c asdfas{g_argvneedmem += strlen(argv[i]) + 1; //+1是给\0留空间。} //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for(i = 0; environ[i]; i++) {g_envneedmem += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end forg_os_argc = argc;           //保存参数个数g_os_argv = (char **) argv; //保存参数指针// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }// printf("--------------------------------------------------------");// ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//(2)初始化失败,就要直接退出的//配置文件必须最先要,后边初始化啥的都用,所以先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存        {        ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf");//exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件exitcode = 2; //标记找不到文件goto lblexit;}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();//(3)一些初始化函数,准备放这里    ngx_log_init();             //日志初始化(创建/打开日志文件)if(ngx_init_signals() != 0) //信号初始化{exitcode = 1;goto lblexit;}//(4)一些不好归类的其他类别的代码,准备放这里ngx_init_setproctitle();    //把环境变量搬家//(5)开始正式的主工作流程,主流程一致在下边这个函数里循环,暂时不会走下来,资源释放啥的日后再慢慢完善和考虑// ngx_master_process_cycle(); //不管父进程还是子进程,正常工作期间都在这个函数里循环;//ngx_log_stderr调用演示代码:// ngx_log_stderr(0, "invalid option: %s, %d", "testInfo", 326);// ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"// ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格// ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0// ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000// ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00// ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68e// ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E// ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326 (15: Block device required) // ngx_log_stderr(0, "invalid option: %d", 1678);         //nginx: invalid option: 1678//测试ngx_log_error_core函数的调用// ngx_log_error_core(5, 8, "这个XXX工作的有问题, 显示的结果=%s", "YYYY");for(;;){sleep(1); //休息1秒printf("休息1秒\n");}lblexit://(5)该释放的资源要释放掉freeresource();  //一系列的main返回前的释放动作函数printf("程序退出,再见!\n");return exitcode;
}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】
void freeresource() {//(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem) {delete []gp_envmem;gp_envmem = NULL;}//(2)关闭日志文件if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1) {        close(ngx_log.fd); //不用判断结果了ngx_log.fd = -1; //标记下,防止被再次close    }
}

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~$ ps -eo pid,ppid,sid,tty,pgrp,comm,cmd,stat | grep -E 'bash|PID|nginx'

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Nginx中创建worker子进程

SIG_BLOCK和SIG_SETMASK的区别

在这里插入图片描述

ngx_process_cycle.cxx

在这里插入图片描述

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970//    sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    //    printf("执行到sigsuspend()下边来了\n");        ///        printf("master进程休息1秒\n");      //sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......//printf("worker进程休息1秒");       //fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;//sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程,编号为%d,pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

config.mk

#定义项目编译的根目录,通过export把某个变量声明为全局的[其他文件中可以用],这里获取当前这个文件所在的路径作为根目录;
#BUILD_ROOT = /mnt/hgfs/linux/nginx
export BUILD_ROOT = $(shell pwd)#定义头文件的路径变量
export INCLUDE_PATH = $(BUILD_ROOT)/_include#定义我们要编译的目录
BUILD_DIRS = $(BUILD_ROOT)/signal/ \$(BUILD_ROOT)/proc/   \$(BUILD_ROOT)/app/ #编译时是否生成调试信息。GNU调试器可以利用该信息
#很多调试工具,包括Valgrind工具集都会因为这个为true能够输出更多的调试信息;
export DEBUG = true

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>#include "ngx_macro.h"   //各种宏定义
#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
size_t  g_argvneedmem=0;        //保存下这些argv参数所需要的内存大小
size_t  g_envneedmem=0;         //环境变量所占内存大小
int     g_os_argc;              //参数个数 char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char  *gp_envmem=NULL;        //指向自己分配的env环境变量的内存,在ngx_init_setproctitle()函数中会被分配内存
int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pid
pid_t ngx_parent;             //父进程的pid//标记子进程状态变化[一般是子进程发来SIGCHLD信号表示退出],sig_atomic_t:系统定义的类型:访问或改变这些变量需要在计算机的一条指令内完成
//一般等价于int【通常情况下,int类型的变量通常是原子访问的,也可以认为 sig_atomic_t就是int类型的数据】int main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出int i;                      //临时用//(1)无伤大雅也不需要释放的放最上边    ngx_pid    = getpid();      //取得进程pidngx_parent = getppid();     //取得父进程的id //统计argv所占的内存g_argvneedmem = 0;for(i = 0; i < argc; i++)  //argv =  ./nginx -a -b -c asdfas{g_argvneedmem += strlen(argv[i]) + 1; //+1是给\0留空间。} //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for(i = 0; environ[i]; i++) {g_envneedmem += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end forg_os_argc = argc;           //保存参数个数g_os_argv = (char **) argv; //保存参数指针// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }// printf("--------------------------------------------------------");// ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//(2)初始化失败,就要直接退出的//配置文件必须最先要,后边初始化啥的都用,所以先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存        {        ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf");//exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件exitcode = 2; //标记找不到文件goto lblexit;}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();//(3)一些初始化函数,准备放这里    ngx_log_init();             //日志初始化(创建/打开日志文件)if(ngx_init_signals() != 0) //信号初始化{exitcode = 1;goto lblexit;}//(4)一些不好归类的其他类别的代码,准备放这里ngx_init_setproctitle();    //把环境变量搬家//(5)开始正式的主工作流程,主流程一致在下边这个函数里循环,暂时不会走下来,资源释放啥的日后再慢慢完善和考虑ngx_master_process_cycle(); //不管父进程还是子进程,正常工作期间都在这个函数里循环;//ngx_log_stderr调用演示代码:// ngx_log_stderr(0, "invalid option: %s, %d", "testInfo", 326);// ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"// ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格// ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0// ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000// ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00// ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68e// ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E// ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326 (15: Block device required) // ngx_log_stderr(0, "invalid option: %d", 1678);         //nginx: invalid option: 1678//测试ngx_log_error_core函数的调用// ngx_log_error_core(5, 8, "这个XXX工作的有问题, 显示的结果=%s", "YYYY");// for(;;)// {//    sleep(1); //休息1秒//    printf("休息1秒\n");// }lblexit://(5)该释放的资源要释放掉freeresource();  //一系列的main返回前的释放动作函数printf("程序退出,再见!\n");return exitcode;
}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】
void freeresource() {//(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem) {delete []gp_envmem;gp_envmem = NULL;}//(2)关闭日志文件if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1) {        close(ngx_log.fd); //不用判断结果了ngx_log.fd = -1; //标记下,防止被再次close    }
}

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~$ ps -eo pid,ppid,sid,tty,pgrp,comm,cmd,stat | grep -E 'bash|PID|nginx'

在这里插入图片描述
如果希望把所有的master和worker进程同时用kill命令杀掉,需要用到进程组ID(确切地说用到的是进程组ID×(-1)之后的值)

kill -9 -2920

在这里插入图片描述

sigsuspend函数

在这里插入图片描述

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    //    printf("执行到sigsuspend()下边来了\n");        ///        printf("master进程休息1秒\n");      //sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......//printf("worker进程休息1秒");       //fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;//sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程,编号为%d,pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

sigsuspend函数。该函数也是官方master进程中的for无限循环中用到的一个主要函数(请观察ngx_master_process_cycle的for无限循环中的针对sigsuspend函数调用的语句行)。这个函数的作用是:阻塞在这里,等待一个信号。阻塞时进程是挂起的,不占用CPU时间,只有收到信号才会被唤醒(sigsuspend函数返回),所以其后面的语句才是处理信号的语句。

该函数其实是把一系列操作捏到一起成为一个原子操作,即不能被打断的操作

sigprocmask函数,当时用这个函数屏蔽了一些信号,然后又用该函数取消对信号的屏蔽。但是因为操作系统内部对信号实现的一些原因,可能存在正调用sigprocmask来取消对信号屏蔽操作的时刻,收到一个信号,此时收到的信号就可能会丢失。这是谁都不希望发生的事

所以引人sigsuspend函数,用于在某些特定场合取代sigprocmask。sigsuspend函数把一系列操作捏到一起成为一个原子操作,不能被打断,从而保证了调用sigsuspend函数的时刻收到一个信号,也不会丢失这个信号。实际上,sigsuspend所包含的一系列操作中就包含了对sigprocmask的调用

  • (1)根据给定的参数设置新的mask(信号屏蔽字)并阻塞当前进程。因为在for无限循环执行之前就调用了sigemptyset(&set);,所以sigsuspend(&set);在这一步就相当于取消了所有的信号屏蔽(不阻塞任何信号)。
  • (2)此时一旦收到信号,便恢复原来的信号屏蔽(原来的信号屏蔽指的是ngx_master_processeycle函数开始处屏蔽的那10个信号,也就是调用sigsuspend函数之前,所屏蔽的SIGCHLD、SIGALRM、SIGIO、SIGINT、SIGHUP、SIGUSR1、SIGUSR2、SIGWINCH、SIGTERM和SIGQUIT)。
  • (3)调用该信号对应的信号处理函数(ngx_signal_handler)。
  • (4)信号处理函数返回后,sigsuspend函数才会返回,使程序流程继续往下走

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    printf("执行到sigsuspend()下边来了\n");        ///        printf("master进程休息1秒\n");      //sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......//printf("worker进程休息1秒");       //fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;//sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程,编号为%d,pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

在这里插入图片描述

在这里插入图片描述

sigsuspend函数执行步骤

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

pause函数用法

在这里插入图片描述

master进程定位为管理进程,在for无限循环中,可以使用sigsuspend函数,只让信号来驱动程序执行流程往下走。但是,worker进程是业务处理流程(真正要干活的),不仅是接收信号,还有很多业务(比如收到的一些数据流)都能触发worker进程的流程往下走,所以worker进程不适合用sigsuspend函数来触发

日志输出细节

watch -n 可以实时监视文件大小变化

watch -n 2 ls -lh ./myProj/nginx/logs/error.log

在这里插入图片描述
在这里插入图片描述

printf函数不加“\n”无法及时输出的解释

在使用printf函数输出时,如果在输出的内容中不使用“\n”,则无法及时输出到屏幕。

保存修改后的文件,用make重新编译,输入./nginx命令执行nginx可执行程序。观察结果,等待了好几十秒,发现屏幕上迟迟没有输出结果,然后突然之间,在屏幕上出现一大堆输出结果

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    printf("执行到sigsuspend()下边来了\n");        printf("master进程休息1秒\n");      sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......printf("worker进程休息1秒");       //fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程,编号为%d,pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

在这里插入图片描述

加入fflush

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    printf("执行到sigsuspend()下边来了\n");        printf("master进程休息1秒\n");      sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......printf("worker进程休息1秒");       fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程,编号为%d,pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

在这里插入图片描述

ubuntu@VM-20-6-ubuntu:~$ ps -eo pid,ppid,sid,tty,pgrp,comm,cmd,stat | grep -E 'bash|PID|nginx'

在这里插入图片描述

导致出现上述现象的原因,一般就是行缓存(输出缓冲区)问题。标准输入输出函数(如这里的printf函数)都是带有缓存的,一般是行缓存(Windows操作系统上应该没这个问题,但是类UNIX操作系统上有)。需要输出的数据并不是直接输出到终端上,而是首先缓存到某个地方,当遇到行刷新标志或者该缓存已满的情况下,才会把缓存的数据显示到终端设备(如屏幕)上。

换行符“\n”可以认为是行刷新标志。printf函数没有带“\n”,不会自动刷新输出缓冲区,直至缓存被填满才显示到屏幕上。所以在使用printf的时候,要注意在输出内容的末尾加“\n”。

调用fflush也可以刷新输出缓冲区,将结果及时显示到屏幕上。

保存修改后的文件,用make重新编译,输入./nginx命令执行nginx可执行程序。观察结果,发现这一次printf输出的结果可以立即显示在屏幕上了。

上面这种每次printf输出都调用fflush的方法比较麻烦,也可以使用setvbuf函数直接禁用输出缓冲区,每次执行printf就不会缓存,而是直接输出到屏幕上了。改造一下上述代码,在for无限循环代码之前增加针对setvbuf函数的调用,而后就可以去掉for循环中的fflush调用了

//暂时先放个死循环,我们在这个循环里一直不出来setvbuf(stdout, NULL, _IONBF, 0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......printf("worker进程休息1秒");       // fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程,编号为%d,pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)

在这里插入图片描述

write函数细节点

从文件中读,这事比较简单,多个进程都去读、什么时候去读都应该没问题。但是,如果多个进程同时去写一个文件会怎样呢?

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970//    sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    //    printf("执行到sigsuspend()下边来了\n");        //    printf("master进程休息1秒\n");      //    sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来// setvbuf(stdout, NULL, _IONBF, 0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......// printf("worker进程休息1秒");       // fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;// sleep(1); //休息1秒       //usleep(100000);ngx_log_error_core(0, 0, "good--这是子进程, 编号为%d, pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}//ngx_log_stderr(0,"good--这是子进程,pid为%P",ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

在这里插入图片描述

总结

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

读取数据时也是从磁盘先读到内核缓冲区,然后再从内核缓冲区返回应用程序缓冲区。就算程序员只需要read函数读1字节的数据,操作系统也要读1~4KB的数据(而不是只读1字节)。读取时进行数据缓存也有优点,试想一下,读取一次后,下次再读取时,如果数据已经在内核缓冲区内(称缓存命中),就不用到磁盘上去读了,而是直接从内核缓冲区返回应用程序缓冲区,这个速度就快多了。

从理论上讲,内核可以在任何时候写磁盘,但并不是所有的write操作都会导致内核的写动作。内核会把待写数据暂存在缓冲区,积累到一定数量后再一次性写入磁盘。如果出现意外,如断电、计算机崩溃等,内核还没来得及把内核缓冲区中的数据写入磁盘,这些数据就会丢失。

如果用户空间应用程序发出write系统调用,Linux内核会先进行若干检查,接着将数据复制进内核缓冲区。稍后,内核会在后台收集所有“脏”(有数据写人)缓冲区,将其安排成最佳顺序,接着写进磁盘。这种做法让write(写人)调用的执行快如闪电,几乎立即返回,也让内核可以将实际的磁盘写入操作延后到较空闲的时段再进行,并且多次写入操作会整批一起进行。

数据刚写入内核缓冲区、尚未写回磁盘时,如果收到读取请求,此请求可在内核缓冲区得到满足,系统将不会读取磁盘上的旧数据。这可以实际提高性能。

总之,应用程序即便调用write成功(成功写入数据),也不代表数据已经被真实地写到了磁盘上。

为了确保内核缓冲区中的数据被及时写入磁盘,内核缓冲区中设立了一个时间上限,达到时间上限后,内核会把所有内核缓冲区中的“脏数据”直接写到磁盘上。这些和“脏数据”有关的文件保存在/proc/sys/vm目录中

在这里插入图片描述

在这里插入图片描述

上面这些结果中的单位一般都是厘秒(cs),即1/100s,500代表5s,3000代表30s。

总之,在调用open函数打开文件时,记得使用O_APPEND标记。这样,在多个进程(有父子关系)同时用write来写日志文件时,就能保证内容可以被正确写入

掉电导致write写入的数据丢失破解法

1.直接I/O, 直接访问物理磁盘

直接访问物理磁盘,以避免write写入数据丢失

在这里插入图片描述

在这里插入图片描述

2.open文件时的O_SYNC选项

O_SYNC选项叫作同步选项(把数据直接同步到磁盘),只针对write函数有效,使每次write操作等待物理I/O操作完成。具体就是将写入内核缓冲区的数据立即写入磁盘,将断电、计算机崩溃等意外发生时造成的数据丢失减到最小。也是通过调整open函数的第2个参数来实现的:

在这里插入图片描述

在这里插入图片描述

这种直接向磁盘写数据的效率肯定不高。如果每次写2~4KB数据,效率尚可,但如果每次只写几字节,效率就会非常低,因为磁盘寻道(找写的位置)要花时间;另外虽然只需要写几字节,但系统内部并不仅仅是写几字节,可能是按页或者扇区来写,所以很可能是一次写人512B~4KB数据。

所以,使用O_SYNC标记写数据时要批量写,不要每次写几字节。同理,直接I/O操作写数据时也要批量写。

3.缓存同步

缓存同步是推荐的方法,也是为了解决断电、计算机崩溃时数据丢失的问题,保证缓存数据和写到磁盘上数据的一致性。该方法涉及3个函数:sync、fsync、fdatasync。

  • (1)sync(void)。将所有修改过的块缓冲区排入写队列,然后立即返回,不等待实际写磁盘操作结束。数据是否写人磁盘并没有保障。
  • (2)fsync(int fd)。将fd对应文件的缓冲区立即写入磁盘,并等待实际写磁盘操作结束后返回。可用于数据库这样的应用程序,因为这种应用程序需要确保修改过的数据立即写到磁盘上。
  • (3)fdatasync(int fd)。类似于fsync,但只影响文件的数据部分。除数据外,fsync还会同步更新文件属性(如文件大小、文件访问时间等。文件属性和文件内容是分开存储的,写磁盘会涉及2次寻道)。所以fdatasync比fsync执行速度更快。

在这里插入图片描述

标准I/O库

在这里插入图片描述

在这里插入图片描述

printf属于标准I/O库函数,当用printf输出信息时,常规情况下会输出到缓冲区,当缓冲区满后会通过系统调用(如调用write函数)将信息输出到屏幕上。

fgets函数

在这里插入图片描述

fwrite函数精细解读

在这里插入图片描述

当调用fwrite函数(或其他具备写入功能的标准I/O库函数)时,写人的内容会被放入一个系统的CLib缓冲区中(可以理解成 stdio这个库里面提供的缓冲区)。当CLib缓冲区满之后,会将内容移至内核缓冲区。所以这里相当于在应用程序和内核之间又夹了一层。

在这里插入图片描述
分别有控制流和数据流,因为调用函数(如fwrite)时,相当于一面下达写的命令(控制流),一面提供要写的数据(数据流)。

所以,标准I/O库函数相当于包了一层(用于缓存),最终还是调用底层I/O,也就是系统调用,来实现相关功能。

所谓缓存,其实就是一块内存,用于在输入输出设备和CPU之间临时保存数据,使低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

CLib缓存的存在,常引起一些开发问题,所以还是建议,如果对标准I/O库函数不太熟悉或者说手里没有比较成熟的代码,在写程序时还是优先考虑使用底层的I/O(系统调用)。如果程序员使用了printf函数,但是末尾没有加“\n”,就无法及时看到printf的输出结果(因为printf实际是用到了行缓冲区—CLib缓冲区),这肯定会引起程序员的困惑。

本项目中写日志用的是write系统调用,工作是没问题的,但是如果使用fwrite来写日志,可能父子进程数据就会混乱,因为write是linux系统调用(所有系统调用都是原子性的,由内核来保证),而fwrite是C语言的标准I/O库函数,内部实现是有缓冲区的,此时写日志可能就要用到锁机制。

有一句话叫作“所有系统调用都是原子性的”,这句话来自《Linux/UNIX系统编程手册(上册)》。书中的“文件I/O缓冲”一章,对文件I/O缓存有比较清晰的描述,如果工作需对文件进行频繁的读写操作,建议阅读这一章(其中有一个I/O缓冲小结的图,绘制得挺好,可以参考借鉴)。

守护进程功能的实现

守护进程有个最大的特点:它一运行就在后台,不会占用终端。对于一个要求长期运行的服务器程序,实现这个功能还是非常必要的。

这里要解决的问题是把守护进程功能融入本项目中,以解放终端,甚至实现开机就能够自动启动本项目,因为本项目实现开机自动启动也是非常重要和必要的

nginx.conf

#是注释行,
#每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符;#[开头的表示组信息,也等价于注释行
[Socket]
ListenPort = 5678    
DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g#日志相关
[Log]
#日志文件输出目录和文件名
Log=logs/error.log
# Log=error.log#只打印日志等级<= 数字 的日志到日志文件中 ,日志等级0-8,0级别最高,8级别最低。
LogLevel = 8#进程相关
[Proc]
#创建 这些个 worker进程
WorkerProcesses = 2#是否按守护进程方式运行,1:按守护进程方式运行,0:不按守护进程方式运行
Daemon = 1

ngx_daemon.cxx

//和守护进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>     //errno
#include <sys/stat.h>
#include <fcntl.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"//描述:守护进程初始化
//执行失败:返回-1,   子进程:返回0,父进程:返回1
int ngx_daemon() {//(1)创建守护进程的第一步,fork()一个子进程出来//fork()出来这个子进程才会成为咱们这里的master进程;switch (fork()) {case -1://创建子进程失败ngx_log_error_core(NGX_LOG_EMERG, errno, "ngx_daemon()中fork()失败!");return -1;case 0://子进程,走到这里直接break;break;default://父进程以往 直接退出exit(0);现在希望回到主流程去释放一些资源return 1;  //父进程直接返回1;} //end switch//只有fork()出来的子进程才能走到这个流程ngx_parent = ngx_pid;     //ngx_pid是原来父进程的id,因为这里是子进程,所以子进程的ngx_parent设置为原来父进程的pidngx_pid = getpid();       //当前子进程的id要重新取得//(2)脱离终端,终端关闭,将跟此子进程无关if (setsid() == -1) {ngx_log_error_core(NGX_LOG_EMERG, errno, "ngx_daemon()中setsid()失败!");return -1;}//(3)设置为0,不要让它来限制文件权限,以免引起混乱umask(0); //(4)打开黑洞设备,以读写方式打开int fd = open("/dev/null", O_RDWR);if (fd == -1) {ngx_log_error_core(NGX_LOG_EMERG, errno, "ngx_daemon()中open(\"/dev/null\")失败!");        return -1;}if (dup2(fd, STDIN_FILENO) == -1) //先关闭STDIN_FILENO[这是规矩,已经打开的描述符,动他之前,先close],类似于指针指向null,让/dev/null成为标准输入;{ngx_log_error_core(NGX_LOG_EMERG, errno, "ngx_daemon()中dup2(STDIN)失败!");        return -1;}if (dup2(fd, STDOUT_FILENO) == -1) //再关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出;{ngx_log_error_core(NGX_LOG_EMERG, errno, "ngx_daemon()中dup2(STDOUT)失败!");return -1;}//fd应该是3,这个应该成立if (fd > STDERR_FILENO) {if (close(fd) == -1)  //释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着;{ngx_log_error_core(NGX_LOG_EMERG,errno, "ngx_daemon()中close(fd)失败!");return -1;}}return 0; //子进程返回0
}
  • (1)fork出了一个子进程,核心目的是让这个子进程作为master进程。旧进程需要退出。
  • (2)该函数的返回值分几种情况:①执行失败,返回-1;②子进程,返回0;③父进程,返回1。

nginx.cxx

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>#include "ngx_macro.h"   //各种宏定义
#include "ngx_c_conf.h"  //和配置文件处理相关的类,名字带c_表示和类有关
#include "ngx_func.h"    //各种函数声明
#include "ngx_global.h"//本文件用的函数声明
static void freeresource();//和设置标题有关的全局量
size_t  g_argvneedmem=0;        //保存下这些argv参数所需要的内存大小
size_t  g_envneedmem=0;         //环境变量所占内存大小
int     g_os_argc;              //参数个数 char **g_os_argv;            //原始命令行参数数组,在main中会被赋值
char  *gp_envmem = NULL;        //指向自己分配的env环境变量的内存,在ngx_init_setproctitle()函数中会被分配内存
int     g_daemonized = 0;         //守护进程标记,标记是否启用了守护进程模式,0:未启用,1:启用了int g_environlen = 0;       //环境变量所占内存大小//和进程本身有关的全局量
pid_t ngx_pid;               //当前进程的pid
pid_t ngx_parent;             //父进程的pid
int     ngx_process;            //进程类型,比如master,worker进程等sig_atomic_t  ngx_reap;         
//标记子进程状态变化[一般是子进程发来SIGCHLD信号表示退出],sig_atomic_t:系统定义的类型:访问或改变这些变量需要在计算机的一条指令内完成
//一般等价于int【通常情况下,int类型的变量通常是原子访问的,也可以认为 sig_atomic_t就是int类型的数据】int main(int argc, char *argv[]) {/* code *///测试代码:// printf("argc = %d, argv[0] = %s\n", argc, argv[0]);// ngx_log_stderr(0, "invalid option: %s , %d", "testInfo",326);   int exitcode = 0;           //退出代码,先给0表示正常退出int i;                      //临时用//(1)无伤大雅也不需要释放的放最上边    ngx_pid    = getpid();      //取得进程pidngx_parent = getppid();     //取得父进程的id //统计argv所占的内存g_argvneedmem = 0;for(i = 0; i < argc; i++)  //argv =  ./nginx -a -b -c asdfas{g_argvneedmem += strlen(argv[i]) + 1; //+1是给\0留空间。} //统计环境变量所占的内存。注意判断方法是environ[i]是否为空作为环境变量结束标记for(i = 0; environ[i]; i++) {g_envneedmem += strlen(environ[i]) + 1; //+1是因为末尾有\0,是占实际内存位置的,要算进来} //end forg_os_argc = argc;           //保存参数个数g_os_argv = (char **) argv; //保存参数指针// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }// printf("--------------------------------------------------------");// ngx_init_setproctitle();    //把环境变量搬家// 测试代码// for (int i = 0; environ[i]; i++)// {//     printf("environ[%d]地址=%x    " ,i,(unsigned int)((unsigned long)environ[i]));//     printf("environ[%d]内容=%s\n" ,i,environ[i]);// }//全局量有必要初始化的ngx_log.fd = -1;                  //-1:表示日志文件尚未打开;因为后边ngx_log_stderr要用所以这里先给-1ngx_process = NGX_PROCESS_MASTER; //先标记本进程是master进程ngx_reap = 0;                     //标记子进程没有发生变化//(2)初始化失败,就要直接退出的//配置文件必须最先要,后边初始化啥的都用,所以先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存            {   ngx_log_init();    //初始化日志,否则下行代码无法正常执行ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf");//exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件exitcode = 2; //标记找不到文件goto lblexit;}//---------------------------------------------------------//获取配置文件信息的用法    //int port = p_config->GetIntDefault("ListenPort",0); //0是缺省值//printf("port=%d\n",port);//const char *pDBInfo = p_config->GetString("DBInfo");//if(pDBInfo != NULL)//{//   printf("DBInfo=%s\n",pDBInfo);//}//---------------------------------------------------------//测试内存泄漏可以写如下代码,让程序顺利退出// if(true)// {//    printf("为了检测程序内存泄漏而进行的程序退出,再见!\n");//    return 0;// }//---------------------------------------------------------//printf("argc=%d,argv[0]=%s\n",argc,argv[0]);// strcpy(argv[0], "ce");// strcpy(argv[0],"c2212212121322324323e"); //肯定会覆盖掉相当一部分内存// printf("environ[0]=%s\n" , environ[0]);// printf("environ[1]=%s\n" , environ[1]);// printf("environ[2]=%s\n" , environ[2]);// printf("environ[3]=%s\n" , environ[3]);// printf("environ[4]=%s\n" , environ[4]);//验证argv指向的内存和environ指向的内存紧挨着// for(int i = 0; i < argc; ++i) {        //    printf("argv[%d]地址=%x    ", i, (unsigned int)((unsigned long)argv[i]));//    printf("argv[%d]内容=%s\n", i, argv[i]);// }//下面环境变量随便输出2个// for(int i = 0; i < 2; ++i) {//    printf("evriron[%d]地址=%x    ", i, (unsigned int)((unsigned long)environ[i]));//    printf("evriron[%d]内容=%s\n" , i, environ[i]);// }//要保证所有命令行参数从下面这行代码开始都不再使用,才能调用ngx_setproctitle函数,因为调用后,命令行参数的内容可能会被覆盖掉// ngx_setproctitle("nginx: master process");//myconf();//mysignal();//(3)一些必须事先准备好的资源,先初始化ngx_log_init();             //日志初始化(创建/打开日志文件),这个需要配置项,所以必须放配置文件载入的后边;//(4)一些初始化函数,准备放这里        if(ngx_init_signals() != 0) //信号初始化{exitcode = 1;goto lblexit;}    //(5)一些不好归类的其他类别的代码,准备放这里ngx_init_setproctitle();    //把环境变量搬家//------------------------------------//(6)创建守护进程//读配置文件,拿到配置文件中是否按守护进程方式启动的选项if(p_config->GetIntDefault("Daemon", 0) == 1) {//1:按守护进程方式运行int cdaemonresult = ngx_daemon();if(cdaemonresult == -1) //fork()失败{exitcode = 1;    //标记失败goto lblexit;}if(cdaemonresult == 1){//这是原始的父进程freeresource();   //只有进程退出了才goto到 lblexit,用于提醒用户进程退出了//而我现在这个情况属于正常fork()守护进程后的正常退出,不应该跑到lblexit()去执行,因为那里有一条打印语句标记整个进程的退出,这里不该显示该条打印语句exitcode = 0;return exitcode;  //整个进程直接在这里退出}//走到这里,成功创建了守护进程并且这里已经是fork()出来的进程,现在这个进程做了master进程//守护进程标记,标记是否启用了守护进程模式,0:未启用,1:启用了g_daemonized = 1;}//(7)开始正式的主工作流程,主流程一致在下边这个函数里循环,暂时不会走下来,资源释放啥的日后再慢慢完善和考虑    ngx_master_process_cycle(); //不管父进程还是子进程,正常工作期间都在这个函数里循环;//ngx_log_stderr调用演示代码:// ngx_log_stderr(0, "invalid option: %s, %d", "testInfo", 326);// ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"// ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格// ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0// ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000// ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00// ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68e// ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E// ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326 (15: Block device required) // ngx_log_stderr(0, "invalid option: %d", 1678);         //nginx: invalid option: 1678//测试ngx_log_error_core函数的调用// ngx_log_error_core(5, 8, "这个XXX工作的有问题, 显示的结果=%s", "YYYY");// for(;;)// {//    sleep(1); //休息1秒//    printf("休息1秒\n");// }lblexit://(5)该释放的资源要释放掉//先显示信息,下一行才关闭所有资源,顺序不能错,否则本行信息无法输出了ngx_log_stderr(0,"程序退出,再见了!"); freeresource();  //一系列的main返回前的释放动作函数//printf("程序退出,再见!\n");    return exitcode;
}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】
void freeresource() {//(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放if(gp_envmem) {delete []gp_envmem;gp_envmem = NULL;}//(2)关闭日志文件if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1) {        close(ngx_log.fd); //不用判断结果了ngx_log.fd = -1; //标记下,防止被再次close    }
}

在这里插入图片描述

保存修改后的文件,用make重新编译,输入./nginx命令执行nginx可执行程序。
输人“./nginx”执行nginx可执行程序后,顺利出现了提示符“$”,在提示符下还可以执行其他命令(如ls)。据此可以判断,守护进程应该是创建成功了

在这里插入图片描述

在这里插入图片描述

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);// ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970//sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    //    printf("执行到sigsuspend()下边来了\n");        ngx_log_stderr(0, "haha--这是父进程, pid为%P", ngx_pid);//    printf("master进程休息1秒\n");      sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来// setvbuf(stdout, NULL, _IONBF, 0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......// printf("worker进程休息1秒");       // fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;sleep(1); //休息1秒       //usleep(100000);// ngx_log_error_core(0, 0, "good--这是子进程, 编号为%d, pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}ngx_log_stderr(0, "good--这是子进程, 编号为%d, pid为%P", inum, ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

ngx_process_cycle.cxx

//和开启子进程相关#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   //信号相关头文件 
#include <errno.h>    //errno
#include <unistd.h>#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"
#include "ngx_global.h"//函数声明
static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);//变量声明
static u_char master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle() {    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集//下列这些信号在执行本函数期间不希望收到 (保护不希望由信号中断的代码临界区)//建议fork()子进程时学习这种写法,防止信号的干扰;sigaddset(&set, SIGCHLD);     //子进程状态改变sigaddset(&set, SIGALRM);     //定时器超时sigaddset(&set, SIGIO);       //异步I/Osigaddset(&set, SIGINT);      //终端中断符sigaddset(&set, SIGHUP);      //连接断开sigaddset(&set, SIGUSR1);     //用户定义信号sigaddset(&set, SIGUSR2);     //用户定义信号sigaddset(&set, SIGWINCH);    //终端窗口大小改变sigaddset(&set, SIGTERM);     //终止sigaddset(&set, SIGQUIT);     //终端退出符//.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......//设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集{        ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_master_process_cycle()中sigprocmask()失败!");}//即便sigprocmask失败,程序流程 也继续往下走//首先设置主进程标题---------beginsize_t size;int i;size = sizeof(master_process);  //注意这里用的是sizeof,所以字符串末尾的\0是被计算进来了的size += g_argvneedmem;          //argv参数长度加进来    if(size < 1000) //长度小于这个,才设置标题{char title[1000] = {0};strcpy(title, (const char *)master_process); //"master process"strcat(title, " ");  //跟一个空格分开一些,清晰    //"master process "for (i = 0; i < g_os_argc; i++)         //"master process ./nginx"{strcat(title, g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题}//首先设置主进程标题---------end//从配置文件中读取要创建的worker进程数量CConfig *p_config = CConfig::GetInstance(); //单例类//从配置文件中得到要创建的worker进程数量int workprocess = p_config->GetIntDefault("WorkerProcesses", 1); ngx_start_worker_processes(workprocess);  //这里要创建worker子进程//创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号//sigaddset(&set, SIGHUP); //-1//setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for ( ;; ) {//    usleep(100000);// ngx_log_error_core(0, 0, "haha--这是父进程, pid为 %P", ngx_pid);//a)根据给定的参数设置新的mask 并阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】//c)调用该信号对应的信号处理函数//d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走//printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    //    printf("执行到sigsuspend()下边来了\n");        // ngx_log_stderr(0, "haha--这是父进程, pid为%P", ngx_pid);//    printf("master进程休息1秒\n");      sleep(1); //休息1秒        //以后扩充.......}// end for(;;)return;
}//描述:根据给定的参数创建指定数量的子进程,因为以后可能要扩展功能,增加参数,所以单独写成一个函数
//threadnums:要创建的子进程数量
static void ngx_start_worker_processes(int threadnums) {int i;//master进程在走这个循环,来创建若干个子进程for (i = 0; i < threadnums; i++) {ngx_spawn_process(i, "worker process");} //end forreturn;
}//描述:产生一个子进程
//inum:进程编号【0开始】
//pprocname:子进程名字"worker process"
static int ngx_spawn_process(int inum, const char *pprocname) {pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_spawn_process()fork()产生子进程num=%d, procname=\"%s\"失败!", inum, pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum, pprocname);    //希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走        break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
//     子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum, const char *pprocname) {//重新为子进程设置进程名,不要与父进程重复------ngx_worker_process_init(inum);ngx_setproctitle(pprocname); //设置标题   //暂时先放个死循环,我们在这个循环里一直不出来// setvbuf(stdout, NULL, _IONBF, 0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。for(;;) {//先sleep一下 以后扩充.......// printf("worker进程休息1秒");       // fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上,则printf里的东西会立即输出;sleep(1); //休息1秒       //usleep(100000);// ngx_log_error_core(0, 0, "good--这是子进程, 编号为%d, pid为%P!", inum, ngx_pid);//printf("1212");//if(inum == 1)//{//ngx_log_stderr(0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid); //printf("good--这是子进程,编号为%d,pid为%d\r\n",inum,ngx_pid);//ngx_log_error_core(0,0,"good--这是子进程,编号为%d",inum,ngx_pid);//printf("我的测试哈inum=%d",inum++);//fflush(stdout);//}// ngx_log_stderr(0, "good--这是子进程, 编号为%d, pid为%P", inum, ngx_pid); //ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P",inum,ngx_pid);} //end for(;;)return;
}//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum) {sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT, errno, "ngx_worker_process_init()中sigprocmask()失败!");}//....将来再扩充代码//....return;
}

信号处理函数的进一步完善(避免僵尸子进程)

曾经提到过“僵尸进程”的概念。使用kill-9命令来杀死一个worker进程后,再次使用ps命令列出进程,这个worker进程并没有消失,而是变成了僵尸进程

在这里插入图片描述

sig_atomic_t类型

在这里插入图片描述

siginfo_t 结构

ubuntu@VM-20-6-ubuntu:~$ man sigaction

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

errno

在这里插入图片描述

WTERMSIG 和 WEXITSTATUS

在这里插入图片描述
之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!


http://www.mrgr.cn/news/52782.html

相关文章:

  • 【网络】IP协议的地址管理
  • Air780E的固件的远程升级,一文讲清
  • 第十七章 Java多线程--线程池-ScheduledThreadPoolExecutor
  • 基于深度学习的在线小分子Kinome选择性预测平台的Python实现-更加的详细版本
  • Linux入门:进程状态,优先级与进程调度
  • Redis持久化与淘汰机制深度解析
  • 互联网新赛道 互联网转型 互联网前景
  • ICM20948 DMP代码详解(85)
  • 2024.10月17日- 关于Vue2-(2)
  • 集成电路学习:什么是WLAN无线局域网
  • Linux操作系统分析实验-文件操作,实验三
  • 图计算框架之Giraph
  • 向左,向右,都是人生
  • 非常漂亮html公告弹窗代码
  • 常用晶闸管移向触发集成电路
  • MySQL的事务隔离
  • 05 熵编码
  • ORDER BY 排序
  • 采用全差分放大器的差分输入至差分输出电路
  • ES6面试题:(第一天)