禁用时钟中断,jiffies就不准了吗?

Linux驱动学习–时间、延迟及延缓操作

  延迟执行

  设备驱动常常需要延后一段时间执行一个特定片段的代码, 常常允许硬件完成某个任务.

  长延迟

  有时,驱动需要延后执行相对长时间,长于一个时钟嘀哒。

  忙等待(尽量别用)

  若想延迟执行若干个时钟嘀哒,精度要求不高。最容易的( 尽管不推荐 ) 实现是一个监视 jiffy 计数器的循环。这种忙等待实现的代码如下:

  while (time_before(jiffies, j1))

  cpu_relax();

  对 cpu_relex 的调用将以体系相关的方式执行,在许多系统中它根本不做任何事,这个方法应当明确地避免。对于ARM体系来说:

  #define cpu_relax() barrier()

  也就是说在ARM上运行忙等待相当于:

  while (time_before(jiffies, j1)) ;

  这种忙等待严重地降低了系统性能。如果未配置内核为抢占式, 这个循环在延时期间完全锁住了处理器,计算机直到时间 j1 到时会完全死掉。如果运行一个可抢占的内核时会改善一点,但是忙等待在可抢占系统中仍然是浪费资源的。更糟的是, 当进入循环时如果中断碰巧被禁止, jiffies 将不会被更新, 并且 while 条件永远保持真,运行一个抢占的内核也不会有帮助, 唯一的解决方法是重启。

  让出处理器

  忙等待加重了系统负载,必须找出一个更好的技术:不需要CPU时释放CPU 。 这可通过调用schedule函数实现(在 中声明):

  while (time_before(jiffies, j1)) {

  schedule();

  }

  在计算机空闲时运行空闲任务(进程号 0, 由于历史原因也称为swapper)可减轻处理器工作负载、降低温度、增加寿命。

  超时

  实现延迟的最好方法应该是让内核为我们完成相应的工作。

  (1)若驱动使用一个等待队列来等待某些其他事件,并想确保它在一个特定时间段内运行,可使用:

  #include

  long wait_event_timeout(wait_queue_head_t q, condition, long timeout);

  long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);

  /*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。如果超时,函数返回 0; 如果这个进程被其他事件唤醒,则返回以 jiffies 表示的剩余的延迟实现;返回值从不会是负值*/

  (2)为了实现进程在超时到期时被唤醒而又不等待特定事件(避免声明和使用一个多余的等待队列头),内核提供了 schedule_timeout 函数:

  #include

  signed long schedule_timeout(signed long timeout);

  /*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。一个典型调用如下:*/

  set_current_state(TASK_INTERRUPTIBLE);

  schedule_timeout (delay);

  短延迟

  当一个设备驱动需要处理硬件的延迟(latency潜伏期), 涉及到的延时通常最多几个毫秒,在这个情况下, 不应依靠时钟嘀哒,而是内核函数 ndelay, udelay和 mdelay ,他们分别延后执行指定的纳秒数, 微秒数或者毫秒数,定义在 ,原型如下:

  #include

  void ndelay(unsigned long nsecs);

  void udelay(unsigned long usecs);

  void mdelay(unsigned long msecs);

  重要的是记住这 3 个延时函数是忙等待; 其他任务在时间流失时不能运行。每个体系都实现 udelay, 但是其他的函数可能未定义; 如果它们没有定义, 提供一个缺省的基于 udelay 的版本。在所有的情况中, 获得的延时至少是要求的值, 但可能更多。udelay 的实现使用一个软件循环, 它基于在启动时计算的处理器速度和使用整数变量 loos_per_jiffy确定循环次数。

  为避免在循环计算中整数溢出, 传递给udelay 和 ndelay的值有一个上限,如果你的模块无法加载和显示一个未解决的符号:__bad_udelay, 这意味着你调用 udleay时使用太大的参数。

  作为一个通用的规则:若试图延时几千纳秒, 应使用 udelay 而不是 ndelay; 类似地, 毫秒规模的延时应当使用 mdelay 完成而不是一个更细粒度的函数。

  有另一个方法获得毫秒(和更长)延时而不用涉及到忙等待的方法是使用以下函数(在 中声明):

  void msleep(unsigned int millisecs);

  unsigned long msleep_interruptible(unsigned int millisecs);

  void ssleep(unsigned int seconds)

  若能够容忍比请求的更长的延时,应使用 schedule_timeout, msleep 或 ssleep。

  内核定时器

  当需要调度一个以后发生的动作, 而在到达该时间点时不阻塞当前进程, 则可使用内核定时器。内核定时器用来调度一个函数在将来一个特定的时间(基于时钟嘀哒)执行,从而可完成各类任务。

  内核定时器是一个数据结构, 它告诉内核在一个用户定义的时间点使用用户定义的参数执行一个用户定义的函数,函数位于 和 kernel/timer.c 。被调度运行的函数几乎确定不会在注册它们的进程在运行时运行,而是异步运行。实际上, 内核定时器通常被作为一个”软件中断”的结果而实现。当在进程上下文之外(即在中断上下文)中运行程序时, 必须遵守下列规则:

  (1)不允许访问用户空间;

  (2)current 指针在原子态没有意义;

  (3)不能进行睡眠或者调度. 例如:调用 kmalloc(…, GFP_KERNEL) 是非法的,信号量也不能使用因为它们可能睡眠。

  通过调用函数 in_interrupt()能够告知是否它在中断上下文中运行,无需参数并如果处理器当前在中断上下文运行就返回非零。

  通过调用函数 in_atomic()能够告知调度是否被禁止,若调度被禁止返回非零; 调度被禁止包含硬件和软件中断上下文以及任何持有自旋锁的时候。

  在后一种情况, current 可能是有效的,但是访问用户空间是被禁止的,因为它能导致调度发生. 当使用 in_interrupt()时,都应考虑是否真正该使用的是 in_atomic 。他们都在 中声明。

  内核定时器的另一个重要特性是任务可以注册它本身在后面时间重新运行,因为每个 timer_list 结构都会在运行前从激活的定时器链表中去连接,因此能够立即链入其他的链表。一个重新注册它自己的定时器一直运行在同一个 CPU.

  即便在一个单处理器系统,定时器是一个潜在的态源,这是异步运行直接结果。因此任何被定时器函数访问的数据结构应当通过原子类型或自旋锁被保护,避免并发访问。

  定时器 API

  内核提供给驱动许多函数来声明、注册以及删除内核定时器:

  #include

  struct timer_list {

  struct list_head entry;

  unsigned long expires;/*期望定时器运行的绝对 jiffies 值,不是一个 jiffies_**** 值,因为定时器不被期望在将来很久到时*/

  void (*function)(unsigned long); /*期望调用的函数*/

  unsigned long data;/*传递给函数的参数,若需要在参数中传递多个数据项,可以将它们捆绑成单个数据结构并且将它的指针强制转换为 unsiged long 的指针传入。这种做法在所有支持的体系上都是安全的并且在内存管理中相当普遍*/

  struct tvec_t_base_s *base;

  #ifdef CONFIG_TIMER_STATS

  void *start_site;

  char start_comm[16];

  int start_pid;

  #endif

  };

  /*这个结构必须在使用前初始化,以保证所有的成员被正确建立(包括那些对调用者不透明的初始化):*/

  void init_timer(struct timer_list *timer);

  struct timer_list TIMER_INITIALIZER(_function, _expires, _data);

  /*在初始化后和调用 add_timer 前,可以改变 3 个公共成员:expires、function和data*/

  void add_timer(struct timer_list * timer);

  int del_timer(struct timer_list * timer);/*在到时前禁止一个已注册的定时器*/

  int del_timer_sync(struct timer_list *timer); /* 如同 del_timer ,但还保证当它返回时, 定时器函数不在任何 CPU 上运行,以避免在 SMP 系统上竞态, 并且在 单处理器内核中和 del_timer 相同。这个函数应当在大部分情况下优先考虑。 如果它被从非原子上下文调用, 这个函数可能睡眠,但是在其他情况下会忙等待。当持有锁时要小心调用 del_timer_sync ,如果这个定时器函数试图获得同一个锁, 系统会死锁。如果定时器函数重新注册自己, 调用者必须首先确保这个重新注册不会发生; 这通常通过设置一个” 关闭 “标志来实现, 这个标志被定时器函数检查*/

  int mod_timer(struct timer_list *timer, unsigned long expires); /*更新一个定时器的超时时间, 常用于超时定时器。也可在正常使用 add_timer时在不活动的定时器上调用mod_timer*/

  int timer_pending(const struct timer_list * timer); /*通过调用timer_list结构中一个不可见的成员,返回定时器是否在被调度运行*/

  内核定时器的实现《LDD3》介绍的比较笼统,以后看《ULK3》的时候再细细研究。

  一个内核定时器还远未完善,因为它受到 jitter 、硬件中断,还有其他定时器和其他异步任务的影响。虽然一个简单数字 I/O关联的定时器对简单任务是足够的,但不合适在工业环境中的生产系统,对于这样的任务,你将最可能需要实时内核扩展(RT-Linux).



