写点什么

关于 Signal Catcher 线程中对线程的理解

作者:北洋
  • 2022 年 4 月 21 日
  • 本文字数:3222 字

    阅读完需:约 11 分钟

首先简述下 Signal Catcher,Signal Catcher 线程接受到 kernel 系统底层的消息进行 dump 当前虚拟机的信息并且设置每个线程的标志位(check_point)和请求线程状态为挂起,当线程运行过程中进行上下文切换时会检查该标记。等到线程都挂起后,开始遍历 Dump 每个线程的堆栈和线程数据后再唤醒线程。关于 ANR 的更多内容在我的其他博客中进行查阅~~.


==本文重点讲的是在分析 Singal Catcher 时对线程有了更新的了解。==


在 Android 里面只能通过 pthread_create 去创建一个线程,Thread 只是 Android Runtime 里面的一个类,一个 Thread 对象创建之后就会被保存在线程的 TLS 区域,所以一个 Linux 线程都对应了一个 Thread 对象,可以通过 Thread 的 Current()函数来获取当前线程关联的 Thread 对象,通过这个 Thread 对象就可以获取一些重要信息,例如当前线程的 Java 线程状态,Java 栈帧,JNI 函数指针列表等等,之所以说是 Java 线程状态,Java 栈帧,是因为 Android 运行时其实是没有自己单独的线程机制的,Java 线程底层都是一个 Linux 线程,但是 Linux 线程是没有像 Watting,Blocked 等状态的,并且 Linux 线程也是没有 Java 堆栈的,那么这些线程状态和 Java 栈帧必须有一个地方保存,要不然就丢失了,Thread 对象就是一个很理想的“储物柜”。


