Linux时间、定时器、时间中断超时处理

内核时间管理

Linux内核计时、延时函数与内核定时器

内核通过定时器(timer)中断来跟踪时间流
硬件定时器以周期性的间隔产生时间中断,这个间隔(即频率)由内核根据HZ来确定,HZ是一个与体系结构无关的常数。这个时间间隔通常取1ms到10ms.

jiffies计算器

2.1每次当定时器中断发生时,内核内部通过一个64位的变量jiffies_64做加一计数。

2.2驱动程序开发者通常访问的是jiffies变量,它是jiffes_64的低32位。

定时器与时间管理:

1、节拍率——HZ:在alpha体系结构上1024,而在其它平台上,都为10数量级倍。在嵌入式ARM上为100(2.6内核)。这个值的意义是什么呢,也就是在ARM平台上时钟中断100次,为一秒。一般的情况下编程者不要改变这个值,因为内核编很多代码都是有时间要求的,而且内核编写都在很多地方都做了相应的优化与折衷处理,改变HZ的值会对系统的性能有很大的影响。

2、jiffies:这个值是用来记录系统自系统启动以来产生的节拍的总数,启动时,内核将这个变量初始化为0;在每次的时钟中断处理程序都会增加该变量的值,jiffies一秒内增加的值就是HZ,系统运行时间以秒为单位计算,则为系统运行了jiffies/HZ秒。

在如下定义(2.6内核):

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

在2.6的内核中它的变量类型从无符号长整型变为了u64,也就是说,即使是32位的机器,也使用无符号的64位整型表示jiffies。大多数的代码只涉及jiffies的低32位,访问jiffies的代码中会读到jiffies_64的低32位,可以通过get_ jiffies_64()函数读取整个64位。在64位的体系结构中jiffies_64和jiffies指的同一个变量,代码可以既可以通过jiffies也可以通过get_ jiffies_64()读取。

内核超时处理

jiffies 计数器
定时器中断由系统定时硬件以规律地间隔产生; 这个间隔在启动时由内核根据 HZ 值来编程, HZ 是一个体系依赖的值, 每次发生一个时钟中断, 一个内核计数器的值递增. 这个计数器在系统启动时初始化为 0, 因此它代表从最后一次启动以来的时钟嘀哒的数目.
这个计数器和来读取它的实用函数位于 , 尽管你会常常只是包含 ,

#include
unsigned long j, stamp_1, stamp_half, stamp_n;

j = jiffies; /* read the current value /
stamp_1 = j + HZ; /
1 second in the future /
stamp_half = j + HZ/2; /
half a second /
stamp_n = j + n * HZ / 1000; /
n milliseconds */

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

while (time_before(jiffies, j1)){}

超时
到目前为止所展示的次优化的延时循环通过查看 jiffy 计数器而不告诉任何人来工作. 但是最好的实现一个延时的方法, 如你可能猜想的, 常常是请求内核为你做. 有 2 种方法来建立一个基于 jiffy 的超时, 依赖于是否你的驱动在等待其他的事件.
如果你的驱动使用一个等待队列来等待某些其他事件, 但是你也想确保它在一个确定时间段内运行, 可以使用 wait_event_timeout 或者wait_event_interruptible_timeout:

#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 表示)到后返回. 因此, 它们实现一个限定的睡眠不会一直睡下去. 注意超时值表示要等待的jiffies 数, 不是一个绝对时间值. 这个值由一个有符号的数表示, 因为它有时是一个相减运算的结果, 尽管这些函数如果提供的超时值是负值通过一个printk 语句抱怨. 如果超时到, 这些函数返回 0; 如果这个进程被其他事件唤醒, 它返回以 jiffies 表示的剩余超时值. 返回值从不会是负值, 甚至如果延时由于系统负载而比期望的值大.

wait_event_timeout 和 wait_event_interruptible_timeout 被设计为有硬件驱动存在, 这里可以用任何一种方法来恢复执行: 或者有人调用 wake_up 在等待队列上, 或者超时到. 这不适用于 jitqueue, 因为没人在等待队列上调用 wake_up ( 毕竟, 没有其他代码知道它 ), 因此这个进程当超时到时一直唤醒. 为适应这个特别的情况, 这里你想延后执行不等待特定事件, 内核提供了 schedule_timeout 函数, 因此你可以避免声明和使用一个多余的等待队列头:

#include
signed long schedule_timeout(signed long timeout);
这里, timeout 是要延时的 jiffies 数. 返回值是 0 除非这个函数在给定的 timeout 流失前返回(响应一个信号). schedule_timeout 请求调用者首先设置当前的进程状态, 因此一个典型调用看来如此:

set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);
第一行调用 set_current_state 来设定一些东西以便调度器不会再次运行当前进程, 直到超时将它置回 TASK_RUNNING 状态. 为获得一个不可中断的延时, 使用 TASK_UNINTERRUPTIBLE 代替.

(1)内核中的时间概念

