Linux usleep不准问题排查
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 = ¤t->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不准问题排查相关推荐
- Linux安全事件应急响应排查方法总结
Linux安全事件应急响应排查方法总结 Linux是服务器操作系统中最常用的操作系统,因为其拥有高性能.高扩展性.高安全性,受到了越来越多的运维人员追捧.但是针对Linux服务器操作系统的安全事件也非 ...
- Linux 系统故障分析与排查
在Linux系统中,同样需要进行大量的备份来完成系统的维护工作,并且使用复制粘贴命令即可完成,在接下来的时间中介绍一些关于Linux系统故障分析与排查的操作. 日志服务器的部署 通过一台RHEL5作为 ...
- Linux入侵类问题排查思路
深入分析,查找入侵原因 一.检查隐藏帐户及弱口令 检查服务器系统及应用帐户是否存在 弱口令: 检查说明:检查管理员帐户.数据库帐户.MySQL 帐户.tomcat 帐户.网站后台管理员帐户等密码设置是 ...
- Linux丢包问题排查思路
Linux丢包问题排查思路 判断问题与网络丢包有关 通过抓tcpdump,通过wireshark提示查看数据包状态.比如客户端重传多次失败,服务端提示丢包等错误,均是可能由于丢包导致的异常. 丢包可能 ...
- Navicat连接Linux的MySQL出错排查
使用navicat连接不上Linux的MySQL 错误排查: 1.MySQL是否启动,查看MySQL进程. ps -ef|grep mysql 2.关闭防防火墙 systemctl status f ...
- 51CTO学习笔记--Linux运维故障排查思路与系统调优技巧视频课程(高俊峰)
51CTO学习笔记--Linux运维故障排查思路与系统调优技巧视频课程 第一课 Linux运维经验分享与思路 1.一般把主机名,写到hosts下 127.0.0.1 hostname,因为 ...
- 转:记一次linux oom内存溢出排查过程
@转:记一次linux oom内存溢出排查过程 记一次linux oom内存溢出排查过程 2018年08月16日 14:13:49 enchanterblue 阅读数 4099更多 分类专栏: --- ...
- linux usleep 线程控制权_linux多线程同步—信号量
linux多线程编程-信号量 信号量机制 锁机制使用是有限制的,锁只有两种状态,即加锁和解锁,对于互斥的访问一个全局变量,这样的方式还可以对付,但是要是对于其他的临界资源,比如说多台打印机等,这种方式 ...
- 【Linux】远程连接Linux系统及故障排查
参考资料: 1.视频课程<Linux运维> 2.书籍PDF版<Linux运维 Web集群实战> 远程连接Linux系统 原理 互联网上的计算机都会有一个32位的地址,ip地址. ...
最新文章
- vscode for mac怎样关闭自动更新
- fedora 15怎么修改运行级别?
- 违反Apache 2.0许可证再分发被指控,火山引擎回应
- 要配置php环境_只需修改,要配置Apache的PHP环境,只需修改()。
- [TED] Kinect控制的四翼直升机
- 不同管理岗层级的团队影响力_高影响力团队的最高要求
- 20180925-5 代码规范,结对要求
- rpa打开浏览器_rpa.ie
- 孔板流量计计算公式_带你全面了解各种流量计!
- matlab电力系统建模仿真实验,电力系统建模及仿真课程设计
- JSEclipse安装后无法打开js文件_火狐浏览器打开邮箱添加不了附件
- win10任务栏透明+变窄+免安装
- 智能优化算法学习总结
- jmeter实现手机号归属地接口测试案例
- 访问FTP站点下载文件,提示“当前的安全设置不允许从该位置下载文件”
- 【rpm】源码包制作rpm包|修改rpm、重新制作rpm包
- 《Delphi传奇》网络游戏组件安装步骤:Delphi 10.3安装DelphiX
- Linux中ibus输入法中全拼和双拼的问题+解决VNCserver切换不成功问题
- 深度信念网络DBN的一个matlab实例
- Karen Keegans加盟罗克韦尔自动化任人力资源高级副总裁
热门文章
- 处理器架构——多发射处理器技术
- 【Javascript】进阶之实现评论分页与发表评论功能
- OpenCV:Scalar数据类型理解
- springbootadmin 客户端监控配置
- Bilibili视频-对比学习论文综述【论文精读】
- Red Hat Enterprise Linux 官方正式版镜像下载
- PyQt(Python+Qt)入门:Designer组件属性编辑界面中QWidget类相关属性详解
- traceroute命令(unix)/tracert命令(windows)
- 企业公众号文章写作方向要从这几个方面着手
- 短时傅里叶变换(STFT)实例