linux如何计算延迟

2016-07-27 10:31:51 u010487568 阅读数 5427

Linux内核时钟中断设施

linux的时钟中断需要两个全局变量,分别是xtime与jiffies。

1、xtime

一个timeval结构类型变量,是从cmos电路中取得的时间,一般是从某一历史时刻开始到现在的时间,也就是为了取得我们操作系统上显示的日期。这个就是“实时时钟”,它的精确度是微秒。获取方式是通过sys/time.h头文件里面的gettimeofday函数获取。

2、HZ

Linux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来保存每一秒有几次timer interrupts。如HZ为1000,代表每秒有1000次timer interrupts。 HZ可在编译核心时设定,可设定100、250、300或1000。核心版本预设值为250。
HZ这个值可以理解为操作系统的时钟频率,这个时钟的精度远低于硬件的时钟频率。如HZ设置为250,那么为一秒钟250次,每次为4ms,因此操作系统的时钟精度只能达到4ms。

3、Tick

Tick是HZ的倒数,意即timer interrupt每发生一次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。

4、Jiffies

<linux/jiffies.h>,定义了Jiffies(unsigned long),在linux内核中jiffies远比xtime重要。每发生一次timer interrupt,Jiffies变数会被加一。一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就是HZ。
在 Linux 2.6 中,系统时钟每 1 毫秒中断一次(时钟频率,用 HZ 宏表示,定义为 1000,即每秒中断 1000 次,2.4 中定义为 100,很多应用程序也仍然沿用 100 的时钟频率),这个时间单位称为一个 jiffie。jiffies 与绝对时间之间的转换, 用两个宏来完成两种时间单位的互换:JIFFIES_TO_NS()、NS_TO_JIFFIES()
jiffies是记录着从电脑开机到现在总共的时钟中断次数。在linux内核中jiffies远比xtime重要,那么他取决于系统的频率,单位是Hz。
这是硬件给内核提供一个系统定时器用以计算和管理时间,连续累加一年四个多月后就会溢出(假定HZ=100,1个jiffies等于1/100秒,jiffies可记录的最大秒数为 (2^32 -1)/100=42949672.95秒,约合497天或1.38年),即当取值到达最大值时继续加1,就变为了0。
因此为防止溢出需要使用这些宏函数进行:time_before()、 time_after()、time_after_eq()、time_before_eq()。因为jiffies随时钟滴答变化,不能用编译器优化 它,应取volatile值。
另外,80x86架构定义一个与jiffies相关的变数jiffies_64 ,w为64位,要等到此变数溢位可能要好几百万年。jiffies被对应至jiffies_64最低的32位元。因此,经由jiffies_64可以完全不理会溢位的问题便能取得jiffies。

5、RTC

除了系统定时器外,还有一个与时间有关的时钟:实时时钟(RTC),这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时,内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,这是RTC最主要的作用。

延迟函数问题

1、sleep

Linux下的精度为秒,是精确的,实现原理如下:
1. 注册一个信号signal(SIGALRM,handler)。接收内核给出的一个信号。
2. 调用alarm()函数。
3. pause()挂起进程。

alarm是当前进程的私有定时闹钟,pause函数挂起当前进程,内核切换到其他进程运行。当alarm设置的时间到时,内核通过SIGALRM信号去处理,pause函数返回后进程继续执行。

2、usleep

精度为微妙,但实际并不能精确到微妙。通过前面给出的操作系统时钟基础设置,系统精度由HZ系统时钟频率决定,一般设置为250时精度只能到4ms,因此usleep的微妙延迟只能是至少延迟的时间,时间上操作系统进行内核到用户的切换就会花10-30ms级别的时间,因此短时间的延迟一般都会大于设定值。
另外usleep有以下的问题
1. 在一些平台下不是线程安全,如HP-UX以及Linux usleep()会影响信号;
2. 在很多平台,如HP-UX以及某些Linux下,参数的值必须小于1 * 1000 * 1000也就是1秒,否则该函数会报错,并且立即返回。
3. 大部分平台的帮助文档已经明确说了,该函数是已经被舍弃的函数。

