VPOS 에서 쓰레드를 할당하고, 스케줄링 되는 루틴을 설명하고 있다. 어찌보면, 가장 핵심이 되는 부분이라고 하겠다.
부트로더와 스타트업(startup) 코드를 거쳐서, 하드웨어 초기화와 각종 큐(queue) 및 테이블(table) 을 생성하고 초기화 하면, pthread_create 함수를 이용해서 쓰레드를 생성한다. 그리고 나서, vk_scheduler 를 호출해서 각 큐에 등록된, 쓰레드를 검색하여 우선순위가 높은 순으로 vk_current_thread 에 넣는다. 상황에 따라서, 현재의 쓰레드를 저장하고, 새로운 쓰레드를 수행하기도 한다.
하나의 쓰레드가 수행이 되다가, 이때, 타이머 인터럽트가 발생하여, 핸들러에 의해서 vk_scheduler 를 수행하게 되고, 또다시 vk_ready_queue 큐를 검색하여 우선순위가 높은 순으로 vk_current_thread 에 넣고, 쓰레드를 수행한다.
이 문서에서는 앞에서 설명한 루틴을 소스코드 레벨에서 최대한 자세히 살펴볼 것이다.

쓰레드 생성루틴

이 함수는 메모리를 할당받아서, 인자로 받은 함수를 쓰레드 큐에 등록시켜 실행시키는 역할을 한다.

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;
}

다음은 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 함수를 호출한다.

처음에 한번만 호출된다.

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 함수를 호출한다.

인자로 받은 메모리 주소에서 -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() 함수를 호출하지 못하게 한다
}

이제까지 메모리영역을 할당받아서, 쓰레드를 만드는 루틴을 소스파일을 통해 알아보았다. 정리하는 차원에서 기술해보겠다.

  1. 쓰레드를 관리하기 위한 각 큐와 테이블을 초기화하고, 시스템 수행에 필요한 하드웨어(타이머)를 초기화한다.
  2. pthread_create() 를 이용해서 원하는 함수를 쓰레드로 실행시킬 수 있다.
  3. 먼저 쓰레드를 만들기 위해서 메모리를 할당 받아야 하는데, vk_malloc() 을 이용해서 할당 받는다. 총 2 번을 호출하게 되는데, 첫번째는 쓰레드 구조체(vk_thread_t)를 저장할 공간을 할당받기 위함이고, 두번째는 쓰레드 실행시 사용할 스택 공간(vh_thread_stack_size)을 할당받기 위함이다.
  4. vk_thread_t 구조체의 크기(0x84) 를 인자로 받고, 전역변수 freep 는 base 의 주소값인 0x30127FFC 를 가리키도록 한다(참고로, 이주소는 임의로 정해준 것이 아니고, 컴파일 타임에 자동으로 지정된 주소이다). 0x30127FFC 는 뒤에서 설명할 heap[999999] 배열의 다음 메모리주소이다.
  5. 처음 vk_malloc() 을 수행했기 때문에, 사용할 메모리 공간을 할당 해야 한다. 곧이어 heap_memory_alloc() 을 호출한다.
  6. 여기서는 char 타입의 heap[1000000] 개를 생성한다. 이 배열의 시작주소는 0x30033DBC ~ 0x30127FFB 까지이다(참고로 0x30033DBC 는 컴파일 타임에 자동으로 지정된 주소이다).
  7. up 변수는 heap[0] 인 0x30033DBC 를 가리키고, up→size 는 1000000/sizeof(Header) 의 값을 저장한다. 125000 이다.
  8. 할당한 heap 메모리 영역을 초기화 시키기 위해서, (up+1) 의 주소를 vk_free 함수의 인자로 넘긴다. 여기서 (up+1) 는 메모리 주소로는 up+0x8 을 의미한다. 여기서 up 주소가 아닌 up+1 을 넘긴 이유는 메모리 연산도중에 heap[0] 의 값이 바뀌는 것을 방지하기 위해서이다.
  9. 마지막으로 vk_free() 는 인자로 받은 메모리 주소가 heap[] 배열안에 있는 주소인지를 체크하여, bp 에는 heap[0] 의 주소가, p 에는 heap[999999] 의 다음 주소인 0x30127FFC 가 들어간다.
  10. vk_free() 마치고 나면, heap_memory_alloc() → vk_malloc() 순으로 리턴되고, 'for (p = prevp→s.ptr; ; prevp = p, p = p→s.ptr)' 문으로 실행된다.
  11. 이 때, 'p = prevp→s.ptr' 때문에 p 는 0x30033DBC 를 가리킨다.

여기까지가 처음으로 쓰레드에서 vk_malloc() 을 호출했을 때의 상황이다. 결과로는 0x30127F74 가 나온다. 정말 엉뚱한 값이다. 문제의 원인은 바로

  p += p->s.size;   // 이 연산을 수행하면서, Header 구조체가 깨진다. 그리하여 엉뚱한 주소 값이 나온다.

