
머리에 쥐난다.
왠만하면 gpt를 쓰지 않고 이해하려고 했다.
50%는 '같다' 혹은 '듯 하다'이고, 30%는 '뭔지 아직 모르겠다'이므로 주의하자.
그래도 20%는 '이다'로 끝난다. 이는 알고 있거나 gpt를 참고했다는 의미이다.
의식의 흐름에 따라 정리를 했기 때문에
포스트 전체를 왔다갔다하며 수정을 했다.
따라서 처음에 알던 것과 다르거나, 처음에 몰랐던 것에는 취소선이 그어져있다.
mlfqs: multi-level feedback queue scheduler
gdt: global descriptor table
tid: thread id
tp: trap frame
struct thread {
/* Owned by thread.c. */
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
int priority; /* Priority. */
/* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint64_t *pml4; /* Page map level 4 */
#endif
#ifdef VM
/* Table for whole virtual memory owned by thread. */
struct supplemental_page_table spt;
#endif
/* Owned by thread.c. */
struct intr_frame tf; /* Information for switching */
unsigned magic; /* Detects stack overflow. */
};
스레드의 구조를 나타내고 있다.
주석이 아주 잘 적혀져 있어서 주석을 그대로 해석하면 될 듯하다.
tid: 스레드의 식별자
status: 스레드의 상태. 밑에 나온다.
name: 스레드의 이름인데 디버깅 용인가보다. 디버깅 드래곤 DD
priority: 스레드의 우선순위
elem: 얘는 아직 모르겠다. 문서에서는 스레드를 doulby linked list에 넣는데 사용한다고 한다.
근데 보면 구조체 포인터로 들어간 것이 아니고 구조체로 들어가있다.
스레드는 이 엘리먼트를 감싸고 있기 때문에 list_elem을 기준으로 주소 계산을 하면
thread 내부의 다른 변수들에게 접근이 가능하지 않을까?
다시 말해, 우리가 말록lab에서 했던 방식이다.
우리가 어떤 데이터의 head에 접근할 때 payload에서 한 워드만큼 뒤로 갔다.
얘도 마찬가지다. 다른 thread_status, name, priority 등에 접근할 때
주소를 기준으로 계산하면 된다.

검증도 끝났다.
그리고 그 역할을 맡은 게
#define list_entry(LIST_ELEM_PTR, STRUCT_TYPE, MEMBER_NAME) \
((STRUCT_TYPE *) ((uint8_t *) &(LIST_ELEM_PTR)->next - offsetof(STRUCT_TYPE, MEMBER_NAME)))
이거라고 한다.
근데 사실 이게 아무리봐도 이상한 게 자기 스레드의 위치를 자기 자신으로부터 구하는 게 아니고 다음 걸로부터 구하고 있다. 계산 방식도 솔직히 잘 모르겠다.
아예 저걸 수정하고 돌려봤는데, alarm-multiple에서는 잘 돌아가긴 한다. 그래도 혹시나 버그가 생길지 모르니 define을 하나 더 추가할 거다.
#define list_entry_self(LIST_ELEM, STRUCT, MEMBER) \
((STRUCT *) ((uint8_t *) (LIST_ELEM) \
- offsetof (STRUCT, MEMBER))) // ADD
추가했다.
tf: 타고 들어가니 interrupt.h가 나오고 interrupted task가 저장된 레지스터라는 주석이 달려있다. 문서에는 context switching을 위한 정보가 저장돼있다고 한다.

magic: 커널 스택은 아래로 커지는데(주소가 점점 낮아진다.) 0 주소부터 스레드의 정보가 저장되어 있다. 스택에 이 영역을 침범하지 않도록 탐지해주는 것 같다.
커널 스택에는 어떤 정보가 저장될까?
추측하건데, 스레드가 작업하던 내용물이 저장되지 않을까 생각한다.

