[컴][os] mac os x 코드로 바라본 thread 의 설명


thread 에 대해 code 적으로 접근을 해 보고자 source code 를 찾는데, mac 의 source 가 단순히 접근하기 쉬워서 Mac source 로 분석하기로 했다. ^^;;;;

먼저 thread 에 대해 아는 내용을 적어 본다면.
  • 각 process 는 각자 자신의 address space 를 갖는다. 그런데 thread 는 이 process내부에서 만들어지기 때문에 같은 address space 에서 여러 개의 thread 가 존재한다. 그로 인해 자연히 같은 address space 를 사용하게 된다.[ref. 1]

    프로세스는 fork()하여 만든 자식 프로세스와 부모 프로세스가 각각 독립적인 메모리 공간(data, heap, stack 등)을 가지지만, 쓰레드는 다른 메모리 공간은 공유하며 stack만 독립적으로 가집니다.[ref.11]
  • 그리고 thread 는 동작을 수행한다.(execution unit)

그러면 address space 를 갖는다는 의미는 무엇인가? 이것은 process 가 만들어지면서, virtual address memory 가 할당된다. 0~4GB(32bit cpu) 로 할당될 것이다.(불확실) 그때 이 범위 안에서 다시 일정부분을 thread 가 사용할 memory 로 할당해 주게 될 것이다. 이 안에는 call stack 도 있을 것이고, thread structure 도 있을 것이다. 이 것이 thread 라고 할 수 있겠다. 비유를 들어 메모리 관점에서만 설명을 한다면, process 는 os 에서 바라보는 memory 의 fragment 라고 본다면, thread 는 process 가 바라보는 memory 의 fragment 라고 할 수 있을 듯 하다.

그리고 thread 가 하나의 동작을 수행하기 위해서는 함수에 대한 pointer 를 가져야 할 것이고, thread 에게 processor 를 사용할 수 있도록 하는 과정도 필요할 것이다.

위에서 말한 부분들에 대해 간단하게 code 를 찾아서 확인해 보자.

Implementation

여기서는 Mac Os 의 pthread 소스를 가지고 확인 해 보자. 직접 소스를 보는 것이 더욱 도움이 되니, link 가 걸려있는 소스를 참고하도록 하자. 그리고 참고로 잘 모르고 막 적어놓은 경우도 있으니, 틀린 부분은 바로 잡아 주시면 감사하겠습니다. ^0^;;

참고로, mac os 는 unix-like 이기는 한데 Mach OS 의 위에서 만들어졌다. 그래서 Mach 의 thread 와 unix 의 thread 가 같이 공존한다. 이 둘에 대해서는 ref. 7 을 참고하도록 하자.

function pointer

thread 는 특정 동작을 실행하게 해 준다. 그럼 특정 동작을 어떻게 표현할 것인가? 하나의 statement 는 나아가서 하나의 instruction 은 하나의 동작이다. 근데 좀 더 복잡한 동작은 이 statement 와 instruction 들의 집합으로 만들어진다. 이 것이 function 이다. thread 에게 우리가 원하는 function() 을 할 수 있게, function pointer 를 가져야 한다.

이 function pointer 가 pthread_create() 할 때 인자(parameter) 로 넘겨주는 start_routine 이 된다.

call stack

call stack 이 필요하다. call stack 을 통해 갖는 장점은 ref. 4 을 참고하자. 이러한 이점을 살리기 위해 call stack 이 필요하다.

_pthread_create_pthread_onstack(attrs, &stack, &t)  에서 virtual memory 의 일정부분을 mapping 해서 stack 으로 잡는 듯 하다. vm_allocate() 은 실제로 allocate 을 하기 보다는 주소를 조정해서 다시 vm_map_enter() 를 실행한다.

만약 여기서 stack 을 만들지 않으면, bsdthread_create() 내부에서 다시 만들어준다.

struct thread_t

thread 도 하나의 data structure 를 갖는다. thread 를 관리하려고 한다면, 변수가 필요하다. 그래서 thread 에 대한 struct 인 thread_t 를 만들어야 한다.
type thread_t = mach_port_t(osfmk/mach/mach_types.defs)

이 thread_t 는 Mach 가 갖는 thread 이고, BSD thread 는 uthread 라는 struct 를 이용한다.[ref. 7]

결국 thread 의 특성은 이 uthread 의 특성에 좌우될 것이다.(Mach 의 thread struct 인 thread_t 는 좀 더 적은 기능을 가진다.[ref. 7])

이 bound_processor 는 cache miss 를 줄이는 방법에 하나인 듯 하다.[ref. 10]

scheduling


이 부분은 조금 더 분석이 필요하다. 하지만,  thread_setrun() 을 통해 processor 에 thread 를 넘겨주는 부분이 scheduling 의 작용을 하지 않을까 생각된다.(불확실)





Source codes

// source 출처 : ref. 2


#include <stdio.h>
#include <pthread.h> 
main()  {
  pthread_t f2_thread, f1_thread; 
  void *f2(), *f1();
  int i1,i2;
  i1 = 1;
  i2 = 2;
  pthread_create(&f1_thread,NULL,f1,&i1);
  pthread_create(&f2_thread,NULL,f2,&i2);
  pthread_join(f1_thread,NULL);
  pthread_join(f2_thread,NULL);
}
void *f1(int *x){
  int i;
  i = *x;
  sleep(1);
  printf("f1: %d",i);
  pthread_exit(0); 
}
void *f2(int *x){
  int i;
  i = *x;
  sleep(1);
  printf("f2: %d",i);
  pthread_exit(0); 
}




