TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
: x( }- W; s" [ `1 `# x问题引入:
3 ~! L, j' I& a( }# @& `问题是我们重装了一台电脑之后,发现apache
. O7 k: u6 G; }# }的日志里突然增加了很多服务时间超长的请求,如下面两条:
9 P( R: B% a: j9 Q** - - 4913229 11227 [29/Mar/2012:09:59:58 +0800] "GET*****
4 w% P$ N# u `+ @" G" I3 K7 k** - - 4793564 9846 [29/Mar/2012:09:59:58 +0800] "GET *****
/ \* n) n& h8 H# D9 g$ P! h请求服务时间都在4s以上!这绝对无法忍受啊!
" ^, v/ E6 D/ R# p9 L9 s " {* d& e+ j& G& m+ J3 E8 F V6 K
分析过程:% y0 A* |) l( F- V9 V2 n+ Z3 _" g
1 ~+ I/ d2 `! r* U% f h
但是,当我们:7 s: A. {) ` o# E! Y3 E: n% B$ a
* 手动访问这台服务器之后,发现基本上请求都是正常响应的;8 B# g! i a" |! h% | p% e x
* 从前端反向代理的日志来看,服务时间也是正常的;
, o1 Q! Y, A; s8 B: w1 M- R那这样推断,真相只有一个,那就是apache的日志时间记录有问题!
! e* b6 m1 E1 I ' P4 y0 t5 n# ?3 b% q7 b1 e
什么问题呢?不清楚,但是apache
' L ~0 ~$ E9 {) x, ~* [* b的时间记录不外乎是在收到请求时记个时间,请求处理完成之后再记一个时间,既然是时?
* L( F" K( M- I0 e4 q$ L涞奈侍猓敲淳涂纯聪低呈奔浒桑幼罴虻サ?date 开始,多执行几次:1 B! y* E- j. J( g$ p
apache2 # date1 r- G: n/ F) P5 ^
Wed Mar 28 17:25:08 CST 2012
* X' V8 V+ E# b# q2 n6 capache2 # date m, b& M5 ~& K5 e' H# n ]3 Z
Wed Mar 28 17:25:04 CST 2012 时间减少了?4 |* M: y. r5 L& |% t$ E
apache2 # date
j, r6 f5 X4 }2 S' G0 O! V; gWed Mar 28 17:25:06 CST 2012
: ?' N0 a" V) japache2 # date3 B+ `$ G7 j* S1 q/ S
Wed Mar 28 17:25:12 CST 2012# n/ d0 U2 o8 q8 x# Z0 r
apache2 # date% T! T; P* W# C+ _- K, N- X y
Wed Mar 28 17:25:13 CST 2012* s0 b" Q1 S$ e4 e
apache2 # date
" E& m( t. f! ?. K: sWed Mar 28 17:25:09 CST 2012 时间减少了?
$ m) L/ e8 [- sapache2 # date# a( r9 n8 m5 U- T: d& q/ v
Wed Mar 28 17:25:09 CST 20129 }2 H- t4 t# l+ r% g' W
apache2 # date
! i9 Q' {0 y6 Q' R7 G A! [Wed Mar 28 17:25:15 CST 2012+ }" w/ K, [- j6 w! Q9 _
那么第一个怀疑就是系统里有进程在自己不断的调时间,比如ntpdate, ntpd
7 x, b+ h3 ?& @; U& o+ q,那么我们首先把 ntpd给停掉;然后系统也没有任何地方会调用ntpdate;
; k1 y1 m0 t+ p继续观察,发现停掉ntpd后,现象依旧!$ {2 |8 N" H* s( @8 O* @: M+ p
还有什么可能呢? date
n- F: k6 @/ x! n程序是从内核里面读取时间还是说需要读取硬件时间?到底是怎么计算的呢?4 ~$ ~/ |: K1 s! W m
请教下同事们,据说 sys_time 是读的kernel里的时间,而 sys_gettimeofday! P# q0 `2 g4 @3 _+ `- Q( [' i+ m
是需要读硬件时钟的。
3 x0 `: i' X6 y7 |' y+ e那么就测试一把吧:
6 H& r5 z& I, n- J先写个程序调用 time(), 然后打印结果,结果是这样的:
6 Z/ G5 x# \( b. M2 G9 Q/ ]1332986690
Z" G( W* Q3 G9 Y6 Y/ }0 }0 C1332986692
+ Z! H. g/ Q3 h, q% L+ {+ M# Y1332986693
$ W: D l# [, H3 \. h+ p1332986693
# U% Q% } ]6 I: W! |# [+ j1332986694
7 N4 T& J' Z4 Z+ ^' b1332986694
2 n% j$ ^' Q6 {- d0 T, Z1332986695
# s2 j1 K1 W4 N1 h) k6 u+ u13329866959 c- t: H/ I# R8 e2 Z
1332986696) v. Z; {( {+ p2 M! u M# v2 E
1332986696
/ O! U3 L" L. m) ^1332986697, e) X8 Q. ^3 y( V2 k
1332986697" W( G" w2 p( M9 z
1332986698" O; ]& ]! F: r( \
1332986698' p5 i* P s3 ], [" a
1332986699) e1 Q+ a) _8 u2 W; C
确实没有问题了!看来有可能是硬件问题了,再看看clock_gettime 的调用:
( K2 ?2 L( r4 m5 E/ a{, B& J( u( e5 A& ]: O0 X8 Z
struct timespec ts;
8 S- Q& n$ N' ~ int ret = 0; : H" O% v* x$ _3 B" o8 I
ret = clock_gettime(CLOCK_REALTIME, ts); 2 ?7 E& _8 U% a G
printf("ret: %d, time: %d %d", ts.tv_sec, ts.tv_ns
3 ~* O' w* `/ o) v3 E: K}
0 b( L E* ^" t) g编译: gcc -g -o clock_gettime getdate.c -lrt
0 U' R; S2 |8 o, s: T3 E ; K0 G8 A8 A: S3 C* u t& p
~ # ./clock_gettime
- X) I' N' x8 N4 d1 R# ?# ^ret: 1332991087, time: 594076000 -1
, M9 N3 X& u0 o5 S5 r) |: F$ Kret: 1332991088, time: 379009000 -1
& U4 n& ^0 w0 K/ e3 P& N+ F" _; nret: 1332991089, time: 68561000 -1
5 ?& {3 N5 b! U2 B4 X& Gret: 1332991089, time: 714013000 -1
6 ?/ u8 ?$ I" Cret: 1332991086, time: 511250000 -1 时间在跳
& E0 Q( ]2 @1 U' i e$ \ret: 1332991091, time: 945626000 -1
% y" P' O, M, [ret: 1332991092, time: 650479000 -1$ V9 ^" k3 l+ A( W/ E {
ret: 1332991088, time: 734780000 -1, `- ]5 U8 J8 p( Z$ J, E4 L* h. o" Y
ret: 1332991094, time: 506114000 -1时间在跳
& i( ^9 G' S% Q3 Z9 I8 H看来果然有可能是硬件问题!
# i. C. A. U& \+ X' i0 C3 T那么,检查一下,直接调用一下 hwclock --show ,看看是不是可以验证这个问题:3 @# _+ F+ p3 P1 m: i
~ # hwclock --show 3 O; ?) e+ z( ^
Thu 29 Mar 2012 10:22:21 AM CST -0.948923 seconds
- R1 h) i, h+ |9 R3 n; H1 _ ~ # hwclock --show 8 z( g* S. U3 M) Y ~
Thu 29 Mar 2012 10:22:22 AM CST -0.188950 seconds
9 @+ t I4 w4 X, I, {, { ~ # hwclock --show
; q4 G: ]2 O1 g" k3 t% F& ZThu 29 Mar 2012 10:22:23 AM CST -0.244766 seconds
+ P+ J0 \* q& V( m7 K ~ # hwclock --show 7 K3 n1 r- a9 |2 F7 O
Thu 29 Mar 2012 10:22:24 AM CST -0.336868 seconds % x( n7 ~0 w: r- W9 m
~ # hwclock --show 1 W$ G# Z. d$ B0 \/ I/ X
Thu 29 Mar 2012 10:22:25 AM CST 4.237159 seconds 0 C1 F) _) H# ]) r1 q4 V& x
~ # hwclock --show
/ y6 U/ v8 l7 ^5 XThu 29 Mar 2012 10:22:26 AM CST 4.238672 seconds " f, i7 o& P# l8 M% B1 j. J
~ # hwclock --show 0 u; V9 x8 I# }0 q
Thu 29 Mar 2012 10:22:27 AM CST -0.379418 seconds # Z( W6 ^( o! Y* J5 L: C
时间居然都是正确的!显然问题没有那么简单。。。那到底是怎么回事??还是从date
5 b2 Z/ [% n# Z5 N" N: k& \! V开始查起吧:
3 O. m j3 ~. V0 Adate是一个glibc里面的程序,那它的时间源是从哪里来的?查看一下glibc
0 S. R, \+ K8 f7 i% b2 Y6 K+ c. m" h的源码,它的时间从这里来:
/ [1 ?, ^* h+ V2 S// glibc coreutils gettime.c! t8 G7 q- T4 I, t7 I
/* Get the system time into *TS. */
; p+ t0 t: s0 h5 p! [void
4 }% J" W# `5 a2 p: Y$ J2 d9 _gettime (struct timespec *ts)
$ s$ e7 E- W( O) v4 `+ C! G{
: t- ]6 \( W' }+ w' `#if HAVE_NANOTIME P8 F1 h0 K0 l1 Z' e& }
nanotime (ts);- p/ M& J/ K+ j! h
#else
" ^4 P. l9 u! s% U4 ] / y- [& d- S' H( ~' x& C
# if defined CLOCK_REALTIME && HAVE_CLOCK_GETTIME
Z) y: ^ C0 {' w1 G if (clock_gettime (CLOCK_REALTIME, ts) == 0)0 f' K( F( e; U% A. C; m
return;* y: ], I' h& W0 l4 Q
# endif
8 w( ?" T" C0 g7 \6 i6 x 9 H' J! M1 j0 s2 `- p$ S7 S% A4 v
# if HAVE_GETTIMEOFDAY8 q! r$ }8 m8 r w0 v
{
6 l: Z- P: m. D1 D7 k) O; j struct timeval tv;
- N& \' T5 q3 n4 [7 E" C gettimeofday (&tv, NULL);
! n& j0 P. @% e" G! Z0 e+ E ts->tv_sec = tv.tv_sec;
c5 ~3 M, V, I+ B ts->tv_nsec = tv.tv_usec * 1000;
. B* i- B; n5 Q" A }
5 w$ F! l$ y( s; t' P! ~# else
/ S& {8 @. ]: T$ b- H& G& o3 Y/ H ts->tv_sec = time (NULL);
$ F1 S* b( z& Y0 b, C0 F0 G ts->tv_nsec = 0;3 d, ]* ?. O- b: Z0 d; T' q
# endif
; }+ J7 O* i$ N4 F) U! C( D( @
2 b- d! |' V6 l" C8 T/ x#endif9 _3 X. V5 p& c8 o% p
}/ _3 d- u" H" o
按照 nanotime, clock_gettime, gettimeofday的顺序去读取,我们这个系统中就是 ; H" Y/ c/ E, u& D0 E4 V! D
clock_gettime,从上面的小程序中也可以看到clock_gettime的时间是有问题的,那么
9 W6 r, Z7 H9 s# kclock_gettime的时间源是怎么来的呢?* O E! {8 V+ j% B
// linux kernel 2.6.18 arch/x86_64/kernel/time.c
) N$ k& X) i z8 v* X# `$ Svoid do_gettimeofday(struct timeval *tv) N6 g$ w' }6 K0 [/ A) I
{' v. v" J; h% ]7 d. g9 d
unsigned long seq, t;6 z+ n3 Z$ h, ~1 c* b% o0 N
unsigned int sec, usec;
* u4 r4 g& U, w5 E7 v" [
# X: t( `' v6 g) b9 Tdo {* n" c' H( d& e) m$ ^# @
seq = read_seqbegin(&xtime_lock);
* C. V9 F0 K" @( k6 [ A : q$ ?+ N' C. H: J9 D4 R2 p- [
sec = xtime.tv_sec;
3 F. p) W* w4 O+ ^. B* `, \9 v3 J4 t. l8 xusec = xtime.tv_nsec / NSEC_PER_USEC;. I: S f1 J( n# ~3 T
t = (jiffies - wall_jiffies) * USEC_PER_TICK +
5 Q+ D4 n, u$ A3 A7 T$ }: l- [do_gettimeoffset();
3 B$ J+ ]5 ?2 w G( B. M; D8 k4 {usec += t;' Z; }4 z# k! @! Q5 ?" F3 z/ f
+ y5 r: W6 w* N1 i4 a
} while (read_seqretry(&xtime_lock, seq));# H6 o: D8 [& c$ ]3 L
; T! \- E; t; X: @* ]1 ~tv->tv_sec = sec + usec / USEC_PER_SEC;
" u5 I6 p+ F. m4 H. L3 E' L+ Ttv->tv_usec = usec % USEC_PER_SEC;
, c" A6 r/ B1 S: z. _" z}+ D" y! ]6 r6 U, S2 |2 I2 |
反正就是一通计算,其中比较重要的是 do_gettimeoffset9 I" |/ ^" R* j& n! G5 O: g$ a) f
,这个函数的目的是计算上一次更新 xtime
( ^) F- w) }# q5 G/ N/ N% s7 S到当前执行这一段时间的偏移量,然后用这个偏移量去加到 xtime
: g9 M& Q* o% k0 l$ d! g0 g上去,就是当前的时间(因为xtime的精度不够,clock_gettime
. h9 p5 ~( [6 X) |的精度要求更高,所以必须计算一个偏移量)。
0 d8 X$ X( {3 A ~那么,这个 do_gettimeoffset又是从哪来的?同样的文件里面有:6 ?0 L, p; k4 @5 Q' j6 h3 P
static inline unsigned int do_gettimeoffset_tsc(void)
+ N/ F2 T, a! M2 o7 T! B: Z: `{
1 k$ N0 o( H" G0 n1 v* i; m- wunsigned long t;
$ g5 z0 ~& C" w1 R. u% S8 Uunsigned long x;3 N% ~5 W. s- V% _9 h
t = get_cycles_sync();1 `2 A; Q( M$ Y$ g/ ~3 k
if (t < vxtime.last_tsc) 0 O6 Q+ M5 l+ P ]( W, S* ~7 s7 a
t = vxtime.last_tsc; /* hack */
( t5 |* T* W5 g1 E0 [x = ((t - vxtime.last_tsc) * vxtime.tsc_quot) >> US_SCALE;
2 k2 d P- n0 r) Lreturn x;
' _1 u2 ^1 ]+ v2 X" [" }}% H& h R7 \5 L1 B6 b0 j4 w
6 Q v, [% Z" U- O) Z' r! X2 _: H- h# Fstatic inline unsigned int do_gettimeoffset_hpet(void)
8 z) H( i# @0 d# ^{
' e) I+ {$ L! C* H, e/* cap counter read to one tick to avoid inconsistencies */
5 |1 K' w- M4 c8 ]: P* g) Punsigned long counter = hpet_readl(HPET_COUNTER) - vxtime.last;
4 N, |/ j1 c& f5 o& B$ c7 h2 e$ _return (min(counter,hpet_tick) * vxtime.quot) >> US_SCALE;+ q2 N) x0 P6 J$ E t7 l+ p
}
) w/ v- E# [+ x
6 E) R( {6 N8 M$ cunsigned int (*do_gettimeoffset)(void) = do_gettimeoffset_tsc;
+ w% C7 ^8 L# A: V% U* o% r5 U也就是说,go_gettimeoffset 是从 tsc寄存器从拿到的,这又是神马玩意呢?
" ~3 K6 R; Z4 }% d) d+ a1 k参考一下下面的几篇文章【1】【2】【3】,基本就明白了:
4 n6 I1 a9 k' r# {tsc是一个cpu内的寄存器,用来累加从系统启动到目前,所有的时钟周期数,全称是 - j. e0 R" J8 z: q
Timestamp Counter;
) F# P5 O* y2 h" c而且,最重要的是我们发现了一个重要的线索,那就是:# ^1 m r* D% l& @# _, {/ P
在多核cpu的架构中,tsc寄存器是很有可能不一致的,特别是在intel的比较老的多核CPU" p) p& P7 `$ M. [- c
中!坑爹....; C, Q9 }- d' K5 Y; U5 N
很多文章提到过这个问题,比如【1】【2】【4】,显然如果不同cpu
9 l& D( B W- G. e5 i读到的值不同的话,多次执行,显然时间是不一致的。
& E3 g; q: l3 a m6 y看起来就是这个问题了,那么就来验证一把:( ]' M) Q7 g/ S: T# _& a
#include<stdlib.h>
( Y$ C" ]" x+ ^0 V#include<stdio.h>: G& F ^; `# ?" N0 u) I7 @$ F
#include<sys/types.h>
8 D/ \- W5 o! Y; }4 r" I' J) d#include<sys/sysinfo.h>
$ I; h: {! W: X+ v$ h9 G#include<unistd.h>
5 o3 `' w0 x' x' v2 X#include<time.h>. Q- P. n$ Y& g, Q; t) a% x$ b
6 O2 [; Q( K$ r8 \1 i$ B. k G#define __USE_GNU
; p/ e& I4 `9 d# \! p#include<sched.h>( d4 |! d8 I+ q/ g/ T3 `6 w
#include<ctype.h>7 H' ~- X2 |/ H+ B7 o
#include<string.h>3 A( q) T# k0 X. ~, Z9 i3 m& B# I
4 g6 j* l! N& J9 u, O! Q1 w! i $ p. W% Q# y0 z
r; [ i9 P. }6 _" l& k
int main(int argc, char **argv){
8 L1 F& \3 X5 L: y6 A
$ M1 F$ y5 {( s3 U7 L cpu_set_t cs;
! z9 L$ v+ s1 [6 q, A1 [ CPU_ZERO(&cs);: y, O; M* k# ~) u W8 @) i- ?
CPU_SET(0, &cs);8 }) C2 V7 c: `5 S5 J7 {9 d& c2 |
# k2 D" C3 f0 t: P! c2 l# j4 ]
sched_setaffinity(0, sizeof(cpu_set_t), &cs);8 j- _8 `! E Q w+ ?- g
0 T; p8 Q. _1 l' h) c: ]: f struct timespec ts;9 X- k. }6 m1 J( Y% x
int ret = 0;
% A0 a9 {* C1 W" `8 m1 V2 @ ret = clock_gettime(CLOCK_REALTIME, &ts); g! V3 Z. f5 x$ B# o
printf("ret: %d, time: %lld %lld", ret, ts.tv_sec, ts.tv_nsec);, A3 b- L" h c, B& a
* l6 y8 z$ Q+ K+ e* d) A& g
}
+ I& G7 B3 i( y" ?$ h1 K6 I
7 H z7 v+ K/ E j ^- X, \" r; Bgcc -g -o cpu0_clock_gettime cpu_getdate.c -lrt. D4 w4 u3 s o# b/ G0 ?
, V$ Q. b- i$ u5 L8 Jret: 0, time: 1332992815 537481000
: r6 Y+ E3 O7 }% j7 hret: 0, time: 1332992816 506704000
/ ~ q2 }# ~8 T1 H6 wret: 0, time: 1332992817 99100005 `# X8 I& r0 F* f ^2 b' m
ret: 0, time: 1332992817 480431000
7 n: \. [- e7 z) X( {! r9 {ret: 0, time: 1332992817 936965000# i! u, s! D' ~4 }$ M! M6 o5 T4 Q0 k
ret: 0, time: 1332992818 416634000
4 T4 _* w* i2 Y/ V2 h' y; mret: 0, time: 1332992818 8503190008 v+ D5 a9 B# `4 x3 ~$ _) a1 y
ret: 0, time: 1332992819 3050750001 w D/ o I+ y1 J' M9 e. j
ret: 0, time: 1332992819 7291080002 \" F/ N, s* [
ret: 0, time: 1332992820 164726000
3 v, d# O9 {' ^5 U: sret: 0, time: 1332992820 577573000 C, d& T; Z2 e6 \
ret: 0, time: 1332992820 985025000
1 b/ f/ A/ Q' D* O z& Uret: 0, time: 1332992821 394070000; ], p' U. _/ H( y. ]3 U3 G
ret: 0, time: 1332992821 816708000) n7 i4 T# G4 |
ret: 0, time: 1332992822 216811000
$ L3 ]! V8 m7 z4 |0 e% \7 C ]ret: 0, time: 1332992822 616901000
+ ]6 G0 l7 M6 f5 r/ D( Gret: 0, time: 1332992823 18564000w1 |. |& c7 h4 u$ t5 R& R1 E' s1 ~0 _
ret: 0, time: 1332992823 727585000. j: {2 t7 j8 q& J" O) a" f
通过把程序的调度固定在某一个具体的cpu中,那么clock_gettime的结果也是正确的!
0 h. J. U7 n: I% F/ L6 g- ~3 ~经过几次测试,很容易找到两个cpu时间不一致的情况,具体这个例子来说就是 cpu0和
; _5 U: W5 U D$ R8 K6 H% bcpu5,把上面的程序修改一下,可以看到结果基本上每次都会跳一把。
0 `# X% N/ S+ T! I. E; L/ ]) L问题解决:% S6 h4 X* ] i
那么,tsc在多核cpu中的值不同,应该怎么解决呢? 如 【4】所示,在高版本的linux
; m' a+ i, f+ A6 X0 b& y内核中,有人提交了补丁,可以在内核启动时检查并且同步一把,那么升级一下内核到 2" h/ y, \* l9 ]* m" i: j
.6.38。
6 c) s! Q& L( T# i重新启动到新内核之后,问题解决!
4 K) T* n8 @- W( u总结一下:
+ d, e( d3 i: r1. apache的时间统计方式,没具体看实现,但是显然前后的两个时间可能是在不同的cpu
' }8 F) r1 U, L( D! `5 n% A s核上计算出来的。4 u3 p! K" q- X& m
2. tsc的值,并不能保证是同步的,特别是在某些Intel cpu" K" Y M9 M) O; F' N8 g
的情况下;但是新的内核会做一些检查和resync的工作;
5 B( I, v5 I/ @# p- d1 Q1 P3. time()调用直接读取内核的xtime值,不会触及硬件;但是 gettimeofday(),
6 }: q! n1 W7 v0 w1 B* c2 Dclock_gettime() 在 x86_64下都会读取tsc值;1 i. J7 t9 T4 s5 w: V# i% z
4. hwclock读的硬件时钟显然不是 tsc,应该是实时时钟RTC;
6 G W% W/ ^) Z【1】 http://en.wikipedia.org/wiki/Time_Stamp_Counter
8 I7 f q: b9 I' ^* c/ w0 u" p3 L. F- w【2】 http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html
- y* |" I2 b" N+ P. o: E【3】http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=driver&Number=
8 U- {. R/ L* d6 }# z8 _% Z385219&page=0&view=collapsed&sb=5&o=all&fpart , linux时钟中断机制,Copyright % O& i8 d T$ m; `5 ^6 e- ?6 o
2003 by 詹荣开
% s' m0 {0 ~* ?# M【4】http://lwn.net/Articles/211051/ x86: unify/rewrite SMP TSC sync code |
|