第11章 定时器和时间管理

这篇具有很好参考价值的文章主要介绍了第11章 定时器和时间管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第11章 定时器和时间管理

相对于事件驱动而言,内核中有大量的函数基于时间驱动的。有些函数周期执行,对调度程序中的运行队列进行平衡调整或对屏幕进行刷新。有些函数需要等待一个相对函数后才运行。
周期性产生的事件都是由系统定时器驱动的,系统定时器是一个硬件,以固定频率产生中断。
动态定时器,一种用来推迟执行程序的工具。

内核中的时间概念

内核必须在硬件的帮助下才能计算和管理时间。
硬件为内核提供了一个系统定时器,可看成电子时钟资源,比如数字时钟或处理器频率。
系统定时器以某种频率自行触发(击中(hitting)或者射中(poping))时钟中断。该频率可以通过编程预定,称作节拍率(tick rate)。
节拍率对内核可知,两次时钟中断的间隔时间是可知的,称为节拍(tick),等于节拍率分之一秒。
大量内核的函数都离不开时间的控制,如更新系统时间、更新实际时间、均衡运动队列,有些工作在每次时钟中断都要去处理,有些工作是n次时钟中断才执行一次。

节拍率:HZ

静态宏预定义的<asm/param.h>,在系统启动的时候按照HZ对硬件进行设置。
HZ不是一个固定不变的值,大多数体系结构都是可调的。

理想的HZ值

高HZ意味着时钟中断产生的更加频繁,可以提高时钟驱动事件的解析度(resolution),提高时间驱动事件的准确度(accuracy)。

高HZ的优势

  • 内核定时器更高的频度和更高的准确度。
  • 依赖定时器的系统调用,如poll()和select(),能够以更高的精度运行。
  • 对资源消耗和运行时间等的测量有更精细的解析度。
  • 提高进程抢占的准确度。

高HZ的劣势

中断频率越高,意味着系统负但越重。
内核支持无节拍的操作,CONFIG_HZ配置选项,系统会动态调度时钟中断, 如果一个时钟频率被设置为3ms,就没3ms触发一次中断,如果接下来的50ms内无事可做,就50ms重新调度时钟中断,减少了系统的能耗,省电。

jiffies

全局变量jiffies用来记录自系统起来以后产生的节拍的总数。
内核将该变量初始化为0,每次时钟中断都会增加该变量的值。实际情况稍微复杂些:内核给jiffies赋一个特殊的值,引起这个变量的不断溢出,由此捕捉bug。当找到实际的jiffies值后就把这个偏差减去。

/* <linux/jiffies.h> */
extern unsigned long volatile jiffies;

jiffies的内部表示

jiffies变量总是无符号长整数(unsigned long),因此在32位上是32位,在64位上是64位。
32位的jiffies变量,在时钟频率为100HZ的情况下,497天后会溢出。64位的jiffies变量溢出时间是32位的232倍!
由于性能和历史的原因,考虑到与现有代码的兼容性,内核开发者依然希望jiffes为unsigned long。
第二个变量也定义在<linux/jiffies.h>中:

extern u64 jiffies_64;

ld(1)脚本用于链接主内核镜像(在x86上位与arch/x86/kernel/vmlinux.lds.S),然后用jiffies_64变量的初值覆盖jiffies变量:

jiffies = jiffies_64; 

代码可以完全像以前一样继续访问jiffies,大多数代码只关心时间的流失,32位已经足够。时间管理代码仍然使用整个64位。

jiffies的回绕

jiffies在达到最大值后溢出,回绕(wrap around)到0。
内核提供了宏来帮助比较节拍计数。

/* <linux/jiffies.h> */
#define time_after(unknown,known) ((long)(known)-(long)(unknown)<0))
#define time_before(unknown,known) ((long)(unknown)-(long)(unknown)<0)
#define time_after_eq(unknown,known) ((long)(unknown)-(long)(unknown)>=0)
#define time_before_eq(unknown,known) ((long)(known)-(long)(unknown)>=0))

用户空间和HZ

USER_HZ来代表内核空间看到的HZ值。
内核可以使用函数jiffies_to_clock_t()(定义在kernel/time.c中),将一个由HZ表示的节拍数转换成一个由USER_HZ表示的节拍计数。

硬时钟和定时器

实时时钟

