【nginx流程分析之时间和并发控制】

继承上一篇 nginx流程分析,首先进行分析nginx的ngx_time_init,然后先看在代码中的位置
在这里插入图片描述
好的我们接下来慢慢分析

ngx_time_init

我们先来看看这个具体方法,
在这里插入图片描述
这个方法前面都是比较好理解的,分别初始化ngx_cached_err_log_time,ngx_cached_http_time,ngx_cached_http_log_time,ngx_cached_http_log_iso8601和ngx_cached_syslog_time的长度,当然这些也是这些日志的格式。
然后就是初始化了ngx_cached_time,取的是cached_time的第一个地址,然后看一下cached_time 的定义,在这里插入图片描述
然后NGX_TIME_SLOTS是64,说明cached_time是一个大小为64的数组,类型为ngx_time_t。
然后我们接下来看ngx_time_update这个方法

ngx_time_update

接下来我们来看这个方法,然后因为这个方法比较长我们一段一段的来看,先看一部分:
在这里插入图片描述
首先是定义了几个变量,这些都是时间的一些变量,然后就是ngx_trylock这个方法。我们来看一下。

开始加锁 ngx_trylock

加锁,其实不严谨,因为是原子操作,ngx_trylock,这个其实就是c中的原子操作 ,我们来看一下实现:
在这里插入图片描述
然后再看看ngx_atomic_cmp_set这个方法的实现,
在这里插入图片描述
可以看出来一个原子操作,关键字atomic,然后我们再看一下ngx_trylock(&ngx_time_lock))中
ngx_time_lock这个变量,
在这里插入图片描述
在这里插入图片描述
可以看到 关键字volatile进行了修饰,这个说明对于ngx_time_lock这个变量,强制系统每次都是内存中读取最新的值,还不会去寄存器中或者cpu缓存中去读取,同时不会受到编译器优化的影响,关于c语言的volatile,推荐文章C语言再学习 – 关键字volatile ,其实会有代码说明。

综上所述,ngx_trylock方法同时配合原子操作和volatile,判断是否有其他线程在操作这个方法。

ngx_gettimeofday

接下来继续看,就是ngx_gettimeofday,点进去看一下具体的实现:
在这里插入图片描述

可以看到ngx_gettimeofday这个c语言中自带的gettimeofday方法,这个方法的具体作用就是获取当前的时间存到了类型为timeval 名称为 tv的结构体中,然后我们来看一下timeval这个结构体定义:

 struct timeval {  

  long tv_sec; // 秒数  

     long tv_usec; //微秒数  

} 

就是把当前时间存在了这个结构体中,然后接下来,就是把当前的时间的秒存在了sec这个变量中,然后通过将微秒数除以一千得到了毫秒数。

ngx_monotonic_time

然后就是ngx_monotonic_time这个方法,首先还是来看一下定义

static ngx_msec_t
ngx_monotonic_time(time_t sec, ngx_uint_t msec)
{
#if (NGX_HAVE_CLOCK_MONOTONIC)
    struct timespec  ts;

#if defined(CLOCK_MONOTONIC_FAST)
    clock_gettime(CLOCK_MONOTONIC_FAST, &ts);

#elif defined(CLOCK_MONOTONIC_COARSE)
    clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);

#else
    clock_gettime(CLOCK_MONOTONIC, &ts);
#endif

    sec = ts.tv_sec;
    msec = ts.tv_nsec / 1000000;

#endif

    return (ngx_msec_t) sec * 1000 + msec;
}

然后还是来分析一下,我们把宏去掉其实就是下面的代码

static ngx_msec_t
ngx_monotonic_time(time_t sec, ngx_uint_t msec)
{
    struct timespec  ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    sec = ts.tv_sec;
    msec = ts.tv_nsec / 1000000;
    return (ngx_msec_t) sec * 1000 + msec;
}

然后我们看一下clock_gettime(CLOCK_MONOTONIC, &ts)这个方法,这个方法其实就是获取当前系统启动这一刻起开始计时的时间,并且不受到用户设置的影响,其实作用和cat /proc/uptime一样,看一下我们当前系统,
在这里插入图片描述
然后将值存到了