有关时钟中断,请多指教

陈老师:
您好,有幸拜读了您的《深入分析Linux内核源码》
一书。在读到时钟中断的时候有个疑惑老是缠绕着我。您在书中提到OS会每十一分钟去刷新RTC的时间,但OS的时钟计数是靠中断来进行的。但如果中断被长时间屏蔽的话,jiffies得不到该有的增加,再用它去刷新RTC的话,岂不是用错误的时间去同步正确的时间吗?希望您能看到我的留言。
[email protected]
顶部
[广告] 推荐个超酷的web2.0相册
陈莉君
版主
Rank: 7Rank: 7Rank: 7


UID 26540
精华 1
积分 2281
帖子 152
LUPA币 2203 点
阅读权限 100
注册 2006-11-9
    
#2
发表于 2008-4-5 11:13  资料 个人空间 短消息 
一般来说,RTC是OS 时钟的时间基准,操作系统通过读取RTC来初始化OS时钟,此后二者保持同步运行,共同维持着系统时间。保持同步运行是什么意思呢?就是指操作系统运行过程中,每隔一个固定时间(比如说每隔11分钟)会刷新或校正RTC中的信息。你担心的问题不会出现,因为每一个时钟中断jiffies的值都会增加1。

可以再仔细看看《ULK》第三版的第6章,定时测量。

