关于jiffies回绕以及time_after,time_before

本文探讨了在计算机系统中使用有限长度数值表示单调递增计数器时遇到的回绕问题,如TCP序列号和jiffies。通过将无符号整数解释为有符号整数的方法解决了这一问题,并讨论了这种方法的有效范围。
系统中有非常多变量用来记录一个单调递增的现实,典型的有两个,一个是TCP的序列号。还有一个就是jiffies,可是由于计算机内表示的数字都是有限无界的,所以不论什么数字都不能做到全然意义的单调递增,它们仅仅是在绕圈圈,就像钟表一样,值域就是那些有限的数字。周而复始。

实际上无论是TCP序列号还是jiffies,都面临一类问题。就是回绕问题。就像我们看钟表,1点时在11点前呢。还是在11点后呢?事实上这个问题并没有想象的那么easy回答。


       我们用char型来简单描写叙述一下这个问题的解法。char仅仅有8位,二进制从00000000到11111111,假设用一个char表示jiffies,那么当其值已经达到11111111的时候,将面临一次跳变。即回归到0,这是由于11111111加1的话,将会得到100000000。然而char仅仅有8位,造成最高位溢出,所得到的结果就是00000000,这就是0点跳变。本质原因就是在跳变点附近,值域并非连续的。

11111111的十进制是255,显然和0并非连续的。
       然而,11111111除了能够表示255之外,还能够表示-1。而-1和0却是连续的。我们知道。计算机内对于数据的存储是不区分符号的,符号仅仅是在计算的时候才依据指令的不同给与不同的解释,因此仅仅须要将无符号的jiffies看作是有符号的数字,就能够解决这个跳变问题。显然0是在-1后面的,这也就是说。0在255后面。

将有符号数字解释为无符号数字,仅仅是一种解释方式的不同。对于数据的表示没有不论什么影响,它仅仅影响计算。

有符号计算指令会将值域坐标轴往左平移半个值域宽度。从而将0点变成中点,而不是左端点。解决0点跳变问题。

然而这个仅仅在后半个值域空间有效,也就是说。对于char而言,系统仅仅能正确识别128以后的跳变,假设你測试129和1水更靠前,答案是129。然而假设你測试127和1谁更靠前。答案就是1!可是这已经足够了。由于对于jiffies的比較。基本都是附近值得比較。没有人会去比較如今和几千几万年以后的时间。
       详情參见time_after,time_before宏。

转载于:https://www.cnblogs.com/liguangsunls/p/7235574.html

# 内核定时器 Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。 ```c # undef HZ # define HZ CONFIG_HZ # define USER_HZ 100 # define CLOCKS_PER_SEC (USER_HZ) ``` - 宏HZ表示1秒的节拍数,也就是频率. ## jiffies 全局变量 jiffies 用来**记录自系统启动以来产生的节拍的总数**。 $$ time(秒) = \frac{jiffies}{HZ} $$ 如果节拍数达到了**最大值后还要继续增加**的话, 它的值会**回绕到 0**. **时间比较**函数: ![在这里插入图片描述](Linux 驱动开发.assets/5bc7666e625d6b8329d56a6d6d2a8f62.png) - 如果 unkown 超过 known 的话, time_after 函数返回真, 否则返回假。 - 如果 unkown 没有超过 known的话 time_before 函数返回真, 否则返回假. - `time_after_eq` 函数在`time_after`上,**多判断等于这个条件**. ```c unsigned long timeout; timeout = jiffies + (2 * HZ); /* 超时的时间点 */ .... /* 判断有没有超时 */ if(time_before(jiffies, timeout)) { /* 超时未发生 */ } else { /* 超时发生 */ } ``` 为了方便开发, Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数 ![在这里插入图片描述](Linux 驱动开发.assets/1b1bd1774ad7918a12a663d13fc13912.png) - 定时10ms `jiffies + mesces_to_jeffies(10)` ## 定时器编程 ​ 内核定时器**并不是周期性运行**的,超时以后就会自动关闭,因此如果想要**实现周期性定时**,那么就需要在**定时处理函数中重新开启定时器。**内核定时器结构体: `timer_list` ```c struct timer_list { struct list_head entry; unsigned long expires; /* 定时器超时时间, 单位是节拍数 */ void (*function)(unsigned long); /* 定时处理函数指针 */ u32 flags; }; ``` ![在这里插入图片描述](Linux 驱动开发.assets/32511492017299ed884b6a688ef335d2.png) **注意**: init_timer已经过时现在推荐采用: `timer_setup` ```c /** * timer, struct timer_list对象 * callback, timer 超时回调 * flags, 通常为0 */ timer_setup(timer, callback, flags); /* callback 函数原型 */ void (*) (struct timer_list *timer); ``` ### 容器技巧 由于timer的内核回调接口为: `void (*) (struct timer_list *timer);`无法传递用户参数,为此可以采用`catainer_of()`容器技术来传递用户参数,具体实施如下: 1. 构建外层结构体,包含`struct timer_list`对象 2. 在回调函数中使用`container_of()`得到外层结构体指针. ### 定时器示例 ```c #include <linux/init.h> #include <linux/module.h> // for module_init/exit #include <linux/timer.h> #include <linux/jiffies.h> struct _TIMER_DEV { unsigned long expire; /* delay time */ struct timer_list m_timer; } timer_dev; void timer_function(struct timer_list* timer) { struct _TIMER_DEV *data = container_of(timer, struct _TIMER_DEV, m_timer); printk("[liujiarui] timeout mode, data expire: %lu.\n", data->expire); if(timer) { mod_timer(timer, jiffies + msecs_to_jiffies(data->expire)); } } /* 驱动入口函数 */ static int __init timer_init(void) { timer_setup(&timer_dev.m_timer, timer_function, 0); timer_dev.expire = 1000; /* 1s */ timer_dev.m_timer.function = timer_function; mod_timer(&timer_dev.m_timer, jiffies + msecs_to_jiffies(timer_dev.expire)); return 0; } /* 驱动出口函数 */ static void __exit timer_exit(void) { del_timer_sync(&timer_dev.m_timer); } /* 将上面两个函数指定为驱动的入口和出口函数 */ module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL");//GPL模块许可证 MODULE_AUTHOR("ljr_rjl");//作者信息 ``` 检查这份文档有什么问题?
最新发布
09-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值