HotSpot模板解释器目标代码生成过程源码分析

  虽然说解释执行模式是逐字逐句翻译给目标平台运行的,但这样的过程未免太过缓慢,如果能把字节码说的话做成纸条,运行时只要把对应的纸条交给目标平台就可以了,这样,执行速度就会明显提升。JVM的Hotspot虚拟机的模板解释器就是用这种方法来解释执行的。在开始分析之前,先了解一下JVM的执行方式。

  (1).边解释边运行,即每次解释一条字节码并运行其解释的本地代码,这种执行引擎速度相对很慢 
  (2).JIT(即时编译)具有更快的运行速度但是需要更多的内存,方法被第一次调用时,字节码编译生成的本地代码将会被缓存,这样在该方法下次被调用的时候,将取出缓冲的本地代码直接运行 
  (3).自适应优化,对于经常被调用的方法,则会缓存其编译产生成的本地代码,对于其他较少被调用的代码,仍对其采用解释执行的方法。 
  (4).片上虚拟机,即虚拟机的执行引擎直接嵌入在片上

  HotSpot虚拟机可以配置为以下运行模式: 
    -Xint:解释模式 
    -Xcomp:编译模式 
    -Xmixed:混合模式 
(通过java -version就可以查看虚拟机的运行模式)

  HotSpot在启动时,会为所有字节码创建在目标平台上运行的解释运行的机器码,并存放在CodeCache中,在解释执行字节码的过程中,就会从CodeCache中取出这些本地机器码并执行。

  Hotspot虚拟机的细节技术实现值得借鉴,如果你觉得源码甚至汇编代码比较枯燥的话,也可以大致了解相关模块的组件、工作流程,对相关实现有一定的认识。

  下面就从模板解释器的初始化开始,分析HotSpot的解释代码的生成。
  在创建虚拟机时,在初始化全局模块过程中,会调用interpreter_init()初始化模板解释器,模板解释器的初始化包括抽象解释器AbstractInterpreter的初始化、模板表TemplateTable的初始化、CodeCache的Stub队列StubQueue的初始化、解释器生成器InterpreterGenerator的初始化。

 1 void TemplateInterpreter::initialize() {
 2   if (_code != NULL) return;
 3   // assertions
 4   //...
 5
 6   AbstractInterpreter::initialize();
 7
 8   TemplateTable::initialize();
 9
10   // generate interpreter
11   { ResourceMark rm;
12     TraceTime timer("Interpreter generation", TraceStartupTime);
13     int code_size = InterpreterCodeSize;
14     NOT_PRODUCT(code_size *= 4;)  // debug uses extra interpreter code space
15     _code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,
16                           "Interpreter");
17     InterpreterGenerator g(_code);
18     if (PrintInterpreter) print();
19   }
20
21   // initialize dispatch table
22   _active_table = _normal_table;
23 }

1.AbstractInterpreter是基于汇编模型的解释器的共同基类,定义了解释器和解释器生成器的抽象接口。 
2.模板表TemplateTable保存了各个字节码的模板(目标代码生成函数和参数)。 
TemplateTable的初始化调用def()将所有字节码的目标代码生成函数和参数保存在_template_table或_template_table_wide(wide指令)模板数组中

  //                              interpr. templates
  // Java spec bytecodes          ubcp|disp|clvm|iswd  in    out   generator      argument
  def(Bytecodes::_nop           , ____|____|____|____, vtos, vtos, nop           ,  _      );
  def(Bytecodes::_aconst_null   , ____|____|____|____, vtos, atos, aconst_null   ,  _      );
  def(Bytecodes::_iconst_m1     , ____|____|____|____, vtos, itos, iconst        , -1      );
  def(Bytecodes::_iconst_0      , ____|____|____|____, vtos, itos, iconst        ,  0      );
  def(Bytecodes::_iconst_1      , ____|____|____|____, vtos, itos, iconst        ,  1      );
  def(Bytecodes::_iconst_2      , ____|____|____|____, vtos, itos, iconst        ,  2      );
//...其他字节码的模板定义

其中,def()是查看数组对应项是否为空,若为空则初始化该数组项。

