在android 手机上,如果call usleep(2*1000),结果sleep时间不定,甚至结果sleep了50+ms,是不是有点过分,测试代码如下:各位可以在手机上测试下,特别是把程序放在后台运行的情况下。
#include<stdio.h>
#include<stdlib.h>
intmain(int argc,char **argv)
{
struct timeval oldTime, newTime;
int iStime,i,j;
iStime=5;
for(i=0;i<60;i++)
{
for(j=0;j<10;j++)
{
gettimeofday( &oldTime, NULL );
usleep( iStime * 1000 );
gettimeofday( &newTime, NULL );
printf("iStime:%d,actualtime:%lld\n",iStime,((long long)(newTime.tv_sec*1000 +newTime.tv_usec/1000)-(long long)(oldTime.tv_sec*1000 +oldTime.tv_usec/1000)));
}
iStime++;
}
}
缘起
Call path
nanosleep()-->sys_nanosleep()--> hrtimer_nanosleep()--> do_nanosleep()-->hrtimer_start()--> enqueue_hrtimer() -->hrtimer_enqueue_reprogram()-->hrtimer_reprogram()-->int tick_program_event(ktime_t expires, intforce)->
(struct clock_event_device *dev =__get_cpu_var(tick_cpu_device).evtdev; 获得clock_event_device)int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires, intforce)->
intclockevents_program_event(struct clock_event_device *dev, ktime_texpires,ktime_t now) ->
dev->set_next_event((unsignedlong) clc, dev)<在注册的clock_event_device中提供此函数,其主要功能是设置相关寄存器,以设置此超时事件>
/bionic/libc/upstream-freebsd/lib/libc/gen/usleep.c
int
__usleep(useconds_t useconds)
{
struct timespectime_to_sleep;
time_to_sleep.tv_nsec= (useconds % 1000000) * 1000;
time_to_sleep.tv_sec= useconds / 1000000;
return(_nanosleep(&time_to_sleep, NULL));
}
__weak_reference(__usleep, usleep);
__weak_reference(__usleep, _usleep);
nanosleep,它能提供纳秒级的延时精度,该用户空间函数对应的内核实现是sys_nanosleep,它的工作交由高精度定时器系统的hrtimer_nanosleep函数实现,最终的大部分工作则由do_nanosleep完成。hrtimer_nanosleep函数首先在堆栈中创建一个高精度定时器,设置它的到期时间,然后通过do_nanosleep完成最终的延时工作,当前进程在挂起相应的延时时间后,退出do_nanosleep函数,销毁堆栈中的定时器并返回0值表示执行成功。不过do_nanosleep可能在没有达到所需延时数量时由于其它原因退出,如果出现这种情况,hrtimer_nanosleep的最后部分把剩余的延时时间记入进程的restart_block中,并返回ERESTART_RESTARTBLOCK错误代码,系统或者用户空间可以根据此返回值决定是否重新调用nanosleep以便把剩余的延时继续执行完成。下面是hrtimer_nanosleep的代码:
long hrtimer_nanosleep(struct timespec *rqtp, structtimespec __user *rmtp,
const enum hrtimer_mode mode, constclockid_t clockid)
{
structrestart_block *restart;
structhrtimer_sleeper t;
int ret= 0;
unsignedlong slack;
slack =current->timer_slack_ns;
if(rt_task(current))
slack= 0;
hrtimer_init_on_stack(&t.timer,clockid, mode);
hrtimer_set_expires_range_ns(&t.timer,timespec_to_ktime(*rqtp), slack);
if (do_nanosleep(&t, mode))
gotoout;
/*Absolute timers do not update the rmtp value and restart: */
if(mode == HRTIMER_MODE_ABS) {
ret= -ERESTARTNOHAND;
gotoout;
}
if(rmtp) {
ret= update_rmtp(&t.timer, rmtp);
if(ret <= 0)
gotoout;
}
restart= t_thread_info()->restart_block;
restart->fn= hrtimer_nanosleep_restart;
restart->nanosleep.index= t.timer.base->index;
restart->nanosleep.rmtp= rmtp;
restart->nanosleep.expires= hrtimer_get_expires_tv64(&t.timer);
ret =-ERESTART_RESTARTBLOCK;
out:
destroy_hrtimer_on_stack(&t.timer);
returnret;
}
· 获取task的slack= current->timer_slack_ns; 如果是RT 线程slack是0
· hrtimer_init_on_stack(&t.timer,clockid, mode);
· hrtimer_set_expires_range_ns(&t.timer,timespec_to_ktime(*rqtp), slack);
· static inline void hrtimer_set_expires_range_ns(struct hrtimer *timer, ktime_t time, u64 delta)
· {
· timer->_softexpires = time;
· timer->node.expires = ktime_add_safe(time, ns_to_ktime(delta));
· }
该function设定了hrtimer的到期时间的一个范围,重要的事情说三遍,设置的是一个到期的范围!一个到期的范围!!一个到期的范围!!!hrtimer可以在hrtimer._softexpires(usleep输入时间)至timerqueue_node.expires(timer_slack_ns+ softexpires)之间的任何时刻到期,我们也称timerqueue_node.expires(timer_slack_ns+ softexpires)为硬过期时间(hard),意思很明显:到了此时刻,定时器一定会到期,也就是说usleep的时间的sleep的范围为输入时间到timer_slack_ns+inputtime,而不是输入时间。 这样设计的好处是有了这个范围可以选择,定时器系统可以让范围接近的多个定时器在同一时刻同时到期,这种设计可以降低进程频繁地被hrtimer进行唤醒,从而优化系统性能和功耗。
hrtimer就是高精度定时器,精度是高,happy的同时也带来一个问题,那就是中断太多了,虽说中断是个好东西,但是有时候两个定时器相差几十个纳秒, 你完全没必要搞两个中断!那么对于精度要求不高的情况,是可以降低硬件中断次数.这个就是structtask_struct->timer_slack_ns的目的。通过timer_slack_ns来设置一个范围(range),在这个范围里面的hrtimer,我可以顺带一起处理了。那么怎么个一起处理法?
以上面的图为例,一个hrtimeA,其timeout时间为T+delat,但是如果在T~T+delat之间的某一点上,hrtimer B到期, 中断触发,在中断处理函数hrtimer_interrupt里面,顺带把hrtimer A也处理了。
android系统会根据每个线程SchedPolicy采用不同的time_slack_ns.
当前分为两个level的time_slack_ns 值。分别为TimerSlackHigh(40ms)和TimerSlackNormal(50us),所有后台的线程的time_slack_ns 为TimerSlackHigh(40ms),这就意味如果在后台线程里call usleep,并且不是RT thread,那么这个usleep的时间不是准确的。这个时间是随机的在sleeptime-40ms+sleeptime。
最常见的可能就是后台播放音乐会有“啪啪啪”的噪音,其原因就是usleep 不准确,而导致拿不到数据而出现noise。
typedef enum {
SP_DEFAULT = -1,
SP_BACKGROUND = 0,
SP_FOREGROUND = 1,
SP_SYSTEM = 2, // can't be used with set_sched_policy()
SP_AUDIO_APP = 3,
SP_AUDIO_SYS = 4,
SP_TOP_APP = 5,
SP_RT_APP = 6,
SP_RESTRICTED = 7,
SP_CNT,
SP_MAX = SP_CNT - 1,
SP_SYSTEM_DEFAULT = SP_FOREGROUND,
} SchedPolicy;
{
"Name": "TimerSlackHigh",
"Actions": [
{
"Name": "SetTimerSlack",
"Params":
{
"Slack": "40000000"
}
}
]
},
{
"Name": "TimerSlackNormal",
"Actions": [
{
"Name": "SetTimerSlack",
"Params":
{
"Slack": "50000"
}
}
]
},
int set_sched_policy(int tid, SchedPolicy policy) {
if (tid == 0) {
tid = GetThreadId();
}
policy = _policy(policy);
…
switch (policy) {
case SP_BACKGROUND:
return SetTaskProfiles(tid, {"HighEnergySaving", "TimerSlackHigh", "BlkIOBackground"}, true)
? 0 : -1;
case SP_FOREGROUND:
return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
? 0 : -1;
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
? 0 : -1;
case SP_TOP_APP:
return SetTaskProfiles(tid, {"MaxPerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
? 0 : -1;
case SP_RT_APP:
return SetTaskProfiles(tid, {"RealtimePerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
? 0 : -1;
default:
return SetTaskProfiles(tid, {"TimerSlackNormal"}, true) ? 0 : -1;
}
return 0;
}
· AudioTrack msleep 5ms , time_slack_ns 是40ms (audiotrack)在后台运行。
所以hrtimer 的expires=softexpires(inputtime)+ time_slack_ns=72800725083129+40000000=72800765083129
AudioTrack-21685 (19878) [003] d..2 287729.318093:hrtimer_start: hrtimer=ffffffe03827bc40 function=hrtimer_wakeupexpires=72800765083129 softexpires=72800725083129
AudioTrack-21685 (19878) [003] d..3 287729.318134:sched_switch: prev_comm=AudioTrack prev_pid=21685 prev_prio=104 prev_state=S==> next_comm=logd.writer next_pid=614 next_prio=130
<idle>-0 (-----) [003] d.h3 287729.353256:hrtimer_cancel: hrtimer=ffffffe03827bc40
<idle>-0 (-----) [003] d.h2 287729.353258:hrtimer_expire_entry: hrtimer=ffffffe03827bc40 function=hrtimer_wakeupnow=72800760201809
<idle>-0 (-----) [003]d.h3 287729.353263: sched_waking: comm=AudioTrack pid=21685 prio=104target_cpu=003
<idle>-0 (-----) [003]dnh4 287729.353292: sched_wakeup: comm=AudioTrack pid=21685 prio=104target_cpu=003
<idle>-0 (-----) [003]dnh2 287729.353297: hrtimer_expire_exit: hrtimer=ffffffe03827bc40
AudioTrack-21685 (19878) [003] ...1 287729.353604:tracing_mark_write: E|19878
由于是android把后台进程的time_slack_ns 设为了较大的值,从而可能导致后台进程的usleep 不准确,可能导致相关的性能或其他问题。怎么破解?
· 把相关的callusleep的后台线程改为RT线程。
· 在callusleep前,改变time_slack_ns的值。
prctl(PR_SET_TIMERSLACK, slack_)
· 修改SchedPolicy