timerslack 与 sleep()/usleep()
一、简介
1. timerslack 是 Linux 系统为了降低系统功耗,避免 timer 时间参差不齐,过于的频繁的唤醒 cpu,而设置的一种对齐策略。内核中将相近的定时器到期时间汇聚在一起,这会导致到期时间比定时时间晚一些(但不会提前)。
2. 会被timer slack到期时间影响的函数集有 select(2), pselect(2), poll(2), ppoll(2), epoll_wait(2), epoll_pwait(2), clock_nanosleep(2), nanosleep(2), futex(2), sigtimedwait(2)。还有通过futexes实现的库函数,包括 pthread_cond_timedwait(3), pthread_mutex_timedlock(3), pthread_rwlock_timedrdlock(3), pthread_rwlock_timedwrlock(3), sem_timedwait(3).
3. timerslack 是per-task的文件,其通过 /proc/<pid>/timerslack_ns 导出到用户空间, 定时时,定时到期时间若设置为time,则实际的到期时间点在区间 (time, time + p->timer_slack_ns) 中。
二、sleep()/usleep() 函数实现
timerslack 影响的函数众多,这里我们以常用的休眠函数为例进行讲解。
1. Android 中的 usleep() 实现
//bionic/libc/bionic/usleep.cpp int usleep(useconds_t us) { timespec ts; ts.tv_sec = us / 1000000; ts.tv_nsec = (us % 1000000) * 1000; return nanosleep(&ts, nullptr); }
2. Android 中的 sleep() 实现
//bionic/libc/bionic/sleep.cpp unsigned sleep(unsigned s) { #if !defined(__LP64__) // `s` is `unsigned`, but tv_sec is `int` on LP32. if (s > INT_MAX) return s - INT_MAX + sleep(INT_MAX); #endif timespec ts = {.tv_sec = static_cast<time_t>(s)}; return (nanosleep(&ts, &ts) == -1) ? ts.tv_sec : 0; }
可见这两个函数都是通过 nanosleep() 系统调用进入与内核进行交互的。
三、nanosleep()系统调用与实现
1. nanosleep()函数
#include <time.h> int nanosleep(const struct timespec *req, struct timespec *rem); struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
nanosleep() 是一个高精度休眠函数,它会休眠至少 req 时间,若被信号打断返回-1并将errno设置为EINTR,然后将剩余时间写到rem指针中(rem若不为NULL)。若成功休眠了请求的时间,返回0,若失败或被信号打断返回-1并将错误码设置进errno。和 sleep(3)、usleep(3) 比较起来 nanosleep() 为指定睡眠间隔提供了更高的分辨率。
2. nanosleep系统调用
//kernel/time/hrtimer.c SYSCALL_DEFINE2(nanosleep, struct __kernel_timespec __user *, rqtp, struct __kernel_timespec __user *, rmtp) { struct timespec64 tu; if (get_timespec64(&tu, rqtp)) return -EFAULT; if (!timespec64_valid(&tu)) return -EINVAL; current->restart_block.nanosleep.type = rmtp ? TT_NATIVE : TT_NONE; current->restart_block.nanosleep.rmtp = rmtp; return hrtimer_nanosleep(&tu, HRTIMER_MODE_REL, CLOCK_MONOTONIC); }
3. nanosleep内核实现
nanosleep 在内核中的实现函数为 hrtimer_nanosleep()
/* nanosleep: (&tu, HRTIMER_MODE_REL, CLOCK_MONOTONIC) */ //kernel/time/hrtimer.c long hrtimer_nanosleep(const struct timespec64 *rqtp, const enum hrtimer_mode mode, const clockid_t clockid) { struct restart_block *restart; struct hrtimer_sleeper t; int ret = 0; u64 slack; /* 注意这里使用到了 timer_slack_ns */ slack = current->timer_slack_ns; /* rt和dl任务不受timer_slack影响 */ if (dl_task(current) || rt_task(current)) slack = 0; /* 将 t->timer.function = hrtimer_wakeup, t->task = current; */ hrtimer_init_sleeper_on_stack(&t, clockid, mode); /* 超时时间点在区间(rqtp, rqtp+slack)中 */ hrtimer_set_expires_range_ns(&t.timer, timespec64_to_ktime(*rqtp), slack); /* 将当前进程切走,唤醒后从这里继续往下执行,正常返回0 */ ret = do_nanosleep(&t, mode); if (ret != -ERESTART_RESTARTBLOCK) goto out; /* Absolute timers do not update the rmtp value and restart: */ if (mode == HRTIMER_MODE_ABS) { ret = -ERESTARTNOHAND; goto out; } /* 下面应该是重复定时机制的实现 */ restart = ¤t->restart_block; restart->nanosleep.clockid = t.timer.base->clockid; restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer); set_restart_fn(restart, hrtimer_nanosleep_restart); out: destroy_hrtimer_on_stack(&t.timer); return ret; }
定时器到期回调函数:
//kernel/time/hrtimer.c static enum hrtimer_restart hrtimer_wakeup(struct hrtimer *timer) { struct hrtimer_sleeper *t = container_of(timer, struct hrtimer_sleeper, timer); struct task_struct *task = t->task; /* 定时到期将 t->task 设置为NULL, 这个在对端作为退出等待的判断条件 */ t->task = NULL; if (task) wake_up_process(task); return HRTIMER_NORESTART; }
四、timerslack 的设置方法
1. 通过proc文件进行设置,会设置到任务 p->timer_slack_ns 中。
echo slack_ns > /proc/pod/timerslack_ns
timerslack_ns_write
p->timer_slack_ns = slack_ns
2. 在Android下,Process.java中封装的cgroup分组切换函数会进行设置,
Process.java 中的 setProcessGroup(pid, THREAD_GROUP_TOP_APP) 在 task_profiles.json 中对应的集合属性 SCHED_SP_TOP_APP 中的 TimerSlackNormal 属性就会根据任务分组重新配置任务的
timerslack 值。
3. Android java 中 java.lang.Thread 类封装的 setPriority() 也会进行调用。
Thread t = new Thread(); t.start(); t.setPriority(3); 这是个错误用法,这样设置由于线程pid还是0导致设置的是调用线程的优先级,但是作为讲解的例子足够了。
t.setPriority() 最终会调用到 SetNativePriority():
//art/runtime/thread.cc void Thread::SetNativePriority(int new_priority) { palette_status_t status = PaletteSchedSetPriority(GetTid(), new_priority); } /* 若Java中设置 Thread t = new Thread(); t.setPriority(3); 就是设置nice=13 */ palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t managed_priority) { /* Thread::setPriority(X),这层的取值范围是 [1, 10] */ if (managed_priority < art::palette::kMinManagedThreadPriority || managed_priority > art::palette::kMaxManagedThreadPriority) { return PALETTE_STATUS_INVALID_ARGUMENT; } /* 若传3,就是3-1,对应 ANDROID_PRIORITY_BACKGROUND + 3,nice=13 */ int new_nice = kNiceValues[managed_priority - art::palette::kMinManagedThreadPriority]; int curr_nice = getpriority(PRIO_PROCESS, tid); if (curr_nice == new_nice) { return PALETTE_STATUS_OK; } /* 还与cgroup挂钩,切cgroup */ if (new_nice >= ANDROID_PRIORITY_BACKGROUND) { //10 /* libprocessgroup 中的函数,其会设置任务的 timerslack */ SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true); } else if (curr_nice >= ANDROID_PRIORITY_BACKGROUND) { SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true); } /* 设当前进程nice值,new_nice取值[-20, 19] */ if (setpriority(PRIO_PROCESS, tid, new_nice) != 0) { return PALETTE_STATUS_CHECK_ERRNO; } return PALETTE_STATUS_OK; }
t.setPriority() 是怎么调用下来的,向上逐层查找:
//art/runtime/thread.h pid_t GetTid() const { return tls32_.tid;} //art/runtime/thread.cc tls32_.tid 的赋值位置: void Thread::InitTid() { tls32_.tid = ::art::GetTid(); } bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) { ... InitTid(); ...} void* Thread::CreateCallback(void* arg) { ... self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env); ...} /* Thread::CreateCallback 就是线程的执行函数体,它先做一些初始化后再调用的用户的回调函数 */ void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { ... pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread); ...} /* art/openjdkjvm/OpenjdkJvm.cc */ JNIEXPORT void JVM_StartThread(JNIEnv* env, jobject jthread, jlong stack_size, jboolean daemon) { ... art::Thread::CreateNativeThread(env, jthread, stack_size, daemon == JNI_TRUE); } /* external/oj-libjdwp/src/share/javavm/export/jvm.h java.lang.Thread 类的 */ JNIEXPORT void JNICALL JVM_StartThread(JNIEnv *env, jobject thread);
上面设置方法是错误的原因是:在回调函数,也就是创建的线程执行后,GetTid()的值才是新建线程的。在调用设置优先级函数时,线程回调函数还没来得及调用,因此此时 GetTid() 的值还是0。对于 setpriority() 系统调用,若是0就表示设置的是当前进程。正确设置方法:
Thread thread = new Thread( new Runnable(){ public void run() { Thread.currentThread().setPriority(3); Process.setThreadPriority(13); }}); thread.start();
五、timerslack 相关问题
1. 一个微信卡音相关分享:https://cloud.tencent.com/developer/article/1836285
六、总结
1. timerslack 是一个per-task的可调的参数,它会影响CFS任务(不会影响RT任务)sleep、wait等众多函数的唤醒及时性。
2. timerslack 在上层cgroup分组切换中,和java代码的 Thread 类实现函数中都有调用。
七、补充
1. futex中对hrtime的使用函数是 futex_setup_timer(), 其里面也使用到了 current->timer_slack_ns 将超时设定在一个区间中。
posted on 2022-10-19 22:34 Hello-World3 阅读(931) 评论(0) 编辑 收藏 举报