Template* t = is_wide ? template_for_wide(code) : template_for(code);
  // setup entry
  t->initialize(flags, in, out, gen, arg);

_template_table或_template_table_wide的数组项就是Template对象,即字节码的模板,Template的结构如下:

class Template VALUE_OBJ_CLASS_SPEC {
 private:
  enum Flags {
    uses_bcp_bit,                                // set if template needs the bcp pointing to bytecode
    does_dispatch_bit,                           // set if template dispatches on its own
    calls_vm_bit,                                // set if template calls the vm
    wide_bit                                     // set if template belongs to a wide instruction
  };

  typedef void (*generator)(int arg);

  int       _flags;                  // describes interpreter template properties (bcp unknown)
  TosState  _tos_in;                 // tos cache state before template execution
  TosState  _tos_out;                // tos cache state after  template execution
  generator _gen;                    // template code generator
  int       _arg;   

_flags为标志,该项的低四位分别标志:

  • uses_bcp_bit,标志需要使用字节码指针(byte code pointer,数值为字节码基址+字节码偏移量)
  • does_dispatch_bit,标志是否在模板范围内进行转发,如跳转类指令会设置该位
  • calls_vm_bit,标志是否需要调用JVM函数
  • wide_bit,标志是否是wide指令(使用附加字节扩展全局变量索引)

 _tos_in表示模板执行前的TosState(操作数栈栈顶元素的数据类型,TopOfStack,用来检查模板所声明的输出输入类型是否和该函数一致,以确保栈顶元素被正确使用) 
 _tos_out表示模板执行后的TosState 
 _gen表示模板生成器(函数指针) 
 _arg表示模板生成器参数

3.StubQueue是用来保存生成的本地代码的Stub队列,队列每一个元素对应一个InterpreterCodelet对象,InterpreterCodelet对象继承自抽象基类Stub,包含了字节码对应的本地代码以及一些调试和输出信息。 
其内存结构如下:在对齐至CodeEntryAlignment后,紧接着InterpreterCodelet的就是生成的目标代码。

      

4.InterpreterGenerator根据虚拟机使用的解释器模型不同分为别CppInterpreterGenerator和TemplateInterpreterGenerator 
根据不同平台的实现,以x86_64平台为例,TemplateInterpreterGenerator定义在/hotspot/src/cpu/x86/vm/templateInterpreter_x86_64.cpp

1 InterpreterGenerator::InterpreterGenerator(StubQueue* code)
2   : TemplateInterpreterGenerator(code) {
3    generate_all(); // down here so it can be "virtual"
4 }

(1).在TemplateInterpreterGenerator的generate_all()中,将生成一系列JVM运行过程中所执行的一些公共代码和所有字节码的InterpreterCodelet:

  • error exits:出错退出处理入口
  • 字节码追踪入口(配置了-XX:+TraceBytecodes)
  • 函数返回入口
  • JVMTI的EarlyReturn入口
  • 逆优化调用返回入口
  • native调用返回值处理handlers入口
  • continuation入口
  • safepoint入口
  • 异常处理入口
  • 抛出异常入口
  • 方法入口(native方法和非native方法)
  • 字节码入口

(2).其中,set_entry_points_for_all_bytes()会对所有被定义的字节码生成目标代码并设置对应的入口(这里只考虑is_defined的情况)

 1 void TemplateInterpreterGenerator::set_entry_points_for_all_bytes() {
 2   for (int i = 0; i < DispatchTable::length; i++) {
 3     Bytecodes::Code code = (Bytecodes::Code)i;
 4     if (Bytecodes::is_defined(code)) {
 5       set_entry_points(code);
 6     } else {
 7       //未被实现的字节码(操作码)
 8       set_unimplemented(i);
 9     }
10   }
11 }

(3).set_entry_points()将取出该字节码对应的Template模板,并调用set_short_enrty_points()进行处理,并将入口地址保存在转发表(DispatchTable)_normal_table或_wentry_table(使用wide指令)中

