文章目录

  • 1.设置时区
  • 2.日志打印
  • 3.main函数中代码执行顺序

1.设置时区

我们要设置成 CST 时区,以保证正确地显示日期、时间。

我们常看到的时区有如下几个:

  • PST:美国太平洋标准时间,PST = GMT - 8
  • GMT:格林尼治平均时间,等同于英国伦敦本地时间
  • UTC:通用协调时间,UTC = GMT
  • CST:北京时间,北京时区是东八区,领先 UTC 八个小时

设置时区的命令如下:

tzselect --> 4 --> 9 --> 1 --> 1

将产生的 Asia/Shanghai 文件拷贝到 /etc/localtime 目录下:

sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

2.日志打印

ngx_macro.h:

// 各种#define宏定义相关的定义放这里#ifndef __NGX_MACRO_H__
#define __NGX_MACRO_H__#define NGX_MAX_ERROR_STR 2048 // 显示的错误信息最大数组长度// 简单功能函数--------------------
// ngx_cpymem类似于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)// 日志相关--------------------
// 我们把日志一共分成8个等级,级别从高到低,数字最小的级别最高,数字最大的级别最低,以方便管理、显示、过滤等等
#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

ngx_log.cxx:

// 和日志相关的函数放这里#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <errno.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;/*
函数功能:
通过可变参数组合出字符串(支持...省略号形参),自动往字符串最末尾增加换行符(所以调用者不用加\n),往标准错误上输出这个字符串。参数含义:
err:如果err不为0,表示有错误,会将该错误编号以及对应的错误信息一并放到组合出的字符串中一起显示;
fmt:格式化字符串。调用格式:
ngx_log_stderr(0, "invalid option: %d", 1678);                    // nginx: invalid option: 1678
ngx_log_stderr(0, "invalid option: %10d", 1678);                  // nginx: invalid option:       1678
ngx_log_stderr(0, "invalid option: %010d", 21);                   // nginx: invalid option: 0000000021
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(0, "invalid option: %.6f", 21.378);                // nginx: invalid option: 21.378000
ngx_log_stderr(0, "invalid option: %.6f", 12.999);                // nginx: invalid option: 12.999000
ngx_log_stderr(0, "invalid option: %10.6f", 12.999);              // nginx: invalid option:         12.999000
ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);             // nginx: invalid option: "./nginx"
ngx_log_stderr(0, "invalid option: \"%s\", %d", "testinfo", 123); // nginx: invalid option: "testinfo", 123
ngx_log_stderr(15, "invalid option: %s, %d", "testInfo", 326);    // nginx: invalid option: testInfo, 326 (15: Block device required)
*/
void ngx_log_stderr(int err, const char* fmt, ...)
{va_list args; // 创建一个va_list类型变量u_char errstr[NGX_MAX_ERROR_STR + 1]; // 2048u_char *p, *last;memset(errstr, 0, sizeof(errstr)); // 这块有必要加,至少在va_end处理之前有必要,否则字符串没有结束标记是不行的last = errstr + NGX_MAX_ERROR_STR;// last作为一个标记,防止输出内容超过这么长。// last指向整个buffer最后去了,即指向最后一个有效位置的后面,也就是非有效位,所以才在上边errstr[NGX_MAX_ERROR_STR + 1]给加了1p = ngx_cpymem(errstr, "nginx: ", 7); // p指向"nginx: "的后面va_start(args, fmt); // 使args指向起始的参数p = ngx_vslprintf(p, last, fmt, args); // 组合出这个字符串保存在errstr里va_end(args); // 释放argsif (err) // 如果错误代码不是0,表示有错误发生{// 错误代码和错误信息也要显示出来p = ngx_log_errno(p, last, err);}// 若位置不够,那换行也要硬插入到末尾,哪怕覆盖到其他内容if (p >= (last - 1)){p = (last - 1) - 1; // last-1是最后一个有效的内存,而这个位置要保存\0,再减1,这个位置才适合保存\n}*p++ = '\n'; // 增加个换行符    // 往标准错误(一般是屏幕)输出信息write(STDERR_FILENO, errstr, p - errstr);return;
}/*
函数功能:
给一段内存和一个错误编号,组合出一个字符串放到给的这段内存中去,形如" (错误编号: 错误原因) "。参数含义:
buf:是个内存,要往这里保存数据
last:放的数据不要超过这里
err:错误编号,我们是要取得这个错误编号对应的错误字符串,保存到buffer中
*/
u_char* ngx_log_errno(u_char* buf, u_char* last, int err)
{char* perrorinfo = strerror(err); // strerror函数的作用就是将错误码给转化成错误信息,并返回一个指向错误消息字符串的指针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。
如果有其他错误,则会直接重定向到标准错误,此时日志就打印到屏幕上。参数含义:
level:一个等级数字,我们把日志分成8个等级,以方便管理、显示、过滤等等,如果这个等级数字比配置文件中的等级数字"LogLevel"大,那么该条信息不被写到日志文件中
err:是个错误代码,如果不是0,就应该转换成显示对应的错误信息,一起写到日志文件中调用格式:
ngx_log_error_core(5, 8, "这个XXX工作的有问题,显示的结果是=%s", "YYYY");
2022/10/26 19:58:53 [warn] 1305: 这个XXX工作的有问题,显示的结果是=YYYY (8: Exec format error)
*/
void ngx_log_error_core(int level, int err, const char* fmt, ...)
{u_char* last;u_char errstr[NGX_MAX_ERROR_STR + 1];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); // localtime_r函数把第一个参数的time_t转换为本地时间,保存到第二个参数中去,带_r的是线程安全的版本,尽量使用tm.tm_mon++; // 月份要调整下才正常tm.tm_year += 1900; // 年份要调整下才正常// 先组合出一个当前时间字符串,格式形如"2019/01/08 19:57:11"// 若用一个u_char*接一个(u_char*)-1,则得到的结果是0xffffffff...,这个值足够大u_char strcurrtime[40] = {0};ngx_slprintf(strcurrtime, (u_char *)-1, "%4d/%02d/%02d %02d:%02d:%02d", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);// 日期增加进来,得到形如"2019/01/08 20:26:07"p = ngx_cpymem(errstr, strcurrtime, strlen((const char *)strcurrtime));// 日志级别增加进来,得到形如"2019/01/08 20:26:07 [crit] "p = ngx_slprintf(p, last, " [%s] ", err_levels[level]);// 进程id增加进来,支持%P格式,得到形如"2019/01/08 20:50:15 [crit] 2037: "p = ngx_slprintf(p, last, "%P: ", ngx_pid);va_start(args, fmt); // 使args指向起始的参数p = ngx_vslprintf(p, last, fmt, args); // 把fmt和args参数弄进去,组合出来这个字符串va_end(args); // 释放argsif (err) // 如果错误代码不是0,表示有错误发生{// 错误代码和错误信息也要显示出来p = ngx_log_errno(p, last, err);}// 若位置不够,那换行也要硬插入到末尾,哪怕覆盖到其他内容if (p >= (last - 1)){p = (last - 1) - 1; // 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;}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目录需要提前建立出来}// 这里将缺省日志等级设置为NGX_LOG_NOTICE(6),如果读失败,就给缺省日志等级ngx_log.log_level = p_config->GetIntDefault("LogLevel", NGX_LOG_NOTICE);// O_WRONLY:以只写的方式打开// O_APPEND:追加到末尾// O_CREAT:文件不存在则创建// 6:110,用户:可读 可写 不可执行// 4:100,用户所在组:可读 不可写 不可执行// 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;
}

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);/*
函数功能:
该函数只不过针对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); // 释放argsreturn p;
}/*
函数功能:
对于nginx自定义的数据结构进行标准格式化输出,就像printf、vprintf一样。
例如,给进来一个("abc=%d", 13),最终buf里得到的应该是"abc=13"这种结果参数含义:
buf:往这里放数据
last:放的数据不要超过这里
fmt:格式化字符串支持的格式:
数字:%d、%ud、%xd、%Xd、%10d、%010d
字符串:%s
浮点数:%f、%.6f、%10.6f
pid_t:%P调用格式:
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)
{// 临时用到的一些变量u_char zero;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位后的内容while (*fmt && buf < last) // 每次处理一个字符,处理的是"invalid option: \"%s\", %d"中的字符{if (*fmt == '%') // 以%开头的一般都是需要被可变参数取代的{// 变量初始化// *和++优先级相同,结合性都是从右到左// 对于*++fmt来说,先求的是++fmt,并且前缀++是先加后用,因此*++fmt等价于*(++fmt),即fmt先往后走一个字节位置,然后再判断该位置的内容zero  = (u_char) ((*++fmt == '0') ? '0' : ' ');// 判断%后边接的是否是个'0',如果是,则zero='0',否则zero=' '// 例如ngx_log_stderr(0, "数字是%010d", 12),这里的zero便是前面的填充字符width = 0; // 格式字符%后边如果是个数字,这个数字最终会弄到width里边来,这东西目前只对数字格式有效,比如%d、%f这种sign = 1; // 显示的是否是有符号数,这里给1,表示是有符号数,除非你用%u,这个u表示无符号数hex = 0; // 是否以十六进制形式显示(比如显示一些地址),0代表不是;1代表是,并以小写字母a-f显示;2代表是,并以大写字母A-F显示frac_width = 0; // 小数点后几位数字,一般需要和%.10f配合使用,这里10就是frac_widthi64 = 0; // 一般%d对应的可变参中的实际数字会保存在这里ui64 = 0; // 一般%ud对应的可变参中的实际数字会保存在这里// 这个while就是判断%后边是否是个数字,如果是个数字,就把这个数字取出来// 比如%16d,最终这个循环就能够把16取出来,弄到width里边去while (*fmt >= '0' && *fmt <= '9') // 如果%后边接的字符是'0'~'9'之间的内容,比如%16这种   {// 第一次,width = 1;第二次,width = 16;所以,整个width = 16width = width * 10 + (*fmt++ - '0');}for (;;) // 一些特殊的格式,我们做一些特殊的标记(给一些变量赋特殊值等等){switch (*fmt)   // 处理一些%之后的特殊字符{case 'u':       // %u,这个u表示无符号sign = 0;   // 标记这是个无符号数fmt++;      // 往后走一个字符continue;   // 回到for继续判断case 'X':       // %X,X表示十六进制,并且以大写字母A-F显示,不要单独使用,一般是%Xdhex = 2;    // 标记以大写字母A-F显示十六进制sign = 0;fmt++;continue;   // 回到for继续判断case 'x':       // %x,x表示十六进制,并且以小写字母a-f显示,不要单独使用,一般是%xdhex = 1;    // 标记以小写字母a-f显示十六进制sign = 0;fmt++;continue;   // 回到for继续判断case '.':       // 其后边必须跟个数字,必须与%f配合使用,比如%.10f,表示转换浮点数时小数点后必须保证10位数字,不足10位则用0来填补fmt++;      // 往后走一个字符,后边这个字符肯定是'0'~'9'之间while (*fmt >= '0' && *fmt <= '9') // 如果是数字,一直循环,这个循环最终就能把诸如%.10f中的10提取出来{frac_width = frac_width * 10 + (*fmt++ - '0'); }break;default:break;}break;}switch (*fmt){case '%': // 只有%%时才会遇到这个情形,其本意是打印一个%*buf++ = '%';fmt++;continue;  // 回到外层的while继续执行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++;}fmt++;continue; // 回到外层的while继续执行case 'P': // 转换一个pid_t类型i64 = (int64_t) va_arg(args, pid_t);sign = 1;break; // 这break掉,直接跳到switch后边的代码去执行,凡是这种break的,都不做fmt++,因为switch后仍旧需要进一步处理case 'f': // 一般用于显示浮点类型数据,如果要显示小数部分,则要形如%.5ff = va_arg(args, double); // va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变参数的类型if (f < 0) // 负数的处理{*buf++ = '-'; // 单独搞个负号出来f = -f; // 那么这里的f应该是正数了}ui64 = (int64_t) f; // 走到这里,保证f肯定大于等于0(不为负数),正整数部分给到ui64里frac = 0;if (frac_width) // 如果要求小数点后显示多少位小数,形如%.2f,那么frac_width就会是这里的2{scale = 1; // 缩放从1开始for (n = frac_width; n; n--) {scale *= 10; // 这里可能溢出哦}// 接下来把小数部分取出来,比如%.2f,对应的参数是12.537,那么取得的就是小数点后的2位四舍五入,也就是54// (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)// 即frac为54frac = (uint64_t) ((f - (double) ui64) * scale + 0.5);// 接下来判断进位,比如%.2f,对应的参数是12.999// (uint64_t) (0.999 * 100 + 0.5)// (uint64_t) (99.9 + 0.5)// (uint64_t) (100.4)// 即frac为100,而此时的scale也为100,两者正好相等if (frac == scale){ui64++; // 正整数部分进位frac = 0; // 小数部分归0}}buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width); // 将正整数部分显示出来,不够的前边填充zero字符if (frac_width) // 指定了显示多少位小数{if (buf < last){*buf++ = '.'; // 因为指定显示多少位小数,先把小数点增加进来}buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width); // frac是保留好位数的小数部分,直接将其显示出来即可,不需要填充字符}fmt++;continue;  // 回到外层的while继续执行default: // 其他格式日后逐步完善*buf++ = *fmt++; // 往下移动一个字符continue; // 注意这里不break,而是continue,回到外层的while继续执行}// 格式为%d和%P的会走下来,其他格式日后逐步完善// 统一把显示的数字都保存到ui64里去if (sign) // 显示的是有符号数{if (i64 < 0){*buf++ = '-'; // 小于0,自然要把负号先显示出来ui64 = (uint64_t) -i64; // 变成无符号数(正数)}else // 显示正数{ui64 = (uint64_t) i64;}}// ngx_sprintf_num()函数把ui64弄到buf中显示,如果不足width位,则前边会填充zero字符,其中第5个参数hex表示是否以十六进制显示buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);fmt++;}else // 当成正常字符,源fmt拷贝到目标buf里{// 把fmt当前指向的字符赋给buf当前指向的位置,然后buf往前走一个字符位置,fmt往前走一个字符位置*buf++ = *fmt++;// *和++优先级相同,结合性都是从右到左// 对于*fmt++来说,先求的是fmt++,但后缀++是先用后加,因此*fmt++等价于*(fmt++),同理*buf++等价于*(buf++)}}return buf;
}/*
函数功能:
以一个指定的宽度width把一个数字ui64显示在buf对应的内存中,如果实际显示的数字位数比指定的宽度要小,则在前面填充zero字符。
当然,如果不指定宽度(参数width=0),则按实际宽度显示。
如果给进来一个%Xd、%xd之类的,还能以十六进制数字格式显示出来。参数含义:
buf:往这里放数据
last:放的数据不要超过这里
ui64:显示的数字
zero:一般显示的数字位数不足要求的,则在前面用这个字符填充。格式字符%后边接的如果是个'0',则zero='0',否则zero=' '。
hexadecimal:是否显示成十六进制数字
width:格式化字符%后接的如果是个数字,比如%16,那么width=16,即width是希望显示的宽度值。如果实际显示的内容不够,则前面用zero字符填充。
*/
static u_char* ngx_sprintf_num(u_char* buf, u_char* last, uint64_t ui64, u_char zero, uintptr_t hexadecimal, uintptr_t width)
{// #define NGX_INT64_LEN (sizeof("-9223372036854775808") - 1)// 注意这里是sizeof,是包括末尾\0的,不是strlen,故NGX_INT64_LEN=20u_char *p, temp[NGX_INT64_LEN + 1]; // temp[21]size_t len;uint32_t ui32;static u_char hex[] = "0123456789abcdef"; // 和%xd格式符有关,以小写字母a-f显示十六进制static u_char HEX[] = "0123456789ABCDEF"; // 和%Xd格式符有关,以大写字母A-F显示十六进制p = temp + NGX_INT64_LEN; // NGX_INT64_LEN=20,所以p指向的是temp[20]那个位置,也就是数组最后一个元素位置if (hexadecimal == 0){if (ui64 <= (uint64_t) NGX_MAX_UINT32_VALUE) // NGX_MAX_UINT32_VALUE是最大的32位无符号数,对应的十进制是4294967295{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);}}else if (hexadecimal == 1) // 和%xd格式符有关,以小写字母a-f显示十六进制{// 比如想要显示一个十进制数 1234567,其对应的二进制数是 12d687,那么这个 12d687 是怎么显示出来的呢?// 0xf 就是二进制的 1111,一个数和 0xf 按位与,相当于把一个数的最末尾的4个二进制位拿出来// ui64 & 0xf 能分别得到7,8,6,13,2,1这些数字,转成uint32_t,然后以这些数字为hex数组的下标,找到这些数字对应的能够显示的字符7,8,6,d,2,1do{*--p = hex[(uint32_t) (ui64 & 0xf)];} while (ui64 >>= 4);// ui64 >>= 4 即 ui64 = ui64 >> 4,上面把最末尾的4个二进制位拿出来之后,这里就右移4位// 右移4位就是除以16,相当于把该十六进制数的最末尾一位干掉// 1234567(0x12D687) / 16 = 77160(0x12D68)// 77160(0x12D68) / 16 = 4822(0x12D6)// 如此反复,最终肯定有等于0时导致while不成立退出循环}else // hexadecimal == 2,和%Xd格式符有关,以大写字母A-F显示十六进制{// 参考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){*buf++ = zero; // 如果不足width位,则前边会填充zero字符}len = (temp + NGX_INT64_LEN) - p; // 因为上边的while循环改变了len的值,所以这里还原len,也就是要显示的数字的实际宽度// 接下来把实际的数字比如"7654321"添加到buf里if ((buf + len) >= last) // 如果buf剩余的空间不够拷贝整个数字{len = last - buf; // 那么buf剩余多少就拷贝多少}return ngx_cpymem(buf, p, len); //把最新buf返回去;
}