저번에도 뭔가 아니라고 했던 것 같은데
또 물어본 것 같다.
enum thread_status {
THREAD_RUNNING, /* Running thread. */
THREAD_READY, /* Not running but ready to run. */
THREAD_BLOCKED, /* Waiting for an event to trigger. */
THREAD_DYING /* About to be destroyed. */
};
스레드의 4가지 상태를 나타낸 것으로 보인다.
void
thread_init (void) {
ASSERT (intr_get_level () == INTR_OFF);
/* Reload the temporal gdt for the kernel
* This gdt does not include the user context.
* The kernel will rebuild the gdt with user context, in gdt_init (). */
struct desc_ptr gdt_ds = {
.size = sizeof (gdt) - 1,
.address = (uint64_t) gdt
};
lgdt (&gdt_ds);
/* Init the globla thread context */
lock_init (&tid_lock);
list_init (&ready_list);
list_init (&destruction_req);
/* Set up a thread structure for the running thread. */
initial_thread = running_thread ();
init_thread (initial_thread, "main", PRI_DEFAULT);
initial_thread->status = THREAD_RUNNING;
initial_thread->tid = allocate_tid ();
}
으 악
struct desc_ptr gdt_ds = {
.size = sizeof (gdt) - 1,
.address = (uint64_t) gdt
};
lgdt (&gdt_ds);
gdt는 전역 디스크립터 테이블인 것 같고
lgdt는 해당 테이블의 주소를 저장하는 레지스트리같다.
lock_init은 타고 넘어가니 synch.c에서 semaphore가 나왔고,
list_init은 타고 넘어가니 doubly linked list가 나왔다.
init_thread (initial_thread, "main", PRI_DEFAULT);
initial_thread->status = THREAD_RUNNING;
initial_thread->tid = allocate_tid ();
초기화 스레드는 메인? 스레드인 것 같고
상태는 running, 식별자는 allocate_tid를 통해 할당된 듯하다.
//project 1-2 (프로젝트 1-2를 공부할 때 달린 주석이라는 의미이다.)
초기화 스레드는 초기화에 필요한 함수들을 실행시킨다.
thread_start를 통해 idle 스레드를 생성한다.
void
thread_start (void) {
/* Create the idle thread. */
struct semaphore idle_started;
sema_init (&idle_started, 0);
thread_create ("idle", PRI_MIN, idle, &idle_started);
/* Start preemptive thread scheduling. */
intr_enable ();
/* Wait for the idle thread to initialize idle_thread. */
sema_down (&idle_started);
}
스레드를 시작할 때 semaphore 구조체의 주소를
semaphore 초기화 및 스레드 생성에 넘긴다.
근데 함수가 끝나면 사라질 운명인데, 왜 지역변수의 주소로 넘겼을까?
//project 1-2
여기서는 idle thread를 만들고 초기화하기 위해 semaphore를 쓴다.
sema down을 통해 대기열에 있는 다른 스레드들이 대기하고 있는 상태가 유지된다고 하는데 그래서 synch.c의 semaphore 함수(재귀)들을 정리하기 시작했다.
무슨 말인지 또 이해가 안됐다.
정리하다보니 이해가 됐다. 순서는 다음과 같다.
처음에 semaphore를 초기화하고 idle 스레드를 생성한다.
만들어진 idle 스레드는 처음이자 마지막으로 ready list에 들어간다.
그 다음 sema down을 통해 main 스레드를 sema->waiter 리스트에 넣고 block 상태로 만든다.
schedule 함수에 의해 idle 스레드가 cpu로 들어가며 idle 함수가 동작하게 된다.
나머지 부분은 idle 함수에서 이어서 설명하겠다.
//project 1-1
또한 idle 스레드는 초기화 스레드가 생성되고 동적으로 생성되는 스레드인 줄 알았다.
그렇게 이해하고 질문을 했더니

아니라고 한다.
intr_enable은 아마도 스레드 스케줄링 시 interrupt가 가능하게끔 하는 함수인 듯 하다.
interrupt가 뭔지 보고 가자