 1 void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) {
 2   CodeletMark cm(_masm, Bytecodes::name(code), code);
 3   // initialize entry points
 4   // ... asserts
 5   address bep = _illegal_bytecode_sequence;
 6   address cep = _illegal_bytecode_sequence;
 7   address sep = _illegal_bytecode_sequence;
 8   address aep = _illegal_bytecode_sequence;
 9   address iep = _illegal_bytecode_sequence;
10   address lep = _illegal_bytecode_sequence;
11   address fep = _illegal_bytecode_sequence;
12   address dep = _illegal_bytecode_sequence;
13   address vep = _unimplemented_bytecode;
14   address wep = _unimplemented_bytecode;
15   // code for short & wide version of bytecode
16   if (Bytecodes::is_defined(code)) {
17     Template* t = TemplateTable::template_for(code);
18     assert(t->is_valid(), "just checking");
19     set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);
20   }
21   if (Bytecodes::wide_is_defined(code)) {
22     Template* t = TemplateTable::template_for_wide(code);
23     assert(t->is_valid(), "just checking");
24     set_wide_entry_point(t, wep);
25   }
26   // set entry points
27   EntryPoint entry(bep, cep, sep, aep, iep, lep, fep, dep, vep);
28   Interpreter::_normal_table.set_entry(code, entry);
29   Interpreter::_wentry_point[code] = wep;
30 }

这里以非wide指令为例分析set_short_entry_points()。bep(byte entry point), cep, sep, aep, iep, lep, fep, dep, vep分别为指令执行前栈顶元素状态为byte/boolean、char、short、array/reference(对象引用)、int、long、float、double、void类型时的入口地址。

(4).set_short_entry_points()根据操作数栈栈顶元素类型进行判断,首先byte类型、char类型和short类型都应被当做int类型进行处理,对于非void类型将调用generate_and_dispatch()产生目标代码,这里以iconst_0为例对TOS的处理进行介绍: 
对于iconst,其期望的_tos_in(执行前栈顶元素类型)是void类型(vtos),期望的_tos_out(执行后栈顶元素类型)是int类型(itos)

 1 void TemplateInterpreterGenerator::set_short_entry_points(Template* t, address& bep, address& cep, address& sep, address& aep, address& iep, address& lep, address& fep, address& dep, address& vep) {
 2   assert(t->is_valid(), "template must exist");
 3   switch (t->tos_in()) {
 4     case btos:
 5     case ctos:
 6     case stos:
 7       ShouldNotReachHere();  // btos/ctos/stos should use itos.
 8       break;
 9     case atos: vep = __ pc(); __ pop(atos); aep = __ pc(); generate_and_dispatch(t); break;
10     case itos: vep = __ pc(); __ pop(itos); iep = __ pc(); generate_and_dispatch(t); break;
11     case ltos: vep = __ pc(); __ pop(ltos); lep = __ pc(); generate_and_dispatch(t); break;
12     case ftos: vep = __ pc(); __ pop(ftos); fep = __ pc(); generate_and_dispatch(t); break;
13     case dtos: vep = __ pc(); __ pop(dtos); dep = __ pc(); generate_and_dispatch(t); break;
14     case vtos: set_vtos_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);     break;
15     default  : ShouldNotReachHere();                                                 break;
16   }
17 }

其中__定义如下:

# define __ _masm->

即模板解释器的宏汇编器

(5).以期望的栈顶状态为vtos状态为例,分析set_vtos_entry_points():

 1 void TemplateInterpreterGenerator::set_vtos_entry_points(Template* t,
 2                                                          address& bep,
 3                                                          address& cep,
 4                                                          address& sep,
 5                                                          address& aep,
 6                                                          address& iep,
 7                                                          address& lep,
 8                                                          address& fep,
 9                                                          address& dep,
10                                                          address& vep) {
11   assert(t->is_valid() && t->tos_in() == vtos, "illegal template");
12   Label L;
13   aep = __ pc();  __ push_ptr();  __ jmp(L);
14   fep = __ pc();  __ push_f();    __ jmp(L);
15   dep = __ pc();  __ push_d();    __ jmp(L);
16   lep = __ pc();  __ push_l();    __ jmp(L);
17   bep = cep = sep =
18   iep = __ pc();  __ push_i();
19   vep = __ pc();
20   __ bind(L);
21   generate_and_dispatch(t);
22 }