3、高精度延迟nanosleep、select

针对Linux平台下的高精度延迟,Linux2.0.x新增了nanosleep系统调用。精度虽然可以设置到纳秒,但是依然没有精确到纳秒,不过对于毫秒级别的延迟的精度已经足够高了,可以完全满足usleep无法达到的精度。但使用nanosleep应注意判断返回值和错误代码,否则容易造成cpu占用率100%。另外,nanosleep()没有usleep函数的缺点,在Solaris的多线程环境下编译器会自动把usleep()连接成nanosleep()。
select系统调用也可以实现延迟,精度可以达到微妙,而且是精确的,因此是首选。

struct timeval delay;
delay.tv_sec = 0;
delay.tv_usec = 20 * 1000; // 20 ms
select(0, NULL, NULL, NULL, &delay);
2019-12-23 20:23:46 weixin_43835659 阅读数 569

Linux开机延迟启动脚本

项目中,可能遇到几台服务器都放在一个机房,某天突然断电,恢复电源后,几台服务器同时重启的情况。但这个时候服务器A的开机自启动程序依赖服务器B程序,这个时候由于服务器B还未启动完成,因此服务器A的程序运行失败。

这个时候就需要使用开机延迟启动,下面是我想到的一种办法。

问题现状

我使用的是nfs的开机挂载,原本配置了开机挂载如下:

在192.168.1.14服务器的/etc/fstab的文件最后添加了
192.168.1.13:/data/disk13 /data/disk13 nfs ro 0 0
但由于13、14服务器同时断电重启后,14启动后发现13的nfs服务暂未启动,结果挂载失败。
暂时的解决办法是登录14服务器运行mount -a命令即可。

我的办法

但是这样每次登录服务器运行挂载命令也不是长久的办法。

于是,有没有什么开机延迟启动的办法呢?

1.编写脚本

vim /path/mounta_13.sh

写入下面的内容:

#!/bin/sh
sleep 180s
mount -a

2.修改开机启动文件

vim /etc/rc.local

在文档最后添加下面的指令:

nohup /path/mounta_13.sh &

就可以了~


欢迎指正,随时修改~

2011-09-09 17:01:40 turkeyzhou 阅读数 18203

案例一:同事随手写个压力测试程序,其实现逻辑为:每秒钟先连续发N个132字节的包,然后连续收N个由后台服务回显回来的132字节包。其代码简化如下:

char sndBuf[132];

char rcvBuf[132];

while (1) {

    for (int i = 0; i < N; i++){

        send(fd, sndBuf, sizeof(sndBuf), 0);

        ...    

    }

    for (int i = 0; i < N; i++) {

        recv(fd, rcvBuf, sizeof(rcvBuf), 0);

        ...

    }

    sleep(1);

}

在实际测试中发现,当N大于等于3的情况,第2秒之后,每次第三个recv调用,总会阻塞40毫秒左右,但在分析Server端日志时,发现所有请求在Server端处理时耗均在2ms以下。

当时的具体定位过程如下:先试图用strace跟踪客户端进程,但奇怪的是:一旦strace attach上进程,所有收发又都正常,不会有阻塞现象,一旦退出strace,问题重现。经同事提醒,很可能是strace改变了程序或系统的某些东西(这个问题现在也还没搞清楚),于是再用tcpdump抓包分析,发现Server后端在回现应答包后,Client端并没有立即对该数据进行ACK确认,而是等待了近40毫秒后才确认。经过Google,并查阅《TCP/IP详解卷一:协议》得知,此即TCP的延迟确认(Delayed Ack)机制。

其解决办法如下:在recv系统调用后,调用一次setsockopt函数,设置TCP_QUICKACK。最终代码如下:

char sndBuf[132];

char rcvBuf[132];

while (1) {

    for (int i = 0; i < N; i++) {

        send(fd, sndBuf, 132, 0);

        ...    

    }

    for (int i = 0; i < N; i++) {

        recv(fd, rcvBuf, 132, 0); 

        setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int[]){1}, sizeof(int)); 

    }

    sleep(1);

}

 