[ 本帖最后由 陈莉君 于 2008-4-5 11:21 编辑 ]




透析真谛,似拨云穿雾;共享智慧,如春风沐浴
http://www.kerneltravel.net
顶部
[广告] 推荐个超酷的web2.0相册
lin_2005
关注开源
Rank: 2



UID 174342
精华 1
积分 104
帖子 4
LUPA币 100 点
阅读权限 20
注册 2008-4-4
    
#3
发表于 2008-4-6 20:47  资料 短消息 
仍有疑惑,请陈老师帮忙

我把定时测量这一章看了一下,仍然找不到问题的答案。
我的理解是
      只要时钟中断是可屏蔽的(通过8259A的任何一个引脚申请中断),那么中断的丢失是会有可能的(驱动程序又会经常关中断,这又增加丢失中断的概率)。若此时IRR寄存器的第0位为1(即IRQ0)CPU还没受理的情况下,8254又发送了一个中断请求,它也只是把IRR寄存器的第0位置1,对CPU来说应该无法知道之前置1的那个中断请求。虽然每一次时钟中断,中断服务程序都将jiffies加1,但如果要精确计时的话,应该要准确知道丢失的中断个数(如果有的话),这样才能准确计时,是不是这样子呢?如果是这样的话,软件时间是不可靠的,怎么会用它去写RTC呢?
     让我们想象这么一种极端的情况,若某个程序禁止外部中断,在那里死循环就是不开中断或是过一俩个钟头再开中断,这样即使8254不断地在IRQ0上发中断请求也不会得到受理,等到过了很久才开中断,再把jiffies加一,显然是不合适的.
     到底系统是靠哪种机制 正确计时的,请陈老师帮忙.再一次谢谢陈老师.
顶部
[广告] 推荐个超酷的web2.0相册
陈莉君
版主
Rank: 7Rank: 7Rank: 7


UID 26540
精华 1
积分 2281
帖子 152
LUPA币 2203 点
阅读权限 100
注册 2006-11-9
    
#4
发表于 2008-4-7 18:00  资料 个人空间 短消息 
内核中除了使用可编程间隔定时器PIT外,还用到了时间戳计数器TSC(64位),高精度事件定时器HPET和ACPI电源管理定时器。后三种保证精确性。其中有一个mark_offset方法检查自上一个节拍以来是否丢失时钟中断,如果丢失,则更新Jiffies_64。 



时钟中断也是可屏蔽中断, 我在自己的module中local_irq_disable之后,执行了大约90万次无意义的循环,然后local_irq_enable,发现jiffies跟禁止中断之前是一样的。(如果没有禁止中断,这90万次循环大约消耗3个jiffies) 