https://velog.io/@mogiyoon/Krafton-Jungle-Sixth#예외적인-제어흐름 여기에 interrupt 관련 내용이 나온다.
interrupt 발생하면 문맥전환이 일어나는데 interrupt disable을 사용하면 이를 방지할 수 있다.
근데, 생각해보면 thread는 항상 문맥전환의 위험?에 노출되어 있다. 그럼 이 intterupt disable을 언제 사용하는 것이 좋을까?
void
thread_tick (void) {
struct thread *t = thread_current ();
/* Update statistics. */
if (t == idle_thread)
idle_ticks++;
#ifdef USERPROG
else if (t->pml4 != NULL)
user_ticks++;
#endif
else
kernel_ticks++;
/* Enforce preemption. */
if (++thread_ticks >= TIME_SLICE)
intr_yield_on_return ();
}
아마도 타임 슬라이스와 관련이 있는 내용 같다.
근데 이제 스레드가 어떤 스레드냐에 따라서
idle tick이 바뀌거나 user tick이 바뀌거나 kernel tick이 바뀌는 듯하다.
user tick과 kernel tick의 용도는 잘 모르겠지만,
아마 파일 실행 후에 결과 출력할 때 쓰는 용도라고 생각한다.
아무튼 마지막에 time slice와 관련된 내용은 잘 구현돼있다.
물론 intr_yield_on_return이 뭔지는 아직 모르겠다.
void
thread_print_stats (void) {
printf ("Thread: %lld idle ticks, %lld kernel ticks, %lld user ticks\n",
idle_ticks, kernel_ticks, user_ticks);
}
스레드의 상태를 보여주는 함수이다.
아마도 디버깅 용이 아닐까 싶다. 디버깅 드래곤
tid_t
thread_create (const char *name, int priority,
thread_func *function, void *aux) {
struct thread *t;
tid_t tid;
ASSERT (function != NULL);
/* Allocate thread. */
t = palloc_get_page (PAL_ZERO);
if (t == NULL)
return TID_ERROR;
/* Initialize thread. */
init_thread (t, name, priority);
tid = t->tid = allocate_tid ();
/* Call the kernel_thread if it scheduled.
* Note) rdi is 1st argument, and rsi is 2nd argument. */
t->tf.rip = (uintptr_t) kernel_thread;
t->tf.R.rdi = (uint64_t) function;
t->tf.R.rsi = (uint64_t) aux;
t->tf.ds = SEL_KDSEG;
t->tf.es = SEL_KDSEG;
t->tf.ss = SEL_KDSEG;
t->tf.cs = SEL_KCSEG;
t->tf.eflags = FLAG_IF;
/* Add to run queue. */
thread_unblock (t);
return tid;
}
본격적으로 스레드 생성한다. 스레드 식별자를 반환한다.
t = palloc_get_page (PAL_ZERO);
if (t == NULL)
return TID_ERROR;
피얼록... 팔록이다.
페이지를 할당해주는 함수가 아닐까 싶다.
페이지와 스레드는 무슨 관계일까?