案例二:在营销平台内存化CDKEY版本做性能测试时,发现请求时耗分布异常:90%的请求均在2ms以内,而10%左右时耗始终在38-42ms之间,这是一个很有规律的数字:40ms。因为之前经历过案例一,所以猜测同样是因为延迟确认机制引起的时耗问题,经过简单的抓包验证后,通过设置TCP_QUICKACK选项,得以解决时延问题。

 

延迟确认机制

在《TCP/IP详解卷一:协议》第19章对其进行原理进行了详细描述:TCP在处理交互数据流(即Interactive Data Flow,区别于Bulk Data Flow,即成块数据流,典型的交互数据流如telnet、rlogin等)时,采用了Delayed Ack机制以及Nagle算法来减少小分组数目。

书上已经对这两种机制的原理讲的很清晰,这里不再做复述。本文后续部分将通过分析TCP/IP在Linux下的实现,来解释一下TCP的延迟确认机制。

 

1、为什么TCP延迟确认会导致延迟?

其实仅有延迟确认机制,是不会导致请求延迟的(初以为是必须等到ACK包发出去,recv系统调用才会返回)。一般来说,只有当该机制与Nagle算法或拥塞控制(慢启动或拥塞避免)混合作用时,才可能会导致时耗增长。我们下面来详细看看是如何相互作用的:

延迟确认与Nagle算法

我们先看看Nagle算法的规则(可参考tcp_output.c文件里tcp_nagle_check函数注释):

1)如果包长度达到MSS,则允许发送;

2)如果该包含有FIN,则允许发送;

3)设置了TCP_NODELAY选项,则允许发送;

4)未设置TCP_CORK选项时,若所有发出去的包均被确认,或所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送。

对于规则4),就是说要求一个TCP连接上最多只能有一个未被确认的小数据包,在该分组的确认到达之前,不能发送其他的小数据包。如果某个小分组的确认被延迟了(案例中的40ms),那么后续小分组的发送就会相应的延迟。也就是说延迟确认影响的并不是被延迟确认的那个数据包,而是后续的应答包。

1 00:44:37.878027 IP 172.25.38.135.44792 > 172.25.81.16.9877: S 3512052379:3512052379(0) win 5840 <mss 1448,wscale 7>

2 00:44:37.878045 IP 172.25.81.16.9877 > 172.25.38.135.44792: S 3581620571:3581620571(0) ack 3512052380 win 5792 <mss 1460,wscale 2>

3 00:44:37.879080 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 1 win 46

......

4 00:44:38.885325 IP 172.25.38.135.44792 > 172.25.81.16.9877: P 1321:1453(132) ack 1321 win 86

5 00:44:38.886037 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 1321:1453(132) ack 1453 win 2310

6 00:44:38.887174 IP 172.25.38.135.44792 > 172.25.81.16.9877: P 1453:2641(1188) ack 1453 win 102

7 00:44:38.887888 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 1453:2476(1023) ack 2641 win 2904

8 00:44:38.925270 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 2476 win 118

9 00:44:38.925276 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 2476:2641(165) ack 2641 win 2904

10 00:44:38.926328 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 2641 win 134

从上面的tcpdump抓包分析看,第8个包是延迟确认的,而第9个包的数据,在Server端(172.25.81.16)虽然早就已放到TCP发送缓冲区里面(应用层调用的send已经返回)了,但按照Nagle算法,第9个包需要等到第个7包(小于MSS)的ACK到达后才能发出。

 

延迟确认与拥塞控制

我们先利用TCP_NODELAY选项关闭Nagle算法,再来分析延迟确认与TCP拥塞控制是如何互相作用的。

