源码阅读系列——进阶篇(Android的线程分析)你是否真的了解线程

  • Post author:
  • Post category:其他




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。如果创建线程的时候,未设置线程组,则线程组使用父线程的线程组。相应的如果创建线程组时,此线程组的父线程组就是当前线程的线程组。



版权声明:本文为luoluofeixia原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。