最近用了一下alarm定时器,之前有过接触,一直没有怎么整理,所以现在写写,方便以后回来看看。

一、definition:

接触过linux内核和android的应该都不会感到陌生,他是android基于内核rtc实现的一个定时器。

一个硬件定时器,用于把设备从睡眠状态唤醒,定时时间到后可以唤醒系统,此时系统就可以做你想在特定时间要做的事情,基于该定时器还可以实现关机闹铃的功能。

之前也有接触过高精度定时器hrtimer,但是它并不能唤醒系统,所以alarm更像一个外部中断,这是觉得这样理解他的作用就显而易见了。

因为alarm是依赖于rtc实现的,所以很显然他还有一个功能就是掉电后还能正常工作的实时时钟。

 

二、简单介绍一下RTC:

RTC顾名思义real time clock(实时时钟),他是干什么的呢?

    当手机掉电后,再次开机时,手机的时间显示仍然是正常的,就是他的功劳了。

RTC是靠手机电源管理芯片中实现的,他是靠电池供电的实时时钟,所以系统关机后,靠它来记录系统时间;

当系统启动的时候,内核通过读取RTC来初始化墙上时间(当前的实际时间),并将该变量保存在xtime变量中。

系统在运行的过程中,将不再使用RTC,因为他有属于自己的系统定时器。

当系统关机的时候,内核又把当前的时间写入到RTC中。

 

接下来,就让我们看看该驱动框架。

三、alarm driver :

alarm是在rtc框架上实现的,它主要是由alarm.c和alarm-dev.c实现的。alarm.c实现了所有通用的接口,并且创建了一个设备class,而alarm-dev.c则注册了misc设备,为上层

访问提供了标准的miscdevice接口。也可以这么认为alarm.c实现了机制和框架,而alarm-dev.c实现了这个框架的设备驱动。

先从头文件进行分析:

 include/linux/android_alarm.h 

48 /**
 49  * struct alarm - the basic alarm structure
 50  * @node:       red black tree node for time ordered insertion
 51  * @type:       alarm type. rtc/elapsed-realtime/systemtime, wakeup/non-wakeup.
 52  * @softexpires: the absolute earliest expiry time of the alarm.
 53  * @expires:    the absolute expiry time.
 54  * @function:   alarm expiry callback function
 55  *
 56  * The alarm structure must be initialized by alarm_init()
 57  *
 58  */
 59 
 60 struct alarm {
 61         struct rb_node          node;
 62         enum android_alarm_type type;//android定义的五中alarm类型
 63         ktime_t                 softexpires;//最早的到期时间
 64         ktime_t                 expires;//绝对到期时间
 65         void                    (*function)(struct alarm *);//当到期时间时,系统的回调函数。
 66 };

该结构体里面的第一个数据成员是红黑树节点,这个就跟alarm实现的原理有关。alarm会按照到期的先后顺序组织成为一个红黑树。

54 struct alarm_queue {                                                                                                                                                                       
 55         struct rb_root alarms;//表示红黑树的根;
 56         struct rb_node *first;//指向第一个到期的alarm设备;
 57         struct hrtimer timer;//高精度定时器(内核定时器),马达驱动里面设置震动时间就是利用该定时器实现的;android也是利用它来实现alarm到期时间的;
 58         ktime_t delta;//这个参数使用来计算alarm到期时间的一个修正值;
 59         bool stopped;
 60         ktime_t stopped_time;
 61 };

该结构体是在driver/rtc/alarm.c文件中声明的。该结构体的作用就是将alarm表示的设备组织成一颗红黑树。

 

接下来对alarm.c和alarm-dev.c文件进行分析:

前面有所介绍alarm.c主要是实现了其框架,而alarm-dev.c就是实现了其上层访问接口;

在alarm-dev.c的初始化函数中做了两件事:将其注册为miscdevice设备,为其上层访问提供标准的接口;还有就是对每一个alarm type创建一个alarm设备;并且还初始化了一个

wake lock唤醒锁,当获得该锁时,会阻止系统进入suspend状态;把整个alarm-dev.c读完以后发现,他也是只做了这个三件事。