时间管理在linux内核中占有非常重要的作用。 
相对于事件驱动而言,内核中有大量函数是基于时间驱动的。 
有些函数是周期执行的,比如每10毫秒刷新一次屏幕; 
有些函数是推后一定时间执行的,比如内核在500毫秒后执行某项任务。 
要区分: 
*绝对时间和相对时间 
*周期性产生的事件和推迟执行的事件 
周期性事件是由系统系统定时器驱动的 

(2)HZ值
内核必须在硬件定时器的帮助下才能计算和管理时间。
定时器产生中断的频率称为节拍率(tick rate)。
在内核中指定了一个变量HZ,内核初始化的时候会根据这个值确定定时器的节拍率。
HZ定义在<asm/param.h>,在i386平台上,目前采用的HZ值是1000。
也就是时钟中断每秒发生1000次,周期为1毫秒。即:
#define HZ 1000

注意!HZ不是个固定不变的值,它是可以更改的,可以在内核源代码配置的时候输入。 
不同的体系结构其HZ值是不一样的,比如arm就采用100。 
如果在驱动中要使用系统的中断频率,直接使用HZ,而不要用100或1000 


a.理想的HZ值 
    i386的HZ值一直采用100,直到2.5版后才改为1000。 
    提高节拍率意味着时钟中断产生的更加频繁,中断处理程序也会更频繁地执行。 

    带来的好处有: 
    *内核定时器能够以更高的频率和更高的准确度运行 
    *依赖定时器执行的系统调用,比如poll()和select(),运行的精度更高 
    *提高进程抢占的准确度 
    (缩短了调度延时,如果进程还剩2ms时间片,在10ms的调度周期下,进程会多运行8ms。 
    由于耽误了抢占,对于一些对时间要求严格的任务会产生影响) 

    坏处有: 
    *节拍率要高,系统负担越重。 
    中断处理程序将占用更多的处理器时间。 

(3)jiffies
全局变量jiffies用于记录系统启动以来产生的节拍的总数。
启动时,jiffies初始化为0,此后每次时钟中断处理程序都会增加该变量的值。
这样,系统启动后的运行时间就是jiffies/HZ秒

jiffies定义于<linux/jiffies.h>中: 
extern unsigned long volatile jiffies; 

jiffies变量总是为unsigned long型。 
因此在32位体系结构上是32位,而在64位体系上是64位。 
对于32位的jiffies,如果HZ为1000,49.7天后会溢出。 
虽然溢出的情况不常见,但程序在检测超时时仍然可能因为回绕而导致错误。 
linux提供了4个宏来比较节拍计数,它们能正确地处理节拍计数回绕。 

#include <linux/jiffies.h>
#define time_after(unknown, known) // unknow > known
#define time_before(unknown, known) // unknow < known
#define time_after_eq(unknown, known) // unknow >= known
#define time_before_eq(unknown, known) // unknow <= known
unknown通常是指jiffies,known是需要对比的值(常常是一个jiffies加减后计算出的相对值)

例: 

unsigned long timeout = jiffies + HZ/2; /* 0.5秒后超时 /

if(time_before(jiffies, timeout)){
/
没有超时,很好 /
}else{
/
超时了,发生错误 */

time_before可以理解为如果在超时(timeout)之前(before)完成 


*系统中还声明了一个64位的值jiffies_64,在64位系统中jiffies_64和jiffies是一个值。 
可以通过get_jiffies_64()获得这个值。 

*使用 

1
2
u64 j2;
j2 = get_jiffies_64();

(4)获得当前时间
驱动程序中一般不需要知道墙钟时间(也就是年月日的时间)。但驱动可能需要处理绝对时间。
为此,内核提供了两个结构体,都定义在<linux/time.h>:

a.

struct timeval {
time_t tv_sec; /* seconds /
suseconds_t tv_usec; /
microseconds */
};
较老,但很流行。采用秒和毫秒值,保存了1970年1月1日0点以来的秒数

b.

struct timespec {
time_t tv_sec; /* seconds /
long tv_nsec; /
nanoseconds */
};
较新,采用秒和纳秒值保存时间。

c.do_gettimeofday() 
    该函数用通常的秒或微秒来填充一个指向struct timeval的指针变量,原型如下: 

#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
d.current_kernel_time()
该函数可用于获得timespec

#include <linux/time.h>
struct timespec current_kernel_time(void);

确定时间的延迟执行
设备驱动程序经常需要将某些特定代码延迟一段时间后执行,通常是为了让硬件能完成某些任务。
长于定时器周期(也称为时钟嘀嗒)的延迟可以通过使用系统时钟完成,而非常短的延时则通过软件循环的方式完成

(1)短延时
对于那些最多几十个毫秒的延迟,无法借助系统定时器。
系统通过软件循环提供了下面的延迟函数:
内核时间管理
(1)内核中的时间概念
时间管理在linux内核中占有非常重要的作用。
相对于事件驱动而言,内核中有大量函数是基于时间驱动的。
有些函数是周期执行的,比如每10毫秒刷新一次屏幕;
有些函数是推后一定时间执行的,比如内核在500毫秒后执行某项任务。
要区分:
*绝对时间和相对时间
*周期性产生的事件和推迟执行的事件
周期性事件是由系统系统定时器驱动的