实时时钟(RTC)是持久用来存放系统时间的设备。
当系统启动后,内核读取RTC初始化时间,初始化xtime变量。

系统定时器

根本思想,提供一种周期性触发中断机制。

时钟中断处理程序

可以划分两个部分,体系结构相关部分和体系结构无关部分。
最低限度的工作:

  • 获得xtime_lock锁,访问jiffies_64和保护xtime。
  • 需要时应答和设置系统时钟。
  • 周期性使用墙上时间更新实时时钟。
  • 调用和体系无关的时钟例程,tick_periodic()。

和体系结构无关工作tick_periodic():

  • jiffies_64 + 1
  • 更新资源消耗的统计值,如当前进程所消耗的系统时间和用户时间。
  • 执行sheduler_tick()函数。
  • 更新墙上时间,存放在xtime中。
  • 计算平均负载值。
/* 和体系无关的执行函数 */
static void tick_periodic(int cpu)
{
	if(tick_do_timer_cpu == cpu)
	{
		write_seqlock(&xtime_lock);
		/* 记录下一个节拍事件 */
		tick_next_period = ktime_add(tick_next_period,tick_period);
		do_timer(1);
		write_sequnlock(&xtime_lock);
	}
	update_process_times(user_mode(get_irq_regs()));
	prifile_tick(CPU_PROFILING);
}
/* 增加jiffies_64 */
void do_timer(unsigned long ticks)
{
	jiffies_64 += ticks;
	update_wall_time();  /* 根据流逝的时间更新墙上时钟 */
	calc_global_load();	 /* 更新系统的平均负载统计值 */
}

/* 更新所耗费的各种节拍数,通过user_tick区别花费在用户空间还是在内核空间 */
void update_process_times(int user_tick)
{
	struct task_struct *p=current;
	int cpu = smp_processor_id();
	account_process_tick(p,user_tick);
	run_local_timers();
	rcu_check_callbacks(cpu,user_tick);
	printk_tick();
	scheduler_tick();
	run_posix_cpu_timers(p);
}
/* 对进程的时间进行实质性更新 
	计数时根据中断发生时所处的模式进行分类统计的,把上一个节拍全部算给了进程,不是很准确
	但也是没有办法的事情,所以要采用更高频率
*/
void account_process_tick(struct task_struct *p, int user_tick)
{
	cputime_t one_jiffy_scaled = cputime_to_scaled(cputime_one_jiffy);
	struct rq *rq = this_rq();
	if(user_tick)
		account_user_time(p,cputime_one_jiffy,one_jiffy_scaled);
	else if((p!= rq->idle)||(irq_count()!=HAEDIRQ_OFFSET))
		account_system_time(p,HARDIRQ_OFFSET,cputime_one_jiffy,one_jiffy_scaled);
	else
		account_idle_time(cputime_one_jiffy);
		
}
run_lock_timers()标记一个软中断去处理所有到期的定时器。
scheduler_tick()负责减少当前进程的时间片计数值,并在需要时设置need_resched标志,对于SMP,还要平衡运行队列。

实际时间

当前实际时间(墙上时间),定义在kernel/time/timekeeping.c中:

struct timespec xtime;
/*
	xtime.tv_sec存放自1970年1月1日(UTC,纪元)以来的时间。
	xtime.tvnsec记录自上一秒开始的ns数。
*/

读写xtime变量需要使用xtime_lock锁,该锁是一个seqlock。
更新要申请锁:

write_seqlock(&xtime_lock);
/* update xtime... */
write_sequnlock(&xtime_lock);

读取时也要使用read_seqbegin()和read_seqretry()函数:

/* 确保读取过程中没有写数据介入 */
unsigned long seq;
do{
	unsigned long lost;
	seq = read_seqbegin(&xtime_lock);
	usec = timer->get_offset();
	lost = jiffies-wall_jiffies;
	if(lost)
		usec += lost*(1000000/HZ);
	sec = xtime.tv_sec;
	usec += (xtime.tv_nsec/1000);
}while(read_seqretry(&xtime_lock,seq));

定时器

定时器使用步骤,初始化,设置一个超时时间,指定超时发生后执行的函数,然后激活定时器就可以了。定时器并不周期运行,在超时后就自行撤销。

使用定时器