struct timespec
{
    time_t tv_sec; /* 秒*/
    long tv_nsec; /* 纳秒*/
};

然后就是将tv_nsec除以1000000,得到了当前毫秒数加上秒数,然后再进行了返回并且存到了ngx_current_msec这个变量中。

初始化 slot

然后就是初始化slot,然后一下代码

    if (slot == NGX_TIME_SLOTS - 1) {
        slot = 0;
    } else {
        slot++;
    }

    tp = &cached_time[slot];

    tp->sec = sec;
    tp->msec = msec;

可以看到,首先是判断slot是不是已经和当前最大值一样了,如果不是那么就进行+1操作,这个也就是意味着每次都会对slot其实是不断更新的一个过程。然后就是把缓存的时间存到了cached_time这个数组里面。

ngx_gmtime

首先看一下这个方法具体操作:
在这里插入图片描述
虽然nginx没有给出注释,我们也可以看出,是获取时间然后存到了gmt,然后gmt这个类型是ngx_tm_t,然后我们看一下ngx_tm_t的类型定义,

struct tm {
	int	tm_sec;		/* seconds after the minute [0-60] */
	int	tm_min;		/* minutes after the hour [0-59] */
	int	tm_hour;	/* hours since midnight [0-23] */
	int	tm_mday;	/* day of the month [1-31] */
	int	tm_mon;		/* months since January [0-11] */
	int	tm_year;	/* years since 1900 */
	int	tm_wday;	/* days since Sunday [0-6] */
	int	tm_yday;	/* days since January 1 [0-365] */
	int	tm_isdst;	/* Daylight Savings Time flag */
	long	tm_gmtoff;	/* offset from UTC in seconds */
	char	*tm_zone;	/* timezone abbreviation */
};

这个结构体注释的还是很好的,可以看出来,分别给出了时分秒,日期,月,年等等。
当然我来看看一下这个方法

void
ngx_gmtime(time_t t, ngx_tm_t *tp)
{
    ngx_int_t   yday;
    ngx_uint_t  sec, min, hour, mday, mon, year, wday, days, leap;

    /* the calculation is valid for positive time_t only */

    if (t < 0) {
        t = 0;
    }

    days = t / 86400;
    sec = t % 86400;

    /*
     * no more than 4 year digits supported,
     * truncate to December 31, 9999, 23:59:59
     */

    if (days > 2932896) {
        days = 2932896;
        sec = 86399;
    }

    /* January 1, 1970 was Thursday */

    wday = (4 + days) % 7;

    hour = sec / 3600;
    sec %= 3600;
    min = sec / 60;
    sec %= 60;

    /*
     * the algorithm based on Gauss' formula,
     * see src/core/ngx_parse_time.c
     */

    /* days since March 1, 1 BC */
    days = days - (31 + 28) + 719527;

    /*
     * The "days" should be adjusted to 1 only, however, some March 1st's go
     * to previous year, so we adjust them to 2.  This causes also shift of the
     * last February days to next year, but we catch the case when "yday"
     * becomes negative.
     */

    year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1);

    yday = days - (365 * year + year / 4 - year / 100 + year / 400);

    if (yday < 0) {
        leap = (year % 4 == 0) && (year % 100 || (year % 400 == 0));
        yday = 365 + leap + yday;
        year--;
    }

    /*
     * The empirical formula that maps "yday" to month.
     * There are at least 10 variants, some of them are:
     *     mon = (yday + 31) * 15 / 459
     *     mon = (yday + 31) * 17 / 520
     *     mon = (yday + 31) * 20 / 612
     */

    mon = (yday + 31) * 10 / 306;

    /* the Gauss' formula that evaluates days before the month */

    mday = yday - (367 * mon / 12 - 30) + 1;

    if (yday >= 306) {

        year++;
        mon -= 10;

        /*
         * there is no "yday" in Win32 SYSTEMTIME
         *
         * yday -= 306;
         */

    } else {

        mon += 2;

        /*
         * there is no "yday" in Win32 SYSTEMTIME
         *
         * yday += 31 + 28 + leap;
         */
    }

    tp->ngx_tm_sec = (ngx_tm_sec_t) sec;
    tp->ngx_tm_min = (ngx_tm_min_t) min;
    tp->ngx_tm_hour = (ngx_tm_hour_t) hour;
    tp->ngx_tm_mday = (ngx_tm_mday_t) mday;
    tp->ngx_tm_mon = (ngx_tm_mon_t) mon;
    tp->ngx_tm_year = (ngx_tm_year_t) year;
    tp->ngx_tm_wday = (ngx_tm_wday_t) wday;
}