(2)HZ值
内核必须在硬件定时器的帮助下才能计算和管理时间。
定时器产生中断的频率称为节拍率(tick rate)。
在内核中指定了一个变量HZ,内核初始化的时候会根据这个值确定定时器的节拍率。
HZ定义在<asm/param.h>,在i386平台上,目前采用的HZ值是1000。
也就是时钟中断每秒发生1000次,周期为1毫秒。即:
#define HZ 1000

注意!HZ不是个固定不变的值,它是可以更改的,可以在内核源代码配置的时候输入。 
不同的体系结构其HZ值是不一样的,比如arm就采用100。 
如果在驱动中要使用系统的中断频率,直接使用HZ,而不要用100或1000 


a.理想的HZ值 
    i386的HZ值一直采用100,直到2.5版后才改为1000。 
    提高节拍率意味着时钟中断产生的更加频繁,中断处理程序也会更频繁地执行。 

    带来的好处有: 
    *内核定时器能够以更高的频率和更高的准确度运行 
    *依赖定时器执行的系统调用,比如poll()和select(),运行的精度更高 
    *提高进程抢占的准确度 
    (缩短了调度延时,如果进程还剩2ms时间片,在10ms的调度周期下,进程会多运行8ms。 
    由于耽误了抢占,对于一些对时间要求严格的任务会产生影响) 

    坏处有: 
    *节拍率要高,系统负担越重。 
    中断处理程序将占用更多的处理器时间。 

(3)jiffies
全局变量jiffies用于记录系统启动以来产生的节拍的总数。
启动时,jiffies初始化为0,此后每次时钟中断处理程序都会增加该变量的值。
这样,系统启动后的运行时间就是jiffies/HZ秒

jiffies定义于<linux/jiffies.h>中: 
extern unsigned long volatile jiffies; 

jiffies变量总是为unsigned long型。 
因此在32位体系结构上是32位,而在64位体系上是64位。 
对于32位的jiffies,如果HZ为1000,49.7天后会溢出。 
虽然溢出的情况不常见,但程序在检测超时时仍然可能因为回绕而导致错误。 
linux提供了4个宏来比较节拍计数,它们能正确地处理节拍计数回绕。 

#include <linux/jiffies.h>
#define time_after(unknown, known) // unknow > known
#define time_before(unknown, known) // unknow < known
#define time_after_eq(unknown, known) // unknow >= known
#define time_before_eq(unknown, known) // unknow <= known
unknown通常是指jiffies,known是需要对比的值(常常是一个jiffies加减后计算出的相对值)

例: 

unsigned long timeout = jiffies + HZ/2; /* 0.5秒后超时 /

if(time_before(jiffies, timeout)){
/
没有超时,很好 /
}else{
/
超时了,发生错误 */

time_before可以理解为如果在超时(timeout)之前(before)完成 


*系统中还声明了一个64位的值jiffies_64,在64位系统中jiffies_64和jiffies是一个值。 
可以通过get_jiffies_64()获得这个值。 

*使用 

1
2
u64 j2;
j2 = get_jiffies_64();

(4)获得当前时间
驱动程序中一般不需要知道墙钟时间(也就是年月日的时间)。但驱动可能需要处理绝对时间。
为此,内核提供了两个结构体,都定义在<linux/time.h>:

a.

struct timeval {
time_t tv_sec; /* seconds /
suseconds_t tv_usec; /
microseconds */
};
较老,但很流行。采用秒和毫秒值,保存了1970年1月1日0点以来的秒数

b.

struct timespec {
time_t tv_sec; /* seconds /
long tv_nsec; /
nanoseconds */
};
较新,采用秒和纳秒值保存时间。

c.do_gettimeofday() 
    该函数用通常的秒或微秒来填充一个指向struct timeval的指针变量,原型如下: 

#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
d.current_kernel_time()
该函数可用于获得timespec

#include <linux/time.h>
struct timespec current_kernel_time(void);

确定时间的延迟执行
设备驱动程序经常需要将某些特定代码延迟一段时间后执行,通常是为了让硬件能完成某些任务。
长于定时器周期(也称为时钟嘀嗒)的延迟可以通过使用系统时钟完成,而非常短的延时则通过软件循环的方式完成

(1)短延时
对于那些最多几十个毫秒的延迟,无法借助系统定时器。
系统通过软件循环提供了下面的延迟函数:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux内核定时器是内核用于在未来某个时间点或者特定时间段内调度执行某个函数的一种机制。它是一个软定时器,最终依赖于CPU的硬件定时器实现。对于Linux内核来说,它依赖于系统时钟节拍。内核定时器处理函数在软中断中执行。它有几个特点:依赖于系统时钟节拍、只执行一次,超时后即退出。如果需要周期性的定时器,需要在超时处理函数中重新开启定时器。在Linux内核编程中常常会使用定时器,例如在驱动程序中使用定时器解决按键消抖、延时等待硬件就绪等问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【Linux驱动编程】如何使用内核定时器](https://blog.csdn.net/qq_20553613/article/details/106028620)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [【嵌入式Linux驱动开发】十四、了解Linux内核定时器使用流程,实现LED闪烁](https://download.csdn.net/download/weixin_38664427/14883898)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值