Linux usleep不准问题排查

  • 参考文章
  • 测试代码
  • 系统调用
  • clock_event中断服务函数
  • usleep不准问题说明
    • 流程梳理
    • 原因分析
    • 解决方案

  最近在工作中遇到了一个应用程序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);elsetick_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;elsedev->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之间,相比上面的情况,误差就更容易让人接受。当然,修改系统节拍存在坏处,会增加系统的负载。不建议修改。

Linux usleep不准问题排查相关推荐

  1. Linux安全事件应急响应排查方法总结

    Linux安全事件应急响应排查方法总结 Linux是服务器操作系统中最常用的操作系统,因为其拥有高性能.高扩展性.高安全性,受到了越来越多的运维人员追捧.但是针对Linux服务器操作系统的安全事件也非 ...

  2. Linux 系统故障分析与排查

    在Linux系统中,同样需要进行大量的备份来完成系统的维护工作,并且使用复制粘贴命令即可完成,在接下来的时间中介绍一些关于Linux系统故障分析与排查的操作. 日志服务器的部署 通过一台RHEL5作为 ...

  3. Linux入侵类问题排查思路

    深入分析,查找入侵原因 一.检查隐藏帐户及弱口令 检查服务器系统及应用帐户是否存在 弱口令: 检查说明:检查管理员帐户.数据库帐户.MySQL 帐户.tomcat 帐户.网站后台管理员帐户等密码设置是 ...

  4. Linux丢包问题排查思路

    Linux丢包问题排查思路 判断问题与网络丢包有关 通过抓tcpdump,通过wireshark提示查看数据包状态.比如客户端重传多次失败,服务端提示丢包等错误,均是可能由于丢包导致的异常. 丢包可能 ...

  5. Navicat连接Linux的MySQL出错排查

    使用navicat连接不上Linux的MySQL 错误排查: 1.MySQL是否启动,查看MySQL进程. ps -ef|grep mysql  2.关闭防防火墙 systemctl status f ...

  6. 51CTO学习笔记--Linux运维故障排查思路与系统调优技巧视频课程(高俊峰)

    51CTO学习笔记--Linux运维故障排查思路与系统调优技巧视频课程 第一课 Linux运维经验分享与思路 1.一般把主机名,写到hosts下    127.0.0.1    hostname,因为 ...

  7. 转:记一次linux oom内存溢出排查过程

    @转:记一次linux oom内存溢出排查过程 记一次linux oom内存溢出排查过程 2018年08月16日 14:13:49 enchanterblue 阅读数 4099更多 分类专栏: --- ...

  8. linux usleep 线程控制权_linux多线程同步—信号量

    linux多线程编程-信号量 信号量机制 锁机制使用是有限制的,锁只有两种状态,即加锁和解锁,对于互斥的访问一个全局变量,这样的方式还可以对付,但是要是对于其他的临界资源,比如说多台打印机等,这种方式 ...

  9. 【Linux】远程连接Linux系统及故障排查

    参考资料: 1.视频课程<Linux运维> 2.书籍PDF版<Linux运维 Web集群实战> 远程连接Linux系统 原理 互联网上的计算机都会有一个32位的地址,ip地址. ...

最新文章

  1. vscode for mac怎样关闭自动更新
  2. fedora 15怎么修改运行级别?
  3. 违反Apache 2.0许可证再分发被指控,火山引擎回应
  4. 要配置php环境_只需修改,要配置Apache的PHP环境,只需修改()。
  5. [TED] Kinect控制的四翼直升机
  6. 不同管理岗层级的团队影响力_高影响力团队的最高要求
  7. 20180925-5 代码规范,结对要求
  8. rpa打开浏览器_rpa.ie
  9. 孔板流量计计算公式_带你全面了解各种流量计!
  10. matlab电力系统建模仿真实验,电力系统建模及仿真课程设计
  11. JSEclipse安装后无法打开js文件_火狐浏览器打开邮箱添加不了附件
  12. win10任务栏透明+变窄+免安装
  13. 智能优化算法学习总结
  14. jmeter实现手机号归属地接口测试案例
  15. 访问FTP站点下载文件,提示“当前的安全设置不允许从该位置下载文件”
  16. 【rpm】源码包制作rpm包|修改rpm、重新制作rpm包
  17. 《Delphi传奇》网络游戏组件安装步骤:Delphi 10.3安装DelphiX
  18. Linux中ibus输入法中全拼和双拼的问题+解决VNCserver切换不成功问题
  19. 深度信念网络DBN的一个matlab实例
  20. Karen Keegans加盟罗克韦尔自动化任人力资源高级副总裁

热门文章

  1. 处理器架构——多发射处理器技术
  2. 【Javascript】进阶之实现评论分页与发表评论功能
  3. OpenCV:Scalar数据类型理解
  4. springbootadmin 客户端监控配置
  5. Bilibili视频-对比学习论文综述【论文精读】
  6. Red Hat Enterprise Linux 官方正式版镜像下载
  7. PyQt(Python+Qt)入门:Designer组件属性编辑界面中QWidget类相关属性详解
  8. traceroute命令(unix)/tracert命令(windows)
  9. 企业公众号文章写作方向要从这几个方面着手
  10. 短时傅里叶变换(STFT)实例