以ftos入口类型为例(vtos即当前字节码的实现不关心栈顶元素的状态),分析该入口的处理指令: 
push_f(): 
  定义在 /hotspot/src/cpu/x86/vm/interp_masm_x86_64.cpp中

1 void InterpreterMacroAssembler::push_f(XMMRegister r) {
2   subptr(rsp, wordSize);
3   movflt(Address(rsp, 0), r);
4 }

  其中r的默认值为xmm0,wordSize为机器字长(如64位机器为8字节)

subptr()实际上调用了subq():

1 void MacroAssembler::subptr(Register dst, int32_t imm32) {
2   LP64_ONLY(subq(dst, imm32)) NOT_LP64(subl(dst, imm32));
3 }

subq()的实现如下:

1 void Assembler::subq(Register dst, int32_t imm32) {
2   (void) prefixq_and_encode(dst->encoding());
3   emit_arith(0x81, 0xE8, dst, imm32);
4 }

而emit_arith()将调用emit_byte()/emit_long()写入指令的二进制代码”83 EC 08”(由于8可由8位有符号数表示,第一个字节为0x81 | 0x02,即0x83,rsp的寄存器号为4,第二个字节为0xE8 | 0x04,即0xEC,第三个字节为0x08 & 0xFF,即0x08),该指令即AT&T风格的sub $0x8,%rsp

 1 void Assembler::emit_arith(int op1, int op2, Register dst, int32_t imm32) {
 2   assert(isByte(op1) && isByte(op2), "wrong opcode");
 3   assert((op1 & 0x01) == 1, "should be 32bit operation");
 4   assert((op1 & 0x02) == 0, "sign-extension bit should not be set");
 5   if (is8bit(imm32)) { //iconst_0的操作数为0,即可以用8位二进制数表示
 6     emit_byte(op1 | 0x02); // set sign bit
 7     emit_byte(op2 | encode(dst));
 8     emit_byte(imm32 & 0xFF);
 9   } else {
10     emit_byte(op1);
11     emit_byte(op2 | encode(dst));
12     emit_long(imm32);
13   }
14 }

emit_byte()定义在/hotspot/src/share/vm/asm/assembler.inlilne.hpp中: 
该函数将把该字节复制到_code_pos处

1 inline void AbstractAssembler::emit_byte(int x) {
2   assert(isByte(x), "not a byte");
3   *(unsigned char*)_code_pos = (unsigned char)x;
4   _code_pos += sizeof(unsigned char);
5   sync();
6 }

故subq()向代码缓冲写入了指令sub $0x8,%rsp 
类似地,movflt()向代码缓冲写入了指令 movss %xmm0,(%rsp) 
jmp()向代码缓冲写入了指令jmpq (addr为字节码的本地代码入口)

set_vtos_entry_points()产生的入口部分代码如下:

 1 push %rax        .....(atos entry)
 2 jmpq <addr>
 3 sub $0x8,%rsp     .....(ftos entry)
 4 movss %xmm0,(%rsp)
 5 jmpq <addr>(addr为字节码的本地代码入口)
 6 sub $0x10,%rsp    .....(dtos entry)
 7 movsd %xmm0,(%rsp)
 8 jmpq <addr>
 9 sub $0x10,%rsp     .....(ltos entry)
10 mov %rax,(%rsp)
11 jmpq <addr>
12 push %rax         ...(itos entry)

set_vtos_entry_points()的最后调用generate_and_dispatch()写入了当前字节码的解释代码和跳转到下一个字节码继续执行的逻辑处理部分

generate_and_dispatch()主要内容如下:

 1 void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) {
 2   // ...
 3   // generate template
 4   t->generate(_masm);
 5   // advance
 6   if (t->does_dispatch()) {
 7     //asserts
 8   } else {
 9     // dispatch to next bytecode
10     __ dispatch_epilog(tos_out, step);
11   }
12 }

这里我们以iconst()为目标代码生成器为例,分析generate():

