VPOS 에서 쓰레드를 할당하고, 스케줄링 되는 루틴을 설명하고 있다. 어찌보면, 가장 핵심이 되는 부분이라고 하겠다.
부트로더와 스타트업(startup) 코드를 거쳐서, 하드웨어 초기화와 각종 큐(queue) 및 테이블(table) 을 생성하고 초기화 하면, pthread_create 함수를 이용해서 쓰레드를 생성한다. 그리고 나서, vk_scheduler 를 호출해서 각 큐에 등록된, 쓰레드를 검색하여 우선순위가 높은 순으로 vk_current_thread 에 넣는다. 상황에 따라서, 현재의 쓰레드를 저장하고, 새로운 쓰레드를 수행하기도 한다.
하나의 쓰레드가 수행이 되다가, 이때, 타이머 인터럽트가 발생하여, 핸들러에 의해서 vk_scheduler 를 수행하게 되고, 또다시 vk_ready_queue 큐를 검색하여 우선순위가 높은 순으로 vk_current_thread 에 넣고, 쓰레드를 수행한다.
이 문서에서는 앞에서 설명한 루틴을 소스코드 레벨에서 최대한 자세히 살펴볼 것이다.
쓰레드 생성루틴
pthread_create
이 함수는 메모리를 할당받아서, 인자로 받은 함수를 쓰레드 큐에 등록시켜 실행시키는 역할을 한다.
int pthread_create(pthread_t *p_thread, void *reserved, void *(*function)(void *), void *arg) { unsigned int* stk; vk_thread_t* thread; // 쓰레기 값이 할당됨 vk_scheduler_lock(); // 스케줄러 락 시킴 // (버그 : vk_current_thread->sched_lock_counter, vk_current_thread->saved_sched_lock 가 초기화되지 않은 상태에서 수행) thread=(vk_thread_t *)vk_malloc(sizeof(vk_thread_t)); // 1. sizeof(vk_thread_t) = 0x84, 쓰레드 메모리 영역 할당 if(thread == NULL) { printk("Thread can not create because it is not enough memory\n"); return 1; } stk=(unsigned int *)vk_malloc(vh_thread_stack_size); // 2. vh_thread_stack_size = 1024, 쓰레드 스택 메모리 영역 할당 if(stk == NULL) { printk("stack can not create because it is not enough memory\n"); return 1; } thread->thread_id = vk_thread_id; // 0 vk_thread_id++; // 1 thread->state = READY; // READY = 1 if(function == VPOS_SHELL) { thread->priority = 0; thread->init_priority = 0; } else if(!((p_thread->priority > 0) && (p_thread->priority < 32))) { // p_thread->priority 에는 쓰레기값 저장 thread->priority = D_PRIORITY; // 15 thread->init_priority = D_PRIORITY; // 15 } else { thread->priority = p_thread->priority; thread->init_priority = p_thread->priority; } thread->saved_sched_lock = 0; thread->join_thread = NULL; thread->join_value = 0; thread->return_value = NULL; thread->swi_number = 0; thread->sched_lock_counter = 0; thread->cpu_tick = 0; thread->tcb_bottom=&(thread->mode); // 0x30127Fb4 thread->stack = stk; #ifndef MYSEO thread->func=function; // 쓰레드를 수행할 함수의 포인터 주소 #endif thread->interrupt_state = 0; thread->next=NULL; thread->mode = vh_user_mode; // 16 thread->rtcfunc = (unsigned int)thread->func; // 쓰레드를 수행할 함수의 포인터 주소 thread->rtcarg1 = (unsigned int)arg; // 0 thread->rtcarg3 = (unsigned int)((char *)(stk)+vh_thread_stack_size); // 0x30127F6C thread->rtcarg4 = (unsigned int)pthread_exit; // pthread_exit() 함수의 포인터 주소 p_thread->thread = thread; vk_thread_enqueue(thread, vk_tcb_log_queue); // thread 를 vk_tcb_log_queue 에 넣는다 vk_thread_enqueue(thread, vk_ready_queue); // thread 를 vk_ready_queue 에 넣는다 vk_scheduler_unlock(); return 0; }
vk_malloc
다음은 1번 위치에 있는 메모리를 할당해주는 vk_malloc 함수이다. pthread_create 함수에서 총 2 번 호출된다. 첫번째는 쓰레드의 영역(vk_thread_t*)을 할당받기 위해서이고, 두번째는 쓰레드 스택 영역을 할당받기 위해서이다.
void* vk_malloc(unsigned nbytes) { Header *p, *prevp; Header *heap_memory_alloc(void); // 함수 포인터 unsigned nunits; nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1; // ((132 + 8 - 1) / 8) + 1 = 18 if ((prevp = freep) == NULL) { /* no free list yet */ // freep = 0 base.s.ptr = freep = prevp = &base; // free list 초기화 (특정 메모리 스택 주소 영역 할당) base.s.size = 0; // free list size 초기화 } for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) { if (p->s.size >= nunits) { /* big enough */ // p->s.size = 0, nunits = 18 if (p->s.size == nunits) /* exactly */ prevp->s.ptr = p->s.ptr; else { /* allocate tail end */ p->s.size -= nunits; p += p->s.size; p->s.size = nunits; } freep = prevp; return (void *)(p+1); } if (p == freep) /* wrapped around free list */ // p == free 는 같음, 그러므로 heap_memory_alloc 함수 호출 if ((p = heap_memory_alloc()) == NULL) return NULL; /* none left */ } }
디버거에 확인한 결과, vk_malloc() 에서 마지막으로 할당한 결과값은 0x30127FFC 이다. 하지만, 결과적으로 thread 에 저장된 값은 0x30127F74 이다.
두 값의 차이는 정확히 0x88 차이이다. sizeof(vk_thread_t) 의 사이즈가 0x84 인것으로 보아 메모리를 각 구조체에 맞게 할당하고, 다음 할당할 스택주소(+0x4)를 가리키는 것 같다.
이번에는 2번 위치에 있는 쓰레드의 스택 영역을 할당해주는 vk_malloc 함수이다.
void* vk_malloc(unsigned nbytes) { Header *p, *prevp; Header *heap_memory_alloc(void); unsigned nunits; nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1; // nunits = 129 if ((prevp = freep) == NULL) { /* no free list yet */ // 기존의 할당된 값이 있음 base.s.ptr = freep = prevp = &base; // 수행 안함 base.s.size = 0; } for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) { // 기존 값(p->s.size = 18) 에서 p->s.size = 124982 로 바뀜 if (p->s.size >= nunits) { /* big enough */ // nunits = 129, 이번에는 참이므로 서브루틴이 수행됨 if (p->s.size == nunits) /* exactly */ prevp->s.ptr = p->s.ptr; else { /* allocate tail end */ p->s.size -= nunits; // 124982 - 129 = 124853 p += p->s.size; // p(0x30033DBC) + p->s.size = nunits; // p->s.size = 129 } freep = prevp; // 0x30127FFC return (void *)(p+1); // p+1 = 0x30127B6C, 주소값 리턴 후에 빠져나옴 } if (p == freep) /* wrapped around free list */ // 두번째는 수행안됨 if ((p = heap_memory_alloc()) == NULL) return NULL; /* none left */ } }
위의 루틴을 수행결과, 리턴값으로 0x30127B6C 을 돌려준다.
1 의 경우, 가장 처음에 한 번만 먼저 힙(heap) 메모리를 설정하기 위해 heap_memory 함수를 호출한다.
heap_memory
처음에 한번만 호출된다.
static Header *heap_memory_alloc(void) { char *cp; Header *up; static unsigned int alloc_number; static char heap[vh_heap_size]; // vh_heap_size = 1000000 (heap[0] 주소 : 0x30033DBC, heap[999999] 주소 : 0x30127FFB) alloc_number=0; if (alloc_number == 0){ alloc_number =1; cp =(char *)heap; // cp = 0x30033DBC } else{ /* no space at all */ return NULL; } up = (Header *) cp; // up = 0x30033DBC up->s.size = ((vh_heap_size/sizeof(Header))); // up->s.size = 125000(=0x1E848) vk_free((void *)(up+1)); // vk_free 함수 호출 (0x30033DBC + 0x8 = 0x30033DC4) return freep; // freep = 0x30127FFFC }
위의 루틴에서 heap[1000000] 의 배열을 할당한다. 메모리 주소로는 0x30033DBC ~ 0x30127FFB 가 되는데, 이 곳에다가 쓰레드와 스택 영역을 할당한다. 마지막에서 vk_free 함수를 호출한다.
vk_free
인자로 받은 메모리 주소에서 -1(0x8) 을 빼서, block 헤더의 포인터를 가리킨다. 그리고 받아온 주소가 힙 메모리 영역에 있는지를 체크한다.
그리고 연결리스트에서 제거하듯이 링크를 제거한 후에, 메모리를 해제한다.
void vk_free(void *ap) { Header *bp, *p; bp = (Header *)ap - 1; /* point to block header */ // 다시 -1(8) 을 빼줌으로서, 원래의 주소를 지정 for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr) // 지정된 주소가 힙 메모리 영역안에 존재하는가? if (p >= p->s.ptr && (bp > p || bp < p->s.ptr)) break; /* freed block at start or end of arena */ if (bp + bp->s.size == p->s.ptr) { /* join to upper nbr */ // bp + bp->s.size == (p->s.ptr)0x30127FFC(힙 메모리 영역의 상위 끝) 과 같은가 ? bp->s.size += p->s.ptr->s.size; bp->s.ptr = p->s.ptr->s.ptr; } else bp->s.ptr = p->s.ptr; if (p + p->s.size == bp) { /* join to lower nbr */ // p + p->s.size == 0x30033DBC(힙 메모리 영역의 하위 끝)와 같은가? p->s.size += bp->s.size; p->s.ptr = bp->s.ptr; } else p->s.ptr = bp; freep = p; ap = NULL; }
다음은 앞에서 할당받은 thread 를 각각의 queue 에 넣는 역할을 하는 함수이다.
void vk_thread_enqueue(vk_thread_t *thread, vk_threadq_t *list_t) { vk_threadq_t *queue; vk_thread_t *temp; unsigned int prio; temp = thread; queue = list_t; prio = (thread->priority); // 15 if(queue[prio].list_head != NULL) // 처음 수행할 경우, NULL { temp = queue[prio].list_tail; queue[prio].list_tail = thread; queue[prio].list_tail->next = NULL; temp->next = thread; ++(queue[prio].counter); } else { queue[prio].list_head = thread; // 인자로 받은 thread 의 주소를 list 의 head 와 tail 에 저장 queue[prio].list_tail = thread; queue[prio].list_tail->next = NULL; ++(queue[prio].counter); // queue[15].counter = 1 } }
위 루틴의 수행 후의 상태는 다음과 같다.
구조체 | 변수값 |
vk_ready_queue[0x0F] | priority = 0x0F |
counter = 1 | |
list_head = 0x30127F74(자기자신) | |
list_tail = 0x30127F74(자기자신) |
구조체 | 변수값 |
vk_tcb_log_queue[0x0F] | priority = 0x0F |
counter = 1 | |
list_head = 0x30127F74(자기자신) | |
list_tail = 0x30127F74(자기자신) |
쓰레드 생성 루틴의 맨 마지막으로 vk_scheduler_unlock() 을 호출한다.
void vk_scheduler_unlock(void) { vk_sched_lock = 0; // 전역변수(vk_sched_lock) 를 0 으로 설정함으로써, timer 인터럽트 핸들러가 발생했을 때, vk_scheduler() 함수를 호출하지 못하게 한다 }
메모리 할당 순서
이제까지 메모리영역을 할당받아서, 쓰레드를 만드는 루틴을 소스파일을 통해 알아보았다. 정리하는 차원에서 기술해보겠다.
- 쓰레드를 관리하기 위한 각 큐와 테이블을 초기화하고, 시스템 수행에 필요한 하드웨어(타이머)를 초기화한다.
- pthread_create() 를 이용해서 원하는 함수를 쓰레드로 실행시킬 수 있다.
- 먼저 쓰레드를 만들기 위해서 메모리를 할당 받아야 하는데, vk_malloc() 을 이용해서 할당 받는다. 총 2 번을 호출하게 되는데, 첫번째는 쓰레드 구조체(vk_thread_t)를 저장할 공간을 할당받기 위함이고, 두번째는 쓰레드 실행시 사용할 스택 공간(vh_thread_stack_size)을 할당받기 위함이다.
- vk_thread_t 구조체의 크기(0x84) 를 인자로 받고, 전역변수 freep 는 base 의 주소값인 0x30127FFC 를 가리키도록 한다(참고로, 이주소는 임의로 정해준 것이 아니고, 컴파일 타임에 자동으로 지정된 주소이다). 0x30127FFC 는 뒤에서 설명할 heap[999999] 배열의 다음 메모리주소이다.
- 처음 vk_malloc() 을 수행했기 때문에, 사용할 메모리 공간을 할당 해야 한다. 곧이어 heap_memory_alloc() 을 호출한다.
- 여기서는 char 타입의 heap[1000000] 개를 생성한다. 이 배열의 시작주소는 0x30033DBC ~ 0x30127FFB 까지이다(참고로 0x30033DBC 는 컴파일 타임에 자동으로 지정된 주소이다).
- up 변수는 heap[0] 인 0x30033DBC 를 가리키고, up→size 는 1000000/sizeof(Header) 의 값을 저장한다. 125000 이다.
- 할당한 heap 메모리 영역을 초기화 시키기 위해서, (up+1) 의 주소를 vk_free 함수의 인자로 넘긴다. 여기서 (up+1) 는 메모리 주소로는 up+0x8 을 의미한다. 여기서 up 주소가 아닌 up+1 을 넘긴 이유는 메모리 연산도중에 heap[0] 의 값이 바뀌는 것을 방지하기 위해서이다.
- 마지막으로 vk_free() 는 인자로 받은 메모리 주소가 heap[] 배열안에 있는 주소인지를 체크하여, bp 에는 heap[0] 의 주소가, p 에는 heap[999999] 의 다음 주소인 0x30127FFC 가 들어간다.
- vk_free() 마치고 나면, heap_memory_alloc() → vk_malloc() 순으로 리턴되고, 'for (p = prevp→s.ptr; ; prevp = p, p = p→s.ptr)' 문으로 실행된다.
- 이 때, 'p = prevp→s.ptr' 때문에 p 는 0x30033DBC 를 가리킨다.
주의할 점
여기까지가 처음으로 쓰레드에서 vk_malloc() 을 호출했을 때의 상황이다. 결과로는 0x30127F74 가 나온다. 정말 엉뚱한 값이다. 문제의 원인은 바로
p += p->s.size; // 이 연산을 수행하면서, Header 구조체가 깨진다. 그리하여 엉뚱한 주소 값이 나온다.
한가지 우연인 것은 엉뚱한 주소 값이 heap[] 안에 존재하는 주소라는 점이고, 각각 호출때마다 일정 영역을 두고 규칙적으로 메모리 주소를 반환한다는 것이다. 이것은 치명적인 버그이며, 반드시 수정되어야 한다. 이로 인해 어떤 문제가 발생할지 모른다.
스케줄링 루틴
VPOS_start
앞에서 pthread_create 함수를 이용해서 쓰레드를 생성했다. 쓰레드를 실제적으로 수행하기 위해서는 스케줄러의 역할이 필수적이다.
다음은 VPOS_kernel_main() 의 기본 루틴인 VPOS_start 이다.
void VPOS_start(void) { vk_current_thread = NULL; // 현재 수행 중인, 쓰레드를 없앤다(아무래도 쓰레드를 만들고, 처음으로 호출하는 함수이므로) vk_scheduler(); }
가장 핵심적이라고 할 수 있는 vk_scheduler 함수를 살펴보자.
vk_scheduler
각 vk_ready_queue 를 돌면서, 쓰레드를 검색한다. 자세한 내용은 아래의 주석을 참고하기 바란다. 타이머 인터럽트에 의해서 수행되어질 때는 irq 모드에서 실행된다.
void vk_scheduler(void) { vk_thread_t *vk_save_thread; unsigned int i; vk_scheduler_lock(); // 스케줄러 잠금 vk_thread_check(); // 현존하는 쓰레드 중에 좀비 쓰레드인 경우에, 잠금 vk_save_thread = NULL; // 컨텍스트 스위칭 시에 저장한 쓰레드 구조체 초기화 for(i=31; i>=0; --i) // 각 우선순위 별로 쓰레드를 검색 { if (vk_ready_queue[i].counter != 0) // ready_queue 가 0 이 아니라면, 최소 하나의 쓰레드가 존재한다 { vk_save_thread = vk_current_thread; // 현재의 쓰레드를 저장할 쓰레드로 지정 if(i != (vk_current_thread->priority)) vk_current_thread = NULL; // 현재의 쓰레드의 우선순위가 현재 ready_queue 의 우선순위와 같지 않으면, 현재 쓰레드를 NULL 로 초기화 if (vk_current_thread == NULL) // 현재 수행되는 쓰레드가 없다면, { // 처음 수행시만 실행된다 vk_current_thread = vk_ready_queue[i].list_head; // ready_queue 의 처음 쓰레드를 수행 if (vk_current_thread->next == NULL) vk_next_thread = vk_ready_queue[i].list_head; // 다음 쓰레드가 없다면, 현재 ready_queue 의 list_head 를 넣는다 else vk_next_thread = vk_current_thread->next; // 그렇지 않으면, 다음 쓰레드를 넣는다 break; } else // 현재 수행되는 쓰레드가 있으면 { // 두번째 수행부터 실행된다 vk_current_thread = vk_next_thread; // 현재 쓰레드를 다음 쓰레드로 지정한다 if (vk_current_thread->next == NULL) vk_next_thread = vk_ready_queue[i].list_head; // 다음 쓰레드가 없다면, 현재 ready_queue 의 list_head 를 넣는다 else vk_next_thread = vk_current_thread->next; // 그렇지 않으면, 다음 쓰레드를 넣는다. break; } } } if (vk_save_thread == NULL) { // 저장할 쓰레드가 없다면, // 처음 수행시만 수행된다 vk_current_thread->state = RUNNING; // 현재의 쓰레드 상태를 RUNNING 로 설정 vk_scheduler_unlock(); // 스케줄러를 푼다 vk_restore_thread_ctx(vk_current_thread); // 현재 쓰레드를 저장 } else if (vk_save_thread != vk_current_thread) { // 저장한 쓰레드가 현재의 쓰레드가 아니면 // 두번째 수행부터 실행된다 vk_thread_switch_ctx(vk_save_thread, vk_current_thread); // 컨텍스트 스위치를 통해 저장된 쓰레드를 현재 쓰레드로 교체 } vk_scheduler_unlock(); // 스케줄러를 푼다 }
이 함수가 하는 역할을 정리하면 다음과 같다.
- 이 함수가 가장 많이 호출되는 경우는 바로, 타이머 인터럽트에 의해서 호출되는 경우이다.
- 수행이 되면, 스케줄러를 락(lock) 하기위해 먼저 현재 쓰레드의 sched_lock_counter 를 하나 증가시키고 현재 쓰레드의 saved_sched_lock 에 0 을 넣고, 전역변수인 vk_sched_lock 를 1 로 바꾼다.
- vk_thread_check() 호출하지만, 이 함수는 현재 수행되는 쓰레드가 없고, 현재 쓰레드의 상태가 좀비일 때만 수행되므로, 여기서는 전혀 수행이 안된다.
- 기존의 저장되어 있던 쓰레드(vk_save_thread)의 주소를 초기화 시킨다. 새로운 쓰레드로 컨텍스트 스위치를 하기 위함이다.(For 루프 시작 전)
- 우선순위가 높은 순서대로, 루프를 돌면서 ready_queue 를 검색한다. 만일 ready_queue 에 하나 이상의 쓰레드가 존재하면, 일단 현재 수행되는 쓰레드를 vk_save_thread 에 저장한다. → ⑦번
- 만일 현재 ready_queue 에 쓰레드가 하나도 존재하지 않으면, i 값을 감소시키면서, i-1 우선 순위 ready_queue 를 검색한다.
현재 수행되고 있는 쓰레드의 우선순위가 현재 검색중인 우선순위와 같지 않다면, 현재 수행중인 쓰레드(vk_current_thread)를 NULL 로 초기화한다.
- 우선순위가 같지 않다면, 현재 ready_queue 의 list_head 의 주소값을 현재 쓰레드에 저장한다.(vk_current_thread = vk_ready_queue[i].list_head;)
- 이때, ready_queue 의 list_head 의 쓰레드에 다음(→next) 쓰레드가 없다면, 다음 쓰레드(vk_next_thread)에 list_head 의 쓰레드 주소를 다시 저장한다. 이것은 현재 우선순위의 ready_queue 에서는 하나밖에 없기 때문에, 하나만 실행시키기 위함이다.
- ready_queue 의 list_head 의 쓰레드에 다음(→next) 쓰레드가 있으면, 다음 쓰레드(vk_next_thread)에 현재 쓰레드의 다음(→next) 을 저장한다.
- 현재 수행되고 있는 쓰레드의 우선순위가 현재 검색중인 우선순위와 같으면, 다음 쓰레드를 현재 쓰레드로 저장한다(vk_current_thread = vk_next_thread)
- 이때, ready_queue 의 list_head 의 쓰레드에 다음(→next) 쓰레드가 없다면, 다음 쓰레드(vk_next_thread)에 list_head 의 쓰레드 주소를 다시 저장한다. 이것은 현재 우선순위의 ready_queue 에서는 하나밖에 없기 때문에, 하나만 실행시키기 위함이다.
ready_queue 의 list_head 의 쓰레드에 다음(→next) 쓰레드가 있으면, 다음 쓰레드(vk_next_thread)에 현재 쓰레드의 다음(→next) 을 저장한다.
앞에서 for 루프를 통해, vk_ready_queue 를 검색해서 가장 높은 우선순위를 가지는 queue 중에서 list_head 에 존재하는 쓰레드를 vk_current_thread 로 저장했다.
이제는 실제적으로 이 쓰레드를 실행하기 위해서 필요한 루틴을 설명할 차례다.
- 검색결과, ready_queue 에 쓰레드가 하나도 존재하지 않으면, 저장되어 있는 쓰레드가 있는지(vk_save_thread) 확인한다. 만일 없다면, 현재 수행중인 쓰레드를 계속 실행시키기 위해서 현재 수행중인 쓰레드의 상태를 RUNNING 상태로 설정하고, 스케줄러를 풀어준다. 그리고, 현재 수행 중인 쓰레드를 저장한다.
- 만일 ready_queue 에 쓰레드가 하나도 존재하지 않고, 저장된 쓰레드가 있지만 현재 수행중인 쓰레드와 다르다면, vk_thread_switch_ctx() 을 호출함으로써, 컨텍스트 스위칭을 한다.
- 쓰레드를 실행을 시키기 위해서 스케줄러를 풀어준다.
vk_restore_thread_ctx
인자로 들어온 쓰레드를 저장하는 역할을 한다.
void vk_restore_thread_ctx(vk_thread_t *thread) { unsigned temp; thread->swi_number = RC; // RC = 43 temp = (unsigned)thread; // temp 에는 저장할 쓰레드의 주소가 할당됨 vh_swi(temp); }
마지막으로 쓰레드의 주소값을 vh_swi() 의 인자로 넘긴다.
vh_swi
쓰레드의 주소를 인자값으로 받아서 소프트웨어 인터럽트를 발생시킨다.
void vh_swi(unsigned thread) { __asm__ __volatile("swi 0x00"); // 인라인 어셈블러, 수행 후에 모드가 user -> svc 로 바뀌면서 자동으로 0x8 번지로 점프한다 }
0x8 번지, 즉 부트로더의 인터럽트 벡터 테이블에 의해서 '0x8 → 0xC8 → 0x30000008 → 0x300000D4' 으로 점프한다. 결국, HAL_arch_startup.S 파일의 아래의 루틴을 호출한다.
vh_software_interrupt: vh_entering_swi: str sp, =vk_save_swi_mode_stack_ptr // r13, 0x30000220 (여기서 0x30000220 에는 30800000 이 들어있다, // 30800000 인 것은 현재 svc 모드에서 stack 가 0x30800000 이기 때문이다) stmfd sp!, {r0-r14}^ // r0 ~ r14 레지스터의 값을 sp 주소(0x307FFFFC) 에서부터 위로 저장된다, 범위(0x307FFFFC ~ 0x307FFFC4) mrs r0, spsr_all // spsr 의 값을 r0 에 저장 stmfd sp!, {r0, lr} // r0 와 r14 레지스터의 값을 0x307FFFC4 이후 주소에 저장한다. 총 (15+2 = 17 개) 저장 mrs r0, cpsr_all // cpsr 의 값을 r0 에 저장 orr r0, r0, #0x80 // 변화없음 msr cpsr_all, r0 // 변화없음 str sp, =vk_save_swi_current_tcb_bottom // r13, 0x30000224 (여기서 0x30000224 에는 307FFFBC 가 들어있다, 마지막으로 저장된 명령어 주소) ldr r0, [sp, #8] // r0 에 뒤에서 0x307FFFC4 에 저장된 0x30127F74 값을 r0 에 저장, 0x8 을 뺀 주소 bl vk_swi_classifier // vk_swi_classifier 호출
주석을 참고하기 바란다. 따로 설명하지 않겠다.
vk_swi_classifier
앞에서 r0 에 0x30127F74, 즉 저장할 쓰레드의 주소값을 저장했다.
void vk_swi_classifier(unsigned thread) // 쓰레드의 주소를 인수로 받음 { unsigned number; vk_thread_t *vector; unsigned temp; vector=(vk_thread_t *)thread; // 인수를 통해, 쓰레드의 주소 추출 number=vector->swi_number; // number = 43 switch(number) { case EI: vector->interrupt_state = FALSE; vh_enable_interrupt(vector); break; case DI: vector->interrupt_state = TRUE; vh_disable_interrupt(vector); break; case SC: // 사용하지 않음 vh_save_thread_ctx((unsigned)vector->tcb_bottom); break; case RC: // 수행 temp = (unsigned)vector->func; // 쓰레드 생성시 지정했던 함수의 주소 저장 vh_restore_thread_ctx((unsigned)vector->tcb_bottom); // 쓰레드 생성지 지정한 vector->tcb_bottom(0x30127FB4) 인자로 넘김 break; case CS: // 쓰레드가 종료되었을 때 사용 vk_scheduler(); break; } }
vh_restore_thread_ctx
앞에서 호출한 vh_restore_thread_ctx() 는 HAL_arch_startup.S 파일에 구현되어 있다.
vh_restore_thread_ctx: mrs r1, cpsr_all // cpsr 의 값을 r1 에 저장 bic r1, r1, #0xfffffff0 // r1 에 3 저장 mov r2, #0x2 // r2 에 2 저장 cmps r1, r2 // r1, r2 비교 mov sp, r0 // sp 에 r0 의 값을 저장, sp 의 주소가 0x30127FB4 로 변경 ldmfd sp!, {r0, lr} // r0 와 r14 레지스터에 sp, 즉 0x30127FB4 와 0x30127FB8 에 저장되어 있는 값을 저장한다 msr spsr_all, r0 // spsr 에 r0 의 값을 저장, spsr = 10 ldmfd sp!, {r0-r14}^ // sp 의 주소(0x30127FBC)에서 부터 저장된 값을 r0 ~ r14 까지 저장(실제적으로 r12 까지) ldreq sp, =vk_save_irq_mode_stack_ptr // r13, 0x30000228, sp 가 0x30000228 와 같으면 (0x30000228 에는 0x3000020C 저장) ldrne sp, =vk_save_swi_mode_stack_ptr // r13, 0x30000220, sp 가 0x30000220 가 아니면 (0x30000220 에는 0x30800000 저장) movs pc, lr // lr 에는 앞에서 저장한 vector->func 값(0x300236B4)이 저장되어 있다, 함수 실행을 위해 점프, 또한 이때 svc -> user 모드로 전환된다
마지막에서 lr 이 가리키는 값은 쓰레드의 함수 주소이므로, 함수를 호출했을 때와 마찬가지로 pc 값이 옮겨진다. 수행중에 타이머 인터럽트가 발생하면, vk_scheduler() 가 호출되어, 앞에서 설명한 절차를 다시 실행하게 된다.
vk_thread_switch_ctx
이 함수는 ready_queue 에 쓰레드가 하나도 존재하지 않고, 저장된 쓰레드가 있지만 현재 수행중인 쓰레드와 다를때 수행된다.
void vk_thread_switch_ctx(vk_thread_t* from_thread, vk_thread_t* to_thread) // 저장된 쓰레드, 현재 수행중인 쓰레드 { unsigned bottom; unsigned dispatcher_arg; if(from_thread->state == RUNNING) from_thread->state = READY; // 참이므로, 저장된 쓰레드(from_thread) 의 state 에 1 저장 // 현재 수행중인 쓰레드의 상태를 RUNNING -> READY 로 변경 bottom = (unsigned)(from_thread->tcb_bottom); // bottom = 0x30127FB4 (주의할 점 항목을 읽어볼 것) vh_save_thread_ctx(bottom); // 0x30127FB4 값을 인자로 넘김 (HAL_arch_startup.S 파일에서 선언) to_thread->state = RUNNING; dispatcher_arg = (unsigned)(to_thread->tcb_bottom); vk_scheduler_unlock(); vh_restore_thread_ctx(dispatcher_arg); }
vh_save_thread_ctx
앞에서 저장된 쓰레드(from_thread)의 tcb_bottom 값을 인자로 넘겨받았다. irq 모드로 바뀐다.
vh_save_thread_ctx: mrs r1, cpsr_all // cpsr 값을 r1 에 저장 bic r1, r1, #0xfffffff0 // r1 에 2 를 저장 mov r2, #0x2 // r2 에 2 를 저장 cmps r1, r2 // r1 과 r2 를 비교, 참 ldreq r2, =vk_save_irq_current_tcb_bottom // ldrne r2, =vk_save_swi_current_tcb_bottom .rept 17 ldr r1, [r2], #4 str r1, [r0], #4 .endr mov pc, lr