慢启动:TCP的发送方维护一个拥塞窗口,记为cwnd。TCP连接建立是,该值初始化为1个报文段,每收到一个ACK,该值就增加1个报文段。发送方取拥塞窗口与通告窗口(与滑动窗口机制对应)中的最小值作为发送上限(拥塞窗口是发送方使用的流控,而通告窗口则是接收方使用的流控)。发送方开始发送1个报文段,收到ACK后,cwnd从1增加到2,即可以发送2个报文段,当收到这两个报文段的ACK后,cwnd就增加为4,即指数增长:例如第一个RTT内,发送一个包,并收到其ACK,cwnd增加1,而第二个RTT内,可以发送两个包,并收到对应的两个ACK,则cwnd每收到一个ACK就增加1,最终变为4,实现了指数增长。

在Linux实现里,并不是每收到一个ACK包,cwnd就增加1,如果在收到ACK时,并没有其他数据包在等待被ACK,则不增加。

本人使用案例1的测试代码,在实际测试中,cwnd从初始值2开始,最终保持3个报文段的值,tcpdump结果如下:

1 16:46:14.288604 IP 172.16.1.3.1913 > 172.16.1.2.20001: S 1324697951:1324697951(0) win 5840 <mss 1460,wscale 2>

2 16:46:14.289549 IP 172.16.1.2.20001 > 172.16.1.3.1913: S 2866427156:2866427156(0) ack 1324697952 win 5792 <mss 1460,wscale 2>

3 16:46:14.288690 IP 172.16.1.3.1913 > 172.16.1.2.20001: . ack 1 win 1460

......

4 16:46:15.327493 IP 172.16.1.3.1913 > 172.16.1.2.20001: P 1321:1453(132) ack 1321 win 4140

5 16:46:15.329749 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1321:1453(132) ack 1453 win 2904

6 16:46:15.330001 IP 172.16.1.3.1913 > 172.16.1.2.20001: P 1453:2641(1188) ack 1453 win 4140

7 16:46:15.333629 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1453:1585(132) ack 2641 win 3498

8 16:46:15.337629 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1585:1717(132) ack 2641 win 3498

9 16:46:15.340035 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1717:1849(132) ack 2641 win 3498

10 16:46:15.371416 IP 172.16.1.3.1913 > 172.16.1.2.20001: . ack 1849 win 4140

11 16:46:15.371461 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1849:2641(792) ack 2641 win 3498

12 16:46:15.371581 IP 172.16.1.3.1913 > 172.16.1.2.20001: . ack 2641 win 4536

上表中的包,是在设置TCP_NODELAY,且cwnd已经增长到3的情况,第7、8、9发出后,受限于拥塞窗口大小,即使此时TCP缓冲区有数据可以发送亦不能继续发送,即第11个包必须等到第10个包到达后,才能发出,而第10个包明显有一个40ms的延迟。

 

注:通过getsockopt的TCP_INFO选项(man 7 tcp)可以查看TCP连接的详细信息,例如当前拥塞窗口大小,MSS等。

 

2、为什么是40ms?这个时间能不能调整呢?

首先在redhat的官方文档中,有如下说明:

一些应用在发送小的报文时,可能会因为TCP的Delayed Ack机制,导致一定的延迟。其值默认为40ms。可以通过修改tcp_delack_min,调整系统级别的最小延迟确认时间。例如:

         # echo 1 > /proc/sys/net/ipv4/tcp_delack_min

即是期望设置最小的延迟确认超时时间为1ms。

不过在slackware和suse系统下,均未找到这个选项,也就是说40ms这个最小值,在这两个系统下,是无法通过配置调整的。

 

linux-2.6.39.1/net/tcp.h下有如下一个宏定义:

#define TCP_DELACK_MIN    ((unsigned)(HZ/25))    /* minimal time to delay before sending an ACK */

注:Linux内核每隔固定周期会发出timer interrupt(IRQ 0),HZ是用来定义每秒有几次timer interrupts的。举例来说,HZ为1000,代表每秒有1000次timer interrupts。HZ可在编译内核时设置。在我们现有服务器上跑的系统,HZ值均为250。

以此可知,最小的延迟确认时间为40ms。