当然这个方法有点长,我们看核心就好了,最后就是把时分秒等等参数塞到了tp这个结构体中。

将时间写入

上面时间分析完成,接下来就是将时间写入,我们先看一下代码:在这里插入图片描述
我们先看一下cached_http_time这个结构体,

static u_char            cached_http_time[NGX_TIME_SLOTS]
                                    [sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];

可以看出来这个是一个二维数组,并且值是u_char的类型。这个二维数组,第一维是64,第二位是sizeof(“Mon, 28 Sep 1970 06:00:00 GMT”)就是30,然后是一个字节,其实就是这个二位数组就等于是一维数组,然后第二维是连续的30个字节的内存。
然后看一下ngx_sprintf这个方法

u_char * ngx_cdecl
ngx_sprintf(u_char *buf, const char *fmt, ...)
{
    u_char   *p;
    va_list   args;

    va_start(args, fmt);
    p = ngx_vslprintf(buf, (void *) -1, fmt, args);
    va_end(args);

    return p;
}

然后ngx_vslprintf其实和c语言中的vsnprintf的使用是一样的,但是估计写nginx的大佬觉得vsnprintf性能不够好,或者为了定制化的需求,自己单独写了一下。因为比较多我截了个图,简单说一下,有兴趣的同学可以了解一下vsnprintf哈:
在这里插入图片描述
其实一个一个判断,进行填充。我们看一下p0的输出这个值:Sat, 05 Mar 2022 13:05:50 GMT
其实和上面的Mon, 28 Sep 1970 06:00:00 GMT,类型是一样的,不得不感慨,nginx真的一个字节都要省。

然后我们看一下接下来的p1,p2,p3,p4的操作
在这里插入图片描述
其实道理是一样的,这里就是重复描述了。

ngx_memory_barrier

可以看到这里面出现了ngx_memory_barrier,然后看一下定义是OSMemoryBarrier().这里面解释一下,OSMemoryBarrier这个从名称上面可以看出来内存屏障的意思,接触过go和java的gc原理的同学会有一些熟悉,其实就是保证前面的指令编辑器在编译的时候,不会乱序执行,因为在编译器在编译代码的时候,往往不会按照程序员写的逻辑去执行,往往会有一些优化,在单线程的逻辑中可能没问题,但是在多线程的过程可能就有问题了。而内存屏障往往和多线程中,原子操作结合在一起使用。
内存屏障是一种屏障和指令类,可以让CPU或编译器强制将barrier之前和之后的内存操作分开。CPU采用了一些可能导致乱序执行的性能优化。在单个线程的执行中,内存操作的顺序一般是悄无声息的,但是在并发编程和设备驱动程序中就可能出现一些不可预知的行为,除非我们小心地去控制。排序约束的特性是依赖于硬件的,并由架构的内存顺序模型来定义。一些架构定义了多种barrier来执行不同的顺序约束。

概括一下,OSMemoryBarrier()函数就是用来设置内存屏障,然后保证cpu每次不对读取cpu的缓存,每次都会从内存中读取,并且,保证OSMemoryBarrier()前面和后面的代码在编译器在编译的时候,是真正分开的,不会乱序执行

赋值

然后再看看,最后的实现:
在这里插入图片描述
其实这里就是进行了赋值,没有什么特殊的

释放锁 ngx_unlock

我们来看一下实现

#define ngx_unlock(lock)    *(lock) = 0

其实就是把lock这个变量赋值成0,这样别的进程就可以调用了,至此,分析结束了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值