한가지 우연인 것은 엉뚱한 주소 값이 heap[] 안에 존재하는 주소라는 점이고, 각각 호출때마다 일정 영역을 두고 규칙적으로 메모리 주소를 반환한다는 것이다. 이것은 치명적인 버그이며, 반드시 수정되어야 한다. 이로 인해 어떤 문제가 발생할지 모른다.

스케줄링 루틴

앞에서 pthread_create 함수를 이용해서 쓰레드를 생성했다. 쓰레드를 실제적으로 수행하기 위해서는 스케줄러의 역할이 필수적이다.
다음은 VPOS_kernel_main() 의 기본 루틴인 VPOS_start 이다.

void VPOS_start(void)
{
	vk_current_thread = NULL;   // 현재 수행 중인, 쓰레드를 없앤다(아무래도 쓰레드를 만들고, 처음으로 호출하는 함수이므로)
	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();        // 스케줄러를 푼다
}

이 함수가 하는 역할을 정리하면 다음과 같다.

  1. 이 함수가 가장 많이 호출되는 경우는 바로, 타이머 인터럽트에 의해서 호출되는 경우이다.
  2. 수행이 되면, 스케줄러를 락(lock) 하기위해 먼저 현재 쓰레드의 sched_lock_counter 를 하나 증가시키고 현재 쓰레드의 saved_sched_lock 에 0 을 넣고, 전역변수인 vk_sched_lock 를 1 로 바꾼다.
  3. vk_thread_check() 호출하지만, 이 함수는 현재 수행되는 쓰레드가 없고, 현재 쓰레드의 상태가 좀비일 때만 수행되므로, 여기서는 전혀 수행이 안된다.
  4. 기존의 저장되어 있던 쓰레드(vk_save_thread)의 주소를 초기화 시킨다. 새로운 쓰레드로 컨텍스트 스위치를 하기 위함이다.(For 루프 시작 전)
  5. 우선순위가 높은 순서대로, 루프를 돌면서 ready_queue 를 검색한다. 만일 ready_queue 에 하나 이상의 쓰레드가 존재하면, 일단 현재 수행되는 쓰레드를 vk_save_thread 에 저장한다. → ⑦번
  6. 만일 현재 ready_queue 에 쓰레드가 하나도 존재하지 않으면, i 값을 감소시키면서, i-1 우선 순위 ready_queue 를 검색한다.

현재 수행되고 있는 쓰레드의 우선순위가 현재 검색중인 우선순위와 같지 않다면, 현재 수행중인 쓰레드(vk_current_thread)를 NULL 로 초기화한다.

  1. 우선순위가 같지 않다면, 현재 ready_queue 의 list_head 의 주소값을 현재 쓰레드에 저장한다.(vk_current_thread = vk_ready_queue[i].list_head;)
  2. 이때, ready_queue 의 list_head 의 쓰레드에 다음(→next) 쓰레드가 없다면, 다음 쓰레드(vk_next_thread)에 list_head 의 쓰레드 주소를 다시 저장한다. 이것은 현재 우선순위의 ready_queue 에서는 하나밖에 없기 때문에, 하나만 실행시키기 위함이다.
  3. ready_queue 의 list_head 의 쓰레드에 다음(→next) 쓰레드가 있으면, 다음 쓰레드(vk_next_thread)에 현재 쓰레드의 다음(→next) 을 저장한다.
  4. 현재 수행되고 있는 쓰레드의 우선순위가 현재 검색중인 우선순위와 같으면, 다음 쓰레드를 현재 쓰레드로 저장한다(vk_current_thread = vk_next_thread)
  5. 이때, 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 로 저장했다.
이제는 실제적으로 이 쓰레드를 실행하기 위해서 필요한 루틴을 설명할 차례다.

  1. 검색결과, ready_queue 에 쓰레드가 하나도 존재하지 않으면, 저장되어 있는 쓰레드가 있는지(vk_save_thread) 확인한다. 만일 없다면, 현재 수행중인 쓰레드를 계속 실행시키기 위해서 현재 수행중인 쓰레드의 상태를 RUNNING 상태로 설정하고, 스케줄러를 풀어준다. 그리고, 현재 수행 중인 쓰레드를 저장한다.
  2. 만일 ready_queue 에 쓰레드가 하나도 존재하지 않고, 저장된 쓰레드가 있지만 현재 수행중인 쓰레드와 다르다면, vk_thread_switch_ctx() 을 호출함으로써, 컨텍스트 스위칭을 한다.
  3. 쓰레드를 실행을 시키기 위해서 스케줄러를 풀어준다.

인자로 들어온 쓰레드를 저장하는 역할을 한다.

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() 의 인자로 넘긴다.

쓰레드의 주소를 인자값으로 받아서 소프트웨어 인터럽트를 발생시킨다.

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 호출

주석을 참고하기 바란다. 따로 설명하지 않겠다.

앞에서 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() 는 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() 가 호출되어, 앞에서 설명한 절차를 다시 실행하게 된다.

이 함수는 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);
}

앞에서 저장된 쓰레드(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	
  • computer/rtcclab/vpos_분석_-_5.쓰레드_생성과_스케줄링.txt
  • Last modified: 3 years ago
  • by likewind