linux usleep精度问题

原文地址:https://blog.csdn.net/weixin_42262944/article/details/107585320

  最近在工作中遇到了一个应用程序usleep不准的问题,排查过程中了解了一下usleep的内核实现,简单的讲一下低精度模式下的usleep机制。
  先把最终结论贴出来,内核使能 CONFIG_HIGH_RES_TIMERS选项,且平台支持高精度定时器模式,即可解决该问题。
  下面主要来分析为什么在未使能高精度定时器的情况下,usleep不准的问题。
参考文章
可参考链接中系列时间子系统介绍文章
Linux时间子系统之一:clock source(时钟源)

测试代码
  测试代码如下:

#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>

#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>

int main(void)
{
    int sleepTime = 0;
    int sleepus = 0;

    struct timeval t1;
    struct timeval t2;

    while(1) {
        gettimeofday(&t1, NULL);
        printf("start time %d(s).%06d(us)\n", (int)t1.tv_sec, (int)t1.tv_usec);
        usleep(10000);
        gettimeofday(&t2, NULL);
        printf("end   time %d(s).%06d(us)\n", (int)t2.tv_sec, (int)t2.tv_usec);
        sleepTime = (int)((t2.tv_sec - t1.tv_sec) * 1000 + (t2.tv_usec - t1.tv_usec) / 1000);
        sleepus = (int)((t2.tv_usec - t1.tv_usec) % 1000);
        printf("sleep cost %d(ms).%d(us)\n", sleepTime, sleepus);
    }

    return 0;
}


  测试结果为:
1)在代码中usleep10 ms,实际测试下来会变成20 ms,usleep 5ms会变成10ms。usleep 9900us(9.9ms),实际测试会变成10ms。
2)屏蔽代码中的while(1)循环,编译为单次执行,会发现usleep 5ms会随机分布在5-15 ms之间。usleep 10ms会随机分布在10-19ms之间。

系统调用
  usleep经过libc库封装,最终内核系统调用为nanosleep,位于内核kernel/time/hrtimer.c中代码如下:

SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp,
        struct timespec __user *, rmtp)
{
    struct timespec64 tu;

    if (get_timespec64(&tu, rqtp))
        return -EFAULT;

    if (!timespec64_valid(&tu))
        return -EINVAL;

    current->restart_block.nanosleep.type = rmtp ? TT_NATIVE : TT_NONE;
    current->restart_block.nanosleep.rmtp = rmtp;
    return hrtimer_nanosleep(&tu, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
}

long hrtimer_nanosleep(const struct timespec64 *rqtp,
               const enum hrtimer_mode mode, const clockid_t clockid)
{
    struct restart_block *restart;
    struct hrtimer_sleeper t;
    int ret = 0;
    u64 slack;

    slack = current->timer_slack_ns;
    if (dl_task(current) || rt_task(current))
        slack = 0;

    hrtimer_init_on_stack(&t.timer, clockid, mode);
    hrtimer_set_expires_range_ns(&t.timer, timespec64_to_ktime(*rqtp), slack);
    ret = do_nanosleep(&t, mode);
    if (ret != -ERESTART_RESTARTBLOCK)
        goto out;

    /* Absolute timers do not update the rmtp value and restart: */
    if (mode == HRTIMER_MODE_ABS) {
        ret = -ERESTARTNOHAND;
        goto out;
    }

    restart = &current->restart_block;
    restart->fn = hrtimer_nanosleep_restart;
    restart->nanosleep.clockid = t.timer.base->clockid;
    restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
out:
    destroy_hrtimer_on_stack(&t.timer);
    return ret;
}


  当应用程序调用usleep时,实际会调用内核的高精度定时器。但是当内核未使能CONFIG_HIGH_RES_TIMERS选项时,虽然会调到nanosleep,会创建对应的高精度定时器,内核也会按照定精度定时器的模式进行处理。即在系统节拍到来时处理(系统HZ)。

 

 这里我的平台是100HZ。即每秒每个cpu的clock_even定时器会产生100次中断,10ms一次。低精度定时器在定时中断中处理。
  未使能CONFIG_HIGH_RES_TIMERS选项时,nanosleep创建的高精度定时器也会在此中断服务函数中处理。即高精度定时器工作在低精度模式下。
  某个CPU的clock_event会被选中作为系统节拍维护者,即负责jiffies增加的工作。clock_event中断服务函数
  clock_event_device注册时,会设置中断服务函数,具体调用关系如下:

clockevents_register_device->tick_check_new_device->tick_setup_device

static void tick_setup_device(struct tick_device *td,
                  struct clock_event_device *newdev, int cpu,
                  const struct cpumask *cpumask)
{
    ......
    if (td->mode == TICKDEV_MODE_PERIODIC)
        tick_setup_periodic(newdev, 0);
    else
        tick_setup_oneshot(newdev, handler, next_event);
}



  这里会设置周期模式。即调用tick_setup_periodic设置中断服务函数。
tick_setup_device->tick_setup_periodic->tick_set_periodic_handler

void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
    if (!broadcast)
        dev->event_handler = tick_handle_periodic;
    else
        dev->event_handler = tick_handle_periodic_broadcast;
}


  所以最终的中断服务函数为tick_handle_periodic,每个CPU 的clockevent来临时,会调用该函数。
  而中断服务函数中,最终执行定时器的调用关系为:tick_handle_periodic->tick_periodic->update_process_times->run_local_timers。顾名思义,运行当前cpu上的定时器。代码如下:

