曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 7993|回复: 3
打印 上一主题 下一主题

内核定时器

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-9-29 15:27:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
设备驱动程序免不了周期性地检测硬件的状态,或者控制了硬件后,再检测硬件是否正常运行,此时设备驱动程序就要具备超过指定时间便执行特定函数的功能。这些工作可以利用内核定时器来完成。

Linux 内核在发生了定时器中断后,会监测称为内核定时器目录的数据结构。在这个结构里,包含了将要运行的函数和与函数运行相关的时间信息。通过检查该结构体的时间域,如果发现某个函数超出了约定的时间,内核便调用该函数。函数运行完毕,便从内核定时器目录中删除掉包含该函数的结构体。内核定时器运行的函数在定时器中断服务函数中运行,与中断服务例程具有相同的特性,因此,采取与中断服务函数相同的形式显式内核定时器中注册的函数。

设备驱动程序中使用内核定时器时,会利用到下面的结构体和函数:
      struct timer_list  :  内核定时器结构体
      init_timer ()  :  初始化内核定时器结构体函数
      add_timer()  :  注册内核定时器运行的函数
      del_timer()   :  从内核定时器目录中删除相应结构体
内核与设备驱动程序的运行方法如下图所示:

在上图中,使用内核定时器时,利用 init_timer()  函数初始化定义的 struct timer_list 结构体类型的变量,在设置结束时间,运行函数,数据后,再利用 add_timer() 函数注册到内核上。内核中的 _run_timers() 函数管理由 add_timer() 函数注册的内核定时器,而该函数的运行则依赖于以 1/Hz 间隔发生的定时器周期性中断,它会比较内核定时器的结束时间与当前的 jiffies_64 值,大于或等于该值时运行定时器函数。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-9-29 16:30:43 | 只看该作者

struct timer_list 变量的初始化

为了把将要运行的函数注册到内核定时器目录上,需要使用包含函数处理信息的 struct timer_list 类型变量。定义了 #include <linux/timer> 后就可以使用 struct timer_list 结构体。 struct timer_list 结构体包含了多个域,但是多数用在内核内部。
下面是 struct timer_list 结构体的定义:
struct timer_list {
        struct list_head entry;
        unsigned long expires;

        void (*function)(unsigned long);
        unsigned long data;

        struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
        void *start_site;
        char start_comm[16];
        int start_pid;
#endif
};
其中,与设备驱动相关的几个域为 unsigned long expires;   unsigned long data;   void (*function) (unsigned long); 。

1、unsigned long expires
是函数运行的起点,即设定内核定时器的结束时间。2.4 内核中 expires 值 expires 值与 jiffies 进行比较,当大小相同时启动函数。同样地,2.6 内核中则与 jiffies_64 比较,因此,expires 值为注册内核定时器起点的 jiffies 值 (或 jiffies_64) 与以 Hz 单位计时的延迟时间的和。下面是 2.4 内核和 2.6 内核中经过 0.3s 后运行的方法。
      2.4 内核
struct timer_list kerneltimer;
kerneltime.expires = jiffies + (3*HZ/10);
      2.6 内核
kerneltime.expires = get_jiffies_64() + (3*HZ/10);

2、unsigned long data;
内核定时器中运行的函数在定时器中断点运行,因此要获取与函数处理相关的数据参考地址。此时,主要指定可参考数据的起始地址,其值由函数的第一个变量传送。

3、void (*function) (unsigned long);
expires 值为注册在内核定时器上的结构体时间域,该值的大小与 jiffies 值或 jiffies_64 相同时,运行 function 域中的定义的函数。该函数没有返回值,unsigned long 型变量上传送上面 2 中的 data 域。

为了初始化 struct timer_list 结构体,使用 init_timer() 函数,关于 init_timer() 函数见:
http://www.groad.net/bbs/read.php?tid-1227.html

该函数为了处理 list ,初始化 timer 指向的结构体变量,接着再具体初始化 timer 结构体中设备驱动程序需要的 expires, data, function 等域。 下面示例:
char mng_data [128];
struct timer_list timer;

void kerneltimer_handler (unsigned long arg)
{
   ...
}

xxx_timer_init (...)
{
    init_timer (&timer);
    timer.expires = get_jiffies_64() + (3*Hz/10);
    timer.data = (unsigned long) &mng_data[0];
    timer.function = kerneltimer_handler;
}

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2009-10-1 01:20:06 | 只看该作者

内核定时器的注册

注册内核定时器使用 add_timer() 函数。注册到 add_timer() 函数上的函数将会包含到今后要调用的被检测到对象目录中。通常,内核定时器的结构体经一次初始化就能继续使用,但是 expires 域受内核中 jiffies_64 的影响,必须重新设置。关于 add_timer() 函数:http://www.groad.net/bbs/read.php?tid-1228.html

add_timer() 不返回是否正常注册的返回值。内核定时器目录不是复制连接目录结构的结构体内容,而是修改结构体的连接信息。因此,add_timer() 函数不参与结构体变量的分配或取消操作。注册函数实例如下:
timer.expires = get_jiffies_64() + (3*Hz/10);
add_timer (&timer);

使用 add_timer() 时注意,即使注册了许多遍相同的内核定时器结构体,也不会多次调用。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
地板
 楼主| 发表于 2009-10-1 01:36:18 | 只看该作者

内核定时器的注销

注册了内核定时器后,超出约定时间,调用内核定时器上注册的函数后,注册的内核定时器会自动取消。但是,这并不是说不用人为取消内核定时器。

若内核定时器上注册的函数是由模块插入的设备驱动程序的函数,在删除模块后,仍然有可能调用到相应地址的函数,这是因为内核在调用函数之前并不检查函数的地址是否有效。基于此,设备驱动程序应该正确调用并删除注册的内核定时器,若是以模块结构创建的设备驱动程序,在设备驱动程序的结束例程上,调用 del_timer() 函数,从而会注销掉注册了的内核定时器。

关于 del_timer() 见:http://www.groad.net/bbs/read.php?tid-1229.html

通常,del_timer() 的返回值没有太大意义,因此,在设备驱动程序中也不会检查该返回值。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 00:27 , Processed in 0.109783 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表