一、定时器:
1.定时器定义:
定时器是由时钟源和可编程计数器组成的硬件设备。实际上就是Soc当中的一个内部外设。
(1)定时器与计数器
定时器常与计数器扯到一起,计数器也是soc当中的一个内部外设,计数器顾名思义是用来计数的,就和我们的秒表一样,秒表实际上就是一个计数器,每隔一个单位走一个格(就是计一个数),因为计数器的计数时间周期是固定的,因此到了一定时间只要用计数值*计数时间周期,就能得到一个时间段,这个时间段就是我们定的时间(这就是定时器了)。计数器和定时器其实是一回事。
(2)定时器/计数器作为SoC的外设,主要用来实现定时执行代码的功能。定时器相对于SoC来说,就好像闹钟相对于人来说意义一样。单核的CPU是单线程的,只能干一件事情,干完这件事情完去干另一件事情需要定时器来提醒。
2. 定时器有什么用
(1)定时器可以让SoC在执行主程序的同时,可以(通过定时器)具有计时功能,到了一定时间(计时结束)后,定时器会产生中断提醒CPU,CPU会去处理中断并执行定时器的ISR。从而去执行预先设定好的事件。
(2)定时器就好像是CPU的一个秘书一样,这个秘书专门管帮CPU来计时,并到时间后提醒CPU要做某件事情。所以CPU有了定时器之后,只需要预先把自己XX时间之后必须要做的事情绑定到定时器中断ISR即可,到了时间之后定时器就会以中断的方式提醒CPU来处理这个事情。
3. 定时器的原理
(1)定时器计时其实是通过计数来实现的。定时器内部有一个计数器,这个计数器根据一个时钟(这个时钟来自于ARM的APB总线,然后经过时钟模块内部的分频器来分频得到)来工作。每隔一个时钟周期,计数器就就计数一次,定时器的时间就是计数器计数值x时钟周期。
(2)定时器内部有1个寄存器TCNT,计时开始时我们会把一个总的计数值(譬如说300)放入TCNT寄存器中,然后每隔一个时钟周期(假设为1ms)TCNT中的值会自动减1(硬件自动完成,不需要CPU软件去干预),知道TCNT中减为0的时候,TCNT就会触发定时器中断。最后的计时时间就是300ms。
(3)定时时间是由2个东西共同决定的:一个是TCNT中的计数值,一个是时钟周期。譬如上例中,定时周期就为300x1ms=300ms。
4. 定时器和看门狗、RTC、蜂鸣器的关系
(1)这几个东西都是和时间有关的部件
(2)看门狗其实就一个定时器,只不过定时时间到了之后不只是中断,还可以复位CPU
(3)RTC是是实时时钟,它和定时器的差别就好像闹钟(定时器)和钟表(RTC)的差别一样。
(4)蜂鸣器是一个发声的设备,
在ARM里面蜂鸣器是用定时器模块来驱动的。
个人计算机定时器
(1)实时时钟RTC(real time clock)
区分时间段和时间点:时间段是相对的,两个时间点相减就会得到一个时间段;而时间点是绝对的,是绝无仅有的一个时间点。
定时器关注的是时间段(而不是时间点),定时器计时从开启定时器的那一刻开始,到定时间段结束为止中断。RTC中工作用的是时间点(xx年x月x日x分x秒星期x)。
(2) 可编程间隔定时器PIT(Programmable Interval Timer)
每个PC机中都有一个PIT,通过IRQ产生周期性的时钟中断信号来充当系统定时器。i386中使用的通常是Intel 8254 PIT芯片,它的I/O端口地址范围是40h~43h。
8254 PIT有3个计时通道,每个通道都有其不同的用途:
通道0用来负责更新系统时钟。它在每一个时钟滴答会通过IRQ0向系统发出一次时钟中断信号。
通道1通常用于控制DMAC对RAM的刷新。
通道2被连接到PC机的扬声器,以产生方波信号。
中断处理
中断的概念
中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。
软件对硬件进行配置后,软件期望等待硬件的某种状态(比如,收到了数据),这里有两种方式,一种是轮询(polling): CPU 不断的去读硬件状态。另一种是当硬件完成某种事件后,给 CPU 一个中断,让 CPU 停下手上的事情,去处理这个中断。很显然,中断的交互方式提高了系统的吞吐。
当 CPU 收到一个中断 (IRQ)的时候,会去执行该中断对应的处理函数(ISR)。普通情况下,会有一个中断向量表,向量表中定义了 CPU 对应的每一个外设资源的中断处理程序的入口,当发生对应的中断的时候, CPU 直接跳转到这个入口执行程序。也就是中断上下文。(注意:中断上下文中,不可阻塞睡眠)。
中断服务程序的设计最好是快速完成任务并退出,因为此刻系统处于被中断中。但是在 ISR 中又有一些必须完成的事情,比如:清中断标志,读/写数据,寄存器操作等。
在 Linux 中,同样也是这个要求,希望尽快的完成 ISR。但事与愿违,有些 ISR 中任务繁重,会消耗很多时间,导致响应速度变差。Linux 中针对这种情况,将中断分为了两部分:
-
上半部(top half):收到一个中断,立即执行,有严格的时间限制,只做一些必要的工作,比如:应答,复位等。这些工作都是在所有中断被禁止的情况下完成的。
-
底半部(bottom half):能够被推迟到后面完成的任务会在底半部进行。在适合的时机,下半部会被开中断执行。
间隔定时器itimer
进程间隔定时器itimer
所谓“间隔定时器(Interval Timer,简称itimer)就是指定时器采用“间隔”值(interval)来作为计时方式,当定时器启动后,间隔值interval将不断减小。当 interval值减到0时,我们就说该间隔定时器到期。与上一节所说的内核动态定时器相比,二者最大的区别在于定时器的计时方式不同。内核定时器是通过 它的到期时刻expires值来计时的,当全局变量jiffies值大于或等于内核动态定时器的expires值时,我们说内核内核定时器到期。而间隔定 时器则实际上是通过一个不断减小的计数器来计时的。虽然这两种定时器并不相同,但却也是相互联系的。假如我们每个时钟节拍都使间隔定时器的间隔计数器减 1,那么在这种情形下间隔定时器实际上就是内核动态定时器(下面我们会看到进程的真实间隔定时器就是这样通过内核定时器来实现的)。
间隔定时器主要被应用在用户进程上。每个Linux进程都有三个相互关联的间隔定时器。其各自的间隔计数器都定义在进程的task_struct结构中,如下所示
(include/linux/sched.h):
struct task_struct{
……
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;
……
}
(1)真实间隔定时器(ITIMER_REAL):这种间隔定时器在启动后,不管进程是否运行,每个时钟滴答都将其间隔计数器减1。当减到0值时,内核向 进程发送SIGALRM信号。结构类型task_struct中的成员it_real_incr则表示真实间隔定时器的间隔计数器的初始值,而成员 it_real_value则表示真实间隔定时器的间隔计数器的当前值。由于这种间隔定时器本质上与上一节的内核定时器时一样的,因此Linux实际上是 通过real_timer这个内嵌在task_struct结构中的内核动态定时器来实现真实间隔定时器ITIMER_REAL的。
(2)虚拟间隔定时器ITIMER_VIRTUAL:也称为进程的用户态间隔定时器。结构类型task_struct中成员it_virt_incr和 it_virt_value分别表示虚拟间隔定时器的间隔计数器的初始值和当前值,二者均以时钟滴答次数位计数单位。当虚拟间隔定时器启动后,只有当进程 在用户态下运行时,一次时钟滴答才能使间隔计数器当前值it_virt_value减1。当减到0值时,内核向进程发送SIGVTALRM信号(虚拟闹钟 信号),并将it_virt_value重置为初值it_virt_incr。具体请见4.3节中的do_it_virt()函数的实现。
(3)PROF间隔定时器ITIMER_PROF:进程的task_struct结构中的it_prof_value和it_prof_incr成员分别 表示PROF间隔定时器的间隔计数器的当前值和初始值(均以时钟滴答为单位)。当一个进程的PROF间隔定时器启动后,则只要该进程处于运行中,而不管是 在用户态或核心态下执行,每个时钟滴答都使间隔计数器it_prof_value值减1。当减到0值时,内核向进程发送SIGPROF信号,并将 it_prof_value重置为初值it_prof_incr。具体请见4.3节的do_it_prof()函数。
Linux在include/linux/time.h头文件中为上述三种进程间隔定时器定义了索引标识,如下所示:
#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2
数据结构itimerval
虽然,在内核中间隔定时器的间隔计数器是以时钟滴答次数为单位,但是让用户以时钟滴答为单位来指定间隔定时器的间隔计数器的初值显然是不太方便的,因为用 户习惯的时间单位是秒、毫秒或微秒等。所以Linux定义了数据结构itimerval来让用户以秒或微秒为单位指定间隔定时器的时间间隔值。其定义如下
(include/linux/time.h):
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
其中,it_interval成员表示间隔计数器的初始值,而it_value成员表示间隔计数器的当前值。这两个成员都是timeval结构类型的变量,因此其精度可以达到微秒级。
临界区
所谓临界区(也称为临界段)就是访问和操作共享数据的代码段。
多个执行线程并发访问同一个资源是不安全的,为了避免临界区中的并发访问,编程者必须保证这些代码的原子地执行---也就是说,操作想执行结束前不可被打断,就如同临界区是一个不可分割的指令一样。
如果两个执行线程有可能处于同一临界区中同时执行,那么这个程序就包含一个Bug。如果这种情况情况发生了,我们成为竞争条件(race conditions)。
我们需要一种方法确保一次且只有一个线程对数据结构进行操作,或者当另外一个线程在对临界区标记时,就禁止其他访问。
锁提供了这样一种机制:门后的房间可以想象成一个临界区。在一个指定的时间内,房间里只能有一个执行线程存在,当一个线程进入房间后,它会锁住身后的房门;当他结束对共享数据的操作后,就会走出房间,打开门锁
如果另外一个线程在房门上锁的时候来了,那么它必须等待房间内的线程出来并打开门锁后,才能进入房间。
这样线程持有锁,而锁保护了数据。
代码练习:
https://gitee.com/zhang_yu_peng/practice-code/blob/master/t.c
https://gitee.com/zhang_yu_peng/practice-code/blob/master/ts.s