📄 (ldd) ch06-时间流(转载).txt
字号:
(LDD) Ch06-时间流(转载)
第6章 时间流
至此,我们知道怎样写一个特性比较完全的字符模块了。我们将在后面几章陆续讨论驱
动程序可以访问的一些内核资源。本章,我们先来看看内核代码是如何对时间问题进行
处理的。该问题包括(按复杂程度排列):
l 如何获得当前时间
l 如何将操作延迟指定的一段时间
l 如何调度函数到指定的时间后异步执行
内核中的时间间隔
我们首先要涉及的是时钟中断,操作系统通过时钟中断来确定时间间隔。时钟中断的发
生频率设定为HZ,HZ是一个与体系结构无关的常数,在文件<linux/param.h>中定义。至
少从2.0版到2.1.43版,Alpha平台上Linux定义HZ的值为1024,而其他平台上定义为100
。
当时钟中断发生时,jiffies值就加1。因此,jiffies值就是自操作系统启动以来的时钟
滴答的数目;jiffies在头文件<linux/sched.h>中被定义为数据类型为unsigned long
volatile (32位无符号长整型)的变量,因此连续累加一年又四个多月后就会溢出(假定H
Z=100,1个jiffies等于1/100秒,jiffies可记录的最大秒数为42949672.96秒,约合1.3
8年)。如果你打算连续运行Linux一年又四个多月以上,你最好买台Alpha,那么,就是
跑五亿年也不会溢出了-Alpha机器上jiffies有64位。我是无法准确地告诉你jiffies溢
出时会发生些什么的,我可没有那么长的时间来等这件事发生。
如果你修改HZ值后重编译内核,在用户空间你不会注意到有什么不同。尽管jiffies值以
不同的步长增长,但一切似乎还正常。会产生更多的中断,系统开销更大了,但是因为
处理器调度得更频繁了,系统会很不稳定。
我在我的PC上测试了一些jiffies值:在100Hz时,系统的响应很慢;100Hz是缺省值;在
1kHz时,系统跑的相当慢,但响应得很快;在10kHz时,系统极慢;在50kHz时,系统已
经令人无法忍受了。修改中断频率还有副作用,jiffies值溢出要花的时间不同了(10kHz
的时钟频率下,只要五天),BogoMips值的计算精度也不同了*。而且还有一些别处都没
提及的硬件上的限制。例如,19是PC上时钟频率的能设的最小值,其他体系结构上也存
在着类似的限制。
此外,在使用模块时还要小心。如果你改变了HZ的定义,你必须重新编译和安装你使用
的所有模块。内核中一切都与HZ值有关,包括模块。我是在增加了HZ值因而无法双击鼠
标后,发现到这一点的。
总而言之,时钟中断的最好方法是保留HZ的缺省值,因为我们可以完全相信内核的开发
者们,他们一定已经为我们挑选了最佳值。关于本专题的更多信息可参见头文件<linux/
timex.h>。
获取当前时间
内核一般通过jiffies值来获取当前时间。尽管该数值表示的是自上次系统启动
到当前的时间间隔,但因为驱动程序的生命期只限于系统的运行期(uptime),所以也是
可行的。驱动程序利用jiffies的当前值来计算不同事件间的时间间隔(我在kmouse模块
中就用它来分辨鼠标的单双击)。简而言之,利用jiffies值来测量时间间隔还是很有效
的。
驱动程序一般不需要知道墙上时间,通常只有象cron和at这样用户程序才需要墙
上时间。需要墙上时间的情形是使用设备驱动程序的特殊情况,此时可以通过用户程序
来将墙上时间转换成系统时钟。
如果驱动程序真的需要获取当前时间,可以使用do_gettimeofday函数。该函数
并不返回今天是本周的星期几或类似的信息;它是用微秒值来填充一个指向struct
并不返回今天是本周的星期几或类似的信息;它是用微秒值来填充一个指向struct
timeval的指针变量。相应的原型如下:
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
源码中声明的do_gettimeofday在Alpha和Sparc之外的体系结构上有“接近微秒级的分辨
率” ,在Alpha和Sparc上和jiffies值的分辨率一样。Sparc的移植版本在2.1.34版的内
核中升级了,可以支持更细粒度的时间度量。当前时间也可以通过xtime变量(类型为str
uct timeval)获得(但精度差些);但是,并不鼓励直接使用该变量,因为除非关闭中断
,无法原子性地访问timeval变量的两个域tv_sec和tv_usec。使用do_gettimeofday填充
的timeval结构变量会更快些。
令人遗憾的是,1.2版的Linux并未开放do_gettimeofday函数。如果你要获取当前时间,
又希望程序能够向后兼容,你应该使用该函数下面的这个版本:
#if LINUX_VERSION_CODE < VERSION_CODE(1,3,46)
/*
* 内核头文件已经该函数是非静态的。
* 我们应先用其他名字来实现它,再 #define 它。
*/
extern inline void redo_gettimeofday(struct timeval *tv)
{
unsigned long flags;
save_flags(flags);
cli();
*tv=xtime;
restore_flags(flags);
}
#define do_gettimeofday(tv) redo_gettimeofday(tv)
#endif
这个版本比实际的版本精度要差些,因为它只使用xtime结构的当前值,这个值
并不会比jiffies值的粒度更细。但是,它却能在不同的Linux平台间移植。“实际的”
函数是通过与体系结构相关的代码查询硬件时钟来获得更高的分辨率。
获取当前时间的代码可见于jit("Just In Time")模块,源文件可以从O'Reilly
公司的FTP站点获得。jit模块将创建/proc/currentime文件,它以ASCII码的形式返回它
公司的FTP站点获得。jit模块将创建/proc/currentime文件,它以ASCII码的形式返回它
读该文件的时间。我选择用动态的/proc文件,是因为这样模块代码量会小些-不值得为
返回两行文本而写一整个设备驱动程序。
如果你用cat命令在一个时钟滴答内多次读该文件,就会发现xtime和do_gettime
ofday两者的差异了:
morgana%cat /proc/currentime /proc/currentime /proc/currentime
gettime: 846157215.937221
xtime: 846157215.931188
jiffies: 1308094
gettime: 846157215.939950
xtime: 846157215.931188
jiffies: 1308094
gettime: 846157215.942465
xtime: 846157215.941188
jiffies: 1308095
延迟执行
使用定时器中断和jiffies值,时钟滴答的整数倍的时间间隔很容易获得,但更小的时延
,程序员必须通过软件循环来获得,这将在本节稍后处介绍。
尽管我将会介绍一些很奇特的技术,但我认为最好先看些简单的延迟实现代码,尽管下
面要介绍的第一种实现并不是最好的。
长延迟
如果你想将执行延迟几个时钟滴答或者你对延迟的精度要求不高(比如,你想延迟整数数
目的秒数),最简单的实现(最笨的)如下,也就是忙等待:
unsigned long j=jiffies+jit_delay*HZ;
while (jiffies<j)
/* 什么也不做 */
这种实现当然要避免*。我在这提到它只是因为有时你可以运行这段代码来更好地理解其
他实现(在本章稍后处我会说明如何利用忙等待来做测试)。
还是先看看这段代码是如何工作的。因为内核的头文件中jiffies被声明为volatile型变
量,每次C代码访问它时都会重新读取它,因此该循环可以起到延迟的作用。尽管也是“
正确”的实现,但这个忙等待循环在延迟期间会锁住整台计算机;因为调度器不会中断
运行在内核空间的进程。而且当前的内核实现为不可重入的,因此内核中的忙等待循环
将会锁住一台SMP机器的所有处理器。
更糟糕的是,如果在进入循环之前关闭了中断,jiffies值就不会得到更新,那么while
循环的条件就永真。你将不得不按下大红按钮(指冷启动)。
这种延迟和下面的几种延迟方法都在jit模块中实现了。由该模块创建的所有/proc/jit*
文件每次被读时都延迟整整1秒。如果你想测试忙等待代码,可以读/proc/jitbusy文件
,当该文件的read方法被调用时它将进入忙等待循环,延迟1秒;而象dd
if=/proc/jitbusy bs=1这样的命令每次读一个字符就要延迟1秒。
可以想见,读/proc/jitbusy文件会大大影响系统性能,因为此时计算机要到1秒后才能
运行其他进程。
更好的延迟方法如下,它允许其他进程在延迟的时间间隔内运行,尽管这种方法不能用
于实时任务或者其他时间要求很严格的场合:
while (jiffies<j)
schedule();
这个例子和下面各例中的变量j应是延迟到达时的jiffies值,在忙等待时一般就
是象这样使用的。
这种循环(可以通过读/proc/jitsched文件来测试它)延迟方法还不是最优的。系
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -