设备驱动常常需要延后一段时间执行一个特定片段的代码, 常常允许硬件完成某个任务. 在这一节我们涉及许多不同的技术来获得延后. 每种情况的环境决定了使用哪种技术最好; 我们全都仔细检查它们, 并且指出每一个的长处和缺点.

一件要考虑的重要的事情是你需要的延时如何与时钟嘀哒比较, 考虑到 HZ 的跨各种平台 的范围. 那种可靠地比时钟嘀哒长并且不会受损于它的粗粒度的延时, 可以利用系统时钟. 每个短延时典型地必须使用软件循环来实现. 在这 2 种情况中存在一个灰色地带. 在本 章, 我们使用短语" long " 延时来指一个多 jiffy 延时, 在一些平台上它可以如同几个 毫秒一样少, 但是在 CPU 和内核看来仍然是长的.

下面的几节讨论不同的延时, 通过采用一些长路径, 从各种直觉上不适合的方法到正确的 方法. 我们选择这个途径因为它允许对内核相关定时方面的更深入的讨论. 如果你急于找 出正确的代码, 只要快速浏览本节.

长延时

 

偶尔地, 一个驱动需要延后执行相对长时间 -- 多于一个时钟嘀哒. 有几个方法实现这类 延时; 我们从最简单的技术开始, 接着进入到高级些的技术.

忙等待

 

如果你想延时执行多个时钟嘀哒, 允许在值中某些疏忽, 最容易的( 尽管不推荐 ) 的实 现是一个监视 jiffy 计数器的循环. 这种忙等待实现常常看来象下面的代码, 这里 j1 是 jiffies 的在延时超时的值:

while (time_before(jiffies, j1)) cpu_relax();

对 cpu_relex 的调用使用了一个特定于体系的方式来说, 你此时没有在用处理器做事情. 在许多系统中它根本不做任何事; 在对称多线程(" 超线程" ) 系统中, 可能让出核心给 其他线程. 在如何情况下, 无论何时有可能, 这个方法应当明确地避免. 我们展示它是因 为偶尔你可能想运行这个代码来更好理解其他代码的内幕.

我们来看一下这个代码如何工作. 这个循环被保证能工作因为 jiffies 被内核头文件声 明做易失性的, 并且因此, 在任何时候 C 代码寻址它时都从内存中获取. 尽管技术上正 确( 它如同设计的一样工作 ), 这种忙等待严重地降低了系统性能. 如果你不配置你的内 核为抢占操作, 这个循环在延时期间完全锁住了处理器; 调度器永远不会抢占一个在内核 中运行的进程, 并且计算机看起来完全死掉直到时间 j1 到时. 这个问题如果你运行一个 可抢占的内核时会改善一点, 因为, 除非这个代码正持有一个锁, 处理器的一些时间可以 被其他用途获得. 但是, 忙等待在可抢占系统中仍然是昂贵的.

更坏的是, 当你进入循环时如果中断碰巧被禁止, jiffies 将不会被更新, 并且 while 条件永远保持真. 运行一个抢占的内核也不会有帮助, 并且你将被迫去击打大红按钮.

这个延时代码的实现可拿到, 如同下列的, 在 jit 模块中. 模块创建的这些 /proc/jit* 文件每次你读取一行文本就延时一整秒, 并且这些行保证是每个 20 字节. 如果你想测试 忙等待代码, 你可以读取 /proc/jitbusy, 每当它返回一行它忙-循环一秒.

为确保读, 最多, 一行( 或者几行 ) 一次从 /proc/jitbusy. 简化的注册 /proc 文件的 内核机制反复调用 read 方法来填充用户请求的数据缓存. 因此, 一个命令, 例如 cat

/proc/jitbusy, 如果它一次读取 4KB, 会冻住计算机 205 秒.

推荐的读 /proc/jitbusy 的命令是 dd bs=200 < /proc/jitbusy, 可选地同时指定块数 目. 文件返回的每 20-字节 的行表示 jiffy 计数器已有的值, 在延时之前和延时之后. 这是一个例子运行在一个其他方面无负担的计算机上:

phon% dd

bs=20 count=5 < /proc/jitbusy

1686518

1687518

1687519

1688519

1688520

1689520

1689520

1690520

1690521

1691521

看来都挺好: 延时精确地是 1 秒 ( 1000 jiffies ), 并且下一个 read 系统调用在上一 个结束后立刻开始. 但是让我们看看在一个有大量 CPU-密集型进程在运行(并且是非抢占 内核)的系统上会发生什么:

phon% dd

bs=20 count=5 < /proc/jitbusy

1911226

1912226

1913323

1914323

1919529

1920529

1925632

1926632

1931835

1932835

这里, 每个 read 系统调用精确地延时 1 秒, 但是内核耗费多过 5 秒在调度 dd 进程以 便它可以发出下一个系统调用之前. 在一个多任务系统就期望是这样; CPU 时间在所有运 行的进程间共享, 并且一个 CPU-密集型 进程有它的动态减少的优先级. ( 调度策略的讨 论在本书范围之外).

上面所示的在负载下的测试已经在运行 load50 例子程序中进行了. 这个程序派生出许多 什么都不做的进程, 但是以一种 CPU-密集的方式来做. 这个程序是伴随本书的例子文件 的一部分, 并且缺省是派生 50 个进程, 尽管这个数字可以在命令行指定. 在本章, 以及 在本书其他部分, 使用一个有负载的系统的测试已经用 load50 在一个其他方面空闲的计 算机上运行来进行了.

如果你在运行一个可抢占内核时重复这个命令, 你会发现没有显著差别在一个其他方面空 闲的 CPU 上以及下面的在负载下的行为:

phon% dd bs=20 count=5 < /proc/jitbusy 14940680 14942777

14942778 14945430

14945431

14948491

14948492

14951960

14951961

14955840

这里, 没有显著的延时在一个系统调用的末尾和下一个的开始之间, 但是单独的延时远远 比 1 秒长: 直到 3.8 秒在展示的例子中并且随时间上升. 这些值显示了进程在它的延时 当中被中断, 调度其他的进程. 系统调用之间的间隙不是唯一的这个进程的调度选项, 因 此没有特别的延时在那里可以看到.

05-21 17:30