定时器分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Function_Dou/article/details/82772998

定时器分析

时钟分类

实时时钟(RTC): 在电脑断电后还继续工作, 所以他有一个外置电池的一个硬件设施. 它是一个16位的计数器, 而linux系统的日期和时间也是开机后根据RTC来获取的, 然后获取之后就不再需要RTC来获取时间了, 而是通过软件进行时间的维护, 当关机的时候在把时间写回到RTC中, 而内核通过0x70和0x71端口访问RTC.

时间戳计数器(TSC) : 它记录自启动以来处理器消耗的时钟周期. 在每个时钟到来时, 该计数器自动加一. 精准性很高.

可编程间隔定时器(PIT) : 它是根据内核设置的固定频率来发出时钟中断, 频率可以通过编制改编.

CPU本地定时器(APIC) : 能够产生单步中断的或周期性中断的设备.

jiffies变量

jiffies是个全局变量, 它是计数电脑自开机以来到现在经过的节拍总数, 一般设置的是32位, 但是也是通过两个32位扩展到64位, 所以也不会担心溢出问题.

jiffies在开发中初始化的并不是0, 而是0xfffb6c20的值, 这个值大概在5分钟会溢出, 这样用于发现有缺陷的代码.

xtime变量

xtime结构是存放了timespec两个变量,

tv_sec存放自1970.1.1到现在的秒数,

tv_nsec存放自上一秒经过的纳秒.

时钟初始化

内核中time_none是一个虚拟定时器的对象, cur_timer指向的是虚拟定时器对象的地址.

在内核初始化的时候, 会调用time_init()函数来获取当前的时间和日期.

初略过程:

  1. 初始化xtime变量, 设置xtime 的值为1970.1.1到现在的秒数.
  2. 初始化wall_to_monotonic变量 . 他保存的是将要加到xtime变量的秒和纳秒.
  3. 通过select_timer()函数设置cur_timer()选择合适的定时器对象的地址.

时间更新

mark_offset检查丢失的时钟中断. 时间中断的可能是中断被禁止了, 或是其他原因导致时钟中断丢失.

全局时钟中断处理程序通过调用update_times()函数来更新xtime变量.

void update_times(void)
{
	unsigned long ticks;
    ticks = jiffies - wall_jiffies;
    if(ticks)
    {
        wall_jiffies += ticks;
        update_wall_time(ticks);
    }
    calc_load(ticks);
}

检查有没有丢失的中断, 如果有的话就将丢失的中断加上在更新xtime变量.

update_times()的另一个作用是每次节拍都会调用calc_load统计处在TASK_TUNNING或TASK_UNINTERRUPTIBLE进程数, 利用这个来更新平均系统负载.

动态定时器

动态定时器是由内核调用执行的. 动态定时器在被动的创建和撤销, 超过时也会被销毁, 个数也没有限制.

动态定时器结构是由5个不同大小的vec数组链表构成的, tv1…tv5大小分别是255, 2^14-1, 2^20-1, 2^26-1, 2^32-1. 定时器的执行是从tv1开始的, 当tv1的255个执行完后, 会从tv2中选择在添加到tv1中补充, 后面同理的补充前一个tv, tv5不进行补充.


typedef struct tvec_base_c
 {
	spinlock_t lock;
	struct timer_list *running_timer;
	unsigned long timer_jiffies;
	unsigned long next_timer;
	struct tvec_root tv1;
	struct tvec tv2;
	struct tvec tv3;
	struct tvec tv4;
	struct tvec tv5;
} tvec_base_t

动态定时器在撤销的时候有一点麻烦, 因为在撤销的时候定时程序刚好还在其他CPU中执行, 所以需要设置函数一直等待, 知道定时器函数结束.

nanosleep动态定时器

nanosleep是实现将调用进程挂起, 在设置的时间后在被唤醒.

通常在涉及的时候都是设置好挂起时间, 设置task的状态为TASK_INTERRUPTIBLE, 然后通过schedule_timeout()函数进行调度, 在设置的时间间隔之内在通过process_timeout()函数唤醒该进程.

sleep和nanosleep()的区别

sleep()和nanosleep()两者都是使进程睡眠一段时间后被唤醒,但是实现却完全不同。

Linux中并没有提供系统调用sleep(),sleep()是在库函数中实现的,它是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上。

nanosleep()则是Linux中的系统调用,它是使用定时器来实现的,该调用使调用进程睡眠,并往定时器队列上加入一个timer_list型定时器,time_list结构里包括唤醒时间以及唤醒后执行的函数,通过nanosleep()加入的定时器的执行函数仅仅完成唤醒当前进程的功能。系统通过一定的机制定时检查这些队列(比如通过系统调用陷入核心后,从核心返回用户态前,要检查当前进程的时间片是否已经耗尽,如果是则调用schedule()函数重新调度,该函数中就会检查定时器队列,另外慢中断返回前也会做此检查),如果定时时间已超过,则执行定时器指定的函数唤醒调用进程。当然,由于系统时间片可能丢失,所以nanosleep()精度也不是很高。

另外alarm()也是通过定时器实现的,但是其精度只精确到秒级,另外,它设置的定时器执行函数是在指定时间向当前进程发送SIGALRM信号。

猜你喜欢

转载自blog.csdn.net/Function_Dou/article/details/82772998
今日推荐