内核定时器由结构timer_list表示,定义在<linux/timer.h>中。
内核提供的接口位于<linux/timer.h>中,大多数接口在文件kernel/timer.c中实现。

  1. 定义
struct timer_list my_timer;
  1. 初始化
init.timer(&my_timer);

my_timer.expires = jiffies+delay;	/*超时时间,节拍为单位*/
my_timer.data=0;	/*  */
my_timer.function=my_function; /* void my_timer_function(unsigned long data) */
  1. 激活
add_timer(&my_timer);

内核保证不会在超时时间前执行处理函数,但是有可能延误定时器的执行,不能用定时器来实现任何硬实时任务。
4. 修改

mod_timer(&my_timer,jiffies+new_delay); /* 新的定时值,也会激活未激活的定时器 */
  1. 超时之前删除(已经超时的会自动删除)
del_timer(&my_timer);

存在竞争条件,定义器中断可能已经触发,要等待其他处理器上的 定时器处理程序退出。

del_timer_sync(&my_timer); /* 不能在中断上下文中使用 */

定时器竞争条件

定时器于当前执行的代码是异步的,可能存在竞争条件。应该重点保护定时器中断处理程序中的共享数据。

实现定时器

内核在时钟中断发生后执行定时器,定时器作为软中断在下半部上下文中执行。
内核采用分组定时器加快定时器的搜索。

延迟执行

除了定时器和下半部机制以外,还有其他方式来推迟任务。通常是等待硬件完成某些工作时,比如重新设置网卡的以太模式需要花费2ms。

忙等待

最简单的是忙等待(通常最不理想的办法),延时节拍的整数倍或者精确率要求不高时使用。
更好的方法应该是应该在代码等待时,允许内核重新调度执行其他任务(cond_resched())。因为需要调度程序,不能在中断上下文中使用,只能在进程上下文中使用。

短延迟

延迟短暂,时间精确。
<linux/delay.h>
<asm/delay.h>

void udelay(unsigned long usecs);
void ndelay(unsigned long nsecs);
void mdelay(unsigned long msecs);

使用忙循环实现,延时精确,主要用在延时小的地方。

schedule_timeout()

更理想的延迟执行方法是让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回任务队列。

/* 将任务设置为可中断睡眠状态 */
set_current_state(TASK_INTERRUPTIBLE);
/* 小睡一会,“s”秒后唤醒 */
schedule_timeout(s*HZ);

因为任务处于可中断状态,任务收到信号将被唤醒。如果睡眠任务不想被唤醒,可以设置为TASK_UNINTERRUPTIBLE。调用schedule_timeout之前必须设置为这两个状态,否则不会睡眠。
schedule_timeout需要重新调度程序,所以调用他的代码必须保证能够睡眠。简而言之,调用代码必须处于进程上下文中,且不能持有锁。

  1. schedule_timeout的实现
    用法相当简单,是内核定时器的一个简单应用。