3.main函数中代码执行顺序

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" // 各种函数声明// 本文件用的函数声明
static void freeresource();// 和设置标题有关的全局量
char** g_os_argv; // 原始命令行参数数组,在main中会被赋值
char* gp_envmem = NULL; // 指向自己分配的env环境变量的内存,在ngx_init_setproctitle()函数中会被分配内存
int g_environlen = 0; // 环境变量所占内存大小// 和进程本身有关的全局量
pid_t ngx_pid; // 当前进程的pidint main(int argc, char* const* argv)
{int exitcode = 0; // 退出代码,先给0表示正常退出// (1) 无伤大雅也不需要释放的放最上边ngx_pid = getpid(); // 取得进程pidg_os_argv = (char **) argv; // 保存参数指针// (2) 初始化失败,就要直接退出的// 配置文件必须最先要,后边初始化啥的都用,所以先把配置读出来,供后续使用 CConfig* p_config = CConfig::GetInstance(); // 单例类if (p_config->Load("nginx.conf") == false) // 把配置文件内容载入到内存{ngx_log_stderr(0, "配置文件[%s]载入失败,退出!", "nginx.conf");exitcode = 2; // 标记找不到文件goto lblexit;}// (3) 一些初始化函数,准备放这里ngx_log_init(); // 日志初始化(创建/打开日志文件)// (4) 一些不好归类的其他类别的代码,准备放这里ngx_init_setproctitle(); // 把环境变量搬家for (;;){sleep(1);printf("休息1秒\n");}lblexit:// (5) 该释放的资源要释放掉freeresource(); // 一系列的main返回前的释放动作函数printf("程序退出,再见!\n");return exitcode;
}// 专门在程序执行末尾释放资源的函数
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}
}

