直面底层第一站 - Thread 上( Thread 大小)

3,170 阅读2分钟

直面底层第一站 - Thread 上( Thread 大小)

  • Thread 大小 (上)
  • JNIEnv 什么作用,怎么来的 (中)
  • 线程挂起实现,synchronized art原理 Monitor(art/runtime/monitor.cc) (下)

方法栈大小

在Thread 的构造函数中会调用init方法,其第四个long形参数就表示方法栈的大小,一般都是0(由系统指定默认值)

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
	...
}

线程开启,调用start的时候由nativeCreate传向底层

public synchronized void start() {
	...
	nativeCreate(this, stackSize, daemon);
	...
}

由JNI调度nativeCreate会调到thread.cc的CreateNativeThread

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
	// 1.这里赋值完后 stack_size为1064960b(1M + 16kb)
	stack_size = FixStackSize(stack_size);
	pthread_attr_t attr;
    
	// 调用到bionic/libc/bionic/pthread_attr.cpp的pthread_attr_setstacksize
	// attr->stack_size = stack_size;
	// 把stack_size设置到attr
	CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
	//2. 创建线程
	pthread_create_result = pthread_create(&new_pthread,
                                           &attr,
                                           Thread::CreateCallback,
                                           child_thread);
}

注释1. 方法栈栈大小设置

static size_t FixStackSize(size_t stack_size) {
  if (stack_size == 0) {
  // 这里通常情况下为0
    stack_size = Runtime::Current()->GetDefaultStackSize();
  }
  // 1M
  stack_size += 1 * MB;
  ...
  // 所以stack_size最终大小为 1M + 16kb
  stack_size = RoundUp(stack_size, kPageSize);
  return stack_size;
}

gdb调试可以看的stack_size结果

注释2. 线程创建
bionic/libc/bionic/pthread_create.cpp

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

  pthread_internal_t* thread = NULL;
  void* child_stack = NULL;
  //3. __allocate_thread 分配内存
  int result = __allocate_thread(&thread_attr, &thread, &child_stack);
  if (result != 0) {
    return result;
  }
}

注释3. 分配内存 TLS初始化

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) {
    if (__builtin_add_overflow(attr->stack_size, attr->guard_size, &mmap_size)) return EAGAIN;
    if (__builtin_add_overflow(mmap_size, sizeof(pthread_internal_t), &mmap_size)) return EAGAIN;
    mmap_size = __BIONIC_ALIGN(mmap_size, PAGE_SIZE);
    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;
  }
  ...
  //  为了安全访问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) {
    memset(thread, 0, sizeof(pthread_internal_t));
  }
  // 更新stack_zise
  attr->stack_size = stack_top - reinterpret_cast<uint8_t*>(attr->stack_base);

  thread->mmap_size = mmap_size;
  thread->attr = *attr;
  //4. 初始化线程局部存储TLS
  if (!__init_tls(thread)) {
    if (thread->mmap_size != 0) munmap(thread->attr.stack_base, thread->mmap_size);
    return EAGAIN;
  }
  __init_thread_stack_guard(thread);

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

注释4. 本地临时存储TLS 初始化

bool __init_tls(pthread_internal_t* thread) {
    thread->tls[TLS_SLOT_SELF] = thread->tls;
    thread->tls[TLS_SLOT_THREAD_ID] = thread;
    // 本地临时存储TLS大小20K(20480b)
    size_t allocation_size = BIONIC_TLS_SIZE + (2 * PTHREAD_GUARD_SIZE);
    void* allocation = mmap(nullptr, allocation_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (allocation == MAP_FAILED) {
      async_safe_format_log(ANDROID_LOG_WARN, "libc",
                            "pthread_create failed: couldn't allocate TLS: %s", strerror(errno));
      return false;
    }
    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, allocation, allocation_size, "bionic TLS guard");
    thread->bionic_tls = reinterpret_cast<bionic_tls*>(static_cast<char*>(allocation) +
                                                       PTHREAD_GUARD_SIZE);
    if (mprotect(thread->bionic_tls, BIONIC_TLS_SIZE, PROT_READ | PROT_WRITE) != 0) {
      async_safe_format_log(ANDROID_LOG_WARN, "libc",
                            "pthread_create failed: couldn't mprotect TLS: %s", strerror(errno));
      munmap(allocation, allocation_size);
      return false;
    }
  
    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, thread->bionic_tls, BIONIC_TLS_SIZE, "bionic TLS");
    return true;
  }

Thread大小计算

首先看看一个Java Thread对象的大小,由Profile dump 内存,统计到一空的Thread大概132b,加上Runable等引用最少180b

Thread的大小 至少:
方法栈(1M+16K)+程局部存储TLS(20K)+Thread对象的大小(180B)

(ps:其实c++ thread的对象大小(包涵 monitor等各个成员)没有计算)

值得一提的是,mmap分配的都是虚拟内存,不会分配实际的物理内存,真正使用到的时候,linux由缺页中断,才会分配真正的物理内存。

gdb的使用测试了好久,这一篇就先这样拉。。。