alarm学习总结Word下载.docx
《alarm学习总结Word下载.docx》由会员分享,可在线阅读,更多相关《alarm学习总结Word下载.docx(11页珍藏版)》请在冰豆网上搜索。
软件更新题型服务是否被打开)alarm_enabled这个全局量用来标记alarm设备是否被使能;
使能它的地方在
当我们从ui界面设置一些不同类型的alarm服务的时候,与其类型相对应的mask值会被赋给alarm_enabled这个全局量;
例如,如果上层应用下发的alarm设备有三种,其type值分别为1,2,3,那么其mask值分别为10,100,1000.这样根据alarm_enable的每一位是否为1,就可以判断出其对应的alarm设备是否被使能;
3与from_old_alarm_set里使能标志位对应,这里的操作清除了该标志位的mask值,对于已经触发回调函数的alarm设备,其状态应该从使能变成禁止,等待下次使能;
同样在清除闹钟时也会禁止alarm设备使能标志位;
4Alarm_pending这个全局量也用来存储各个类型alarm设备的mask值,当某一类型的alarm触发其回调函数时,表示这类alarm到期了,因此将这类alarm设备对应的mask值赋给alarm_pending,当上层得知这类alarm到期后,alarm_pending的值被清掉;
简单的说alarm_pending
的作用就是将底层到期的alarm事件告知上层;
5Wake_lock_timeout(&
alarm_wake_lock,5*HZ)该操作是给系统加5秒的alarm_wake_lock锁,即当前有alarm设备到期,需要触发上层应用,此时不允许系统休眠;
关于alarm_triggered回调函数:
代码里的注释说这个function是alarm结构体的回调函数,但仔细看看就能发现,alarm结构体不具备调用这个回调函数的条件,简单的说alarm设备在底层被初始化成定时器,而android_alarm.h中声明的structalarm里没有定时器相关信息,而从函数alarm_dev_init(void)初始化到现在也没看见任何起定时器的地方,因此structalarm.function是当alarm设备的时间到期时,会从别的地方调用过来,层层调用到alarm_triggered这个函数;
看到这里alarm设备对上层的接口是初始化完成了,可以确定的是alarm到期时,也是通过alarm_triggered将alarm.type相对应的mask值写入alarm_pending中,给上层查询;
2)第二个初始化函数__initalarm_driver_init(void)
同样作为__init函数,在内核初始化后会被自动执行;
1:
Hrtimer_init的第一个参数timer即structalarm_queue.timer;
hrtimertimer定义在kernel\include\linux中
这里的初始化工作相当于为每个类型的alarm设备初始化其相应的定时器,timer作为alarm设备的初始化信息,包括了expirestime、timer.function、rb_node;
初始化了定时器,注册了回调函数;
那么这里的time.function应该就是alarm定时器到期时所调用的回调函数了;
2:
注册回调函数alarm_timer_triggered;
判断当前定时器是否已经停止;
若停止,将定时器停止时的stop_time赋值给now,若没停止,则获取系统当前的时间,赋值给now,
若alarm的到期时间还在now时间之前,break,
3:
判断满足条件后,对维护alarm定时器的红黑树进行操作,删除掉过期的节点,保持first指针指向最先到期的节点;
4:
之后调用structalarm.function,这个function被第一个初始化函数__initalarm_dev_init(void)初始化成alarm_triggered();
由此可见,第二个初始化函数完成了alarm底层定时器的初始化操作,并且注册了回调函数alarm_timer_triggered(),在定时器到期后,回调函数会调用alarm_triggered;
将到期的alarm设备的mask值写入alarm_pending,给上层查询;
各种类型的alarm(提醒)服务被按类型初始化成定时器;
alarm_dev_init的作用就是针对每一类型的alarm服务初始化其对应的定时器hrtimer_init;
kernel管理这些定时器来确定当前的alarm是否到期;
二、设置alarm服务
Alarm服务的设置都是通过调用AlarmManagerService.java里的set方法将相应的时间和alarm的类型设置到kernel中;
Type即我们设置的alarm类型,在kernel里有不止一种的alarmtype的类型,闹钟服务只是其中的一种,因此在设置闹钟时间时,需要匹配对应的type;
alarmSeconds即平台层需要下发的时间了;
在android中java和c之间还有一个jni层,用来将java的方法与kernel的操作联系起来;
和闹钟有关的jni操作在com_android_server_AlarmMangerService.cpp中;
包括init,和set的方法都在这里与cpp对应起来了,因此调用set方法就会调用到jni的相应函数,
在函数android_server_AlarmManagerService_set()中会有如下调用:
ioctl(fd,ANDROID_ALARM_SET(type),&
ts);
&
ts结构体包括了设置的闹铃时间;
ANDROID_ALARM_SET(type)就是设置闹钟的命令了;
这个ioctl的操作在java中init方法中匹配上的:
open(“/dev/alarm”,O_RDWR);
因此set方法调用的ioctl操作就会对应到/dev/alarm注册的ioctl操作;
这个操作是在alarm-dev.c中注册的;
此时用户通过APK设置的闹钟时间和设置闹钟的操作就传到了kernel层;
2在alarm-dev.c中设置alarm类型会对应到alarm_ioctl中的caseANDROID_ALARM_SET;
alarm_start_range将用户空间得到的时间更新到ktime定时器中;
该函数的第一个参数&
alarms[alarm_type]是第一个初始化函数__initalarm_dev_init(void)中初始化的结构体structalarm;
第二个参数和第三个参数一样,是时间参数;
这个函数更新的是structalarm中的时间变量softexpires和expires,这两个成员被初始化成相同的值;
这样一个alarm设备就设置完成了;
这个时间被作为该alarm设备定时器到期的时间,在到期后调用回调函数alarm_timer_triggered()--alarm_triggered();
关于alarm定时器时间的更新:
从上边的流程可以看到ui界面设置一个alarm服务,在kernel只会更新到结构体structalarm,但该结构体并没有
和定时器相关的信息,而成员变量softexpires和expires何时被赋值给ktime定时器的?
这个是通过structalarm中成员rbnodenode来实现的;
底
层所有的alarm定时器都被维护成一个红黑树,那么每个类型alarm设备对应的红黑树的节点号也是可查询的;
Structhrtimer维护了相应红黑树节点定时器的信息,因此structalarm通过更新相应node的成员信息,就可以更新到hrtimer结构体中定时器的到
期时间了;
三、查询alarm是否到期
底层驱动在alarm设备到期后只会将alarmtypemask的值赋给alarm_pending,而上层是通过不断轮询alarmpending判断哪种类型的alarm到期了;
Alarmthread任务起来后,会一直调用函数waitforalarm(mDescriptor);
该函数在jni层被匹配成;
因此该服务对应到kernel就是alarm_ioctl会被一直调用,命令为android_alarm_wait;
将alarm_pending的值赋给rv,rv作为alarm_ioctl的返回值;
因此当alarm_triggered被触发时,alarm_pending就会被赋予相应的mask值;
所以当上层发命令轮询时,这个alarm_pending的值就会被当成返回值返回;
result=waitforalarm(mDescriptor);
result的值就是alarm_pending的值;
在获取到相应alarm_pending的值后,开始和上层alarm服务的mask值相匹配:
1若result的值匹配到poweroff_wakeup_mask上层就知道了此时闹钟到期了,就会调用相关的apk提醒用户;
Nv关机闹钟补丁分析
大多数的网站上都在说android手机不支持关机闹钟,关机闹钟的部分接口,调用需要根据具体的pmic芯片资料进行添加;
添加后是可以支持的;
Oscar项目使用tps6586xpmic芯片,该芯片支持rtc中断进行系统唤醒;
因此在此项目上可以实现关机闹铃;
NV的工程师给我们提供了支持关机闹钟的补丁,里边的修改如下:
1)tps6586x_rtc_probe
-/*1kHztickmode,enabletickcounting*/
+/*1kHztickmode,enabletickcounting,
+enablepoweroffalarm.
+*/
err=tps6586x_update(tps_dev,RTC_CTRL,
-RTC_ENABLE|OSC_SRC_SEL|((pdata->
cl_sel<
<
CL_SEL_POS)&
-CL_SEL_MASK),
-RTC_ENABLE|OSC_SRC_SEL|PRE_BYPASS|CL_SEL_MASK);
+RTC_ENABLE|OSC_SRC_SEL|RTC_RSVDC00|
+((pdata->
CL_SEL_MASK),
+RTC_ENABLE|OSC_SRC_SEL|RTC_RSVDC00|PRE_BYPASS|CL_SEL_MASK);
Proc函数中的修改如NV补丁中的log所示,增加了使能poweroffalarm的操作;
tps6586x_updata(dev,reg,val,mask);
由此可见此函数的第三个参数是用来写rtc_ctrl寄存器的值,其中:
#defineRTC_RSVDC00BIT(0)即1<
0;
……同理可以计算出其他位寄存器的值;
这里可以确定rtc_ctl寄存器里B0位值为1:
使能RTC_ALARM2寄存器;
这组寄存器可以产生唤醒系统的中断;
B3位为0;
rtc_count的计数频率为1KHZ;
即1024次计数为1秒;
这里可以对比下tps6586x中关于rtc寄存器的说明:
2)tps6586x_rtc_set_alarm
该函数将从kernel传下来的闹钟时间与系统时间进行比较,并计算出差值ticks,若差值大于0;
那么将ticks设置到rtc_alarm寄存器里;
NV给的补丁修改了设置的寄存器:
-err=tps6586x_writes(tps_dev,RTC_ALARM1_HI,sizeof(buff),buff);
+err=tps6586x_writes(tps_dev,RTC_ALARM2_HI,sizeof(buff),buff);
这里询问了NV的工程师,他们给出的答复如下:
OnlyRTC_ALARM2canautomaticallywakethePMUfromtheSLEEPstateoncetheRTC_COUNTvalueequalsthevaluestoredintheRTC_ALARM2Registers
因此设置关机闹钟应该将ticks写入到rtc_alarm2寄存器中;
函数tps6586x_writes将从RTC_ALARM2_Hi的高地址开始写入buff里所存储的ticks值;
这里的ticks值经过如下换算存入buff:
+buff[0]=(ticks>
>
8)&
0xff;
+buff[1]=ticks&
可见存入buff的值也是从高位开始的;
还有一点需要说明:
ticks的值在计算过程中经过了这样的操作:
ticks=(unsignedlonglong)seconds<
10;
......
ticks>
=12
换算成二进制后,最后两位被遗弃了:
这里可以参照下NV补丁中的注释:
/*
+TPS658621Cdatasheetupdate:
+ALM2[15:
0]iscomparedtoRTC[27:
12]regardlessifthe
+pre-scalerisenabledordisabled.
Rtc_count
39
38
37
27
12
11
10
9
2
1
Count以1024HZ的频率计时,那么
在计满1024次时,即count的
第10每秒增加1位;
这样从第
10位开始count所增加的位数就可
ALM2以转换成秒数了
15
ALM2寄存器一共16位,它会和COUNT的[27:
12]位进行比较,这里有个地方需要注意下,由于从第10位开始就是每秒递增计数了,而ALM2寄存器比较是从count的12位开始的,因此在将ticks的值写入ALM2的时候会舍弃2位;
也就有了如上的移位操作了;
舍弃了低2位的秒数,这样会带来3秒的比较误差;
RTC中断的产生会提前3秒;
这样是否有影响?
RTC_ALARM中断是用来在关机时唤醒系统的,并且在唤醒后将此次RTC开机事件报给上层,由上层调用闹钟的相关APK,来使能闹铃;
这么一个开机启动闹铃的过程本来就需要耗费时间;
因此提前了3秒产生中断也刚好在开机的过程中弥补掉了;
这样ALM2寄存器的16位可以表示到2^16<
2大约是72.8hours即ticks_max=72.8hours
通过以上修改,使能了ALM2寄存器,确定了计数频率,这样在设置闹钟的时候就可以把闹铃时间设置到RTC寄存器中,从而在时间到时产生可以唤醒系统的中断;