====== VPOS 분석 - 5.쓰레드 생성과 스케줄링 ======
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
----
{{indexmenu>:#1|skipns=/^(wiki|etc|diary|playground)$/ skipfile=/^(todays|about|guestbook)$/ nsort rsort}}
----