只有当创建出来的 Thread 对象执行了 attach 函数后,一个 Linux 线程在真正和虚拟机运行时关联起来,才变成了 Java 线程,才有了自己的 java 线程状态和 java 栈帧等数据结构,那些纯粹的 native 是不能执行 java 代码的,所以当系统发生 crash 或者 anr 进行 dump 进程的堆栈的时候,有些线程是没有 java 堆栈的,只有 native 和 kernel 堆栈,就是这个原因。那么这个 attach()函数中做了哪些事情呢:


Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group,bool create_peer) {    Runtime* runtime = Runtime::Current();    ......    Thread* self;    {        MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_);        if (runtime->IsShuttingDownLocked()) {        ......        } else {                Runtime::Current()->StartThreadBirth();                self = new Thread(as_daemon); //新建一个Thread对象                bool init_success = self->Init(runtime->GetThreadList(), runtime->GetJavaVM()); //调用init函数                Runtime::Current()->EndThreadBirth();                if (!init_success) {                    delete self;                    return nullptr;                }         }      }    ......    self->InitStringEntryPoints();      CHECK_NE(self->GetState(), kRunnable);    self->SetState(kNative);    ......    return self;}
复制代码


首先创建了一个 Thread 对象,接着执行了 init()函数,然后在最后修改了线程的状态 kNative(Java 线程的状态是保存在 Thread 对象中的,具体来说是由对象中的 tls32_这个结构体保存的,可以通过修改这个结构体来设置线程当前的状态:


inline ThreadState Thread::SetState(ThreadState new_state) {  // Cannot use this code to change into Runnable as changing to Runnable should fail if  // old_state_and_flags.suspend_request is true.  DCHECK_NE(new_state, kRunnable);  if (kIsDebugBuild && this != Thread::Current()) {    std::string name;    GetThreadName(name);    LOG(FATAL) << "Thread \"" << name << "\"(" << this << " != Thread::Current()="               << Thread::Current() << ") changing state to " << new_state;  }  union StateAndFlags old_state_and_flags;  old_state_and_flags.as_int = tls32_.state_and_flags.as_int;  tls32_.state_and_flags.as_struct.state = new_state;  return static_cast<ThreadState>(old_state_and_flags.as_struct.state);}
复制代码


enum ThreadState {  //                                   Thread.State   JDWP state  kTerminated = 66,                 // TERMINATED     TS_ZOMBIE    Thread.run has returned, but Thread* still around  kRunnable,                        // RUNNABLE       TS_RUNNING   runnable  kTimedWaiting,                    // TIMED_WAITING  TS_WAIT      in Object.wait() with a timeout  kSleeping,                        // TIMED_WAITING  TS_SLEEPING  in Thread.sleep()  kBlocked,                         // BLOCKED        TS_MONITOR   blocked on a monitor  kWaiting,                         // WAITING        TS_WAIT      in Object.wait()  kWaitingForGcToComplete,          // WAITING        TS_WAIT      blocked waiting for GC  kWaitingForCheckPointsToRun,      // WAITING        TS_WAIT      GC waiting for checkpoints to run  kWaitingPerformingGc,             // WAITING        TS_WAIT      performing GC  kWaitingForDebuggerSend,          // WAITING        TS_WAIT      blocked waiting for events to be sent  kWaitingForDebuggerToAttach,      // WAITING        TS_WAIT      blocked waiting for debugger to attach  kWaitingInMainDebuggerLoop,       // WAITING        TS_WAIT      blocking/reading/processing debugger events  kWaitingForDebuggerSuspension,    // WAITING        TS_WAIT      waiting for debugger suspend all  kWaitingForJniOnLoad,             // WAITING        TS_WAIT      waiting for execution of dlopen and JNI on load code  kWaitingForSignalCatcherOutput,   // WAITING        TS_WAIT      waiting for signal catcher IO to complete  kWaitingInMainSignalCatcherLoop,  // WAITING        TS_WAIT      blocking/reading/processing signals  kWaitingForDeoptimization,        // WAITING        TS_WAIT      waiting for deoptimization suspend all  kWaitingForMethodTracingStart,    // WAITING        TS_WAIT      waiting for method tracing to start  kWaitingForVisitObjects,          // WAITING        TS_WAIT      waiting for visiting objects  kWaitingForGetObjectsAllocated,   // WAITING        TS_WAIT      waiting for getting the number of allocated objects  kStarting,                        // NEW            TS_WAIT      native thread started, not yet ready to run managed code  kNative,                          // RUNNABLE       TS_RUNNING   running in a JNI native method  kSuspended,                       // RUNNABLE       TS_RUNNING   suspended by GC or debugger};
复制代码


这里主要分析 init()函数,首先要先了解一下 ART 执行代码的方式,ART 虚拟机和 Dalvik 最大的变化就是 ART 虚拟机==不在解释执行字节码,而是直接找到对应的机器码直接执行==。ART 会在安装应用程序的时候执行 dex2oat 进程得到一个 oat 文件完成字节码翻译成本地机器码的工作,这个 oat 文件一般保存在/data/app/应用名称/oat/目录下,这个 oat 文件里面就是编译好的机器码,但是这些机器码不可能单独存在,需要借助于 ART 运行时(执行一个 jni 方法或者在 heap 中操作),这个可以类比于编译 so 库文件的时候引用到了外部函数(其实 oat 和 so 文件都是 ELF 可执行格式文件,只是 oat 文件相比于标准的 ELF 格式文件多出了几个 section)。区别是打开标准的 so 文件的时候,一般用的是 dlopen 这个函数,该函数会把没有加载的 so 库加载进来,然后把这些外部函数重定位好;而 oat 文件为了快速加载,ART 在==线程的 TLS 区域保存了一些函数==,编译好的机器码就是调用这些函数指针来和 AT 运行时建立联系,这些函数就是在 Thread 的 init 过程中初始化好的

发布于: 刚刚阅读数: 6
用户头像

北洋

关注

Android开发 2021.05.25 加入

记录Android学习之路 分享读书心得体会~

评论

发布
暂无评论
关于Signal Catcher线程中对线程的理解_4月月更_北洋_InfoQ写作社区