272 static int __init alarm_dev_init(void)
273 {
274         int err;
275         int i;
276 
277         err = misc_register(&alarm_device);
278         if (err)
279                 return err;
280 
281         for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++)
282                 alarm_init(&alarms[i], i, alarm_triggered);
283         wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm");
284 
285         return 0;
286 }

alarm_triggered是alarm时间到时的回调函数,其实现如下:

243 static void alarm_triggered(struct alarm *alarm)
244 {
245         unsigned long flags;
246         uint32_t alarm_type_mask = 1U << alarm->type;
247 
248         pr_alarm(INT, "alarm_triggered type %d\n", alarm->type);
249         spin_lock_irqsave(&alarm_slock, flags);
250         if (alarm_enabled & alarm_type_mask) {
251                 wake_lock_timeout(&alarm_wake_lock, 5 * HZ);
252                 alarm_enabled &= ~alarm_type_mask;
253                 alarm_pending |= alarm_type_mask;
254                 wake_up(&alarm_wait_queue);
255         }
256         spin_unlock_irqrestore(&alarm_slock, flags);
257 }

当alarm时间到期时,该函数被调用,首先获得一个自旋锁,防止其它alarm发生竞争,然后获得一个超时唤醒锁(5s),wake_up唤醒所有等待在该alarm设备上的进程,

这是AP会对ioctl函数进行操作,即系统调用;这个可以仔细看代码中的alarm_ioctl的swich case部分。

alarm_init函数是在alarm.c中实现的,顾名思义就是初始化一个alarm设备,其函数原型如下:

155 void alarm_init(struct alarm *alarm,
156         enum android_alarm_type type, void (*function)(struct alarm *))

 

alarm.c分析:

alarm.c主要实现的是底层机制,创建了alarm class这个设备;并且将其注册为platform设备,支持suspend和resume。

首先从alarm_driver_init函数分析:

570 static int __init alarm_driver_init(void)
571 {
572         int err;
573         int i;
574 
575         for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {
576                 hrtimer_init(&alarms[i].timer,
577                                 CLOCK_REALTIME, HRTIMER_MODE_ABS);
578                 alarms[i].timer.function = alarm_timer_triggered;
579         }
580         hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].timer,
581                      CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
582         alarms[ANDROID_ALARM_SYSTEMTIME].timer.function = alarm_timer_triggered;
583         err = platform_driver_register(&alarm_driver);
584         if (err < 0)
585                 goto err1;
586         wake_lock_init(&alarm_rtc_wake_lock, WAKE_LOCK_SUSPEND, "alarm_rtc");
587         rtc_alarm_interface.class = rtc_class;
588         err = class_interface_register(&rtc_alarm_interface);
589         if (err < 0)
590                 goto err2;
591 
592         return 0;
593 
594 err2:
595         wake_lock_destroy(&alarm_rtc_wake_lock);
596         platform_driver_unregister(&alarm_driver);
597 err1:
598         return err;
599 }

1、首先,在该init函数中初始化了5个hrtimer高精度定时器,alarm_timer_triggered是其回调函数。注意的是android type有五种类型,在初始化hrtimer定时器时,前四个注册的是CLOCK_REALTIME,而最后一种类型ANDROID_ALARM_SYSTEMTIME注册的是CLOCK_MONOTONIC类型。

2、将其注册为平台设别;

3、初始化了一个alarm_rtc的唤醒锁;

4、注册了classs设备接口;

关于CLOCK_REALTIME和CLOCK_MONOTONIC的区别,网上查了一下,粘贴在下面:

CLOCK_REALTIME:这种类型的时钟可以反映wall clock time,用的是绝对时间,当系统的时钟源被改变,或者系统管理员重置了系统时间之后,这种类型的时钟可以
得到相应的调整,也就是说,系统时间影响这种类型的timer。
CLOCK_MONOTONIC:用的是相对时间,他的时间是通过jiffies值来计算的。该时钟不受系统时钟源的影响,只受jiffies值的影响。

建议使用:
CLOCK_MONOTONIC这种时钟更加稳定,不受系统时钟的影响。如果想反映wall clock time,就使用CLOCK_REALTIME。

