. [PintOS, Project 1] 1/30 : 공부 노트 - 각종 관련 함수들
본문 바로가기
Pintos Project/Project 1

[PintOS, Project 1] 1/30 : 공부 노트 - 각종 관련 함수들

by 불냥이_ 2021. 1. 31.

timer_sleep()

/* Suspends execution for approximately TICKS timer ticks. */
void
timer_sleep (int64_t ticks) {
	int64_t start = timer_ticks ();

	ASSERT (intr_get_level () == INTR_ON);
	while (timer_elapsed (start) < ticks)
		thread_yield ();
}

목적 : (기동 중인 어떤 스레드에 대해서) 기동한 지, 일정 시간이 지나면 thread_yield를 실행한다. 

- assert : 지정한 조건식이 False 이면 프로그램을 중단하고, True이면 프로그램을 계속 실행한다.

- timer_elapsed(start) 가 ticks 보다 작으면 thread_yield()를 계속 실행한다.

 

 

timer_elapsed()

/* Returns the number of timer ticks elapsed since THEN, which
   should be a value once returned by timer_ticks(). */
int64_t
timer_elapsed (int64_t then) {
	return timer_ticks () - then;
}

목적 : 들어온 인자 시간으로부터 얼마나 시간(tick)이 지나갔는지 반환한다. 

 

 

thread_yield()

/* Yields the CPU.  The current thread is not put to sleep and
   may be scheduled again immediately at the scheduler's whim. */
void
thread_yield (void) {
	struct thread *curr = thread_current ();
	enum intr_level old_level;

	ASSERT (!intr_context ());

	old_level = intr_disable ();
	if (curr != idle_thread)
		list_push_back (&ready_list, &curr->elem);
	do_schedule (THREAD_READY);
	intr_set_level (old_level);
}

목적 : 현재 (running 중인) 스레드를 비활성화 시키고, ready_list에 삽입한다.

- 이 과정을 수행하는 동안 들어오는 인터럽트를 모두 무시하고, 작업이 끝나면 thread_yield()하기 직전의 인터럽트의 상태로 되돌린다.

- curr가 idle_thread가 아니면, elem을 ready_list의 맨 끝에 삽입한다. 

- do_schedule로 running인 스레드를 ready로 바꾼다.

 

 

 

do_schedule()

/* Schedules a new process. At entry, interrupts must be off.
 * This function modify current thread's status to status and then
 * finds another thread to run and switches to it.
 * It's not safe to call printf() in the schedule(). */
static void
do_schedule(int status) {
	ASSERT (intr_get_level () == INTR_OFF);
	ASSERT (thread_current()->status == THREAD_RUNNING);
	while (!list_empty (&destruction_req)) {
		struct thread *victim =
			list_entry (list_pop_front (&destruction_req), struct thread, elem);
		palloc_free_page(victim);
	}
	thread_current ()->status = status;
	schedule ();
}

목적 : 현재 (running중인) 스레드를 status로 바꾸고, 새로운 스레드를 실행한다. 

- 인터럽트가 없고, 현재 스레드의 상태가 RUNNING일 때 실행한다.

 

※ 하기의 destruction_req에 있는 스레드들은 지금 yield하려는 스레드와 상관이 없고, 이미 소멸이 확정되어있는 스레드들을 말한다. 단지 shedule()로 새로운 스레드에 할당시키기 전에 메모리 확보를 위해서 시행하는 것이다. 

 

- destruction_req가 있는 리스트의 맨 앞을 victim으로 지정한다. 즉, 삭제 리스트 안의 첫번째 리스트를 victim으로 지정한다. 

- victim 페이지를 해제한다.

- 현재 스레드의 상태를 인자로 받은 상태로 바꾼다. 

 

 

 

list_pop_front()

/* Removes the front element from LIST and returns it.
   Undefined behavior if LIST is empty before removal. */
struct list_elem *
list_pop_front (struct list *list) {
	struct list_elem *front = list_front (list);
	list_remove (front);
	return front;
}

- 리스트의 맨 앞을 삭제하고, 반환한다.

 

 

schedule (void)

static void
schedule (void) {
	struct thread *curr = running_thread ();
	struct thread *next = next_thread_to_run ();

	ASSERT (intr_get_level () == INTR_OFF);
	ASSERT (curr->status != THREAD_RUNNING);
	ASSERT (is_thread (next));
	/* Mark us as running. */
	next->status = THREAD_RUNNING;

	/* Start new time slice. */
	thread_ticks = 0;

#ifdef USERPROG
	/* Activate the new address space. */
	process_activate (next);
#endif

	if (curr != next) {
		/* If the thread we switched from is dying, destroy its struct
		   thread. This must happen late so that thread_exit() doesn't
		   pull out the rug under itself.
		   We just queuing the page free reqeust here because the page is
		   currently used bye the stack.
		   The real destruction logic will be called at the beginning of the
		   schedule(). */
		if (curr && curr->status == THREAD_DYING && curr != initial_thread) {
			ASSERT (curr != next);
			list_push_back (&destruction_req, &curr->elem);
		}

		/* Before switching the thread, we first save the information
		 * of current running. */
		thread_launch (next);
	}
}