아마 여기서 할당해주는 페이지는 3번의 두 번째 설명과 관련이 있지 싶다.
스레드 전용 페이지를 할당해준다고 한다.
스레드 전용 페이지는 앞서 설명한 바 있고, 스레드 구조가 4kb 페이지에 저장된다고 주석으로 적혀져있다.
그 밑에 있는 tf는 context switching시 필요한 내용들이다.
문서에는 fake 스택 프레임이 준비된다고 한다. 스레드는 block 상태로 초기화되며, 리던 되기 전에 unblock된다고 한다.
스레드가 CPU를 사용하지 않고 대기 상태에 들어가는 것이다.
Ready Queue에 들어가서 실행 대기하는 것과는 다르다.
특정 이벤트가 thread를 깨울 때까지 기다리는 것이다.
따라서 스케줄러에게 선택되지 않게 된다.
void
thread_block (void) {
ASSERT (!intr_context ());
ASSERT (intr_get_level () == INTR_OFF);
thread_current ()->status = THREAD_BLOCKED;
schedule ();
}
반환값이 void에 매개변수도 void라 수상했는데
현재 스레드를 함수로 불러오기만 하면 되는거라 생각보다 어렵게 생각하지 않아도 될 듯 하다.
이후 schedule 함수를 부르는데 대기하고 있던 다음 스레드를 사용하는 것 같다.
스레드 block 상황이 해결되면 사용할 걸?
void
thread_unblock (struct thread *t) {
enum intr_level old_level;
ASSERT (is_thread (t));
old_level = intr_disable ();
ASSERT (t->status == THREAD_BLOCKED);
list_push_back (&ready_list, &t->elem);
t->status = THREAD_READY;
intr_set_level (old_level);
}
스레드를 다시 준비 리스트에 넣고, 준비 상태로 만든다.
old_level이 정확히 어떤 역할을 하는지는 모르겠다.
thread unblock을 한다는 건 다시 ready queue에 넣는 것인데
이 때 interrupt가 발생하면 ready queue가 lock이 없는 공유자원처럼 될 수 있다.
따라서 원래 interrupt의 상태를 저장하고
(참고로 intr 함수들의 대부분은 과거 intr 상태를 반환한다.)
old_level = intr_disable ();
ready queue에 unblock한 스레드를 넣은 다음,
원래 interrupt 상태로 되돌린다.
intr_set_level (old_level);
문서에 따르면 블록된 상태의 스레드를 준비된 상태로 바꿔준다고 한다.
struct thread *
thread_current (void) {
struct thread *t = running_thread ();
/* Make sure T is really a thread.
If either of these assertions fire, then your thread may
have overflowed its stack. Each thread has less than 4 kB
of stack, so a few big automatic arrays or moderate
recursion can cause stack overflow. */
ASSERT (is_thread (t));
ASSERT (t->status == THREAD_RUNNING);
return t;
}
현재 실행되고 있는 스레드를 반환한다.
tid_t
thread_tid (void) {
return thread_current ()->tid;
}
현재 실행되고 있는 스레드의 식별자를 반환한다.
thread_exit (void) {
ASSERT (!intr_context ());
#ifdef USERPROG
process_exit ();
#endif
/* Just set our status to dying and schedule another process.
We will be destroyed during the call to schedule_tail(). */
intr_disable ();
do_schedule (THREAD_DYING);
NOT_REACHED ();
}
스레드를 종료시킨다고 하는데, 반환이 없다는 건 뭘까...
완전히 종료한다는 건지 모르겠다.
do_schedule을 참고해보니, 현재 스레드가 실행중이 아니면 assert한다.
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);
}
문서에 따르면 새로 실행 시킬 스레드를 고르도록 CPU를 스케줄러에게 넘겨주는 함수같다.
/* Sets the current thread's priority to NEW_PRIORITY. */
void
thread_set_priority (int new_priority) {
thread_current ()->priority = new_priority;
}
/* Returns the current thread's priority. */
int
thread_get_priority (void) {
return thread_current ()->priority;
}
현재 스레드의 우선순위를 쓰고 읽는 함수이다.
/* Sets the current thread's nice value to NICE. */
void
thread_set_nice (int nice UNUSED) {
/* TODO: Your implementation goes here */
}
/* Returns the current thread's nice value. */
int
thread_get_nice (void) {
/* TODO: Your implementation goes here */
return 0;
}
/* Returns 100 times the system load average. */
int
thread_get_load_avg (void) {
/* TODO: Your implementation goes here */
return 0;
}
/* Returns 100 times the current thread's recent_cpu value. */
int
thread_get_recent_cpu (void) {
/* TODO: Your implementation goes here */
return 0;
}
수많은 todo 스레드들...
여기부턴 문서가 없다.
주석에서는 idle 스레드는 초기화시 나타났다가 ready list가 빌 때만 next_thread_to_run을 통해 나타난다고 한다.
static void
idle (void *idle_started_ UNUSED) {
struct semaphore *idle_started = idle_started_;
idle_thread = thread_current ();
sema_up (idle_started);
for (;;) {
/* Let someone else run. */
intr_disable ();
thread_block ();
/* Re-enable interrupts and wait for the next one.
The `sti' instruction disables interrupts until the
completion of the next instruction, so these two
instructions are executed atomically. This atomicity is
important; otherwise, an interrupt could be handled
between re-enabling interrupts and waiting for the next
one to occur, wasting as much as one clock tick worth of
time.
See [IA32-v2a] "HLT", [IA32-v2b] "STI", and [IA32-v3a]
7.11.1 "HLT Instruction". */
asm volatile ("sti; hlt" : : : "memory");
}
}
interrupt를 disable하고 thread block한다.
thread block에는 schedule 함수가 있어 새롭게 스케줄링 해준다.
interrupt를 먼저 disable하고 thread block하는 이유는

나는 인터럽트 핸들러와 예외처리 핸들러가 같은 건 줄 알았는데,

다르다고 한다.
그리고 커널 스레드와 인터럽트 핸들러가 공유 데이터에 접근하는 주된 이유는

라고 한다.
그럼 어떻게 동기화에서 문제가 생기는 것일까?


