【Linux】内核定时器

在这里插入图片描述

🔥博客主页:PannLZ
🎋系列专栏:《Linux系统之路》


时间是继内存之后常用的资源之一。它用于执行几乎所有的事情:延迟工作、睡眠、调度、超时以及许多其他任务。

时间有两类,内核使用绝对时间来了解具体时间,也就是一天的日期和时间,而相对时间则被内核调度程序使用。

为了处理相对时间,内核依赖于被称作定时器的CPU功能(外设),从内核的角度来看,它被称为`内核定时器

内核定时器分为两个不同的部分:标准定时器高精度定时器

1.标准定时器

1.1标准定时器

标准定时器是内核定时器,它以Jiffy为粒度运行。

Jiffy:Jiffy是在<linux/jiffies.h>中声明的内核时间单位。

HZ:HZ是一个常量,代表Jiffy的大小。它是jiffies1s内递增的次数。每个增量被称为一个TickHZ取决于硬件和内核版本,也决定了时钟中断触发的频率

如果HZ =1000,则递增1000次(每1/1000s一次Tick)。定义之后,当可编程中断定时器(PIT,这是一个硬件组件)发生中断时,可以用该值编程PIT,以增加Jiffy值。

根据平台不同,j iffies可能会导致溢出。在32位系统中,HZ = 1000只会导致大约50天的持续时间,而在64位系统上持续时间大约是6亿年。将Jiffy存储在64位变量中就可以解决这个问题。<linux/j iffies.h>中引入和定义了另一个变量:

extern u64 jiffies_64;

/*32位系统上采用这种方式时,jiffies将指向低32位,jiffies_64将指向高位。在64位平台上,jiffies= jiffies_64。*/

1.2定时器API(初始化标准定时器):

/*定时器在内核中表示为timer_list的一个实例*/
#include <linux/timer.h>
struct timer_list {
		struct list_head entry;
		unsigned long expires;
		struct tvec_t_base_s *base;
		void (*function)(unsigned long);
		unsigned long data;
);
  • expires是以jiffies为单位绝对值,是定时器的到期时间,届时回调函数被调用
  • entry是双向链表
  • data是可选的,被传递给回调函数
  • function是回调函数

初始化定时器的步骤如下:

/*1.设置定时器,提供用户定义的回调函数和数据:*/
void setup_timer( struct timer_list *timer, \
void (*function)(unsigned long), \
unsigned long data);

/*2.设置(修改)过期时间。当定时器初始化时,需要在启动回调之前设置它的过期时间:
*如果定时器之前没有被添加(通过 add_timer 函数),那么 mod_timer 函数将会添加这个定时器
*返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。*/
int mod_timer( struct timer_list *timer,unsigned long expires);

/*3.释放定时器。定时器用过之后需要释放:*/
void del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);
/*del_timer_sync等待处理程序(即使是在另一个CPU上执行)执行完成。不应该持有阻止处理程序完成的锁,这样将导致死锁。*/

上面的1.2步可以用下面的方法代替:

/*定义定时器*/
struct timer_list timer; 

/*定时器回调函数*/
void function(unsigned long arg)
{
		[...]
/*如果需要定时器周期性运行的话就在回调函数里使用 mod_timer函数重新设置超时值并且启动定时器。*/
}

/* 初始化定时器*/
init_timer(&timer); 

/*不使用setup函数设置定时器各参数*/
timer.function = function; 
timer.expires=jffies + msecs_to_jiffies(7000);
timer.data = data; 

/*启动定时器,该函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行。*/
add_timer(&timer); 

应该在模块清理例程中释放定时器,这个函数检查是否有触发的定时器回调函数挂起

int timer_pending( const struct timer_list*timer);

标准定时器示例如下:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>

static struct timer_list my_timer;

void my_timer_callback( unsigned long data )
{
    pr_info( "%s called (%ld).\n", __FUNCTION__, jiffies );
}
 
static int __init my_init( void )
{
    int retval;
    pr_info("Timer module loaded\n");

    setup_timer( &my_timer, my_timer_callback, 0 );
    pr_info( "Setup timer to fire in 300ms (%ld)\n", jiffies );

    retval = mod_timer( &my_timer, jiffies + msecs_to_jiffies(300) );
    if (retval)
        pr_info("Timer firing failed\n");
 
    return 0;
}
 
static void my_exit( void )
{
    int retval;
    retval = del_timer( &my_timer );
    if (retval)
        pr_info("The timer is still in use...\n");

    pr_info("Timer module unloaded\n");
    return;
}

module_init(my_init);
module_exit(my_exit);
MODULE_DESCRIPTION("Standard timer example");
MODULE_LICENSE("GPL");

2.高精度定时器(HRT)

2.1高精度定时器(HRT)

标准的定时器不够精确,不适合实时应用。内核v2.6.16引入了高精度定时器,由内核配置中的CONFIG_HIGH_RES_TIMERS选项启用,其精度达到微秒(取决于平台,最高可达纳秒),而标准定时器的精度则为毫秒。

准定时器取决于HZ(因为它们依赖于jiffies),而HRT实现是基于ktime

在内核中HRT表示为hrtimer的实例:

#include <linux/hrtimer.h>

struct hrtimer {
		struct timerqueue_node node;
		ktime_t _softexpires;
		enum hrtimer_restart (*function)(structhrtimer *);
		struct hrtimer_clock_base *base;
		u8 state;
		u8 is_rel;
};

2.2初始化HRT

初始化HRT的步骤如下:

/*1.初始化hrtimer。hrtimer初始化之前,需要设置ktime,它代表持续时间。*/
void hrtimer_init( struct hrtimer *time,clockid_t which_clock,enum hrtimer_mode mode);

/*2.启动hrtimer。*/
int hrtimer_start( struct hrtimer *timer,ktime_t time,const enum hrtimer_mode mode);

/*3.取消hrtimer。可以取消定时器或者查看是否可能取消它*/
int hrtimer_cancel( struct hrtimer *timer);
int hrtimer_try_to_cancel(struct hrtimer *timer);

用下面的函数可以独立检查hrtimer的回调函数是否仍在运行

int hrtimer_callback_running(struct hrtimer *timer);

hrtimer_try_to_cancel内部调用hrtimer_callback_running

为了防止定时器自动重启,hrtimer回调函数必须返回HRTIMER_ NORESTART

在系统启用HRT的情况下,睡眠和定时器系统调用的精度不再依赖于jiffies,它们会像HRT一样精确。这就是有些系统不支持nanosleep()之类的原因。

3.动态Tick/tickless内核

使用之前的HZ选项,即使处于空闲状态,内核也会每秒中断HZ次以再次调度任务。如果将HZ设置为1000,则每秒会有1000次内核中断,阻止CPU长时间处于空闲状态,因此影响CPU的功耗。

无固定或预定义Tick的内核中,在需要执行某些任务之前禁用Tick。这样的内核称为Tickless(无Tick)内核。实际上,Tick激活是依
据下一个操作安排的,其正确的名字应该是动态Tick内核

内核负责系统中的任务调度,并维护可运行任务列 表(运行队列)。当没有任务需要调度时,调度器切换到空闲线程,它启用动态Tick的方法是,在下一个定时器过期前(新任务排队等待处理),禁用周期性Tick。
内核内部还维护一个任务超时列表(它知道什么时候要睡眠以及睡眠多久)。在空闲状态下,如果下一个Tick比任务列表超时中的最小超时更远,内核则使用该超时值对定时器进行编程。当定时器到期时,内核重新启用周期Tick并调用调度器,它调度与超时相关的任务。

通过以上方式,Tickless内核移除周期性Tick,节省电量。

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值