1 void Template::generate(InterpreterMacroAssembler* masm) {
2   // parameter passing
3   TemplateTable::_desc = this;
4   TemplateTable::_masm = masm;
5   // code generation
6   _gen(_arg);
7   masm->flush();
8 }

generate()会调用生成器函数_gen(_arg),该函数根据平台而不同,如x86_64平台下,定义在/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp中

1 void TemplateTable::iconst(int value) {
2   transition(vtos, itos);
3   if (value == 0) {
4     __ xorl(rax, rax);
5   } else {
6     __ movl(rax, value);
7   }
8 }

我们知道,iconst_i指令是将i压入栈,这里生成器函数iconst()在i为0时,没有直接将0写入rax,而是使用异或运算清零,即向代码缓冲区写入指令”xor %rax, %rax”;在i不为0时,写入指令”mov $0xi, %rax”

当不需要转发时,会调用dispatch_epilog()生成取下一条指令和分派的目标代码:

1 void InterpreterMacroAssembler::dispatch_epilog(TosState state, int step) {
2   dispatch_next(state, step);
3 }

dispatch_next()实现如下:

1 void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {
2   // load next bytecode (load before advancing r13 to prevent AGI)
3   load_unsigned_byte(rbx, Address(r13, step));
4   // advance r13
5   increment(r13, step);
6   dispatch_base(state, Interpreter::dispatch_table(state));
7 }

dispatch_next()首先调用load_unsigned_byte()写入指令”movzbl (%r13),%rbx”,再调用increment()写入指令”inc/add (,)%r13”指令,最后调用dispatch_base()写入”jmp *(%r10,%rbx,8)”。这类似于PC自增一条指令的宽度再继续取值运行的过程。

分析到这里,不禁有一个疑问,_code_pos是哪里?之前说过,StubQueue是用来保存生成的本地代码的Stub队列,队列每一个元素对应一个InterpreterCodelet对象,InterpreterCodelet对象包含了字节码对应的本地代码以及一些调试和输出信息。那么_code_pos是如何和InterpreterCodelet对应的呢?

我们注意到无论是为JVM的各种入口函数,还是为字节码生成本地代码,都会构造一个CodeletMark对象

CodeletMark cm(_masm, Bytecodes::name(code), code);

CodeletMark的构造函数如下:在初始值列表中,调用了StubQueue的request()创建了一个InterpreterCodelet对象,并以该InterpreterCodelet目标代码地址和大小为参数构造了一块CodeBuffer用来存放生成的目标代码。

public:
  CodeletMark(
    InterpreterMacroAssembler*& masm,
    const char* description,
    Bytecodes::Code bytecode = Bytecodes::_illegal):
    _clet((InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size())),
    _cb(_clet->code_begin(), _clet->code_size())

  { // request all space (add some slack for Codelet data)
    assert (_clet != NULL, "we checked not enough space already");

    // initialize Codelet attributes
    _clet->initialize(description, bytecode);
    // create assembler for code generation
    masm  = new InterpreterMacroAssembler(&_cb);
    _masm = &masm;
  }

但在此时还未生成目标代码,所以并不知道生成的目标代码有多大,所以这里会向StubQueue申请全部的空闲空间(只留有一点用来对齐空间,注意StubQueue实际上是一片连续的内存空间,所有Stub都在该空间上进行分配) 
随后初始化该InterpreterCodelet的描述部分和对应字节码,并以该CodeBuffer为参数构造了一个编译器对象InterpreterMacroAssembler

分析到这里,就应该明白编译器的_code_pos指的就是生成代码在CodeBuffer中的当前写位值 
还需一提的就是CodeletMark的析构函数,这里确认编译器的生产代码完全写入到CodeBuffer中后,就会调用StubQueue的commit()将占用的空间划分为当前Stub(InterpreterCodelet)所有

~CodeletMark() {
    // align so printing shows nop‘s instead of random code at the end (Codelets are aligned)
    (*_masm)->align(wordSize);
    // make sure all code is in code buffer
    (*_masm)->flush();

    // commit Codelet
    AbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size());
    // make sure nobody can use _masm outside a CodeletMark lifespan
    *_masm = NULL;
  }
