前言:
最近在啃《 Linux内核设计与实现》,看到第四章CFS时候,读了几遍没太理清这一小节到思路,看到40页这么一句话:“如果这里所讨论的定时器节拍对你来说很陌生,快先去看看第十一章再说。因为这点正是引入CFS的唯一原因”。
于是就先读了十一章:定时器和时间管理,其中提到了linux使用unsigned long jiffies,一个无符号的long记录节拍的数量,如果时钟频率是1000hz,32位的系统上。这个值47.9天就会溢出。如果溢出之前设定了定时器,溢出以后jiffies作为无符号数的值会小于超时时间的值,如果使用current > timeout 就会导致错误的判断。
但是,操作系统提供了宏time_after(current,timeout)可以避免这个问题。看到这个宏的定义,刚开始没有立刻看懂,上网查了一些解析也说的不是很简洁。自己想明白以后,觉得挺有意思,就打算记录一下。希望如果有人在上网查这个的时候,有机会查到我这种思路,看下是不是比较容易理解。
转载:
time_after防止回绕原理
正文:
首先看下这个宏的定义:
#define time_after(current,timeout) ((long)(timeout) - (long)(current)< 0)
WTF?只是把无符号输转化成有符号数就能解决这个问题吗?这是什么原理。。。
下面用我觉得比较简单的思路分析一下这个问题。
首先我们用4位的数字来举例比较简单。
问题是这样发生的:假设当前的时间是13(1101),设定一个节拍以后超时,所以超时时间就是14(1110).然后,过了3个节拍以后,当前时间变成了current = 13 + 3 = 1101 + 0011 = 10000 = 0000 = 0,这个时候虽然超时了,但是(current =0) < (timout=14),如果代码使用if(current > timeout)来判断是否超时就会判断错误。
那么如果把这两个数强制转化成有符号数,是不是就能正确判断了呢?
计算机使用补码存储数字,负数原码转补码是+1以后取反,所以timeout = 1110 转成负数源码就是-1再取反就是(1101取反)1010 也就是-2,current是0,这个时候current = 0 > timeout= -2 是成立的。
但是我的疑问是,这只能保证回绕的时候,timeout - current < 0,那不回绕呢?而且这只测试了回绕的一种情况,不能证明所有的回绕都满足timeout - current < 0吧,怎么证明这个一定成立呢?
time_after防止回绕原理 这个帖子给了我思路,但是感觉他说的还是不够简洁。帖子里说的内容其实可以用一个坐标系图简单的表达出来。其中x轴是无符号数的取值,y轴是对应的有符号数的取值。首先我们在excel中列出4位数的xy所有取值,然后生成曲线图,就很明白了:
current 与 timout 的取值总共分以下4中情况:
1. current 和 timout 都在正数单调递增的部分:
这种情况下 很明显 timeout - current < 0。
2. current 和 timout 都在负数单调递增的部分:
这种情况下同情况1。
3. current 为负数 和 timout 为正数:
这种情况下 timeout - current= 正数-负数 ,应该是一个正数啊,怎么会小于0呢?
问题是最终结果的取值相当于两个点y轴绝对值相加,如果两个点x坐标距离小于8,那么这两个点y的绝对值之和一定大于8,而大于8的无符号数转化成有符号数是一个负数。所以timeout - current <0
4. urrent 为正数 和 timout 为负数:
这种情况下 同情况3,不再赘述。
假设在32位系统使用32位来保存jiffies,1000hz的处理器,想要让两个点的距离超过0xffff,相当于设置一个20多天以后超时的定时任务。这种情况下timeout - current <0才不成立,而内核肯定不会使用这么长的超时时间,所以可以认为timeout - current <0在所有情况下都是成立的。
这样就很清晰的把这个原理表达清楚了。
总结:
第十一章总算是看完了,后边回到第四章在好好理解一下,希望能有新的收获吧~~
最后,让我们保持独立思考,不卑不亢。长成自己想要的样子! (引用自 我非常喜欢的B站up主 ”独立菌儿“->猛戳链接<-的口头禅)