老板说我技术需要有长进,不能只做一个crud boy。 于是我选来选去,终于选定了,来学习操作系统。因为操作系统一直被看做是计算机软件的基石。
本系列是我学习操作系统的笔记,操作系统是以AliOS Things为例子。其他的操作系统也是差不多。
本文主要是讲操作系统的定时器管理,后面会有更多的操作系统内容介绍。
国庆假期学门新技术,拒绝只做crud boy, 就从操作系统开始 - 中断管理
1、背景
定时器,顾名思义,是指从指定的时刻开始,经过一个指定的时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。跟生活中的闹钟类似,我们可以设置闹钟每天什么时候响,还可以设置响的次数,是响一次还是每天都响。
在嵌入式系统中,我们往往需要跟时间相关的操作。比如任务的延时调度、任务的周期性运行等。基于对时钟精确的要求,每个运行的芯片平台都会提供相应的硬件定时机制。
操作系统中最小的时间单位是系统时钟节拍(OS Tick)。本章主要介绍基于硬件定时机制的系统时钟节拍,和基于时钟节拍的软件定时器。软件定时器是AliOS Things中的一个重要模块,使用软件定时器可以方便的实现一些与超时或周期性相关的功能。
本章也将从AliOS Things软件定时器的API入手,来分析AliOS Things软件定时器的运行机理。读完本章,我们将了解系统时钟节拍是如何产生的,并学会如何使用AliOS Things软件定时器。
定时器有硬件定时器和软件定时器之分,首先介绍硬件定时器。
2、AliOS Things 定时器管理
2.1、硬件定时器介绍
硬件定时器是芯片平台本身提供的定时功能。一般是由外部晶振作为输入时钟提供给芯片,芯片向软件模块提供可配置能力,接受控制输入,在到达设定的时间后芯片产生时钟中断,用户在中断服务函数中处理信息。芯片的外部晶振一般选用MHZ级别,所以硬件定时器的精度很高,可以达到纳秒级别。
目前芯片平台一般会提供两种定时模式,原理如下:
- 倒计数模式:硬件定时器提供一个count寄存器配置,设定其初始值后,其随着定时时钟的频率计数递减,递减频率即为定时器频率。当计数值为0时,定时结束,触发对应的定时处理。如果是周期模式,可以设置其每次计数为0后自动复位的count起始计数值(一般也通过寄存器设置),以此来设置触发周期。
- 正计数模式:硬件定时器提供两个基本的寄存器配置——count寄存器和compare寄存器配置。count寄存器的值会随着时钟频率计数递增,当其达到compare设定的值后,即触发对应的定时处理。如果是周期模式,则需要按照时钟频率和延时周期来设置后续的compare值,即在上一次的定时处理内,设置下一次的compare寄存器。
上述两种方式具体参考所使用的芯片平台手册。
2.2、系统时钟节拍工作机制
在嵌入式系统中,通常软件定时器以系统节拍为计时单位。时钟节拍是嵌入式操作操作系统OS运行的“心跳”,任何操作系统都需要提供一个时钟节拍,用于处理所有与时间相关的事件,如任务的延时、任务的时间片轮转调度、软件定时器的超时事件等。
时钟节拍是特定的周期性中断,其本质就是基于芯片的硬件定时机制所设置的一个基础硬件定时器,定时周期一般是1~100ms。在AliOS Things中,系统节拍的长度可以根据
RHINO_CONFIG_TICKS_PER_SECOND
这个宏的定义来调整(该宏在k_config.h中有定义)。
系统时钟节拍的值等于1/RHINO_CONFIG_TICKS_PER_SECOND秒。比如:HaaS100和HaaS EDU平台上,RHINO_CONFIG_TICKS_PER_SECOND的值定义为1000,那么时钟节拍就是1/1000秒,即1ms,也就是每1ms系统会“心跳”一次,产生一个tick中断。
由于系统时钟节拍定义了系统中定时器的精度,系统可以根据实际系统CPU的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为在1秒中系统进入时钟中断的次数也就越多。
2.2.1、时钟节拍的实现方式
时钟节拍由芯片的硬件定时器产生,硬件定时器配置为中断触发模式。如果操作系统希望1ms能产生一次定时触发,则必须将1ms转换为定时器的cycle计数值,并将此cycle值按照实际倒计数或者正计数的模式来配置定时器的相关寄存器。
配置定时器的cycle间隔功能一般由相关芯片平台的驱动提供,不同芯片平台的配置方式略有差别,读者可参考平台适配的代码。
时钟节拍tick的处理指的是每次tick周期触发时,操作系统需要进行的处理。AliOS Things提供了统一的tick函数入口krhino_tick_proc,将此函数加入tick定时器中断处理函数,以此来达到屏蔽硬件差异的目的。
以HaaS100开发板为例,在SysTick_Handler中断处理函数中调用AliOS Things的tick调度函数krhino_tick_proc:
void SysTick_Handler(void)
{
/* 进入中断 */
krhino_intrpt_enter();
/* tick isr */
krhino_tick_proc();
/* 退出中断 */
krhino_intrpt_exit();
}
void krhino_tick_proc(void)
{
#if (RHINO_CONFIG_USER_HOOK > 0)
krhino_tick_hook();
#endif
tick_list_update(1);
#if (RHINO_CONFIG_SCHED_RR > 0)
time_slice_update();
#endif
}
可以看到每经过一个时钟节拍,由操作系统维护的tick值(即系统时间)就会加1,同时会检查当前任务的时间片是否用完,以及是否有定时器超时。具体代码请参考k_time.c文件。
2.2.2、时钟节拍常用接口
时钟节拍(tick模块)提供了几个维测接口用来获取基本的tick信息。
函数名 | 描述 |
sys_time_t krhino_sys_tick_get(void) | 返回当前的系统节拍值 单位:tick数 |
sys_time_t krhino_sys_time_get(void) | 返回当前的系统时间 单位:ms |
tick_t krhino_ms_to_ticks(sys_time_t ms) | 将当前ms数转换为tick计数 |
sys_time_t krhino_ticks_to_ms(tick_t ticks) | 将当前tick计数转换为ms值 |
2.3、软件定时器工作机制
2.3.1、软件定时器介绍
AliOS Things操作系统提供软件实现的定时器
- 指定时间到达后要调用回调函数(也称超时函数),用户在回调函数中处理信息。
- 软件定时器是由操作系统提供统一API接口供用户使用。
- 软件定时器要以时钟节拍(OS Tick)的时间长度为单位,定时周期必须是OS Tick的整数倍,例如AliOS Things OS Tick是10ms,那么上层软件定时器只能是10ms、20ms、100ms等,而不能定时为15ms。
AliOS Things提供的软件定时器支持单次模式和周期模式。
- 单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后,系统就将该定时器删除,不再重新执行。
- 周期模式:定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器停止或者删除,否则将永远执行下去。
2.3.2、软件定时器工作机制
AliOS Things软件定时器模块维护着三个重要的全局变量:
- 当前系统经过的tick时间g_tick_count(当硬件定时器中断来临时,它将加1)。
- 定时器链表g_timer_head。系统新创建并激活开始运行的定时器都会以超过时间排序的方式插g_timer_head链表中。
- 定时器事件队列g_timer_queue,它是一个消息队列buf_queue,系统中所有软件定时器的开始、停止、改变参数、删除等事件都会发送到这个定时器事件队列中依次得到处理。
如图所示:
假设系统当前的tick值为30,在当前系统中已经创建并启动了3个定时器,分别是定时时间为50个tick的timer1、100个tick的time2和500个tick的timer3,这3个定时器分别加上系统当前时间g_tick_count=50,按照从小到大的顺序链接在g_timer_head链表中,形成如下图所示的定时器链表结构。
同时,g_tick_count随着硬件定时器的触发一直在增长(每次硬件定时器中断来临时,g_tick_count加1),50个tick以后,g_tick_count从30增长到80,与timer1的timeout值相等,这时会触发与timer1定时器相关联的超时处理函数,同时将timer1从g_timer_head链表上删除。同理,100个tick和500个tick过去后,与timer2和timer3定时器相关联的超时处理函数会被触发,接着将timer2和timer定时器从g_timer_head链表中删除。
如果系统当前定时器状态在10个tick后(g_tick_count=40)有一个任务新创建了一个tick值为300的timer4定时器,定时器事件队列g_timer_queue会收到这个并处理这个定时器新创建的事件,由于timer4定时器的timerout=40+300=340,因此它将被按序插入到timer2和timer3定时器中间,形成如下图所示的链表结构。
2.3.3、AliOS Things软件定时器任务
AliOS Thing会在kernel初始化的时候,创建一个优先级相对较高的软件定时器任务timer_task,这是AliOS Thing软件定时器的主处理任务,可以及时处理定时器事件,流程如下:
2.4、软件定时器使用方法
上节介绍了AliOS Things软件定时器,并对定时器的工作机制进行了介绍,本节将深入介绍定时器的各个接口,帮助用户在代码层次上理解AliOS Things软件定时器。
相应的API接口汇总如下:
定时器初始化 | ktimer_init(void) |
创建一个定时器 | aos_timer_new |
创建一个定时器 | aos_timer_new_ext |
删除一个定时器 | aos_timer_free |
开始一个定时器 | aos_timer_start |
停止一个定时器 | aos_timer_stop |
改变定时器的参数 | aos_timer_change |
下面具体介绍这些接口的使用。
2.4.1、软件定时器模块初始化
使用软件定时器timer模块,在系统启动时需要初始化定时器管理系统。可以通过下面的接口完成:
void ktimer_init(void)
此接口主要完成三个工作:
- 初始化timer队列g_timer_head;
- 初始化timer定时器管理buffer队列g_timer_queue;
- 创建定时器基本处理任务g_timer_task。
2.4.2、软件定时器的创建和删除
int aos_timer_new(aos_timer_t *timer,void (*fn)(void *, void *),
void *arg, int ms, int repeat)
调用该接口后,内核首先次从动态内存堆中分配一个定时器控制块,然后对该控制块进行基本的初始化。其中的各参数和返回值说明见下表。
参数 | 描述 |
timer | 软件定时器管理句柄 |
fn | 定时器超时函数(当定时器超时时,系统会调用这个函数) |
arg | 定时器超时函数的入口参数(当定时器超时时,调用超时函数会把这个参数作为入口参数传递给超时函数) |
ms | 定时器超时时间(单位ms),即间隔多少时间执行fn |
repeat | 周期定时或单次定时(1:周期,0:单次) |
返回 | 描述 |
0 | 定时器创建成功,并自动开始运行 |
非0 | 定时器创建失败 |
系统不再使用软件定时器时,可使用下面的函数接口。
void aos_timer_free(aos_timer_t *timer)
调用该接口后,系统会把定时器从g_timer_head链表中删除,然后释放相应的定时器控制块,其中的各参数和返回值说明见下表。
参数 | 描述 |
timer | 软件定时器管理句柄,指向要删除的定时器 |
2.4.3、软件定时器的启动和停止
如果使用aos_timer_new_ext创建了定时器,没有启动定时器,需要在主动调用启动定时器函数接口后才能开始工作,启动定时器接口如下:
int aos_timer_start(aos_timer_t *timer)
参数描述如下表
参数 | 描述 |
timer | 软件定时器管理句柄,指向要启动的定时器 |
返回 | 描述 |
0 | 定时器启动成功 |
非0 | 定时器启动失败 |
启动定时器后,如果要停止它,可以使用下面的函数接口:
int aos_timer_stop(aos_timer_t *timer)
参数描述如下表
参数 | 描述 |
timer | 软件定时器管理句柄,指向要停止的定时器 |
返回 | 描述 |
0 | 定时器停止成功 |
非0 | 定时器停止失败 |
2.4.4、软件定时器的控制
AliOS Things还提供了修改定时器参数的接口
int aos_timer_change(aos_timer_t *timer, int ms)
参数描述如下表
参数 | 描述 |
timer | 软件定时器管理句柄,指向要修改的定时器 |
ms | 新的定时器超时时间(单位ms),即间隔多少时间执行定时器超时处理函数 |
返回 | 描述 |
0 | 定时器修改成功 |
非0 | 定时器修改失败 |
注意:需要在定时器处于未启动状态时才能修改。
3、总结
AliOS Things的定时器管理包括了硬件定时器(时钟节拍心跳机制)和软件定时器管理。其中,软件定时器的工作机制,是将所有定时器的callback事件、定时器的管理事件(创建、启动、停止、删除、改变参数)都通过内置的g_timer_queue 发送到后台的高优先级任务timer_task中去执行。
开发者技术支持
如需更多技术支持,可加入钉钉开发者群,或者关注
本文链接:http://m.zhangshiyu.com/post/35337.html