时间: 2024-11-03 09:45:38

HotSpot模板解释器目标代码生成过程源码分析的相关文章

Flume-NG启动过程源码分析(三)(原创)

上一篇文章分析了Flume如何加载配置文件的,动态加载也只是重复运行getConfiguration(). 本篇分析加载配置文件后各个组件是如何运行的? 加载完配置文件订阅者Application类会收到订阅信息执行: @Subscribe public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) { stopAllComponents(); startAllComponents(conf)

【Java】【Flume】Flume-NG启动过程源码分析(一)

从bin/flume 这个shell脚本可以看到Flume的起始于org.apache.flume.node.Application类,这是flume的main函数所在. main方法首先会先解析shell命令,如果指定的配置文件不存在就甩出异常. 根据命令中含有"no-reload-conf"参数,决定采用那种加载配置文件方式:一.没有此参数,会动态加载配置文件,默认每30秒加载一次配置文件,因此可以动态修改配置文件:二.有此参数,则只在启动时加载一次配置文件.实现动态加载功能采用了

【Java】【Flume】Flume-NG启动过程源码分析(二)

本节分析配置文件的解析,即PollingPropertiesFileConfigurationProvider.FileWatcherRunnable.run中的eventBus.post(getConfiguration()).分析getConfiguration()方法.此方法在AbstractConfigurationProvider类中实现了,并且这个类也初始化了三大组件的工厂类:this.sourceFactory = new DefaultSourceFactory();this.s

【Java】【Flume】Flume-NG启动过程源码分析(三)

本篇分析加载配置文件后各个组件是如何运行的? 加载完配置文件订阅者Application类会收到订阅信息执行: @Subscribe public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) { stopAllComponents(); startAllComponents(conf); } MaterializedConfiguration conf就是getConfiguration()

配置一个逻辑CPU专用于实时任务----Kithara RTS工程源码分析

本文以windows实时拓展Kithara RTS安装目录下的smp文件夹内的DedicatedRealTimeTask项目为例,讲解使实时任务以独占一个逻辑CPU的方式运行,并实现任务间的同步. 目前多核计算机已经普及,多数的PC都是多核的.针对这种多核结构,我们设想把计算机划分为不同的硬件区间,其中一部分用于被实时任务专用,另一部分是被windows使用的,两者之间互不干扰,这样实时任务可以实现更好的实时性能.这种硬件划分,一般是按照物理CPU的核心数,即逻辑CPU的数量来配置的.可以配置一

Activity启动流程源码分析之Launcher启动(二)

1.前述 在前一篇文章中我们简要的介绍Activity的启动流程Activity启动流程源码分析之入门(一),当时只是简单的分析了一下流程,而且在上一篇博客中我们也说了Activity的两种启动方式,现在我们就来分析其中的第一种方式--Launcher启动,这种启动方式的特点是会创建一个新的进程来加载相应的Activity(基于Android5.1源码). 2.Activity启动流程时序图 好啦,接下来我们先看一下Launcher启动Activity的时序图: 好啦,接下来我们将上述时序图用代

Android系统默认Home应用程序(Launcher)的启动过程源码分析

在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应用程序就是Launcher了,本文将详细分析Launcher应用程序的启动过程. Android系统的Home应用程序Launcher是由ActivityManagerService启动的,而ActivityManagerService和PackageManagerService一样,都是在开机时由

嵌入式linux开发uboot移植(三)——uboot启动过程源码分析

嵌入式linux开发uboot移植(三)--uboot启动过程源码分析 一.uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段.BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现:BL2阶段主要是对外部设备如网卡.Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现. 1.BL1阶段 uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下:

A2dp初始化流程源码分析

蓝牙启动的时候,会涉及到各个profile 的启动.这篇文章分析一下,蓝牙中a2dp profile的初始化流程. 我们从AdapterState.java中对于USER_TURN_ON 消息的处理说起: switch(msg.what) { case USER_TURN_ON: notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON); mPendingCommandState.setTurningOn(true); transit