Android Java线程的创建过程

基于Android 7.0源码分析,涉及的主要源码如下

~/aosp/libcore/ojluni/src/main/java/java/lang/Thread.java

~/aosp/art/runtime/native/java_lang_Thread.cc
~/aosp/art/runtime/thread.cc
~/aosp/art/runtime/jni_env_ext.cc
~/aosp/art/runtime/jni_internal.cc
~/aosp/art/runtime/reflection.cc

~/aosp/bionic/libc/bionic/pthread_create.cpp
~/aosp/bionic/libc/bionic/pthread_internal.cpp

Java线程的启动方式

(1)
new Thread() {
    @Override
    public void run() {
        // Do something
    }
}.start();

(2)
new Thread(new Runnable() {
    @Override
    public void run() {
        // Do something
    }
}).start();

两种方式原理相同,下面以(1)方式为例分析。

Java线程创建的过程

下面从Thread的构造函数开始分析。

public Thread() {
    // 对于匿名线程,线程名为“Thread-xxx(整数)”
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    // 新线程的父线程
    Thread parent = currentThread();
    if (g == null) {
        g = parent.getThreadGroup();
    }
    g.addUnstarted();
    this.group = g;
    this.target = target;
    // 线程优先级,映射到linux进程优先级
    this.priority = parent.getPriority();
    // 是否为守护线程
    this.daemon = parent.isDaemon();
    // 设置线程名,并未真正执行
    setName(name);
    // 设置线程上下文类加载器,通常为应用类加载器
    init2(parent);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    // 线程序列号
    tid = nextThreadID();
}

Thread的构造方法相对简单,只是简单设置线程参数,下面看Threadstart方法。

public synchronized void start() {
    ......
    // 添加到线程组,修改统计信息
    group.add(this);
    started = false;
    try {
        // 创建启动线程
        nativeCreate(this, stackSize, daemon);
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

nativeCreate是个Native方法,它的实现在java_lang_Thread.cc中。

static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,
                                jboolean daemon) {
  // There are sections in the zygote that forbid thread creation.
  Runtime* runtime = Runtime::Current();
  // 不能在Zygote进程的初始阶段创建线程
  if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) {
    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)->self;
  ......
  Runtime* runtime = Runtime::Current();
  ......
  // 为新线程创建Native层的Thread对象
  Thread* child_thread = new Thread(is_daemon);
  // Use global JNI ref to hold peer live while child thread starts.
  // 创建JNI全局引用
  child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
  // 计算线程栈的大小,这里stack_size = 1040KB
  stack_size = FixStackSize(stack_size);

  // Thread.start is synchronized, so we know that nativePeer is 0, and know that we're not racing to
  // assign it.
  // Native Thread对象地址保存到Java Thread对象的nativePeer域
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
                    reinterpret_cast<jlong>(child_thread));

  // Try to allocate a JNIEnvExt for the thread. We do this here as we might be out of memory and
  // do not have a good way to report this on the child's side.
  // 创建JNIEnvExt
  std::unique_ptr<JNIEnvExt> child_jni_env_ext(
      JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM()));

  int pthread_create_result = 0;
  if (child_jni_env_ext.get() != nullptr) {
    pthread_t new_pthread;
    pthread_attr_t attr;
    // 保存JNIEnvExt
    child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
    // 调用pthread_attr_init初始化线程属性
    CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
    // 调用pthread_attr_setdetachstate设置state为PTHREAD_ATTR_FLAG_DETACHED
    CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED),
                       "PTHREAD_CREATE_DETACHED");
    // 调用pthread_attr_setstacksize设置栈大小到线程属性中
    CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
    // 创建新的线程,线程启动后调用Thread::CreateCallback方法
    pthread_create_result = pthread_create(&new_pthread,
                                           &attr,
                                           Thread::CreateCallback,
                                           child_thread);
    CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
    ......
  }
  ......
}

下面首先分析线程栈大小的计算,接下来分析JNIEnvExt的创建,最后看pthread_create的实现

static size_t FixStackSize(size_t stack_size) {
  // A stack size of zero means "use the default".
  if (stack_size == 0) {
    // 系统默认的栈大小为0  
    stack_size = Runtime::Current()->GetDefaultStackSize();
  }

  // Dalvik used the bionic pthread default stack size for native threads,
  // so include that here to support apps that expect large native stacks.
  // 加上1MB用作Native stack
  stack_size += 1 * MB;
  ......
  if (Runtime::Current()->ExplicitStackOverflowChecks()) {
    // It's likely that callers are trying to ensure they have at least a certain amount of
    // stack space, so we should add our reserved space on top of what they requested, rather
    // than implicitly take it away from them.
    stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
  } else {
    // If we are going to use implicit stack checks, allocate space for the protected
    // region at the bottom of the stack.
    // 加上(8K + 8K)
    stack_size += Thread::kStackOverflowImplicitCheckSize +
        GetStackOverflowReservedBytes(kRuntimeISA);
  }

  // Some systems require the stack size to be a multiple of the system page size, so round up.
  stack_size = RoundUp(stack_size, kPageSize);
  // stack_size = 1040KB
  return stack_size;
}