그래서 저런 순서로 처리한다고 한다.
//proeject 1-2
사실 이건 idle 함수에 대한 설명은 아니다. thread block 및 interrupt에 대한 전반적인 설명이다.
project1의 priority scheduling을 공부하다가 다시금 깨닫고 돌아왔다.
다른 스레드가 없을 때 idle은 CPU에 있다. 근데 되게 독특하다.
https://velog.io/@mogiyoon/pintos-프로젝트1-고찰같다#total-flow
여기에 나와있듯이 thread는 cpu 안에 들어갈 때 thread에 할당된 함수를 실행시킨다.
그리고 CPU에서 나오게 되면 함수는 멈춘다.
idle thread가 실행시키는 함수가 이 idle인데,
struct semaphore *idle_started = idle_started_;
idle_thread = thread_current ();
sema_up (idle_started);
이 부분을 이해하기 위해서는 thread_start를 이해해야 한다.
현재 idle thread는 cpu에 들어와있는 상태이다.
다만 전역 변수 idle thread에는 주소가 할당되지 않은 상태이다.
그래서 thread_current()를 통해 idle thread의 주소를 전달하고
sema_up을 통해 main 함수를 ready list로 돌린다.
메인함수 너... 돌아가는구나?
for (;;) {
/* Let someone else run. */
intr_disable ();
thread_block ();
/* Re-enable interrupts and wait for the next one.
The `sti' instruction disables interrupts until the
completion of the next instruction, so these two
instructions are executed atomically. This atomicity is
important; otherwise, an interrupt could be handled
between re-enabling interrupts and waiting for the next
one to occur, wasting as much as one clock tick worth of
time.
See [IA32-v2a] "HLT", [IA32-v2b] "STI", and [IA32-v3a]
7.11.1 "HLT Instruction". */
asm volatile ("sti; hlt" : : : "memory");
}
이 부분만 쉴새없이 돌린다.
인터럽트 잠그고, idle 스레드 block하고, schedule 함수로 다음 스레드를 찾는다.
없으면 다시 idle 스레드를 불러온 뒤에, 또 다시 for문을 반복한다.
static void
kernel_thread (thread_func *function, void *aux) {
ASSERT (function != NULL);
intr_enable (); /* The scheduler runs with interrupts off. */
function (aux); /* Execute the thread function. */
thread_exit (); /* If function() returns, kill the thread. */
}
커널 스레드인데 어디서 사용하는 지는 아직은 잘 모르겠다.
static void
init_thread (struct thread *t, const char *name, int priority) {
ASSERT (t != NULL);
ASSERT (PRI_MIN <= priority && priority <= PRI_MAX);
ASSERT (name != NULL);
memset (t, 0, sizeof *t);
t->status = THREAD_BLOCKED;
strlcpy (t->name, name, sizeof t->name);
t->tf.rsp = (uint64_t) t + PGSIZE - sizeof (void *);
t->priority = priority;
t->magic = THREAD_MAGIC;
}
초기화 스레드를 생성하거나 새로운 스레드를 생성할 때 사용하는 스레드 초기화 함수이다.
스레드의 주소를 초기화시키고, 스레드를 블록 상태로 만들고, strlcpy로 이름을 복사한다.
t->tf.rsp = (uint64_t) t + PGSIZE - sizeof (void *);
이건 뭘 의미하는지 모르겠다. 이후 우선순위 및 magic 영역을 설정한다.
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를 반환하고
리스트가 비어있지 않은 경우에는 다음 thread를 반환한다.
void
do_iret (struct intr_frame *tf) {
__asm __volatile(
"movq %0, %%rsp\n"
"movq 0(%%rsp),%%r15\n"
"movq 8(%%rsp),%%r14\n"
"movq 16(%%rsp),%%r13\n"
"movq 24(%%rsp),%%r12\n"
"movq 32(%%rsp),%%r11\n"
"movq 40(%%rsp),%%r10\n"
"movq 48(%%rsp),%%r9\n"
"movq 56(%%rsp),%%r8\n"
"movq 64(%%rsp),%%rsi\n"
"movq 72(%%rsp),%%rdi\n"
"movq 80(%%rsp),%%rbp\n"
"movq 88(%%rsp),%%rdx\n"
"movq 96(%%rsp),%%rcx\n"
"movq 104(%%rsp),%%rbx\n"
"movq 112(%%rsp),%%rax\n"
"addq $120,%%rsp\n"
"movw 8(%%rsp),%%ds\n"
"movw (%%rsp),%%es\n"
"addq $32, %%rsp\n"
"iretq"
: : "g" ((uint64_t) tf) : "memory");
}
ㅠㅠ 레지스터로 복사하나보다...
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"
);
}
뭐하는 건데...
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 ();
}
스레드의 상태를 바꾸는 함수이다. 그리고 사용하지 않는 스레드를 해제하는 것 같은데
왜 do_schedule 함수에서 해제하는 지는 모르겠다.
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 by 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);
}
}
현재 스레드가 사용이 끝났을 때, 다음 스레드를 실행시키는 함수이다.
현재 스레드와 다음 스레드가 다를 때, (같은 경우가 있을까?)
<작성중>
idle 스레드의 초기화를 다루다보니 여기까지 왔다.
project1의 priority scheduling에 필요한 내용이니 alarm을 구현하고 있다면 아직은 읽지 않아도 된다.
그리고 하나 더 알게된 것은 synch.c를 파더라도 모든 함수를 팔 필요는 없다는 것이다.
그래서 알게되거나 필요하게 되면 그때그때 채울 예정이다.
struct semaphore {
unsigned value; /* Current value. */
struct list waiters; /* List of waiting threads. */
};
세마포어의 특징을 떠올려보면, value는 아마도 접근할 수 있는 스레드의 수일 것이다.
그리고 waiters는 아마 해당 공유자원을 사용하려고 대기하고 있는 스레드들이 아닐까 싶다.
void
sema_init (struct semaphore *sema, unsigned value) {
ASSERT (sema != NULL);
sema->value = value;
list_init (&sema->waiters);
}
semaphore를 초기화시켜주는 함수이다.
void
sema_down (struct semaphore *sema) {
enum intr_level old_level;
ASSERT (sema != NULL);
ASSERT (!intr_context ());
old_level = intr_disable ();
while (sema->value == 0) {
list_push_back (&sema->waiters, &thread_current ()->elem);
thread_block ();
}
sema->value--;
intr_set_level (old_level);
}
세마를 다운시킨다.
근데 이상하게 sema down을 볼 때마다
머릿속에서 울린다.
세마 다운! 세마 다운!
아무튼 인터럽트를 중단시키고
old_level = intr_disable ();
sema value가 0일 경우에
while (sema->value == 0) {
list_push_back (&sema->waiters, &thread_current ()->elem);
thread_block ();
}
현재 스레드를 sema의 waiter리스트에 넣는다.
그리고 스레드를 block한다.
sema->value--;
intr_set_level (old_level);
이후 세마 value를 하나 줄이고 interrupt를 원래대로 돌려놓는다.
sema->value--문과 while문은 순서를 바꾸기 어렵다.
sema->value--문이 while문 앞으로 가면 sema를 통과한 스레드가 block되기 때문이다.
void
sema_up (struct semaphore *sema) {
enum intr_level old_level;
ASSERT (sema != NULL);
old_level = intr_disable ();
if (!list_empty (&sema->waiters))
thread_unblock (list_entry (list_pop_front (&sema->waiters),
struct thread, elem));
sema->value++;
intr_set_level (old_level);
}
인터럽트 잠그고, sema waiter 리스트에 스레드가 있을 경우 언블록시킨다.
sema value를 1 증가시키고, 인터럽트도 원래대로 돌린다.
struct lock {
struct thread *holder; /* Thread holding lock (for debugging). */
struct semaphore semaphore; /* Binary semaphore controlling access. */
};
lock할 thread와 lock에 사용할 binary semaphore를 구조로 가지고 있다.
디버깅을 위한다는 것이 무슨 말인지 모르겠긴하다.
void
lock_init (struct lock *lock) {
ASSERT (lock != NULL);
lock->holder = NULL;
sema_init (&lock->semaphore, 1);
}
lock이라는 구조체의 주소를 받고
lock이 잠글 대상 및 lock의 semaphore를 1로 초기화한다.
즉, 스레드 하나만 쓸 수 있다는 얘기다.
void
lock_acquire (struct lock *lock) {
ASSERT (lock != NULL);
ASSERT (!intr_context ());
ASSERT (!lock_held_by_current_thread (lock));
sema_down (&lock->semaphore);
lock->holder = thread_current ();
}
sema down하고 (만약 lock이 돼 있다면 waiter로 이동하고 block 된다.)
아닌 경우에 현재 스레드는 lock의 holder가 된다.
void
lock_release (struct lock *lock) {
ASSERT (lock != NULL);
ASSERT (lock_held_by_current_thread (lock));
lock->holder = NULL;
sema_up (&lock->semaphore);
}
holder를 NULL로 만들고 sema up을 통해 semaphore의 value++ 및 waiter 스레드들을 unblock 상태로 만든다.