时钟机制是驱动Linux内核运转的核心组件,他的工作方式有两种,periodic(周期性的)和NO_HZ_FULL(IDLE).在不同的模式下,时钟周期的精度是不同的,下面做实验验证一下.

测试用例:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<sys/time.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/select.h>


int main(int argc, char **argv)
{
unsigned int nTimeTestSec = 0;
unsigned int nTimeTest = 0;
struct timeval tvBegin;
struct timeval tvNow;
int ret = 0;
unsigned int nDelay = 0;
struct timeval tv;
int fd = 1;
int i = 0;
struct timespec req;

unsigned int delay[20] =
{500000, 100000, 50000, 10000, 1000, 900, 500, 100, 10, 1, 0};
int nReduce = 0; //误差

fprintf(stderr, "%19s%12s%12s%12s\n", "fuction", "time(usec)", "realtime", "reduce");
fprintf(stderr, "----------------------------------------------------\n");
for (i = 0; i < 20; i++)
{
if (delay[i] <= 0)
break;
nDelay = delay[i];
//test sleep
gettimeofday(&tvBegin, NULL);
ret = usleep(nDelay);
if(ret == -1)
{
fprintf(stderr, "usleep error, errno=%d [%s]\n", errno, strerror(errno));
}
gettimeofday(&tvNow, NULL);
nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;

fprintf (stderr, "\t usleep %8u %8u %8d\n", nDelay, nTimeTest,nReduce);

//test nanosleep
req.tv_sec = nDelay/1000000;
req.tv_nsec = (nDelay%1000000) * 1000;

gettimeofday(&tvBegin, NULL);
ret = nanosleep(&req, NULL);
if (-1 == ret)
{
fprintf (stderr, "\t nanousleep %8u not support\n", nDelay);
}
gettimeofday(&tvNow, NULL);
nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t nanosleep %8u %8u %8d\n", nDelay, nTimeTest,nReduce);

//test select
tv.tv_sec = 0;
tv.tv_usec = nDelay;

gettimeofday(&tvBegin, NULL);
ret = select(0, NULL, NULL, NULL, &tv);
if (-1 == ret)
{
fprintf(stderr, "select error. errno = %d [%s]\n", errno, strerror(errno));
}

gettimeofday(&tvNow, NULL);
nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t select %8u %8u %8d\n", nDelay, nTimeTest,nReduce);

//pselcet
req.tv_sec = nDelay/1000000;
req.tv_nsec = (nDelay%1000000) * 1000;

gettimeofday(&tvBegin, NULL);
ret = pselect(0, NULL, NULL, NULL, &req, NULL);
if (-1 == ret)
{
fprintf(stderr, "select error. errno = %d [%s]\n", errno, strerror(errno));
}

gettimeofday(&tvNow, NULL);
nTimeTest = (tvNow.tv_sec - tvBegin.tv_sec) * 1000000 + tvNow.tv_usec - tvBegin.tv_usec;
nReduce = nTimeTest - nDelay;
fprintf (stderr, "\t pselect %8u %8u %8d\n", nDelay, nTimeTest,nReduce);

fprintf (stderr, "--------------------------------\n");

}

return 0;
}

在使用高精度定时器的情况下测试开关PREEMPT的情况:

关闭CONFIG_PREEMPT的情况下

关于Linux nanosleep函数时间精度的测试_高精度

关于Linux nanosleep函数时间精度的测试_服务器_02

打开CONFIG_PREEMPT

关于Linux nanosleep函数时间精度的测试_服务器_03

重新编译和安装内核:

关于Linux nanosleep函数时间精度的测试_服务器_04

貌似改善不多,可以得出CONFIG_PREEMPT和精度关系不大的结论,我们继续.

打开周期定时器:

关于Linux nanosleep函数时间精度的测试_服务器_05

关闭高精度定时器:

关于Linux nanosleep函数时间精度的测试_服务器_06

运行用例验证,可以明显发现,由于时钟模式变为periodic的了,时钟粒度瞬间缩减为4ms=4000us,所以例子中即便睡眠1个us,也需要等到4ms后才会被唤醒.和没有打开periodic的模式有显著区别.

关于Linux nanosleep函数时间精度的测试_#include_07

关于Linux nanosleep函数时间精度的测试_linux_08

CONFIG_HZ修改为100,看有没有变化:

关于Linux nanosleep函数时间精度的测试_运维_09

重新编译内核,发现时钟粒度再次变大,CONFIG_HZ从250变为100. 时钟粒度则从4ms变为10ms成反比关系.

关于Linux nanosleep函数时间精度的测试_运维_10

打开CONFIG_HIGH_RES_TIMERS

关于Linux nanosleep函数时间精度的测试_#include_11

重新编译,运行用例:

关于Linux nanosleep函数时间精度的测试_linux_12

可以看到,打开高精度定时器,时间精度恢复了原始的比较精确的误差范围.

关闭CONFIG_NO_HZ

关于Linux nanosleep函数时间精度的测试_运维_13

发现时间精度仍然是高精度的范围

关于Linux nanosleep函数时间精度的测试_高精度_14

所以看起来,控制高精度定时器的是CONFIG_HIGH_RES_TIMERS.


结束!