这里虚拟机栈与Native栈共用。下面看JNIEnvExt的创建。

JNIEnvExt* JNIEnvExt::Create(Thread* self_in, JavaVMExt* vm_in) {
  std::unique_ptr<JNIEnvExt> ret(new JNIEnvExt(self_in, vm_in));
  if (CheckLocalsValid(ret.get())) {
    return ret.release();
  }
  return nullptr;
}

JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in)
    : self(self_in),
      vm(vm_in),
      local_ref_cookie(IRT_FIRST_SEGMENT),
      // JNI本地引用表
      locals(kLocalsInitial, kLocalsMax, kLocal, false),
      check_jni(false),
      runtime_deleted(false),
      critical(0),
      monitors("monitors", kMonitorsInitial, kMonitorsMax) {
  // 初始化JNINativeInterface(实现在jni_internal.cc中)        
  functions = unchecked_functions = GetJniNativeInterface();
  if (vm->IsCheckJniEnabled()) {
    SetCheckJniEnabled(true);
  }
}
  • JNIEnvExt管理JNI本地引用表
  • JavaVMExt管理JNI全局引用表

下面看pthread_create的实现

int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
                   void* (*start_routine)(void*), void* arg) {
  ......                     
  pthread_attr_t thread_attr;
  if (attr == NULL) {
    pthread_attr_init(&thread_attr);
  } else {
    thread_attr = *attr;
    attr = NULL; // Prevent misuse below.
  }

  pthread_internal_t* thread = NULL;
  void* child_stack = NULL;
  // 分配新(子)线程栈空间以及初始化TLS
  int result = __allocate_thread(&thread_attr, &thread, &child_stack);
  if (result != 0) {
    return result;
  }

  // 用于父线程与子线程同步  
  thread->startup_handshake_lock.init(false);
  thread->startup_handshake_lock.lock();
  // 入口函数  
  thread->start_routine = start_routine;
  // 入口函数参数
  thread->start_routine_arg = arg;

  ......
  // clone新的进程(线程),线程启动后执行__pthread_start
  int rc = clone(__pthread_start, child_stack, flags, thread, &(thread->tid), tls, &(thread->tid));
  ......
  // pthread_internal_t添加到全局g_thread_list中
  *thread_out = __pthread_internal_add(thread);
  // 解锁让新线程运行
  thread->startup_handshake_lock.unlock();

  return 0;
}

static int __allocate_thread(pthread_attr_t* attr, pthread_internal_t** threadp, void** child_stack) {
  size_t mmap_size;
  uint8_t* stack_top;

  if (attr->stack_base == NULL) {
    // The caller didn't provide a stack, so allocate one.
    // Make sure the stack size and guard size are multiples of PAGE_SIZE.
    // 32位系统中1KB < sizeof(pthread_internal_t) < 2KB
    // 64为系统上2KB < sizeof(pthread_internal_t) < 3KB
    // mmap_size = 1044KB
    mmap_size = BIONIC_ALIGN(attr->stack_size + sizeof(pthread_internal_t), PAGE_SIZE);
    // guard_size = 4K
    attr->guard_size = BIONIC_ALIGN(attr->guard_size, PAGE_SIZE);
    // 映射线程栈空间
    attr->stack_base = __create_thread_mapped_space(mmap_size, attr->guard_size);
    if (attr->stack_base == NULL) {
      return EAGAIN;
    }
    stack_top = reinterpret_cast<uint8_t*>(attr->stack_base) + mmap_size;
  } else {
    // Remember the mmap size is zero and we don't need to free it.
    mmap_size = 0;
    stack_top = reinterpret_cast<uint8_t*>(attr->stack_base) + attr->stack_size;
  }
  //  调整栈顶,为了安全访问pthread_internal_t和线程栈,边界进行16字节对齐
  stack_top = reinterpret_cast<uint8_t*>(
                (reinterpret_cast<uintptr_t>(stack_top) - sizeof(pthread_internal_t)) & ~0xf);

  pthread_internal_t* thread = reinterpret_cast<pthread_internal_t*>(stack_top);
  if (mmap_size == 0) {
    // If thread was not allocated by mmap(), it may not have been cleared to zero.
    // So assume the worst and zero it.
    memset(thread, 0, sizeof(pthread_internal_t));
  }
  // 更新stack_size
  attr->stack_size = stack_top - reinterpret_cast<uint8_t*>(attr->stack_base);

  thread->mmap_size = mmap_size;
  // 线程属性拷贝至线程TLS
  thread->attr = *attr;
  // 初始化TLS
  __init_tls(thread);

  *threadp = thread;
  *child_stack = stack_top;
  return 0;
}

