Android进程管理2 —nice值/Cgroup
1.Linux中的进程
1.1 实时进程与普通进程
Linux中将进程分为实时进程和普通进程。内核中使用0~139的数值表示内部优先级, 数值越低, 优先级越高。
在用户空间,进程优先级有两种含义:nice value[-20,19]和scheduling priority[0,139]。
实时进程的scheduling priority[范围为099,普通进程为100139(对应nice的值[-20,19])。
对于普通进程来说,优先级就是nice值。对于普通进程来说他们的scheduling priority,被设定为0。普通进程可以看通过修改nice value可以改变普通进程获取cpu资源的比例。
对于实时进程来说,优先级是priority值。
可通过adb直接修改某个进程的nice值: renice prio pid
1.2 实时调度与非实时调度
linux调度策略分为实时(realtime)调度与非实时(normal)调度。
实时调度包括
- SCHED_FIFO
- SHCHED_RR
非实时调度包括(CFS完全公平调度):
- SCHED_OTHER
- SHCED_BATCH
- SHCED_IDLE
实时调度SCHED_FIFO:
实时进程的优先级大于非实时进程的优先级,意味着系统中如果有实时任务时,实时任务抢占非实时任务执行。android实时调度主要用来执行对延迟非常敏感的任务,比如:SurfaceFlinger、Audio、lmkd等系统服务相关进程。
非实时调度SCHED_OTHER:
非实时调度是基于linux CFS,即完全公平调度器。SCHED_OTHER是通过优先级(nice值)来分配cpu资源的。
2. Android中的进程调度
在android中实时调度使用SCHED_FIFO,非实时调度一般使用SCHED_OTHER。
2.1 Android中的实时调度
android中的实时调度使用了SCHED_FIFO策略,进程占有CPU时,如果没有更高优先级的实时进程抢占或主动让出,进程将保持使用CPU。这也说明android系统希望实时进程能高优先级的持续运行,不想其因为时间片的耗尽而中断执行。但系统是不会让实时进程一直运行的,CPU消耗型的进程是不会设置为实时进程的,实时进程更倾向于为实时性较为敏感的IO消耗型进程服务。
2.1.1 Android系统中SCHED_FIFO调度策略的应用
Android系统在以下几个地方设置了SCHED_FIFO调度策略,
- AMS中,当属性"sys.use_fifo_ui"设置为1时,将前台进程的UI线程和Render线程设置为为实时策略,否则为普通进程。同时设置了SCHED_RESET_ON_FORK位,表明其子进程会恢复默认的调度策略。
AMS中的代码如下,很奇怪我的android10没有启用
if (SystemProperties.getInt("sys.use_fifo_ui", 0) != 0) {
mUseFifoUiScheduling = true;
}
if (proc.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
if (mUseFifoUiScheduling) {
setThreadScheduler(proc.renderThreadTid,
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
setThreadPriority(proc.renderThreadTid, TOP_APP_PRIORITY_BOOST);
}
}
- SurfaceFlinger中,在亮屏时使用实时调度,灭屏时使用非实时调度。在SurfaceFlinger.cpp中直接调用
sched_setscheduler - 如果支持Audio FastMixer,FastMixer使用实时调度,通过requestPriority()设置。
- Android新增加的AAudioService中,将client线程设置为实时策略,通过requestPriority()设置。
属性"camera.fifo.disable"没有被设置时,将Cameraservice的request线程设置为实时策略,通过requestPriority()设置。
- Audio HIDL使用实时策略,通过requestPriority()设置。
2.1.2 android实时进程调度策略调整——requestPriority()
frameworks/base/services/core/java/com/android/server/os/SchedulingPolicyService.java
Android对实时调度策略的使用是很谨慎的,只有与硬件相关的数据传输进程才会使用实时策略。大部分的进程还是做为普通进程通过优先级的调整来管理。
public int requestPriority(int pid, int tid, int prio, boolean isForApp) {
// Verify that the caller uid is permitted, priority is in range,
// and that the callback thread specified by app belongs to the app that
// called mediaserver or audioserver.
// Once we've verified that the caller uid is permitted, we can trust the pid but
// we can't trust the tid. No need to explicitly check for pid == 0 || tid == 0,
// since if not the case then the getThreadGroupLeader() test will also fail.
//UID为AudioServer,CameraServer,Bluetooth时才能使用该接口,
//优先级为1~3,线程组ID(tgid)与pid不同时不能使用该接口
if (!isPermitted() || prio < PRIORITY_MIN ||
prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {
return PackageManager.PERMISSION_DENIED;
}
if (Binder.getCallingUid() != Process.BLUETOOTH_UID) {
try {
// make good use of our CAP_SYS_NICE capability
Process.setThreadGroup(tid, !isForApp ?
Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_RT_APP);
} catch (RuntimeException e) {
Log.e(TAG, "Failed setThreadGroup: " + e);
return PackageManager.PERMISSION_DENIED;
}
}
try {
// must be in this order or it fails the schedulability constraint
Process.setThreadScheduler(tid, Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK,
prio);
} catch (RuntimeException e) {
Log.e(TAG, "Failed setThreadScheduler: " + e);
return PackageManager.PERMISSION_DENIED;
}
return PackageManager.PERMISSION_GRANTED;
}
2.1.3 Process.setThreadGroup的实现
这里同步分析setThreadGroup()和setThreadGroupAndCpuset()两个方法
frameworks/base/core/jni/android_util_Process.cpp
void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint grp)
{
ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp);
SchedPolicy sp = (SchedPolicy) grp;
int res = set_sched_policy(tid, sp);
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
}
}
void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int tid, jint grp)
{
ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp);
SchedPolicy sp = (SchedPolicy) grp;
int res = set_sched_policy(tid, sp);
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
}
res = set_cpuset_policy(tid, sp);
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
}
}
system/core/libprocessgroup/sched_policy.cpp
set_sched_policy 设置的是dev/stune
int set_sched_policy(int tid, SchedPolicy policy) {
if (tid == 0) {
tid = GetThreadId();
}
policy = _policy(policy);
#if POLICY_DEBUG
char statfile[64];
char statline[1024];
char thread_name[255];
//打开进程所有 task 目录
snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid);
memset(thread_name, 0, sizeof(thread_name));
unique_fd fd(TEMP_FAILURE_RETRY(open(statfile, O_RDONLY | O_CLOEXEC)));
if (fd >= 0) {
int rc = read(fd, statline, 1023);
statline[rc] = 0;
char* p = statline;
char* q;
for (p = statline; *p != '('; p++)
;
p++;
for (q = p; *q != ')'; q++)
;
strncpy(thread_name, p, (q - p));
}
switch (policy) {
case SP_BACKGROUND:
SLOGD("vvv tid %d (%s)", tid, thread_name);
break;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
case SP_TOP_APP:
SLOGD("^^^ tid %d (%s)", tid, thread_name);
break;
case SP_SYSTEM:
SLOGD("/// tid %d (%s)", tid, thread_name);
break;
case SP_RT_APP:
SLOGD("RT tid %d (%s)", tid, thread_name);
break;
default:
SLOGD("??? tid %d (%s)", tid, thread_name);
break;
}
#endif
switch (policy) {
case SP_BACKGROUND:
return SetTaskProfiles(tid, {"HighEnergySaving", "TimerSlackHigh"}, true) ? 0 : -1;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal"}, true) ? 0 : -1;
case SP_TOP_APP:
return SetTaskProfiles(tid, {"MaxPerformance", "TimerSlackNormal"}, true) ? 0 : -1;
case SP_RT_APP:
return SetTaskProfiles(tid, {"RealtimePerformance", "TimerSlackNormal"}, true) ? 0 : -1;
default:
return SetTaskProfiles(tid, {"TimerSlackNormal"}, true) ? 0 : -1;
}
return 0;
}
set_cpuset_policy 设置的是dev/cpuset
int set_cpuset_policy(int tid, SchedPolicy policy) {
if (tid == 0) {
tid = GetThreadId();
}
policy = _policy(policy);
switch (policy) {
case SP_BACKGROUND:
return SetTaskProfiles(tid,
{"HighEnergySaving", "ProcessCapacityLow", "LowIoPriority",
"TimerSlackHigh"},
true)
? 0
: -1;
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
return SetTaskProfiles(tid,
{"HighPerformance", "ProcessCapacityHigh", "HighIoPriority",
"TimerSlackNormal"},
true)
? 0
: -1;
case SP_TOP_APP:
return SetTaskProfiles(tid,
{"MaxPerformance", "ProcessCapacityMax", "MaxIoPriority",
"TimerSlackNormal"},
true)
? 0
: -1;
case SP_SYSTEM:
return SetTaskProfiles(tid, {"ServiceCapacityLow", "TimerSlackNormal"}, true) ? 0 : -1;
case SP_RESTRICTED:
return SetTaskProfiles(tid, {"ServiceCapacityRestricted", "TimerSlackNormal"}, true)
? 0
: -1;
default:
break;
}
return 0;
}
system/core/libprocessgroup/processgroup.cpp
bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
const TaskProfiles& tp = TaskProfiles::GetInstance();
for (const auto& name : profiles) {
TaskProfile* profile = tp.GetProfile(name);
if (profile != nullptr) {
if (use_fd_cache) {
profile->EnableResourceCaching();
}
if (!profile->ExecuteForTask(tid)) {
PLOG(WARNING) << "Failed to apply " << name << " task profile";
}
} else {
PLOG(WARNING) << "Failed to find " << name << "task profile";
}
}
return true;
}
system/core/libprocessgroup/task_profiles.cpp
bool SetCgroupAction::ExecuteForTask(int tid) const {
std::lock_guard<std::mutex> lock(fd_mutex_);
if (IsFdValid()) {
// fd is cached, reuse it
if (!AddTidToCgroup(tid, fd_)) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
return true;
}
if (fd_ == FDS_INACCESSIBLE) {
// no permissions to access the file, ignore
return true;
}
if (fd_ == FDS_APP_DEPENDENT) {
// application-dependent path can't be used with tid
PLOG(ERROR) << "Application profile can't be applied to a thread";
return false;
}
// fd was not cached because cached fd can't be used
std::string tasks_path = controller()->GetTasksFilePath(path_);
unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
if (tmp_fd < 0) {
PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
return false;
}
if (!AddTidToCgroup(tid, tmp_fd)) {
LOG(ERROR) << "Failed to add task into cgroup";
return false;
}
return true;
}
bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
if (tid <= 0) {
return true;
}
std::string value = std::to_string(tid);
if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
// If the thread is in the process of exiting, don't flag an error
if (errno != ESRCH) {
PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
return false;
}
}
return true;
}
2.1.4 Process.setThreadScheduler的实现
frameworks/base/core/jni/android_util_Process.cpp
void android_os_Process_setThreadScheduler(JNIEnv* env, jclass clazz,
jint tid, jint policy, jint pri)
{
// linux has sched_setscheduler(), others don't.
#if defined(__linux__)
struct sched_param param;
param.sched_priority = pri;
int rc = sched_setscheduler(tid, policy, ¶m);
if (rc) {
signalExceptionForPriorityError(env, errno, tid);
}
#else
signalExceptionForPriorityError(env, ENOSYS, tid);
#endif
}
sched_setscheduler的实现主要在linux中,这部分代码在Android10没有找到具体实现。
小结
Process.setThreadGroup 在上层提供的参数如下表。
| Group参数 | 值 | 描述 |
|---|---|---|
| THREAD_GROUP_DEFAULT | -1 | 默认线程组 |
| THREAD_GROUP_BG_NONINTERACTIVE | 0 | 后台线程组 - 所有线程都以降低的CPU份额进行调度 |
| THREAD_GROUP_FOREGROUND | 1 | 前台线程组 - 所有线程都以正常的CPU份额进行调度 |
| THREAD_GROUP_SYSTEM | 2 | 系统线程组 |
| THREAD_GROUP_AUDIO_APP | 3 | 应用音频线程组 |
| THREAD_GROUP_AUDIO_SYS | 4 | 系统音频线程组 |
| THREAD_GROUP_TOP_APP | 5 | 顶层应用线程组 |
| THREAD_GROUP_RT_APP | 6 | 实时应用线程组 |
| THREAD_GROUP_RESTRICTED | 7 | 绑定前台服务线程组 - 在屏幕关闭时具有额外的CPU限制 |
setThreadGroup和setThreadGroupAndCpuset共计在如下场景被调用
| 调用场景 | 代码 |
|---|---|
| SchedulingPolicyService中 requestPriority() | Process.setThreadGroup(tid, !isForApp ? |
Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_RT_APP); |
| UIThread.run() | Process.setThreadGroup(Process.myTid(), Process.THREAD_GROUP_TOP_APP) | | AppCompactor内存压缩初始化的时候 | Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); | | AMS中初始化时对background和mOomAdjuster的设置 | Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(), Process.THREAD_GROUP_SYSTEM); Process.setThreadGroupAndCpuset( mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); | | oomAdjuster中的adjustThread负责调整cgroup | Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP); | | DisplayManagerservice会把DisplayThread AnimationThread SurfaceAnimationThread设为THREAD_GROUP_TOP_APP | Process.setThreadGroupAndCpuset(DisplayThread.get().getThreadId(), Process.THREAD_GROUP_TOP_APP); Process.setThreadGroupAndCpuset(AnimationThread.get().getThreadId(), Process.THREAD_GROUP_TOP_APP); Process.setThreadGroupAndCpuset(SurfaceAnimationThread.get().getThreadId(), Process.THREAD_GROUP_TOP_APP); |
Process.setThreadScheduler相关参数被使用情况
| 变量名 | 值 | 描述 | 使用 |
|---|---|---|---|
| SCHED_OTHER | 0 | 其他调度策略 | default |
| SCHED_FIFO | 1 | 先进先出调度策略 | renderThread,oomAdjuster前后台切换,SchedulingPolicyService中,以及vr相关类被使用 |
| SCHED_RR | 2 | 轮转调度策略 | 没有调用 |
| SCHED_BATCH | 3 | 批量调度策略 | 没有调用 |
| SCHED_IDLE | 5 | 空闲调度策略 | 没有调用 |
| SCHED_RESET_ON_FORK | 0x40000000 | 在fork时重置调度器选择 | 配合SCHED_FIFO fork之后不再FIFO |
这里参考上一遍进程前后台切换,可以推测设置的priority为nice值。
当进程从非前台切换到前台,如果系统支持 fifo sys.use_fifo_ui=true,则通过Process.setThreadScheduler()设置当前进程为Process.SCHED_FIFO,如果进程的渲染线程存在,则同样设置为Process.SCHED_FIFO。 即实时调度当前进程。 如果系统不支持fifo,则通过true,则通过Process.setThreadPriority()设置当前进程为TOP_APP_PRIORITY_BOOST = -10,渲染线程同样如此。
当进程从前台切换为非前台,同样判断是否支持fifo,如果支持通过setThreadScheduler设置该进程调度策略为SCHED_OTHER,渲染线程同样如此。并setThreadPriority设置渲染线程priority为-4(DISPLAY) 如果不支持则设置当前进程的和渲染进程的优先级均为0.
2.2 Android中的非实时调度
Android中的非实时调度使用SCHED_OTHER调度策略
Android中以通过以下两种方式改变优先级
- android.os.Process类 Process.setThreadPriority(tid, priority)
- java Thread类 java.lang.Thread.setPriority()
两者都是改变线程的nice值。Process根据Android的特点定义了丰富的优先级,更适合Android,但tid只能在当前线程运行中才可以拿到,所以只有在线程执行的时候才能改变线程的优先级。
2.2.1 Process.setThreadPriority调整优先级 android api
Process类中根据android的特点也定义了部分nice值对应的常量,如下:
| 线程优先级常量 | nice值 | 说明 |
|---|---|---|
| THREAD_PRIORITY_DEFAULT | 0 | 默认 |
| THREAD_PRIORITY_LOWEST | 19 | 最低 |
| THREAD_PRIORITY_BACKGROUND | 10 | 后台 |
| THREAD_PRIORITY_FOREGROUND | -2 | 前台 |
| THREAD_PRIORITY_DISPLAY | -4 | 显示 |
| THREAD_PRIORITY_URGENT_DISPLAY | -8 | 重要显示线程的优先级,屏幕合成和获取输入事件使用这个级别 |
| THREAD_PRIORITY_VIDEO | -10 | 视频 |
| THREAD_PRIORITY_AUDIO | -16 | 音频 |
| THREAD_PRIORITY_URGENT_AUDIO | -19 | 重要音频 |
| THREAD_PRIORITY_MORE_FAVORABLE | -1 | 更有利的线程优先级 |
| THREAD_PRIORITY_LESS_FAVORABLE | +1 | 较不利的线程优先级 |
Android中main thread、render thread的nice值<=THREAD_PRIORITY_FOREGROUND,而后台线程nice值一般设置>=THREAD_PRIORITY_BACKGROUND。Android通过(nice值)给main thread分配更高的优先级。
除了THREAD_PRIORITY_DEFAULT、THREAD_PRIORITY_LOWEST和THREAD_PRIORITY_BACKGROUND以外,都标注“用户通常不应该修改为此优先级”。这表明了Android的态度,它并不希望应用开发者随意去修改优先级。但在代码中并没有做出这种限制,实际上用户可以通过android.os.Process.setThreadPriority()设置-2019任意一个值。setThreadPriority()与Linux命令nice的作用相同,在100139间调整普通进程的优先级。
2.2.2 Process.setThreadPriority()代码流程
frameworks/base/core/jni/android_util_Process.cpp
void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
jint pid, jint pri){
int rc = androidSetThreadPriority(pid, pri);//调用libutils中设置优先级的接口
}
system/core/libutils/Threads.cpp
#if defined(__ANDROID__)
int androidSetThreadPriority(pid_t tid, int pri)
{
int rc = 0;
int lasterr = 0;
// 使用Linux系统接口设置优先级
if (pri >= ANDROID_PRIORITY_BACKGROUND) { // 10 nice
rc = set_sched_policy(tid, SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
rc = set_sched_policy(tid, SP_FOREGROUND);
}
return rc;
}
SP_BACKGROUND/SP_FOREGROUND这里是Cgroup相关知识
这里通过priority和ANDROID_PRIORITY_BACKGROUND nice =10比较,如果大于10设置为SP_BACKGROUND。如果小于10,在通过设置进程和tid重新获取
2.2.3 java.lang.Thread.setPriority()代码流程 java api
除了Android,java也提供了api可以设置线程的优先级。
libcore/ojluni/src/main/java/java/lang/Thread.java
**/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
// Android-changed: Improve exception message when the new priority is out of bounds.
throw new IllegalArgumentException("Priority out of range: " + newPriority);
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
// Android-changed: Avoid native call if Thread is not yet started.
// setPriority0(priority = newPriority);
synchronized(this) {
this.priority = newPriority;
if (isAlive()) {
setPriority0(newPriority);
}
}
}
}**
art/runtime/native/java_lang_Thread.cc
/*
* Alter the priority of the specified thread. "new_priority" will range
* from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY (1-10), with "normal"
* threads at Thread.NORM_PRIORITY (5).
*/
static void Thread_setPriority0(JNIEnv* env, jobject java_thread, jint new_priority) {
ScopedObjectAccess soa(env);
MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
Thread* thread = Thread::FromManagedThread(soa, java_thread);
if (thread != nullptr) {
thread->SetNativePriority(new_priority);
}
}
art/runtime/thread.cc
void Thread::SetNativePriority(int new_priority) {
palette_status_t status = PaletteSchedSetPriority(GetTid(), new_priority);
CHECK(status == PALETTE_STATUS_OK || status == PALETTE_STATUS_CHECK_ERRNO);
}
这里底层又提供了kNiceValues数组,定义了10个nice值
system/libartpalette/palette_android.cc
/ We use Android thread priority constants to be consistent with the rest
// of the system. In some cases adjacent entries may overlap.
//
static const int kNiceValues[art::palette::kNumManagedThreadPriorities] = {
ANDROID_PRIORITY_LOWEST, // 1 (MIN_PRIORITY) //19
ANDROID_PRIORITY_BACKGROUND + 6, //16
ANDROID_PRIORITY_BACKGROUND + 3, //13
ANDROID_PRIORITY_BACKGROUND, //10
ANDROID_PRIORITY_NORMAL, // 5 (NORM_PRIORITY)//0
ANDROID_PRIORITY_NORMAL - 2, // -2
ANDROID_PRIORITY_NORMAL - 4, //-4
ANDROID_PRIORITY_URGENT_DISPLAY + 3, //-5
ANDROID_PRIORITY_URGENT_DISPLAY + 2, //-6
ANDROID_PRIORITY_URGENT_DISPLAY //-8
};
// Managed thread definitions
static constexpr int32_t kNormalManagedThreadPriority = 5;
static constexpr int32_t kMinManagedThreadPriority = 1;
static constexpr int32_t kMaxManagedThreadPriority = 10;
static constexpr int32_t kNumManagedThreadPriorities =
kMaxManagedThreadPriority - kMinManagedThreadPriority + 1;
palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t managed_priority) {
if (managed_priority < art::palette::kMinManagedThreadPriority ||
managed_priority > art::palette::kMaxManagedThreadPriority) {
return PALETTE_STATUS_INVALID_ARGUMENT;
}
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;
}
if (new_nice >= ANDROID_PRIORITY_BACKGROUND) { //nice = 10
SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true);
} else if (curr_nice >= ANDROID_PRIORITY_BACKGROUND) {
SchedPolicy policy;
// Change to the sched policy group of the process.
if (get_sched_policy(getpid(), &policy) != 0) {
policy = SP_FOREGROUND;
}
SetTaskProfiles(tid, {get_sched_policy_profile_name(policy)}, true);
}
if (setpriority(PRIO_PROCESS, tid, new_nice) != 0) {
return PALETTE_STATUS_CHECK_ERRNO;
}
return PALETTE_STATUS_OK;
}
Thread.setPriority()的实现最终与Process.setThreadPriority()的实现类似,但Thread.setPriority()的优先级是从小到大设置10个级别,对应到linux中的nice值如下表。
| Java Priority | nice值 |
|---|---|
| 1 | 19 |
| 2 | 16 |
| 3 | 13 |
| 4 | 10 |
| 5 | 0 |
| 6 | -2 |
| 7 | -4 |
| 8 | -5 |
| 9 | -6 |
| 10 | -8 |
可以参考这篇文章更好理解关于线程优先级的应用
2.3小结
- 由于linxu没有线程进程之分,对于android的进程和线程均Process.java进行优先级和调度策更改。
- Android中的调度策略根据实时调度和非实时调度分为SCHED_FIFO和SCHED_OTHER。
- 实时调度只占Android系统中很少一部分,比如硬件相关,显示相关,以及对于前后台进程切换的处理。(前提fifo sys.use_fifo_ui=true)
- nice值只对普通进程(非实时调度)有作用
- 对于非实时调度android和java分别提供自己的api。两者划分级别不同,参数也有对应关系。
- Android应用的默认优先级是THREAD_PRIORITY_DEFAULT = 0(nice),对应Linux内核中的值为120。
3.Linux 进程组与 Cgroups
Cgroup,全称为Control Group,是Linux内核提供的一种机制,用于将进程组织成层次结构,并对每个组别(或者称为Cgroup)分配资源和限制资源使用。Cgroup可用于在操作系统层面上对进程进行资源管理和控制。
3.1 cgroup子系统:
一个子系统就是一个资源控制器,一般挂在在dev/xxx节点下。举例一些常见的子系统如下:
- blkio -- 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
- cpu -- 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
- cpuacct -- 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
- cpuset -- 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
- devices -- 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
- freezer -- 这个子系统挂起或者恢复 cgroup 中的任务。
- memory -- 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
以下是官方文档source.android.com/docs/core/p…
在android9及以下,通常通过.rc文件配置cgroup
android10及以上,通过cgroups.json [task_profiles.json](<https://android.googlesource.com/platform/system/core/+/6e3f64e7d8b60d912ef00f337f10999a3bc8d8bf/libprocessgroup/profiles/task_profiles.json>) json文件配置。
实际上高版本也可以通过rc文件 writepid方式处理
lmkd.c
writepid /dev/cpuset/system-background/tasks
cpuset的用法
特别要注意一点,cpuset和schedboost_enabled功能需要在内核编译时启用。
/*
* Check if Linux kernel enables CPUSETS feature.
*
* Return value: 1 if Linux kernel CONFIG_CPUSETS=y; 0 otherwise.
*/
extern bool cpusets_enabled();
/*
* Check if Linux kernel enables SCHEDTUNE feature (only available in Android
* common kernel or Linaro LSK, not in mainline Linux as of v4.9)
*
* Return value: 1 if Linux kernel CONFIG_CGROUP_SCHEDTUNE=y; 0 otherwise.
*/
extern bool schedboost_enabled();
可以通过下面的命令 **查看已编译内核配置的方法 zcat /proc/config.gz
3.2 高版本上其他代码变化
我目前使用的mtk平台Android12版本,除了system/core/libprocessgroup/include/processgroup,还有在厂商libmtkcutils/sched_policy.c
增加很多逻辑。比如__initialize初始化操作。
static void __initialize(void) {
char* filename;
if (!access("/dev/cpuctl/tasks", F_OK)) {
__sys_supports_schedgroups = 1;
filename = "/dev/cpuctl/tasks";
fg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
if (fg_cgroup_fd < 0) {
SLOGE("open of %s failed: %s\n", filename, strerror(errno));
}
filename = "/dev/cpuctl/bg_non_interactive/tasks";
bg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
if (bg_cgroup_fd < 0) {
SLOGE("open of %s failed: %s\n", filename, strerror(errno));
}
} else {
__sys_supports_schedgroups = 0;
}
#ifdef USE_CPUSETS
if (!access("/dev/cpuset/tasks", F_OK)) {
filename = "/dev/cpuset/foreground/tasks";
fg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
filename = "/dev/cpuset/background/tasks";
bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
filename = "/dev/cpuset/system-background/tasks";
system_bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
filename = "/dev/cpuset/top-app/tasks";
ta_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
#ifdef USE_SCHEDBOOST
filename = "/dev/stune/top-app/tasks";
ta_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
filename = "/dev/stune/foreground/tasks";
fg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
filename = "/dev/stune/background/tasks";
bg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
#endif
}
#endif
}
/*
3.3 java层和native层线程分组对应关系
SchedPolicy
Android 底层对进程分组的操作最后是通过 sched_policy.cpp 文件中的 set_sched_policy(int tid, SchedPolicy policy) 和 set_cpuset_policy(int tid, SchedPolicy policy) 函数添加到对应的进程组的,调用这两个函数的传递的 SchedPolicy 定义在 sched_policy.h 中,定义不同的调度策略:
/* Keep in sync with THREAD_GROUP_* in frameworks/base/core/java/android/os/Process.java */
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;
经过对比可以得到以下列表
| 描述 | ActivityManager.java | SchedPolicy .cpp | value | ProcessList.java |
|---|---|---|---|---|
| 默认线程组 | Process.THREAD_GROUP_DEFAULT | SP_DEFAULT | -1 | SCHED_GROUP_DEFAULT 2 |
| 后台线程组 - 所有线程都以降低的CPU份额进行调度 | Process.THREAD_GROUP_BG_NONINTERACTIVE | SP_BACKGROUND | 0 | SCHED_GROUP_BACKGROUND 0 |
| 前台线程组 - 所有线程都以正常的CPU份额进行调度 | Process.THREAD_GROUP_FOREGROUND | SP_FOREGROUND | 1 | |
| 系统线程组 | Process.THREAD_GROUP_SYSTEM | SP_SYSTEM | 2 | |
| 应用音频线程组 | Process.THREAD_GROUP_AUDIO_APP | SP_AUDIO_APP | 3 | |
| 系统音频线程组 | Process.THREAD_GROUP_AUDIO_SYS | SP_AUDIO_SYS | 4 | |
| 顶层应用线程组 | Process.THREAD_GROUP_TOP_APP | SP_TOP_APP | 5 | SCHED_GROUP_TOP_APP/SCHED_GROUP_TOP_APP_BOUND 3 / 4 |
| 实时应用线程组 | Process.THREAD_GROUP_RT_APP | SP_RT_APP | 6 | |
| 绑定前台服务线程组 - 在屏幕关闭时具有额外的CPU限制 | Process.THREAD_GROUP_RESTRICTED | SP_RESTRICTED | 7 | SCHED_GROUP_RESTRICTED 1 |
THREAD_GROUP_BG_NONINTERACTIVE 和THREAD_GROUP_BACKGROUND是同一个
3.4 cgroup子系统的举例
3.4 cgroup子系统的举例
子系统 CPUCTL
peony-perf1:/dev/cpuctl $ ls -l
total 0
-rw-r--r-- 1 root root 0 2023-07-06 06:12 cgroup.clone_children
-rw-r--r-- 1 root root 0 2023-07-06 06:12 cgroup.procs
-r--r--r-- 1 root root 0 2023-07-06 06:12 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 1970-01-01 08:00 cpu.rt_period_us
-rw-r--r-- 1 root root 0 1970-01-01 08:00 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 2023-07-06 06:12 cpu.shares
-rw-r--r-- 1 root root 0 2023-07-06 06:12 notify_on_release
-rw-r--r-- 1 root root 0 2023-07-06 06:12 release_agent
-rw-rw-rw- 1 system system 0 1970-01-01 08:00 tasks
Android10平台
| 文件/文件夹 | 描述 | value |
|---|---|---|
| cgroup.clone_children | CPU控制组是否会在fork()系统调用中继承父进程的控制组。 | 0 |
| cgroup.procs | 当前在该CPU控制组中运行的进程的PID(进程ID)列表。 | 所有pid |
| cgroup.sane_behavior | 是否启用了CFS(Completely Fair Scheduler)的合理行为。 | 0 |
| cpu.rt_period_us | 实时进程的时间周期(以微秒为单位),即实时进程的时间片长度。 | |
| cpu.rt_runtime_us | 实时进程的最大运行时间(以微秒为单位),即实时进程在一个时间周期内的最大运行时间。 | 1000000 |
| cpu.shares | CPU控制组的相对份额,用于分配CPU时间的比例。 | 1024 |
| notify_on_release | 在进程退出时是否通知CPU控制组。 | 0 |
| release_agent | 当CPU控制组中的最后一个进程退出时要执行的命令。 | |
| tasks | 当前在该CPU控制组中运行的进程的PID(进程ID)列表。 | 所有pid |
Android12
tb8788p1_64_bsp_k419:/dev/cpuctl # ls -l
total 0
drwxr-xr-x 2 system system 0 2023-07-06 18:45 background
drwxr-xr-x 2 system system 0 2023-07-06 18:45 camera-daemon
-rw-r--r-- 1 root root 0 2023-07-07 15:50 cgroup.clone_children
-rw-r--r-- 1 root root 0 2023-07-07 15:50 cgroup.procs
-r--r--r-- 1 root root 0 2023-07-07 15:50 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 2023-07-07 15:50 cpu.shares
drwxr-xr-x 2 system system 0 2023-07-06 18:45 foreground
drwxr-xr-x 2 system system 0 2023-07-06 18:45 nnapi-hal
-rw-r--r-- 1 root root 0 2023-07-07 15:50 notify_on_release
-rw-r--r-- 1 root root 0 2023-07-07 15:50 release_agent
drwxr-xr-x 2 system system 0 2023-07-06 18:45 rt
drwxr-xr-x 2 system system 0 2023-07-06 18:45 system
drwxr-xr-x 2 system system 0 2023-07-06 18:45 system-background
-rw-rw-r-- 1 system system 0 2023-07-06 18:45 tasks
| 参数 | 描述 |
|---|---|
| background | 配置后台任务的CPU控制组。 |
| camera-daemon | 可能与相机守护进程相关,配置相机进程的CPU控制组。 |
| cgroup.clone_children | 指示CPU控制组是否会在fork()系统调用中继承父进程的控制组。 |
| cgroup.procs | 列出当前在该CPU控制组中运行的进程的PID(进程ID)。 |
| cgroup.sane_behavior | 是否启用了CFS(Completely Fair Scheduler)的合理行为。 |
| cpu.shares | 指定该CPU控制组的CPU份额。 |
| foreground | 配置前台任务的CPU控制组。 |
| nnapi-hal | 可能与NNAPI HAL(Neural Networks API Hardware Abstraction Layer)相关,配置NNAPI HAL进程的CPU控制组。 |
| notify_on_release | 是否在进程退出时通知CPU控制组。 |
| release_agent | 当CPU控制组中的最后一个进程退出时要执行的命令。 |
| rt | 配置实时任务的CPU控制组。 |
| system | 配置系统进程的CPU控制组。 |
| system-background | 配置系统后台任务的CPU控制组。 |
| tasks | 列出当前在该CPU控制组中运行的进程的PID(进程ID)。 |
子系统CPUSET
peony-perf1:/dev/cpuset $ ls -l
total 0
drwxr-xr-x 2 system system 40 1970-01-01 08:00 background
drwxr-xr-x 2 system system 60 1970-01-01 08:00 foreground
drwxr-xr-x 2 system system 40 1970-01-01 08:00 restricted
drwxrwxr-x 2 system system 60 1970-01-01 08:00 system-background
drwxr-xr-x 2 system system 40 1970-01-01 08:00 top-app
peony-perf1:/dev/cpuset/foreground # cat tasks
1845peony-perf1:/dev/cpuset/foreground # ps | grep 1845
mediacodec 1845 1 52764 15352 binder_thread_read 0 S media.swcodec
1834peony-perf1:/dev/cpuset/system-background # ls
tasks
peony-perf1:/dev/cpuset/system-background # ps | grep 1834
root 1834 1 15756 4528 binder_thread_read 0 S storaged
除了以上其他分组均为空。 应该是cpuset没有启用的原因
| 文件夹 | 对应进程组 | 描述 |
|---|---|---|
| background | 后台进程组 | 用于控制后台任务的CPU集合,限制后台进程组的资源使用。 |
| foreground | 前台进程组 | 用于控制前台任务的CPU集合,提供前台进程组更多的CPU资源。 |
| restricted | 受限进程组 | 用于控制受限任务的CPU集合,受到资源限制或安全策略的影响。 |
| system-background | 系统后台进程组 | 用于控制系统后台任务的CPU集合,执行由操作系统或系统服务执行的后台任务。 |
| top-app | 顶级应用程序进程组 | 用于控制顶级应用程序的CPU集合,提供顶级应用程序最佳的性能和响应性。 |
tb8788p1_64_bsp_k419:/dev/cpuset # ls -l
total 0
drwxr-xr-x 2 system system 0 2023-07-06 18:45 background
drwxr-xr-x 2 system system 0 2023-07-06 18:45 camera-daemon
-rw-r--r-- 1 root root 0 2023-07-06 18:56 cgroup.clone_children
-rw-r--r-- 1 root root 0 2023-07-06 18:56 cgroup.procs
-r--r--r-- 1 root root 0 2023-07-06 18:56 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 2023-07-06 18:56 cpu_exclusive
-rw-r--r-- 1 root root 0 2023-07-06 18:56 cpus
-r--r--r-- 1 root root 0 2023-07-06 18:56 effective_cpus
-r--r--r-- 1 root root 0 2023-07-06 18:56 effective_mems
drwxr-xr-x 2 system system 0 2023-07-06 18:45 foreground
-rw-r--r-- 1 root root 0 2023-07-06 18:56 mem_exclusive
-rw-r--r-- 1 root root 0 2023-07-06 18:56 mem_hardwall
-rw-r--r-- 1 root root 0 2023-07-06 18:56 memory_migrate
-r--r--r-- 1 root root 0 2023-07-06 18:56 memory_pressure
-rw-r--r-- 1 root root 0 2023-07-06 18:56 memory_pressure_enabled
-rw-r--r-- 1 root root 0 2023-07-06 18:56 memory_spread_page
-rw-r--r-- 1 root root 0 2023-07-06 18:56 memory_spread_slab
-rw-r--r-- 1 root root 0 2023-07-06 18:56 mems
-rw-r--r-- 1 root root 0 2023-07-06 18:56 notify_on_release
-rw-r--r-- 1 root root 0 2023-07-06 18:56 prefer_cpu
-rw-r--r-- 1 root root 0 2023-07-06 18:56 release_agent
drwxr-xr-x 2 system system 0 2023-07-06 18:45 restricted
-rw-r--r-- 1 root root 0 2023-07-06 18:56 sched_load_balance
-rw-r--r-- 1 root root 0 2023-07-06 18:56 sched_relax_domain_level
drwxrwxr-x 2 system system 0 2023-07-06 18:45 system-background
-rw-rw-r-- 1 system system 0 2023-07-06 18:45 tasks
drwxr-xr-x 2 system system 0 2023-07-06 18:45 top-app
| 参数 | 描述 | |
|---|---|---|
| background | 用于指定后台任务的CPU集合。 | |
| camera-daemon | 可能与相机守护进程相关,用于指定相机进程的CPU集合。 | |
| cgroup.clone_children | 指示CPU控制组是否会在fork()系统调用中继承父进程的控制组。 | |
| cgroup.procs | 列出了当前在该CPU控制组中运行的进程的PID(进程ID)。 | |
| cgroup.sane_behavior | 是否启用了CFS(Completely Fair Scheduler)的合理行为。 | |
| cpu_exclusive | 指定该CPU集合是否是独占的。 | |
| cpus | 分配给该CPU集合的具体CPU核心。 | |
| effective_cpus | 实际生效的CPU核心。 | |
| effective_mems | 实际生效的内存节点。 | |
| foreground | 用于指定前台任务的CPU集合。 | |
| mem_exclusive | 指定该内存集合是否是独占的。 | |
| mem_hardwall | 指定该内存集合是否是硬边界(hardwall)。 | |
| memory_migrate | 内存迁移(memory migration)的状态。 | |
| memory_pressure | 内存压力(memory pressure)的级别。 | |
| memory_pressure_enabled | 内存压力检测是否启用。 | |
| memory_spread_page | 页面内存的分散(spread)策略。 | |
| memory_spread_slab | Slab内存的分散(spread)策略。 | |
| mems | 分配给该内存集合的具体内存节点。 | |
| notify_on_release | 在进程退出时是否通知CPU控制组。 | |
| prefer_cpu | 首选的CPU核心。 | |
| release_agent | 当CPU控制组中的最后一个进程退出时要执行的命令。 | |
| restricted | 用于指定受限任务的CPU集合。 | |
| sched_load_balance | 调度负载平衡(load balancing)的状态。 | |
| sched_relax_domain_level | 调度领域松弛(relax)的级别。 | |
| system-background | 用于指定系统后台任务的CPU集合。 | |
| tasks | 列出了当前在该CPU控制组中运行的进程的PID(进程ID)。 | |
| top-app | 用于指定顶级应用程序的CPU集合。 |
子系统STUNE
peony-xxxx:/dev/stune # ls -l
total 0
drwxr-xr-x 2 system system 40 1970-01-01 08:00 background
drwxr-xr-x 2 system system 60 2023-07-06 05:40 foreground
drwxr-xr-x 2 system system 40 1970-01-01 08:00 rt
drwxr-xr-x 2 system system 60 1970-01-01 08:00 top-app
peony-xxxx:/dev/stune/foreground # cat tasks
2781
|peony-xxxx:/dev/stune/foreground # ps -A | grep 2781
system 2580 1749 1127816 142056 SyS_epoll_wait 0 S com.xxx.launcher
具体cgroup的内容还在学习。请参考这篇文章下的参考资料