void run_local_timers(void)
{
    struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);

    hrtimer_run_queues();                                    //查找到期的高精度定时器,并处理回调函数
    /* Raise the softirq only if required. */
    if (time_before(jiffies, base->clk)) {
        if (!IS_ENABLED(CONFIG_NO_HZ_COMMON))
            return;
        /* CPU is awake, so check the deferrable base. */
        base++;
        if (time_before(jiffies, base->clk))
            return;
    }
    raise_softirq(TIMER_SOFTIRQ);                            //唤醒低精度定时器的软中断。
}


  nanosleep插入的高精度定时器,到期时会在hrtimer_run_queues被查找,并执行回调函数,唤醒应用程序进程。具体实现太复杂,就不展开了。

usleep不准问题说明
流程梳理
  梳理下整个流程。
1)执行usleep,触发系统调用
2)系统调用中创建高精度定时器,并将进程设置成睡眠模式。
3)高精度定时器到期,执行定时器回调,唤醒对应进程。
  usleep不准问题主要就出在步骤3上,进程什么时候被唤醒。

原因分析
  假设当前,内核节拍时间轴为0ms、10ms、20ms、30ms。依次类推。
1)单次运行时:命令行中执行可执行文件时,此时在0-10ms之间,假设4ms时插入,代码循环中usleep 10ms,则内核10ms时clock_event中断来临时,检查定时器,此时定时器尚未到期。本次中断中不会执行该定时器回调函数。14ms时,定时器到期,但是没有中断来处理该定时器,需要等到下一个clock_event中断才能执行该定时器的回调函数。即需要在20ms时,才能唤醒睡眠进程。此种情况耗时为16ms+。多次运行后,会发现,时间会在10~19ms之间分布。
2)while(1)循环运行时,命令行中执行该可执行文件,第一次循环费时和单次运行一致。时间随机在10~19ms。当第一次运行完后,进程在20ms时被唤醒执行,就绪队列中可能有其他进程在排队,且系统调用存在开支,while(1)循环中还有别的逻辑代码。则第二次循环调用usleep时间轴为20ms+,此时睡眠10ms,则会错误30ms时间轴时的clock_event中断,需要等待下一次clock_event中断。所以后面每次usleep 10ms都会变成接近20ms。可以修改代码,睡眠时间改为usleep 9ms时,则demo中每次循环睡眠时间会变为10ms。系统HZ决定睡眠误差。

解决方案
  上策:使能内核CONFIG_HIGH_RES_TIMERS选项,高精度定时器使能后,clock_event会根据定时器设置,精准设置下次中断到来的时间,精度可以达到us、ns级别。当然前提是,你使用的芯片原厂驱动支持该模式。
  下策:修改系统节拍。比如当前节拍100HZ,则误差为10ms。当需要睡眠的事件很短,如10ms,则误差会导致睡眠时间在10-20ms之间偏差,严重影响精度。当系统节拍改为1000HZ时,同样睡眠10ms,则睡眠的时间会在10-11ms之间,相比上面的情况,误差就更容易让人接受。当然,修改系统节拍存在坏处,会增加系统的负载。不建议修改。
————————————————
版权声明:本文为CSDN博主「小情歌的小宇宙」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42262944/article/details/107585320

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: usleep是一个Linux系统调用函数,用于让进程睡眠指定的微秒数。其函数原型如下: ```C #include <unistd.h> int usleep(useconds_t usec); ``` 其中,usec参数表示需要睡眠的微秒数。usleep函数的作用类似于sleep函数,不同之处在于usleep函数可以让进程睡眠更小的时间间隔。 在使用usleep函数时,需要包含头文件<unistd.h>。例如,以下代码会让进程睡眠10毫秒(即10,000微秒): ```C #include <unistd.h> int main() { usleep(10000); return 0; } ``` ### 回答2: usleep(10000)是一个Linux命令,该命令的作用是使程序在指定的时间内暂停执行。其中,usleep是一个用于微秒级延迟的函数,参数10000表示暂停程序执行10毫秒。 在Linux系统中,usleep()函数是unistd.h头文件中的一个函数,它与sleep()函数类似,但精度更高。通过使用usleep()命令,程序可以在需要的地方添加延迟,以实现特定的功能。 比如,当一个程序需要在执行某一操作之前等待一段时间时,可以使用usleep(10000)命令来实现指定的延迟。这样可以让程序在延迟期间执行其他的操作,从而提高程序的效率和响应速度。 另外需要注意的是,usleep()函数的参数单位是微秒,即百万分之一秒。因此,参数10000对应的是10毫秒(10 * 1000 微秒)。 总之,usleep(10000)是一个用于Linux系统的命令,用于使程序在指定的时间内暂停执行,以完成特定的延迟操作。 ### 回答3: usleep(10000)是一条Linux命令,用于将当前进程挂起一段时间,精确到微秒级别。它的功能类似于sleep命令,但usleep可以设置更小的时间间隔。 当我们在命令行中输入usleep(10000)时,系统会将当前进程挂起10毫秒,也就是10000微秒。在这段时间内,该进程将暂停执行,不会占用CPU资源。 usleep(10000)常常用于需要控制程序执行速度的场景,例如在循环中模拟延迟操作,或者在多线程编程中对线程进行同步等。通过暂停程序的执行,在合适的时机进行等待,可以提高程序的效率和资源利用率。 同时,值得注意的是,usleep(10000)是一个过时的函数,在较新的Linux系统中可能已经被废弃。现代的Linux系统更倾向于使用nanosleep命令,因为它提供了更高的精确度。 总之,usleep(10000)是一个能够暂停当前进程执行一段时间的Linux命令,可以用于模拟延迟操作和实现程序的同步。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值