Android进程管理2 —nice值/Cgroup

1,658 阅读11分钟

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

Untitled

1.2 实时调度与非实时调度

linux调度策略分为实时(realtime)调度与非实时(normal)调度。

Untitled

实时调度包括

  • 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, &param);
    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_NONINTERACTIVE0后台线程组 - 所有线程都以降低的CPU份额进行调度
THREAD_GROUP_FOREGROUND1前台线程组 - 所有线程都以正常的CPU份额进行调度
THREAD_GROUP_SYSTEM2系统线程组
THREAD_GROUP_AUDIO_APP3应用音频线程组
THREAD_GROUP_AUDIO_SYS4系统音频线程组
THREAD_GROUP_TOP_APP5顶层应用线程组
THREAD_GROUP_RT_APP6实时应用线程组
THREAD_GROUP_RESTRICTED7绑定前台服务线程组 - 在屏幕关闭时具有额外的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_OTHER0其他调度策略default
SCHED_FIFO1先进先出调度策略renderThread,oomAdjuster前后台切换,SchedulingPolicyService中,以及vr相关类被使用
SCHED_RR2轮转调度策略没有调用
SCHED_BATCH3批量调度策略没有调用
SCHED_IDLE5空闲调度策略没有调用
SCHED_RESET_ON_FORK0x40000000在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_DEFAULT0默认
THREAD_PRIORITY_LOWEST19最低
THREAD_PRIORITY_BACKGROUND10后台
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 Prioritynice值
119
216
313
410
50
6-2
7-4
8-5
9-6
10-8

可以参考这篇文章更好理解关于线程优先级的应用

mp.weixin.qq.com/s/oLz_F7zhU…

Untitled

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的用法

Untitled

特别要注意一点,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
}

/*

image.png

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.javaSchedPolicy .cppvalueProcessList.java
默认线程组Process.THREAD_GROUP_DEFAULTSP_DEFAULT-1SCHED_GROUP_DEFAULT 2
后台线程组 - 所有线程都以降低的CPU份额进行调度Process.THREAD_GROUP_BG_NONINTERACTIVESP_BACKGROUND0SCHED_GROUP_BACKGROUND 0
前台线程组 - 所有线程都以正常的CPU份额进行调度Process.THREAD_GROUP_FOREGROUNDSP_FOREGROUND1
系统线程组Process.THREAD_GROUP_SYSTEMSP_SYSTEM2
应用音频线程组Process.THREAD_GROUP_AUDIO_APPSP_AUDIO_APP3
系统音频线程组Process.THREAD_GROUP_AUDIO_SYSSP_AUDIO_SYS4
顶层应用线程组Process.THREAD_GROUP_TOP_APPSP_TOP_APP5SCHED_GROUP_TOP_APP/SCHED_GROUP_TOP_APP_BOUND 3 / 4
实时应用线程组Process.THREAD_GROUP_RT_APPSP_RT_APP6
绑定前台服务线程组 - 在屏幕关闭时具有额外的CPU限制Process.THREAD_GROUP_RESTRICTEDSP_RESTRICTED7SCHED_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_childrenCPU控制组是否会在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.sharesCPU控制组的相对份额,用于分配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_slabSlab内存的分散(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的内容还在学习。请参考这篇文章下的参考资料

paul.pub/android-pro…


blog.csdn.net/hdxx2022/ar…

sq.sf.163.com/blog/articl…

blog.csdn.net/pillarbuaa/…

www.cnblogs.com/arnoldlu/p/…

ohmerhe.com/2018/03/21/…

gityuan.com/2018/05/19/…

mp.weixin.qq.com/s/oLz_F7zhU…

www.cnblogs.com/aosp/p/1639…