TCP连接的延迟确认时间一般初始化为最小值40ms,随后根据连接的重传超时时间(RTO)、上次收到数据包与本次接收数据包的时间间隔等参数进行不断调整。具体调整算法,可以参考linux-2.6.39.1/net/ipv4/tcp_input.c, Line 564的tcp_event_data_recv函数。

 

3、为什么TCP_QUICKACK需要在每次调用recv后重新设置?

在man 7 tcp中,有如下说明:

TCP_QUICKACK

         Enable quickack mode if set or disable quickack mode if cleared. In quickack mode, acks are         sent immediately, rather than delayed if needed in accordance to normal TCP operation.   This flag is not permanent, it only enables a switch to or from quickack mode. Subsequent         operation of the TCP protocol will once again enter/leave quickack mode depending on      internal protocol processing and factors such as delayed ack timeouts occurring and data         transfer. This option should not be used in code intended to be portable.

手册中明确描述TCP_QUICKACK不是永久的。那么其具体实现是如何的呢?参考setsockopt函数关于TCP_QUICKACK选项的实现:

case TCP_QUICKACK:

    if (!val) {

        icsk->icsk_ack.pingpong = 1;

    } else {

        icsk->icsk_ack.pingpong = 0;

        if ((1 << sk->sk_state) &

        (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) &&

        inet_csk_ack_scheduled(sk)) {

            icsk->icsk_ack.pending |= ICSK_ACK_PUSHED;

            tcp_cleanup_rbuf(sk, 1);

            if (!(val & 1))

            icsk->icsk_ack.pingpong = 1;

            }

    }

    break;

其实linux下socket有一个pingpong属性来表明当前链接是否为交互数据流,如其值为1,则表明为交互数据流,会使用延迟确认机制。但是pingpong这个值是会动态变化的。例如TCP链接在要发送一个数据包时,会执行如下函数(linux-2.6.39.1/net/ipv4/tcp_output.c, Line 156):

/* Congestion state accounting after a packet has been sent. */

static void tcp_event_data_sent(struct tcp_sock *tp,

                                     struct sk_buff *skb, struct sock *sk)

{

         ......

         tp->lsndtime = now;

         /* If it is a reply for ato after last received

          * packet, enter pingpong mode.

          */

         if ((u32)(now - icsk->icsk_ack.lrcvtime) < icsk->icsk_ack.ato)

                   icsk->icsk_ack.pingpong = 1;

}

最后两行代码说明:如果当前时间与最近一次接受数据包的时间间隔小于计算的延迟确认超时时间,则重新进入交互数据流模式。也可以这么理解:延迟确认机制被确认有效时,会自动进入交互式。

通过以上分析可知,TCP_QUICKACK选项是需要在每次调用recv后重新设置的。

 

4、为什么不是所有包都延迟确认?

TCP实现里,用tcp_in_quickack_mode(linux-2.6.39.1/net/ipv4/tcp_input.c, Line 197)这个函数来判断是否需要立即发送ACK。其函数实现如下:

/* Send ACKs quickly, if "quick" count is not exhausted

 * and the session is not interactive.

 */

static inline int tcp_in_quickack_mode(const struct sock *sk)

{

         const struct inet_connection_sock *icsk = inet_csk(sk);

         return icsk->icsk_ack.quick && !icsk->icsk_ack.pingpong;

}

要求满足两个条件才能算是quickack模式:

1、pingpong被设置为0。

2、快速确认数(quick)必须为非0。

关于pingpong这个值,在前面有描述。而quick这个属性其代码中的注释为:scheduled number of quick acks,即快速确认的包数量,每次进入quickack模式,quick被初始化为接收窗口除以2倍MSS值(linux-2.6.39.1/net/ipv4/tcp_input.c, Line 174),每次发送一个ACK包,quick即被减1。

 

5、关于TCP_CORK选项

TCP_CORK选项与TCP_NODELAY一样,是控制Nagle化的。

1、打开TCP_NODELAY选项,则意味着无论数据包是多么的小,都立即发送(不考虑拥塞窗口)。