日志打印、main函数中代码执行顺序相关推荐

  1. 【Linux 内核 内存管理】RCU 机制 ④ ( RCU 模式下更新链表项 list_replace_rcu 函数 | 链表操作时使用 smp_wmb() 函数保证代码执行顺序 )

    文章目录 一.RCU 模式下更新链表项 list_replace_rcu 函数 二.链表操作时使用 smp_wmb() 函数保证代码执行顺序 一.RCU 模式下更新链表项 list_replace_r ...

  2. html中代码执行顺序

    在html中代码从上而下执行,从head到body到body以下,包括外部引用的js,所有调用元素必须在它加载完成后调用,同时window.onload中内容在整个html加载完成后加载. 转载于:h ...

  3. Java中static代码块,main函数,构造函数运行顺序

    1.Java中static代码块,main函数,构造函数运行顺序如下: 答:static代码块是主动执行的,因此static代码块先执行,然后是执行构造函数,最后是Main函数. 如下例子: publ ...

  4. java main spring_分享通过在java main函数中执行spring的代码

    脱离tomcat容器在单独的java application的main函数中初始化spring Main.java package com.zuidaima.test; import org.spri ...

  5. [C语言]指针之数组逆序函数:编写函数invert,将数组中的n个整数按相反顺序存放,要求用指针变量作为函数形参,并用指针的方法遍历该数组。在main函数中输入n个整数,存入数组a中;然后调用上述函

    编写函数invert,将数组中的n个整数按相反顺序存放,要求用指针变量作为函数形参,并用指针的方法遍历该数组. 在main函数中输入n个整数,存入数组a中:然后调用上述函数处理数组a,最后逐个输出数组 ...

  6. Linux-C基础知识学习:C语言作业-将5个学生成绩保存在一个数组中,单独实现一个计算平均成绩的average函数, 在main函数中获取该函数返回的平均值,并打印。

    Linux基础知识学习 C语言作业:将5个学生成绩保存在一个数组中,单独实现一个计算平均成绩的average函数, 在main函数中获取该函数返回的平均值,并打印. #include <stdi ...

  7. Java代码执行顺序

    Java代码执行顺序 1. Java初步认知 2. Javayun.java例子 3. 反编译Javayun.class文件 4. 分析Javayun_dxdump文件 5. 再来一个网上的例子Jav ...

  8. java 执行顺序_Java代码执行顺序

    程序中代码执行的顺序非常重要,稍有不慎便会是程序运行出错,那么我将结合实例来分析代码中的执行. 名词解释 首先了解几个名词: 非静态代码块 直接由 { } 包起来的代码,称为非静态代码块 静态代码块 ...

  9. Node.js 异步编程(附几个小练习题学会分析代码执行顺序)

    1. 同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前 ...