小结

  • Android Java线程栈内存空间布局


从stack_begin到stack_end的8KB空间,如果调用Java方法且栈顶指针sp位于stack_end之下则抛出栈溢出错误,此时仍然可以调用Native方法。stack_begin之下的4KB空间,处理栈溢出错误时可能使用。

  • Java线程栈内存映射


Java线程启动后执行Threadrun方法的过程

static int __pthread_start(void* arg) {
  pthread_internal_t* thread = reinterpret_cast<pthread_internal_t*>(arg);

  // Wait for our creating thread to release us. This lets it have time to
  // notify gdb about this thread before we start doing anything.
  // This also provides the memory barrier needed to ensure that all memory
  // accesses previously made by the creating thread are visible to us.
  // 等待父线程释放锁
  thread->startup_handshake_lock.lock();
  // 创建信号栈
  __init_alternate_signal_stack(thread);
  // 调用start_routine,也就是Thread::CreateCallback方法
  void* result = thread->start_routine(thread->start_routine_arg);
  pthread_exit(result);

  return 0;
}

void __init_alternate_signal_stack(pthread_internal_t* thread) {
  // Create and set an alternate signal stack.
  // 映射信号栈地址空间(16KB + 4KB)
  void* stack_base = mmap(NULL, SIGNAL_STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  if (stack_base != MAP_FAILED) {

    // Create a guard page to catch stack overflows in signal handlers.
    // 创建guard page(4KB)捕获信号栈溢出
    if (mprotect(stack_base, PAGE_SIZE, PROT_NONE) == -1) {
      munmap(stack_base, SIGNAL_STACK_SIZE);
      return;
    }
    stack_t ss;
    // 信号栈起始地址
    ss.ss_sp = reinterpret_cast<uint8_t*>(stack_base) + PAGE_SIZE;
    // 信号栈大小
    ss.ss_size = SIGNAL_STACK_SIZE - PAGE_SIZE;
    ss.ss_flags = 0;
    // 通知内核信号栈信息
    sigaltstack(&ss, NULL);
    thread->alternate_signal_stack = stack_base;

    // We can only use const static allocated string for mapped region name, as Android kernel
    // uses the string pointer directly when dumping /proc/pid/maps.
    // 设置虚拟内存区域名称
    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ss.ss_sp, ss.ss_size, "thread signal stack");
    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, stack_base, PAGE_SIZE, "thread signal stack guard page");
  }
}
  • 信号栈映射信息


下面看Thread::CreateCallback的实现

void* Thread::CreateCallback(void* arg) {
  Thread* self = reinterpret_cast<Thread*>(arg);
  Runtime* runtime = Runtime::Current();
  .....
  {
    ......
    // 调用Thread的Init方法
    CHECK(self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env));
    self->tlsPtr_.tmp_jni_env = nullptr;
    ......
  }
  {
    ScopedObjectAccess soa(self);
    // 初始化字符串构造入口
    self->InitStringEntryPoints();

    // Copy peer into self, deleting global reference when done.
    CHECK(self->tlsPtr_.jpeer != nullptr);
    // opeer指向Java Thread对象在堆中的镜像
    self->tlsPtr_.opeer = soa.Decode<mirror::Object*>(self->tlsPtr_.jpeer);
    self->GetJniEnv()->DeleteGlobalRef(self->tlsPtr_.jpeer);
    self->tlsPtr_.jpeer = nullptr;
    // 设置线程名
    self->SetThreadName(self->GetThreadName(soa)->ToModifiedUtf8().c_str());

    ArtField* priorityField = soa.DecodeField(WellKnownClasses::java_lang_Thread_priority);
    // Java线程优先级转换为Linux进程优先级
    self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer));
    Dbg::PostThreadStart(self);

    // Invoke the 'run' method of our java.lang.Thread.
    // 调用Java Thread对象的run方法
    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);
  }
  // Detach and delete self.
  Runtime::Current()->GetThreadList()->Unregister(self);

  return nullptr;
}

bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) {
  // This function does all the initialization that must be run by the native thread it applies to.
  // (When we create a new thread from managed code, we allocate the Thread* in Thread::Create so
  // we can handshake with the corresponding native thread when it's ready.) Check this native
  // thread hasn't been through here already...
  CHECK(Thread::Current() == nullptr);

  // Set pthread_self_ ahead of pthread_setspecific, that makes Thread::Current function, this
  // avoids pthread_self_ ever being invalid when discovered from Thread::Current().
  tlsPtr_.pthread_self = pthread_self();
  CHECK(is_started_);
  // 实现在thread_android.cc中,空函数
  SetUpAlternateSignalStack();
  // 获取栈信息并设置隐式栈溢出保护
  if (!InitStackHwm()) {
    return false;
  }
  InitCpu();
  // 初始化JNI以及Quick entrypoints
  InitTlsEntryPoints();
  RemoveSuspendTrigger();
  InitCardTable();
  InitTid();
  // Mterp解释器信息保存到TLS
  interpreter::InitInterpreterTls(this);

#ifdef ART_TARGET_ANDROID
  // 线程本地存储(在pthread_internal_t内部)
  __get_tls()[TLS_SLOT_ART_THREAD_SELF] = this;
#else
  CHECK_PTHREAD_CALL(pthread_setspecific, (Thread::pthread_key_self_, this), "attach self");
#endif
  DCHECK_EQ(Thread::Current(), this);

  tls32_.thin_lock_thread_id = thread_list->AllocThreadId(this);

  if (jni_env_ext != nullptr) {
    DCHECK_EQ(jni_env_ext->vm, java_vm);
    DCHECK_EQ(jni_env_ext->self, this);
    tlsPtr_.jni_env = jni_env_ext;
  } else {
    tlsPtr_.jni_env = JNIEnvExt::Create(this, java_vm);
    if (tlsPtr_.jni_env == nullptr) {
      return false;
    }
  }
  // 线程添加到thread_list
  thread_list->Register(this);
  return true;
}

bool Thread::InitStackHwm() {
  void* read_stack_base;
  size_t read_stack_size;
  size_t read_guard_size;
  // 从线程本地存储中获取线程栈信息
  GetThreadStack(tlsPtr_.pthread_self, &read_stack_base, &read_stack_size, &read_guard_size);

  tlsPtr_.stack_begin = reinterpret_cast<uint8_t*>(read_stack_base);
  tlsPtr_.stack_size = read_stack_size;
  ......
  // 设置stack_end = stack_begin + 8KB
  ResetDefaultStackEnd();

  // Install the protected region if we are doing implicit overflow checks.
  if (implicit_stack_check && !valgrind_on_arm) {
    // The thread might have protected region at the bottom.  We need
    // to install our own region so we need to move the limits
    // of the stack to make room for it.
    // 调整stack_begin、stack_end以及stack_size,通常read_guard_size + kStackOverflowProtectedSize = 8KB
    tlsPtr_.stack_begin += read_guard_size + kStackOverflowProtectedSize;
    tlsPtr_.stack_end += read_guard_size + kStackOverflowProtectedSize;
    tlsPtr_.stack_size -= read_guard_size;

    InstallImplicitProtection();
  }

  // Sanity check.
  CHECK_GT(FindStackTop(), reinterpret_cast<void*>(tlsPtr_.stack_end));

  return true;
}

下面看InvokeVirtualOrInterfaceWithJValues的实现

JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccessAlreadyRunnable& soa,
                                           jobject obj, jmethodID mid, jvalue* args) {
  // We want to make sure that the stack is not within a small distance from the
  // protected region in case we are calling into a leaf function whose stack
  // check has been elided.
  // 编译器内建函数__builtin_frame_address(0)返回当前函数的帧地址
  // See http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
  if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) {
    // 当前的栈顶地址低于stack_end,抛出栈溢出错误
    ThrowStackOverflowError(soa.Self());
    return JValue();
  }

  // 解引用获取堆中的镜像
  mirror::Object* receiver = soa.Decode<mirror::Object*>(obj);
  // 获取run方法的ArtMethod
  ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid));
  bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
  if (is_string_init) {
    // Replace calls to String.<init> with equivalent StringFactory call.
    // 如果是Java String类的构造方法,替换为StringFactory的构造方法
    method = soa.DecodeMethod(WellKnownClasses::StringInitToStringFactoryMethodID(mid));
    receiver = nullptr;
  }
  uint32_t shorty_len = 0;
  const char* shorty =
      method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len);
  JValue result;
  ArgArray arg_array(shorty, shorty_len);
  arg_array.BuildArgArrayFromJValues(soa, receiver, args);
  // 调用Thread的run方法
  InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
  if (is_string_init) {
    // For string init, remap original receiver to StringFactory result.
    UpdateReference(soa.Self(), obj, result.GetL());
  }
  return result;
}

文中涉及Android ART运行时原理的一些知识暂不详细讨论,以后研究ART运行时再做分析。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容