signed long schedule_timeout(signed long timeout)
{
	timer_list tiemr;
	unsigned long expire;
	switch(timeout)
	{
		case MAX_SCHEDULE_TIMEOUT:
			schedule();
			goto out;
		default:
			if(timeout<0)
			{
				printk(KERN_ERR"schedule_timeout:wrong timeout""value %lx from %p\n",timeout,__builtin_return_address(0));
				current->state = TASK_RUNING;
				goto_out
			}
	}
	expire = timeout + jiffies;
	init_timer(&timer);
	timer.expires=expire;
	timer.data=(unsigned long)current; /* 这里的data用来唤醒 */
	tiemr.function=process_timeout;
	add_timer(&timer);
	schedule();
	del_timer_sync(&timer);
	timeout=expire - jiffies;
out:
	return timeout<0?0:timeout;

当定时器超时时,process_timeout()函数会被调用,该函数将任务设置为TASK_RUNING状态,然后将其放入运行队列:文章来源地址https://www.toymoban.com/news/detail-508734.html

void process_timeout(unsigned long data)
{
	wake_up_process((task_t*)data);
}
  1. 设置超时时间,在等待队列上睡眠。

到了这里,关于第11章 定时器和时间管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 学习笔记|定时器|STC中断|定时器时间计算|STC32G单片机视频开发教程(冲哥)|第十一集:定时器的作用和意义

    什么是定时器:定时器-与非网 上节课的一段代码: TimeCount++然后一个延时1毫秒,每运行1ms,变量就会加一。 系统已经运行了多少个毫秒。 实际使用时的代码如下, 判断按键有沿有按下的时候,我们等待按键松开,还有一个while循环。 如果没有松开,会一直死在这一行。所以,

    2024年02月09日
    浏览(65)
  • STM32定时器输入捕获测量高电平时间

    本篇内容要求读者对STM32通用定时器有一点理解,如有不解,请看 夜深人静学32系列15——通用定时器 输入捕获是STM32通用定时器的一种功能,可以捕获特定引脚的电平变化(上升沿/下降沿) 对于一个变化的信号。只需要测量上升沿与下降沿的时间间隔,即可计算出高电平的

    2024年02月21日
    浏览(42)
  • C++ 实现定时器的两种方法(线程定时和时间轮算法修改版)

    定时器要求在固定的时间异步执行一个操作,比如boost库中的boost::asio::deadline_timer,以及MFC中的定时器。也可以利用c++11的thread, mutex, condition_variable 来实现一个定时器。 1、使用C++11中的thread, mutex, condition_variable来实现一个定时器。 注:此算法会每一个任务创建一个线程,不推

    2024年02月05日
    浏览(50)
  • STM32 定时器时间设定及计算最简单理解

    玩了一段时间STM32,没有经过系统学习,对这个定时器定时时间计算理解很懵懂! 如下面定时器初始化代码 TIM_Period=自动装载值 TIM_Prescaler=预分频值 定时时间计算: 定时时间=(TIM_Period)X(TIM_Prescaler)/Timer clocks(定时器时钟频率) 假如:TIM_Period=5000,TIM_Prescaler=84,Timer clocks=84Mhz

    2024年02月08日
    浏览(45)
  • 单片机学习 11-中断系统(定时器中断+外部中断)

    ​ 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的,中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一,是我们学习单片机必须要掌握的。很多初学者被困在中断中,学了很久仍然不知道中断究竟是

    2024年02月05日
    浏览(51)
  • Linux学习第21天:Linux内核定时器驱动开发: 流淌的时间长河

    Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长           在人类的发展进化中,时间是一个非常重要神秘的物质量。任何事物都是在时间的长河中流淌发生、发展、变化。我们进行驱动开发中对时间的定义和使

    2024年02月07日
    浏览(43)
  • 【理论】STM32定时器时间计算公式 +【实践】TIM中断1s计时一次

     前言:定时器TIM的详细知识点见我的博文:11.TIM定时中断-CSDN博客 公式解释: ARR(TIM_Period):自动重装载值,是定时器溢出前的计数值 PSC(TIM_Prescaler):预分频值,是用来降低定时器时钟频率的参数 Tclk:定时器的输入时钟频率(单位Mhz),通常为系统时钟频率或者定时

    2024年02月03日
    浏览(60)
  • STM32F407高级定时器-死区时间研究-STM32CubeMX

    距离上次写笔记,已经过去好长时间了 中间也折腾过不少东西,但是都没咋整理,主要是这中间都是在干活儿,不是自己想要研究的,也没想着要写。 从去年10月份开始想要学习FOC,10月份研究了一个月,到11月初,实现了SVPWM驱动BLDC电机,使用串口实现开环下转速和力矩调

    2023年04月23日
    浏览(54)
  • 04-4_Qt 5.9 C++开发指南_时间日期与定时器

    时间日期是经常遇到的数据类型,Qt 中时间日期类型的类如下。 QTime:时间数据类型,仅表示时间,如 15:23:13。 QDate:日期数据类型,仅表示日期,如2017-4-5. QDateTime:日期时间数据类型,表示日期和时间,如2017-03-23 08:12:43. Qt 中有专门用于日期、时间编辑和显示的 界面组件 ,介

    2024年02月14日
    浏览(51)
  • HAL STM32基于系统滴答定时器(SysTick)实现多任务时间片轮询

    📑RTOS(实时操作系统)和定时器时间片轮询是两种不同的任务调度和执行方式的差异简介 🔖 以下部分内容,由AI给出的解答: 🔖RTOS(实时操作系统): 🌿RTOS是一种专门设计用于实时系统的操作系统,它可以有效地管理多个任务,提供任务调度、同步和通信等功能。 🌿

    2024年02月21日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包