#ifndef PTHREAD_MACH_CALL
#define PTHREAD_MACH_CALL(expr, ret) (ret) = (expr)
#endifpthread_create()
_new_pthread_create_suspended(thread, attr, start_routine, arg, 0);
 mach_port_t kernel_thread = MACH_PORT_NULL;


 flags |= PTHREAD_START_CUSTOM;
 _pthread_create_pthread_onstack(attrs, &stack, &t)
  kr = vm_map(mach_task_self(), &stackaddr,
     attrs->stacksize + guardsize,
     vm_page_size-1,
     VM_MAKE_TAG(VM_MEMORY_STACK)| VM_FLAGS_ANYWHERE , MEMORY_OBJECT_NULL,
     0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL,
     VM_INHERIT_DEFAULT);
   kr = mach_vm_map(target_map, &map_addr, map_size, map_mask, flags,
        port, obj_offset, copy,
        cur_protection, max_protection, inheritance);
    vm_map_enter(target_map,
        &map_addr, map_size,
        (vm_map_offset_t)mask,
        flags,
        object, offset,
        copy,
        cur_protection, max_protection,
        inheritance);
        
  if (kr != KERN_SUCCESS)
   kr = vm_allocate(mach_task_self(),
       &stackaddr, attrs->stacksize + guardsize,
       VM_MAKE_TAG(VM_MEMORY_STACK)| VM_FLAGS_ANYWHERE);
 

 t->fun = start_routine; 
 __bsdthread_create(start_routine, arg, stack, t, flags))
  kret = thread_create(ctask, &th);
   thread_create_internal2(task, new_thread, FALSE);
    thread_create_internal(task, -1, (thread_continue_t)thread_bootstrap_return, TH_OPTION_NONE, &thread);
     machine_thread_create(new_thread, parent_task)
     ...
     ipc_thread_init(new_thread);
     queue_init(&new_thread->held_ulocks);


  if ((flags & PTHREAD_START_CUSTOM) == 0) {
   th_stacksize = (mach_vm_size_t)user_stack;  /* if it is custom them it is stacksize */
   th_allocsize = th_stacksize + PTH_DEFAULT_GUARDSIZE + p->p_pthsize;

   kret = mach_vm_map(vmap, &stackaddr,
         th_allocsize,
         page_size-1,
         VM_MAKE_TAG(VM_MEMORY_STACK)| VM_FLAGS_ANYWHERE , NULL,
         0, FALSE, VM_PROT_DEFAULT, VM_PROT_ALL,
         VM_INHERIT_DEFAULT);
       if (kret != KERN_SUCCESS)
        kret = mach_vm_allocate(vmap,
          &stackaddr, th_allocsize,
          VM_MAKE_TAG(VM_MEMORY_STACK)| VM_FLAGS_ANYWHERE);
   ...
  }
  
  if (kret != KERN_SUCCESS)
   return(ENOMEM);
  thread_reference(th);
  ...
  thread_set_wq_state64(th, (thread_state_t)ts64);
  ...
  kret = thread_resume(th);
   thread_mtx_lock(thread);
   ...
   if (thread->started)
     thread_wakeup_one(&thread->suspend_count);
    else {
     thread_start_internal(thread);
      
    }
   ...
   thread_mtx_unlock(thread);



thread_create_internal() 하면 machine_thread_create() 에서 cpu 에 따른 state 를 초기화 해준다.
그 후에 thread_start_internal() 에서 wait 하고 있는 state 를 없애 주고 나서, thread_setrun() 에서 thread_t’s bound_processor 의 run queue에 thread 를 할당(dispatch)해 준다.[ref. 9]



thread_start_internal()
 thread_start_internal(thread_t thread);
 

  clear_wait(thread, THREAD_AWAKENED);
   ret = clear_wait_internal(thread, result);
    thread_go(thread, wresult)
     thread_setrun(thread, SCHED_PREEMPT | SCHED_TAILQ);
  
  thread->started = TRUE;



Reference

  1. http://www.programmerinterview.com/index.php/operating-systems/thread-vs-process/
  2. http://en.wikipedia.org/wiki/Mach_(kernel)
  3. https://github.com/KyleBenson/scripts/blob/master/teaching/pthread.c
  4. http://en.wikipedia.org/wiki/Call_stack
  5. cross reference, http://code.metager.de/source/xref/apple/xnu/osfmk/vm/vm_user.c
  6. vm_map manpage
  7. Mac os x and ios internals to the apple's core, Jonathan Levin, Wrox
  8. Mac OS X Internals. A Systems Approach, 2006
  9. thread_setrun(), Mac os x and ios internals to the apple's core, Jonathan Levin, Wrox
  10. shared cache 를 위한 소프트웨어 테크닉
  11. 무결성, 쓰레드, 뮤텍스, 세마포어 에 관한 간단한 정리

댓글 없음:

댓글 쓰기