前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Usleep /timerslack_ns /background thread in android

Usleep /timerslack_ns /background thread in android

作者头像
用户9732312
发布2022-05-13 21:29:45
2K0
发布2022-05-13 21:29:45
举报
文章被收录于专栏:ADAS性能优化

惊奇不惊奇?

在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);

  • Key function

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);

代码语言:javascript
复制
·         static inline void hrtimer_set_expires_range_ns(struct hrtimer *timer, ktime_t time, u64 delta)
代码语言:javascript
复制
·         {
代码语言:javascript
复制
·           timer->_softexpires = time;
代码语言:javascript
复制
·           timer->node.expires = ktime_add_safe(time, ns_to_ktime(delta));
代码语言:javascript
复制
·         }

该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也处理了。

后果

代码语言:javascript
复制
android系统会根据每个线程SchedPolicy采用不同的time_slack_ns.
代码语言:javascript
复制
当前分为两个level的time_slack_ns 值。分别为TimerSlackHigh(40ms)和TimerSlackNormal(50us),所有后台的线程的time_slack_ns 为TimerSlackHigh(40ms),这就意味如果在后台线程里call usleep,并且不是RT thread,那么这个usleep的时间不是准确的。这个时间是随机的在sleeptime-40ms+sleeptime。
代码语言:javascript
复制
最常见的可能就是后台播放音乐会有“啪啪啪”的噪音,其原因就是usleep 不准确,而导致拿不到数据而出现noise。

Key function

代码语言:javascript
复制
typedef enum {
代码语言:javascript
复制
    SP_DEFAULT = -1,
代码语言:javascript
复制
    SP_BACKGROUND = 0,
代码语言:javascript
复制
    SP_FOREGROUND = 1,
代码语言:javascript
复制
    SP_SYSTEM = 2,  // can't be used with set_sched_policy()
代码语言:javascript
复制
    SP_AUDIO_APP = 3,
代码语言:javascript
复制
    SP_AUDIO_SYS = 4,
代码语言:javascript
复制
    SP_TOP_APP = 5,
代码语言:javascript
复制
    SP_RT_APP = 6,
代码语言:javascript
复制
    SP_RESTRICTED = 7,
代码语言:javascript
复制
    SP_CNT,
代码语言:javascript
复制
    SP_MAX = SP_CNT - 1,
代码语言:javascript
复制
    SP_SYSTEM_DEFAULT = SP_FOREGROUND,
代码语言:javascript
复制
} SchedPolicy;
代码语言:javascript
复制
代码语言:javascript
复制
{
代码语言:javascript
复制
      "Name": "TimerSlackHigh",
代码语言:javascript
复制
      "Actions": [
代码语言:javascript
复制
        {
代码语言:javascript
复制
          "Name": "SetTimerSlack",
代码语言:javascript
复制
          "Params":
代码语言:javascript
复制
          {
代码语言:javascript
复制
            "Slack": "40000000"
代码语言:javascript
复制
          }
代码语言:javascript
复制
        }
代码语言:javascript
复制
      ]
代码语言:javascript
复制
    },
代码语言:javascript
复制
    {
代码语言:javascript
复制
      "Name": "TimerSlackNormal",
代码语言:javascript
复制
      "Actions": [
代码语言:javascript
复制
        {
代码语言:javascript
复制
          "Name": "SetTimerSlack",
代码语言:javascript
复制
          "Params":
代码语言:javascript
复制
          {
代码语言:javascript
复制
            "Slack": "50000"
代码语言:javascript
复制
          }
代码语言:javascript
复制
        }
代码语言:javascript
复制
      ]
代码语言:javascript
复制
},
代码语言:javascript
复制
int set_sched_policy(int tid, SchedPolicy policy) {
代码语言:javascript
复制
    if (tid == 0) {
代码语言:javascript
复制
        tid = GetThreadId();
代码语言:javascript
复制
    }
代码语言:javascript
复制
policy = _policy(policy);
代码语言:javascript
复制
代码语言:javascript
复制
代码语言:javascript
复制
    switch (policy) {
代码语言:javascript
复制
        case SP_BACKGROUND:
代码语言:javascript
复制
            return SetTaskProfiles(tid, {"HighEnergySaving", "TimerSlackHigh", "BlkIOBackground"}, true)
代码语言:javascript
复制
                          ? 0 : -1;
代码语言:javascript
复制
        case SP_FOREGROUND:
代码语言:javascript
复制
            return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
代码语言:javascript
复制
                          ? 0 : -1;
代码语言:javascript
复制
        case SP_AUDIO_APP:
代码语言:javascript
复制
        case SP_AUDIO_SYS:
代码语言:javascript
复制
            return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
代码语言:javascript
复制
                          ? 0 : -1;
代码语言:javascript
复制
        case SP_TOP_APP:
代码语言:javascript
复制
            return SetTaskProfiles(tid, {"MaxPerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
代码语言:javascript
复制
                          ? 0 : -1;
代码语言:javascript
复制
        case SP_RT_APP:
代码语言:javascript
复制
            return SetTaskProfiles(tid, {"RealtimePerformance", "TimerSlackNormal", "BlkIOForeground"}, true)
代码语言:javascript
复制
                          ? 0 : -1;
代码语言:javascript
复制
        default:
代码语言:javascript
复制
            return SetTaskProfiles(tid, {"TimerSlackNormal"}, true) ? 0 : -1;
代码语言:javascript
复制
    }
代码语言:javascript
复制
代码语言:javascript
复制
    return 0;
代码语言:javascript
复制
}

One sample :

· 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 调度出去(sleep)

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

  • Hrtimer 被cancel由于到期了

<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

  • 调度AudioTrack.(sleep 35ms)

<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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android性能优化 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 惊奇不惊奇?
    • 后果
      • Key function
        • One sample :
          • 破解:
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档