最新文章

  1. 曲线数学NURBS之bezier曲线
  2. 遮掩java_关于java中的覆写、重载、隐藏、遮掩、遮蔽
  3. Mac系统git clone 慢【解决方案】
  4. php索引数组转键数组,php索引数组和关联数组
  5. 神策数据丨九大行业数字化经营指南集锦,值 100% 收藏
  6. ASP.NET MVC 4 (十) 模型验证
  7. 寄存器分配图着色_富士苹果促进着色技术八大要点!是时候看看了!
  8. DATAGUARD 参数配置
  9. ? php 为啥报错,如何解决js里面的php代码报错问题
  10. java中1和1.0_在Java中如何以0.1f增量在0.1f和1.0f之间进行迭代?
  11. 算法:递归-八皇后问题(回溯算法)
  12. 〔转〕Word域的应用和详解2_等式和公式域
  13. 华表 java_华表基础(转)
  14. 目标决定人生,制定属于你自己的目标
  15. 使用Javascript实现Dropdownlist级联操作中遇到的两个错误
  16. 微信开发者工具无法选择预览和真机调试_小程序开发 第二篇:使用微信小程序开发者工具、wepy框架初始化项目...
  17. 简单网页版的年会抽奖程序,设计个界面套上就可以了,抽奖员工编号姓名改改代码就可以了,很简单的
  18. unity 阿拉伯文显示
  19. php制作万年历的步骤_PHP制作万年历
  20. 狄利克雷卷积学习记录

热门文章

  1. pm2 for linux
  2. Google登录授权详细过程
  3. enter不换行 wps_wps表格按enter键不能换行解决方法教程
  4. 19年拿了19个诺贝尔奖,日本科学为何“井喷”?
  5. 沁透著一片悠然禪意 《掬水》
  6. AE/PR插件AI智能背景抠像颜色键控GoodbyeGreenscreenzxb V1.6.0官方版
  7. “千山之首 大果榛品”2022年辽阳大果榛子地理标志标识推介会开幕
  8. 微信公众号封面一键生成器-续
  9. Mac系统升级中途断电/关机,升级/开机失败的恢复方法
  10. JAVA练习10-累加数