목적 : running인 스레드를 빼내고, next 스레드를 running으로 만든다. 

- curr = running 상태의 스레드

- next = ready_list가 있으면, ready_list의 첫번째 스레드를 가져온다. // ready_list가 비어있으면, idel thread이다.

- next의 상태를 running으로 바꾸고, thread_ticks 을 0으로 바꾼다. (새로운 스레드가 시작했기 때문에)

 

- 만약 curr랑 next가 다른 스레드이고, curr가 존재하면서, curr는 dying 상태이면서, curr가 initial_thread가 아니라면

   -> curr를 삭제 리스트의 마지막에 넣는다.

- next를 thread_launch()한다. (context switching 수행)

 

 

next_thread_to_run()

/* Chooses and returns the next thread to be scheduled.  Should
   return a thread from the run queue, unless the run queue is
   empty.  (If the running thread can continue running, then it
   will be in the run queue.)  If the run queue is empty, return
   idle_thread. */
static struct thread *
next_thread_to_run (void) {
	if (list_empty (&ready_list))
		return idle_thread;
	else
		return list_entry (list_pop_front (&ready_list), struct thread, elem);
}

목적 : 다음 기동할 스레드를 정한다.

- 만약 ready list에 스레드가 없으면 idle_thread를 next로 지정한다.

- 그렇지 않다면, ready list의 첫번째 스레드를 next로 지정한다.

 

 

 

thread_launch()

/* Switching the thread by activating the new thread's page
   tables, and, if the previous thread is dying, destroying it.

   At this function's invocation, we just switched from thread
   PREV, the new thread is already running, and interrupts are
   still disabled.

   It's not safe to call printf() until the thread switch is
   complete.  In practice that means that printf()s should be
   added at the end of the function. */
static void
thread_launch (struct thread *th) {
	uint64_t tf_cur = (uint64_t) &running_thread ()->tf;
	uint64_t tf = (uint64_t) &th->tf;
	ASSERT (intr_get_level () == INTR_OFF);

	/* The main switching logic.
	 * We first restore the whole execution context into the intr_frame
	 * and then switching to the next thread by calling do_iret.
	 * Note that, we SHOULD NOT use any stack from here
	 * until switching is done. */
	__asm __volatile (
			/* Store registers that will be used. */
			"push %%rax\n"
			"push %%rbx\n"
			"push %%rcx\n"
			/* Fetch input once */
			"movq %0, %%rax\n"
			"movq %1, %%rcx\n"
			"movq %%r15, 0(%%rax)\n"
			"movq %%r14, 8(%%rax)\n"
			"movq %%r13, 16(%%rax)\n"
			"movq %%r12, 24(%%rax)\n"
			"movq %%r11, 32(%%rax)\n"
			"movq %%r10, 40(%%rax)\n"
			"movq %%r9, 48(%%rax)\n"
			"movq %%r8, 56(%%rax)\n"
			"movq %%rsi, 64(%%rax)\n"
			"movq %%rdi, 72(%%rax)\n"
			"movq %%rbp, 80(%%rax)\n"
			"movq %%rdx, 88(%%rax)\n"
			"pop %%rbx\n"              // Saved rcx
			"movq %%rbx, 96(%%rax)\n"
			"pop %%rbx\n"              // Saved rbx
			"movq %%rbx, 104(%%rax)\n"
			"pop %%rbx\n"              // Saved rax
			"movq %%rbx, 112(%%rax)\n"
			"addq $120, %%rax\n"
			"movw %%es, (%%rax)\n"
			"movw %%ds, 8(%%rax)\n"
			"addq $32, %%rax\n"
			"call __next\n"         // read the current rip.
			"__next:\n"
			"pop %%rbx\n"
			"addq $(out_iret -  __next), %%rbx\n"
			"movq %%rbx, 0(%%rax)\n" // rip
			"movw %%cs, 8(%%rax)\n"  // cs
			"pushfq\n"
			"popq %%rbx\n"
			"mov %%rbx, 16(%%rax)\n" // eflags
			"mov %%rsp, 24(%%rax)\n" // rsp
			"movw %%ss, 32(%%rax)\n"
			"mov %%rcx, %%rdi\n"
			"call do_iret\n"
			"out_iret:\n"
			: : "g"(tf_cur), "g" (tf) : "memory"
			);
}

목적 : 새로운 스레드가 기동함에따라 context switching을 수행한다.

 

 

 

list_init()

@lib/kernal/list.c

/* Initializes LIST as an empty list. */
void
list_init (struct list *list) {
	ASSERT (list != NULL);
	list->head.prev = NULL;
	list->head.next = &list->tail;
	list->tail.prev = &list->head;
	list->tail.next = NULL;
}

- head와 tail은 리스트와 처음과 끝을 가리킨다.

 

댓글