1、线程概念
1.1 进程与线程
我们知道,在linux系统里,有进程和线程的概念。进程是linux最小的资源管理单位,而线程是最小的执行单元。进程与线程不一样,他拥有自己独立的内存空间,而线程则是共享进程的内存空间。所以,相对而言,会更好的利用资源。
当然,学习过linux内核的人也知道,不管是线程还是进程,在内核中都理解为进程,他们都有Task_Struct结构进行描述。这是因为linux内核本身在cpu调度的时候,进程为最小单元,所以线程在内核中表现为一个轻量化的进程,所以我们也就知道线程的切换,实际上就是内核的进程管理器进行的切换操作,而相应的线程的优先级,同样就取决于在内核中的nice值和priority设置。而相应的用户态下线程的管理本身,则是通过进程首次建立就自动创建的管理线程进行管理,例如线程的停止,线程的创建。
通常我们在进程中创建线程,会调用pthread_create方法,这个方法会调用到_clone方法,这个跟fork()进程时调用的_fork()方法最终都会调用到do_fork()方法,而作为进程和线程定义上的差别,do_fork的参数当然有所区分。
比如我们通过do_fork创建线程时,传入进程参数,包括内存空间(堆、栈、全局变量、数据区等)、文件系统信息、文件描述符表、信号句柄表、进程ID(当前进程id、父进程id)进行共享,并让每个线程仍然保持自己独立的运行栈。这点不同于创建进程,概念上子进程则拥有独立共享内存空间、文件系统空间、信号句柄表和进程ID,当然实际实现的时候,linux使用了一种机制,在父进程fork子进程,会共享整个自己的内存空间,如果子进程只是读取,那么则是共享父进程的空,一旦修改内存中值的时候,父进程会复制一份虚拟地址空间,指向父进程的物理空间,这样就成为完全独立的子进程的内存空间,这也就是我们常说的copy-on-write写时复制,当然父子进程也存在共享的内容,一个是文件描述符表(比如父进程建立socket连接的fd,子进程也会共享,他只是在这个fd上计数+1,这时候,子进程如果用不到,就就需要关闭socket的fd,因为fd只有在计数为0的时候,才会真正关闭,所以这一步必不可少),以及mmap的映射关系。所以这一点与线程的共享存在本质的区别。
2、查看线程进程信息
2.1 进程信息
以Android设备为例,我们可以通过两种方式查看进程信息:
adb shell ps
可以看到类似如下的信息:
USER PID PPID VSZ RSS WCHAN ADDR S NAME
u0_a9 27576 239 1139076 22880 SyS_epoll_wait a7c4c1b8 S android.process.media
其中:
user 表示用户信息,在android里面,u0表示主用户,u10\u20\u30等就代表副用户,后面的数字,就表示userid,u0的userid就是0,当然他们在应用的uid里面也承担信息,比如u0表示的就表示10000,u10就表示110000,依次类推,而u0_a9在android里面就表示10000 + 0xa9 = 10169。一个应用安装之后的uid是不变的。
PID 就表示进程号。跟linux里面一样表示进程信息。
PPID 就表示该进程的父进程号,如果看过ActivityManagerService的源码,可以知道,一般android应用,都是通过zygote孵化的进程,所以这个上面的239一般就表示zygote进程(同样zygote则是由init进程初始化,android初始化进程)。如下:
root 239 1 1055008 9568 poll_schedule_timeout a7c4c390 S zygote
root 1 0 18596 1700 SyS_epoll_wait b5e34 S init
所以通过类似这样的手段,可以找到进程的关系。
除了ps的指令,我们也可以通过linux的伪文件系统,查看进程信息
adb shell ls -al /proc
实际上,运行的进程信息,全部都在/proc目录下。
2.2 线程信息
同样ps也提供的方法:
例如我们查看进程12424的所有线程。
adb shell ps -T | grep 12424
就会列出所有的线程信息,其中-T 表示列出线程信息。
当然/proc里面包含了进程的信息,同样也包含了线程信息,我们可以通过查看:
adb shell ls -al /proc/12424 查看到所有的线程信息
当然我们在定位线程泄漏问题的时候,也可以通过以下方式查看线程的数量,以判断是否不断增长:
adb shell ls /proc/12424/task | wc -l。
3、Android线程
有了以上的基础解析,对线程有了初步的理解,我们正式阅读java源码,Java有很多的线程实现方式,比如Thread,Runnable、Callable、Future、FutureTask等,那这些类如何连接起来,如何实现的呢?我们先从Thread说起。
3.1 Thread的创建和运行
因为线程这块涉及到VM调用底层的机制,android的art这块代码,是区别与Java线程代码的,但是,基本的思路类似,区别仅仅是调用native的vm代码上存在区别,上层基本一样,以start的为例:
// JAVA代码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
Android 代码
public synchronized void start() {
if (threadStatus != 0 || started)
throw new IllegalThreadStateException();
started = false;
try {
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
所以看到只是start0()和nativeCreate()两个native方法有区别,我们这边以Android的线程的方法进行分析。这个代码可以在SDK代码里找到,也可以参见
http://androidxref.com/8.1.0_r33/xref/art/runtime/native/java_lang_Thread.cc
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size, jboolean daemon) {
Runtime* runtime = Runtime::Current();
if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) { // 判断是否为zygote进程,并且是zygote不可建立线程的部分,则抛出异常
jclass internal_error = env->FindClass("java/lang/InternalError");
CHECK(internal_error != nullptr);
env->ThrowNew(internal_error, "Cannot create threads in zygote");
return;
}
Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE); // 创建线程
}
我们继续看线程创建的函数:
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
CHECK(java_peer != nullptr);
Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();
if (VLOG_IS_ON(threads)) { // 判断线程的日志是否打开,打开则给每个Thread命名
ScopedObjectAccess soa(env);
ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name);
ObjPtr<mirror::String> java_name =
f->GetObject(soa.Decode<mirror::Object>(java_peer))->AsString();
std::string thread_name;
if (java_name != nullptr) {
thread_name = java_name->ToModifiedUtf8();
} else {
thread_name = "(Unnamed)";
}
VLOG(threads) << "Creating native thread for " << thread_name;
self->Dump(LOG_STREAM(INFO));
}
Runtime* runtime = Runtime::Current();
bool thread_start_during_shutdown = false;
{
MutexLock mu(self, *Locks::runtime_shutdown_lock_);
if (runtime->IsShuttingDownLocked()) {
thread_start_during_shutdown = true; // 判断当前是否刚好是VM关闭的场景
} else {
runtime->StartThreadBirth(); // 实际就是记录线程创建数+1
}
}
if (thread_start_during_shutdown) { // 如果在VM关闭过程中新建,抛异常
ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");
return;
}
Thread* child_thread = new Thread(is_daemon);
child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
stack_size = FixStackSize(stack_size); // 设置运行栈的大小
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
reinterpret_cast<jlong>(child_thread));
std::string error_msg;
std::unique_ptr<JNIEnvExt> child_jni_env_ext(
JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM(), &error_msg));
int pthread_create_result = 0;
if (child_jni_env_ext.get() != nullptr) {
pthread_t new_pthread;
pthread_attr_t attr;
child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED),
"PTHREAD_CREATE_DETACHED");
CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
// 创建线程,并创建callback用于线程创建完成回调,回调见后面的代码
pthread_create_result = pthread_create(&new_pthread,
&attr,
Thread::CreateCallback,
child_thread);
CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
if (pthread_create_result == 0) {
child_jni_env_ext.release();
return;
}
}
{
MutexLock mu(self, *Locks::runtime_shutdown_lock_);
runtime->EndThreadBirth(); // 当前线程完成创建,如果发现现在没有线程创建,并且VM开始shutdown了,就返回告知退出线程。
}
// 删除掉全局的引用,因为此时init还没有运行。
env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
child_thread->tlsPtr_.jpeer = nullptr;
delete child_thread;
child_thread = nullptr;
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
{
std::string msg(child_jni_env_ext.get() == nullptr ?
StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
StringPrintf("pthread_create (%s stack) failed: %s",
PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
ScopedObjectAccess soa(env);
soa.Self()->ThrowOutOfMemoryError(msg.c_str());
}
}
我们看一下回调之后做了啥
void* Thread::CreateCallback(void* arg) {
Thread* self = reinterpret_cast<Thread*>(arg);
Runtime* runtime = Runtime::Current();
if (runtime == nullptr) {
LOG(ERROR) << "Thread attaching to non-existent runtime: " << *self;
return nullptr;
}
{
// 加锁,直到完成了thread的init操作
MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_);
// 检查是否VM shut down
CHECK(!runtime->IsShuttingDownLocked());
// 对Thread进行init操作
CHECK(self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env));
self->tlsPtr_.tmp_jni_env = nullptr;
Runtime::Current()->EndThreadBirth();
}
{
ScopedObjectAccess soa(self);
self->InitStringEntryPoints();
// Copy peer into self, deleting global reference when done.
CHECK(self->tlsPtr_.jpeer != nullptr);
self->tlsPtr_.opeer = soa.Decode<mirror::Object>(self->tlsPtr_.jpeer).Ptr();
self->GetJniEnv()->DeleteGlobalRef(self->tlsPtr_.jpeer);
self->tlsPtr_.jpeer = nullptr;
self->SetThreadName(self->GetThreadName()->ToModifiedUtf8().c_str());
ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority);
// 设置线程真实的优先级
self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer));
// 告知VM线程的创建
runtime->GetRuntimeCallbacks()->ThreadStart(self);
// 调用Java层Thread的run方法
ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer;
jmethodID mid = WellKnownClasses::java_lang_Thread_run;
ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(receiver));
InvokeVirtualOrInterfaceWithJValues(soa, ref.get(), mid, nullptr);
}
// 将自己从线程列表中删除
Runtime::Current()->GetThreadList()->Unregister(self);
return nullptr;
}
通过这样的过程,我们知道线程的创建还是调用pthread_create方法。并且为了保证VM堆线程可控,也会告知VM(也就是art)线程情况,我们看一下runtime->GetRuntimeCallbacks()->ThreadStart实现:
void RuntimeCallbacks::AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
thread_callbacks_.push_back(cb);
}
void RuntimeCallbacks::ThreadStart(Thread* self) {
for (ThreadLifecycleCallback* cb : thread_callbacks_) {
cb->ThreadStart(self);
}
}
我们看一下AddThreadLifecycleCallback的调用处,此代码在runtime.cc中:
callbacks_->AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback());
实际上回调回来,只是为了Dbg(也就是debugger工具)获取当前的线程状况。
3.2 线程组
3.2.1 线程组概念
什么是线程组,从概念上理解很简单,就是将线程组合成一个集合,这个集合就是线程组,线程组可以包含线程,也可以嵌套线程组,可以理解成一种树状的结构。我们看一下线程组的定义:
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
从这个方法中,我们知道,如果创建ThreadGroup时,没有设置父ThreadGroup,则以当前线程的线程组作为此线程组的父线程组。并且默认情况下线程组的优先级会设置成最大,这个线程组的优先级也会影响到线程的优先级(后面我们会讲到)。
我们看一下线程组在线程上的应用,我们看一下构造函数:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
也就是说,Thread可以让调用者主动设置ThreadGroup,同样也可使设置成null,我们知道线程一定是有线程组的,我们看一下如果设置成null的实现:
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup();
}
}
也就是说,如果没有设置线程组,则以当前运行线程(也就是创建当前线程的父线程)的ThreadGroup作为当前要创建的线程的ThreadGroup。这一点与我们之前讲到主动创建ThreadGroup相似却不一样,主动创建ThreadGroup,是将当前运行线程的线程组作为当前创建线程组的父线程组;而创建线程时,则是设置当前运行线程的线程组为当前要创建线程的线程组,一个是父子关系,一个是公用父亲的关系。
我们在实际使用时,一般都是不手动创建线程组的。
3.2.2 线程组作用
有了线程组的概念后,那么线程组有啥作用,个人认为有两点:
1、因为当前线程持有其线程组,可以通过如下方法获取到线程组:
public final ThreadGroup getThreadGroup() {
// Android-changed: Return null if the thread is terminated.
if (getState() == Thread.State.TERMINATED) {
return null;
}
return group;
}
我们可以获取到同组线程的一些信息,例如
a、因为ThreadGroup在线程创建的过程中,thread也会告知ThreadGroup自己的运行状态,所以就可以获取到如下:
activeGroupCount() 当前活跃线程组、activeCount()当前活跃线程。
b、调用线程组的某个thread的UncaughtException
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
2、通过线程组统一设置的线程优先级对线程进行统一约束,结论先抛出,线程的优先级不高于线程组的优先级。
当然除了以上的两个作用,还有一个更重要的作用,就是协助排查问题,因为有了线程组的关系,我们在分析trace的时候,可以根据场景得到很多线程是由谁创建的。比如我们常见的trace中的group=main,基本上都是有主线程创建的线程,除非被创建的线程主动设置了线程组,那我们一样可以通过线程组的父线程组得到关联关系。
3.2.3 主线程组的概念
讲到这,一定有同学有个疑问,为什么主进程的group=main,name=main,如果阅读过zygote启动虚拟机流程的同学,一定知道进程启动的时候,调用art的源码会走到runtime.cc的
bool Runtime::Create(RuntimeArgumentMap&& runtime_options) {
if (!instance_->Init(std::move(runtime_options))) {
instance_ = nullptr;
return false;
}
return true;
}
继而调用到init方法,我们主要看线程相关的实现:
bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
Thread::Startup();
Thread* self = Thread::Attach("main", false, nullptr, false);
self->TransitionFromSuspendedToRunnable();
}
Startup主要实现创建线程的TLS(线程局部存储)
CHECK_PTHREAD_CALL(pthread_key_create, (&Thread::pthread_key_self_, Thread::ThreadExitCallback),
"self key");
Attach实际就是创建Thread线程并进行初始化的过程,可以看到设置了线程名为main线程,也就是主线程。
执行完Runtime::Create之后,就是执行Runtime::Start(),我们同样看一下主要实现:
bool Runtime::Start() {
Thread* self = Thread::Current();
InitThreadGroups(self);
Thread::FinishStartup();
}
第一句代码:获取当前的主线程
第二句:
void Runtime::InitThreadGroups(Thread* self) {
JNIEnvExt* env = self->GetJniEnv();
ScopedJniEnvLocalRefState env_state(env);
main_thread_group_ =
env->NewGlobalRef(env->GetStaticObjectField(
WellKnownClasses::java_lang_ThreadGroup,
WellKnownClasses::java_lang_ThreadGroup_mainThreadGroup));
system_thread_group_ =
env->NewGlobalRef(env->GetStaticObjectField(
WellKnownClasses::java_lang_ThreadGroup,
WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup));
}
这个java_lang_ThreadGroup_mainThreadGroup实际上就是反射调用到java层的实现,对应的java层代码就是ThreadGroup.java的:
static final ThreadGroup mainThreadGroup = new ThreadGroup(systemThreadGroup, "main");
所以这一步,就是创建mainThreadGroup和systemThreadGroup。
第三句,group获取了,我们最后再把group设置给主线程,就是通过FinishStartup方法:
void Thread::FinishStartup() {
Runtime* runtime = Runtime::Current();
ScopedObjectAccess soa(Thread::Current());
Thread::Current()->CreatePeer("main", false, runtime->GetMainThreadGroup());
Thread::Current()->AssertNoPendingException();
Runtime::Current()->GetClassLinker()->RunRootClinits();
}
主要涉及实现在CreatePeer中,我们看到执行:
env->CallNonvirtualVoidMethod(peer.get(),
WellKnownClasses::java_lang_Thread,
WellKnownClasses::java_lang_Thread_init,
thread_group, thread_name.get(), thread_priority, thread_is_daemon);
经过这样的几个过程,我们创建了主线程,并且将主线程的线程组都设置成main。
3.3 线程优先级
Thread.java的源码中有优先级priority的设置
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) {
// 如果线程优先级高于组max优先级,则设置为组的max优先级
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
synchronized(this) {
this.priority = newPriority;
if (isAlive()) {
nativeSetPriority(newPriority);
}
}
}
}
首先他先给定了优先级设置的范围:1-10
public final static int MIN_PRIORITY = 1;
public final static int MAX_PRIORITY = 10;
然后如果设置优先级大于组最大优先级了,则设置为组最大优先级。
最后,就是调用到nativeSetPriority,我们看一下主要实现:
static void Thread_nativeSetPriority(JNIEnv* env, jobject java_thread, jint new_priority) {
if (thread != nullptr) {
thread->SetNativePriority(new_priority);
}
}
我们看一下实现,代码位于art/runtime/thread_android.cc:
void Thread::SetNativePriority(int newPriority) {
if (newPriority < 1 || newPriority > 10) {
LOG(WARNING) << "bad priority " << newPriority;
newPriority = 5;
}
int newNice = kNiceValues[newPriority-1];
pid_t tid = GetTid();
if (newNice >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(tid, SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
set_sched_policy(tid, SP_FOREGROUND);
}
if (setpriority(PRIO_PROCESS, tid, newNice) != 0) {
PLOG(INFO) << *this << " setPriority(PRIO_PROCESS, " << tid << ", " << newNice << ") failed";
}
}
整个过程,就是先判断优先级设置是否不在区间内,不在的话,统一设置成5。
我们再看一下kNiceValues具体怎么对应到上层的10个优先级:
static const int kNiceValues[10] = {
ANDROID_PRIORITY_LOWEST, // 1 (MIN_PRIORITY)
ANDROID_PRIORITY_BACKGROUND + 6,
ANDROID_PRIORITY_BACKGROUND + 3,
ANDROID_PRIORITY_BACKGROUND,
ANDROID_PRIORITY_NORMAL, // 5 (NORM_PRIORITY)
ANDROID_PRIORITY_NORMAL - 2,
ANDROID_PRIORITY_NORMAL - 4,
ANDROID_PRIORITY_URGENT_DISPLAY + 3,
ANDROID_PRIORITY_URGENT_DISPLAY + 2,
ANDROID_PRIORITY_URGENT_DISPLAY // 10 (MAX_PRIORITY)
};
而就算我们分了10级执行,最终android都会分成这三步设置:
1、如果nice值>ANDROID_PRIORITY_BACKGROUND,设置调度策略为SP_BACKGROUND,关于调度策略函数set_sched_policy我们后面还会提到。
2、如果根据CPU策略,也就是getpriority(PRIO_PROCESS, tid)获取的优先级是大于ANDROID_PRIORITY_BACKGROUND,则调度策略为SP_FOREGROUND。
3、最后setpriority(PRIO_PROCESS, tid, newNice)就是真正设置优先级的调用,PRIO_PROCESS表示设置进程的优先级。tid就表示对应的进程/进程组/用户id,newNice就表示进程的nice值,到这一步其实可以发现,线程的调度最后也是转换成了进程调度(进程调度的依据就是nice值,越小优先级越高),这个我们后面会详细解释。
从这个过程我们可以看到,设置thread分级的10个优先级,并不能有效的让线程出于更高的优先级,因为他会把线程的优先级转换成nice值,这个转换过程也就说明优先级不能完全对等nice值。那么线程优先级我们怎么对等进程的优先级设置等级,Android提供了android.os.Process的:
public static final native void setThreadPriority(int priority)
throws IllegalArgumentException, SecurityException;
才能真正有效的设置线程的优先级,这边主要依赖的就是进程的优先级调度,我们看代码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);
if (rc != 0) {
if (rc == INVALID_OPERATION) {
signalExceptionForPriorityError(env, errno, pid);
} else {
signalExceptionForGroupError(env, errno, pid);
}
}
}
主要就是调用androidSetThreadPriority函数,代码位置/system/core/libutils/Threads.cpp:
int androidSetThreadPriority(pid_t tid, int pri)
{
int rc = 0;
int lasterr = 0;
if (pri >= ANDROID_PRIORITY_BACKGROUND) {
rc = set_sched_policy(tid, SP_BACKGROUND);
} else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) {
rc = set_sched_policy(tid, SP_FOREGROUND);
}
if (rc) {
lasterr = errno;
}
if (setpriority(PRIO_PROCESS, tid, pri) < 0) {
rc = INVALID_OPERATION;
} else {
errno = lasterr;
}
return rc;
}
这个过程跟Thread设置优先级的函数近似,只是没有了转换过程,故而也就讲线程的优先级完全对等到了进程优先级,这样才能真正生效,我们再看一下遗留的问题set_sched_policy如何设置调度策略的,代码在/system/core/libcutils/sched_policy.cpp:
int set_sched_policy(int tid, SchedPolicy policy)
{
if (tid == 0) {
tid = gettid();
}`
policy = _policy(policy);
pthread_once(&the_once, __initialize); // 初始化policy的fd
// 是否开启boost的调度方式
if (schedboost_enabled()) {
int boost_fd = -1;
switch (policy) {
case SP_BACKGROUND:
boost_fd = bg_schedboost_fd;
break;
case SP_FOREGROUND:
boost_fd = fg_schedboost_fd;
break;
default:
boost_fd = -1;
break;
}
// 将进程加入到对应的调度策略组
if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
if (errno != ESRCH && errno != ENOENT)
return -errno;
}
}
// 设置timerslack,这个表示进程可睡眠的最长时间间隔,比如你在用户态sleep了100ms,但是timerslack大于100ms,那么实际线程睡眠的时间会超过设置的sleep时长
set_timerslack_ns(tid, policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG);
return 0;
}
首先看一下initialize初始化fd的操作
static void __initialize() {
const char* filename;
if (cpusets_enabled()) {
if (!access("/dev/cpuset/tasks", W_OK)) {
// 首先如果CPU支持调度策略,则打开cpu对应的fd
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);
filename = "/dev/cpuset/restricted/tasks";
rs_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
// 如果支持boost的调度策略,初始化boost调度侧率fd
if (schedboost_enabled()) {
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);
filename = "/dev/stune/rt/tasks";
rt_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
}
}
}
char buf[64];
snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", getpid());
__sys_supports_timerslack = !access(buf, W_OK);
}
然后我们看一下策略是如何应用的,主要调用函数:
static int add_tid_to_cgroup(int tid, int fd)
{
if (fd < 0) {
SLOGE("add_tid_to_cgroup failed; fd=%d\n", fd);
errno = EINVAL;
return -1;
}
// specialized itoa -- works for tid > 0
char text[22];
char *end = text + sizeof(text) - 1;
char *ptr = end;
*ptr = '\0';
while (tid > 0) {
*--ptr = '0' + (tid % 10);
tid = tid / 10;
}
if (write(fd, ptr, end - ptr) < 0) {
/*
* If the thread is in the process of exiting,
* don't flag an error
*/
if (errno == ESRCH)
return 0;
SLOGW("add_tid_to_cgroup failed to write '%s' (%s); fd=%d\n",
ptr, strerror(errno), fd);
errno = EINVAL;
return -1;
}
return 0;
}
这个过程实际上就是按照规则将进程id写入到对应策略的fd即可。这样cpu在调度的时候就会知道当前进程是怎样调度的。而且从这个分组概念中,我们也看到,每个调度策略有独立的组,这样不同的组使用不同样的调度方式。而且也可以看到,这种设置策略的方式,仅使用与支持boost调度的系统,不然调度策略都是默认的。还有一种是依赖cpu的调度方式,见Process.java的代码setThreadGroupAndCpuset以及相关调用,这里不再继续。
有了调度策略,我们看一下最终设置优先级的函数setpriority,这个实际上就是系统调用,这个就涉及到kernel代码的实现,我们这边就看一下setpriorit这个函数的功能:
int setpriority(int which, int who, int prio);
其中which的取值:PRIO_PROCESS, PRIO_PGRP, PRIO_USER分别表示设置进程
who取值则是相对于which定义的id,比如which是PRIO_PROCESS,就表示进程id,which是PRIO_PGRP,它就表进程组id,which是PRIO_USER,它就表示用户id
prio也就是nice值,nice值的取值是-19-20,nice值月底,优先级越高。
返回值int,取值有:
EINVAL which值错误,不是PRIO_PROCESS、PRIO_PGRP和PRIO_USER其中之一
ESRCH 根据who没有找到相关对象
EACCES 无法降低优先级
EPERM 权限不够
4、总结
经过我们的分析,我们知道线程和进程在内核中都是以进程表现。只是线程之间会公用pid,公用内存空间,但是各自栈独立。这也就促使我们线程优先级调节需要借助进程切换优先级的接口setpriority。
线程调用start的运行线程的时候,本质上还是调用libc的pthread_create接口,也就是说虚拟机中的线程与linux的线程是一一对应的,在底层创建线程成功之后,才会回调java层调用run的实现。这也就是为什么我们在运行线程的时候,一定要调用start的原因。
首个线程创建时即为主线程main,并且其线程组也叫主线程组main。如果创建线程的时候,未设置线程组,则线程组使用父线程的线程组。相应的如果创建线程组时,此线程组的父线程组就是当前线程的线程组。