于是问题就来了:jiffies是不是所有CPU共享的?那每个CPU都是每个一个1/HZ秒就给jiffies增1, 感觉不对啊;如果是每个CPU维护自己的jiffies,那如果某个CPU禁止中断的时间比较长,会不会导致它的jiffies比别的CPU小?还是有某种平衡的策略? 

Sorry问了这么多,哪位大侠指点下?或者指点偶去看哪部分代码也行,谢谢:) 


--------------------
屠龙有技无人赏。

文章选项: 

daemeon
(enthusiast)
05-10-21 11:25
 Re: 时钟中断与jiffies问题请教  [re: albcamus]  

好像是时钟中断发生时,APIC负责将中断只发给一个CPU. 




--------------------
Just a newbie

文章选项: 

albcamus
(enthusiast)
05-10-21 11:32
 Re: 时钟中断与jiffies问题请教  [re: daemeon]  

时钟中断不是每个CPU都有的心跳吗?应该不是IO_APIC管的呀 


--------------------
屠龙有技无人赏。

文章选项: 

daemeon
(enthusiast)
05-10-21 11:48
 Re: 时钟中断与jiffies问题请教  [re: albcamus]  

时钟中断是指的PIT向中断线0发出的中断。 好像不是CPU里那个用来smp同步的timer发出的中断吧。 


--------------------
Just a newbie

文章选项: 

albcamus
(enthusiast)
05-10-21 12:00
 Re: 时钟中断与jiffies问题请教  [re: daemeon]  

>>时钟中断是指的PIT向中断线0发出的中断。 

你的意思是说,这个“中断线0” 是全局APIC上的? 
我很迷惑,PIT是APIC内部的吗?应该是每个CPU一个PIT啊,印象中所有online的CPU都应该以HZ的频率使得PIT产生中断。。 


--------------------
屠龙有技无人赏。

文章选项: 

daemeon
(enthusiast)
05-10-21 13:19
 Re: 时钟中断与jiffies问题请教  [re: albcamus]  

Each IBM-compatible PC includes at least one PIT, which is usually implemented by a 8254 CMOS chip using the 0x40-0x43 I/O ports. 
--ulk2 

>>印象中所有online的CPU都应该以HZ的频率使得PIT产生中断 
可能你把PIT和Local APIC timer搞混了。 


--------------------
Just a newbie

文章选项: 

albcamus
(enthusiast)
05-10-21 16:38
 Re: 时钟中断与jiffies问题请教  [re: daemeon]  

>> >>印象中所有online的CPU都应该以HZ的频率使得PIT产生中断 
>> 可能你把PIT和Local APIC timer搞混了。 

我印象中LKD说的:引导之后,PIT就以1/HZ的时间间隔触发时钟中断。 我们平常说的“时钟中断”,到底是哪一个时钟产生的?我把书放家里了,晚上回去查查。 


--------------------
屠龙有技无人赏。

文章选项: 

eexplorer
(stranger)
05-10-21 18:18
 Re: 时钟中断与jiffies问题请教  [re: albcamus]  

有两种时钟中断: 
1. 全局的时钟中断,整个系统只有一个,以前的机器用PIT,SMP 机器用IO-APIC的时钟中断。 
在这个中断中更新jiffies的值 

2. Local-APIC 时钟中断,每个cpu都有一个。在这个中断中,完成一些PER-CPU的工作,比如进程的运行时间计算,timer_list的调度 等等 

UP的机器只有一个PIT时钟中断,完成以上两个任务

文章选项: 

albcamus
(enthusiast)
05-10-21 19:15
 Re: 时钟中断与jiffies问题请教  [re: eexplorer]  

多谢兄弟!正好周末了,我再仔细看看相关资料,争取弄明白:) 


--------------------
屠龙有技无人赏。

文章选项: 

albcamus
(enthusiast)
05-10-23 13:54
 Re: 时钟中断与jiffies问题请教  [re: eexplorer]  


搞明白了,总结一下: 

1, PIT这个东西,就是以HZ/秒的频率触发时钟中断的时钟。它不是在CPU内部,而是在支持SMP的主板的CMOS中的。在UP机器上,如果你local_irq_disable()很长时间,必然会造成IRQ0无法处理,从而丢失jiffies。 UP的IRQ0处理程序和SMP的IRQ0处理程序有很大的不同。 

2, RTC是主板上的。 

3, TSC是自Pentium以后,每个CPU都有的。local APIC Timer也是这样。 


你可能感兴趣的:(数据结构,timer,list,struct,任务,linux内核)