之前讲了这么多,好像忘记说alarm定时器是怎么使用的。前面介绍了alarm_init初始化一个定时器,到期后执行其回调函数;那么怎么才能触发一个定时器呢?

这时候就需要alarm_start_range函数了,如果你只有alarm_init 那么alarm是永远无法为你办事的,因为你只是初始化了它,并没有时能它,alarm_start_range就相当于enbale

使能函数。

166 /**
167  * alarm_start_range - (re)start an alarm
168  * @alarm:      the alarm to be added
169  * @start:      earliest expiry time
170  * @end:        expiry time
171  */
172 void alarm_start_range(struct alarm *alarm, ktime_t start, ktime_t end)
173 {
174         unsigned long flags;
175 
176         spin_lock_irqsave(&alarm_slock, flags);
177         alarm->softexpires = start;
178         alarm->expires = end;
179         alarm_enqueue_locked(alarm);
180         spin_unlock_irqrestore(&alarm_slock, flags);
181 }

这里面最重要的就是  alarm_enqueue_locked(alarm);函数,因为它起着计时至关重要的作用。还记得刚刚说到的在alarm 初始化函数中注册了hrtimer定时器吗,他的回调函数

alarm_timer_triggered,但是hrtimer定时器和alarm一样,只是初始化没有使能它,他是不会替你办事的,hrtimer_start后hrtimer定时器被使能,而该使能函数是在update_timer_locked函数中被调用的,而这个update_timer_locked函数很不凑巧是在alarm_enqueue_locked函数中被调用的,这下子明白了吧。所以这也就是为什么一定要alarm_start的原因。

所以很好奇,hrtimer的回调函数到底做了什么呢?那就先看看代码吧:

341 static enum hrtimer_restart alarm_timer_triggered(struct hrtimer *timer)
342 {
343         struct alarm_queue *base;
344         struct alarm *alarm;
345         unsigned long flags;
346         ktime_t now;
347 
348         spin_lock_irqsave(&alarm_slock, flags);
349 
350         base = container_of(timer, struct alarm_queue, timer);
351         now = base->stopped ? base->stopped_time : hrtimer_cb_get_time(timer);
352         now = ktime_sub(now, base->delta);
353 
354         pr_alarm(INT, "alarm_timer_triggered type %d at %lld\n",
355                 base - alarms, ktime_to_ns(now));
356 
357         while (base->first) {
358                 alarm = container_of(base->first, struct alarm, node);
359                 if (alarm->softexpires.tv64 > now.tv64) {
360                         pr_alarm(FLOW, "don't call alarm, %pF, %lld (s %lld)\n",
361                                 alarm->function, ktime_to_ns(alarm->expires),
362                                 ktime_to_ns(alarm->softexpires));
363                         break;
364                 }
365                 base->first = rb_next(&alarm->node);
366                 rb_erase(&alarm->node, &base->alarms);
367                 RB_CLEAR_NODE(&alarm->node);
368                 pr_alarm(CALL, "call alarm, type %d, func %pF, %lld (s %lld)\n",
369                         alarm->type, alarm->function,
370                         ktime_to_ns(alarm->expires),
371                         ktime_to_ns(alarm->softexpires));
372                 spin_unlock_irqrestore(&alarm_slock, flags);
373                 alarm->function(alarm);
374                 spin_lock_irqsave(&alarm_slock, flags);
375         }
376         if (!base->first)
377                 pr_alarm(FLOW, "no more alarms of type %d\n", base - alarms);
378         update_timer_locked(base, true);
379         spin_unlock_irqrestore(&alarm_slock, flags);
380         return HRTIMER_NORESTART;
381 }

它会轮询红黑树,谁到了时间符合条件就执行他的回调函数alarm->function,终于整个轮回都通了。

刚刚发现在alarm-dev.c中有一个alarm_triggered的回调函数,在alarm.c中有一个alarm_timer_triggered的回调函数,不过他们是不一样的,

前者是RTC芯片的alarm中断的回调函数,后者是一个具体的alarm定时器到期时的回调函数。