2、如果将TCP连接比喻为一个管道,那TCP_CORK选项的作用就像一个塞子。设置TCP_CORK选项,就是用塞子塞住管道,而取消TCP_CORK选项,就是将塞子拔掉。例如下面这段代码:

int on = 1;

setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); //set TCP_CORK

write(sockfd, ...);    //e.g., http header

sendfile(sockfd, ...); //e.g., http body

on = 0;

setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); //unset TCP_CORK

当TCP_CORK选项被设置时,TCP链接不会发送任何的小包,即只有当数据量达到MSS时,才会被发送。当数据传输完成时,通常需要取消该选项,以便被塞住,但是又不够MSS大小的包能及时发出去。如果应用程序确定能一起发送多个数据集合(例如HTTP响应的头和正文),建议设置TCP_CORK选项,这样在这些数据之间不存在延迟。为提升性能及吞吐量,Web Server、文件服务器这一类一般会使用该选项。

著名的高性能Web服务器Nginx,在使用sendfile模式的情况下,可以设置打开TCP_CORK选项:将nginx.conf配置文件里的tcp_nopush配置为on。(TCP_NOPUSH与TCP_CORK两个选项实现功能类似,只不过NOPUSH是BSD下的实现,而CORK是Linux下的实现)。另外Nginx为了减少系统调用,追求性能极致,针对短连接(一般传送完数据后,立即主动关闭连接,对于Keep-Alive的HTTP持久连接除外),程序并不通过setsockopt调用取消TCP_CORK选项,因为关闭连接会自动取消TCP_CORK选项,将剩余数据发出。

 

2017-06-27 11:02:00 weixin_34354173 阅读数 662
1.在A服务器上运行qperf  &作为服务器节点,由其他服务器来连接测试,默认监听tcp的19765端口。
 2. 在B服务器上运行 qperf  X.X.X.X(A服务器的ip地址)  tcp_bw  tcp_lat  conf
       tcp_bw:B与A节点建立tcp连接能够跑的带宽
      tcp_lat:  B与A节点的延时。
      conf:显示双方的配置,包括cpu型号及系统版本。见下图
[root@cdhs01 hadoop-hdfs]# qperf  10.199.77.33 tcp_bw  tcp_lat  conf

显示A与B节点建立一个tcp的连接中间带宽,在B节点能跑到118M/sec,延迟为49.8us
3.我们可以透过改变消息的大小(msg_size),比如从1个字节到64K,每次倍增的方式,来观察带宽和延迟的
[root@cdhs01 hadoop-hdfs]# qperf 10.199.77.33 -oo msg_size:1:64k:*2 tcp_bw

发现tcp的包越大延迟越高,这些临界点对我们的服务器编程时候对性能的估计和预期非常有帮助

 

2018-12-14 09:27:42 ShuqiaoS 阅读数 3082

本文记录了一种在Linux下实现命令的延迟执行的简单方法,更新于2018.12.14。

这里博主用的是at方式,具体操作步骤如下:

  1. 如果系统中没有安装过at,首先需要运行如下命令安装:
sudo apt install at
  1. 安装完成后,Ctrl+T打开终端,输入at + [开始执行的时间],回车;
  2. 在出现的命令行内输入需要执行的指令;
  3. 按回车可以继续输入第二条命令;
  4. 所有命令输入完毕后,按Ctrl+d执行。

注:

  • 关于时间: ·at·命令可以接受多种时间指定方式,比如at now+2min表示在2分钟后开始执行,at 21:00表示在晚上9点开始执行,at 2018/10/22表示在2018年10月22日开始执行。需要注意的是,对于指定具体时间的运行方式,如果指定的时间已经过了,系统将会在次日这个时间运行程序。
  • 关于执行: 博主试验过后发现,通过at命令开始的程序,在开始执行后似乎没有直接的方法能够停止程序,所以建议在设置命令前,最好确保输入的命令正确。且at是一次性命令,即执行完毕就退出了,不会循环执行。
  • 关于任务查看和取消: at可以查看当前等待执行的任务列表和任务id,并允许取消还未执行的列表内任务(atrm)。具体操作可以在终端输入at查看帮助。