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