CPU 스케줄링, 프로세스, 동시성에 대한 글입니다.
프로젝트 소감
운영체제가 제공하는 핵심 중 하나인 프로세스에 대해 배웠다.
관건은 "누가 CPU 자원을 점유할 것인가"인데,
이는 Ready_list를 어떤 방식으로 정렬하느냐,
그리고 누가 lock을 가지고 semaphore에 접근하느냐에 따라 결정된다.가장 원초적인 형태의 Alarm clock은 timer_sleep으로 각 스레드에 깨어날 시간을 지정하여 잠재운 후, 시간이 되면 Ready_list 맨 뒤로 추가하여
결국엔 가장 먼저 도착한 스레드가 next_thread_to_run이 되는 FIFO 방식이다.그러나 실제로는 작업들이 서로 다른 우선순위를 갖고 있기에
우선순위가 높은 순으로 레디큐를 정렬하여, 맨 앞 원소가 RUNNING 스레드 바로 다음으로 올 수 있도록 한다.눈여겨볼 점은 '선점형' 방식(Priority_Preemption)인데,
새로 생성된 스레드가 현재 RUNNING 스레드보다 우선순위가 높다면 바로
thread_yield()를 함으로써 CPU 자원을 내주어야 한다.
void test_max_priority(void)
{
if (!list_empty (&ready_list) &&
thread_current()->priority
< list_entry(list_front(&ready_list), struct thread, elem)->priority)
thread_yield();
}
void
thread_set_priority (int new_priority) {
thread_current ()->init_priority = new_priority;
refresh_priority();
test_max_priority();
}
핀토스에서는 대부분 Preemptive 방식의 스케줄링을 구현했다.
Alarm clock에서는 타이머 인터럽트를 통해 interrupt handler를 실행하고, 이 시점에 운영체제는 CPU 제어권을 얻어 원하는 일을 할 수 있다.
이는 현재 잠들어있는 스레드 중 awake할 시간이 다 된 스레드를 깨워서 Ready_list로 보내는 작업 등이 있다.
즉, 현재 CPU를 점하고 있는 스레드가 무한루프에 빠지더라도 인터럽트가 매 틱마다 발생하므로 이를 중단하고 관여할 수 있는 것이다.
static void
timer_interrupt (struct intr_frame *args UNUSED) {
ticks++;
thread_tick (); // update the cpu usage for running process
thread_awake(ticks);
}
void
thread_awake(int64_t ticks)
{
struct list_elem *e = list_begin(&sleep_list);
/* sleep_list를 처음부터 끝까지 돌면서 */
while (e != list_end(&sleep_list))
{
struct thread *t = list_entry(e, struct thread, elem); // thread 구조체 포인터 t
if (t->wakeup_tick <= ticks){ // 깨울 대상
e = list_remove(e); // sleep_list에서 e 제거 후 next를 리턴
thread_unblock(t); // 스레드 t UNBLOCK
}
else
e = list_next(e); // sleep_list 계속 탐색
}
}
스케줄링 척도
next_thread_to_run( )이 핵심
구현 고려사항
switch_threads:
pushl %ebx
pushl %ebp
pushl %esi
pushl %edi
.globl thread_stack_ofs
mov thread_stack_ofs, %edx
movl SWITCH_CUR(%esp), %eax
movl %esp, (%eax,%edx,1)
movl SWITCH_NEXT(%esp), %ecx
movl (%ecx,%edx,1), %esp
popl %edi
popl %esi
popl %ebp
popl %ebx
ret
프로세스
스레드
정리하자면 Context switching 비용 측면에서 스레드가 우위. 프로세스는 Context 전환시 메모리 접근이 필요한데, 스레드는 각자의 스택과 CPU 레지스터단에서 빠르게 스위칭이 이뤄질 수 있음
동시성은 CPU가 한 번에 하나의 일만 처리할 수 있지만, child process/thread를 통해 병렬적으로 처리하기 위함
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_insert_ordered (&sema->waiters, &thread_current()->elem,
cmp_donate_priority, 0);
thread_block ();
}
sema->value--;
intr_set_level (old_level);
}
struct semaphore {
unsigned value; /* Current value. */
struct list waiters; /* List of waiting threads. */
};
struct lock {
struct thread *holder; /* Thread holding lock (for debugging). */
struct semaphore semaphore; /* Binary semaphore controlling access. */
};
구현 사항 두 가지
lock_acquire()
void
lock_acquire (struct lock *lock) {
...
if (lock->holder) {
cur->wait_on_lock = lock;
list_insert_ordered(&lock->holder->donations, &cur->donation_elem,
cmp_donate_priority, NULL); // priority 순대로 donation list로 대기
donate_priority();
}
// lock 획득 (lock holder가 lock_release를 호출)
sema_down (&lock->semaphore);
thread_current()->wait_on_lock = NULL;
lock->holder = cur; // lock holder 갱신
}
void
donate_priority(void)
{
int depth;
struct thread *cur = thread_current();
for (depth = 0; depth < 8; depth++){
// 다른 스레드에 의해 락이 걸려있지 않다면 종료
if (!cur->wait_on_lock) break;
// 락이 걸려있으면, 해당 스레드에 기부
struct thread *holder = cur->wait_on_lock->holder;
holder->priority = cur->priority;
cur = holder; // curr = curr.next
}
}
lock_release()
void
lock_release (struct lock *lock) {
ASSERT (lock != NULL);
ASSERT (lock_held_by_current_thread (lock));
lock->holder = NULL;
// donation list 변경 및 cur 우선순위 재설정
remove_with_lock(lock);
refresh_priority();
lock->holder = NULL;
sema_up (&lock->semaphore);
}
void
refresh_priority(void)
{
struct thread *cur = thread_current();
cur->priority = cur->init_priority;
if (!list_empty(&cur->donations)) {
list_sort(&cur->donations, cmp_donate_priority, 0);
struct thread *front = list_entry(list_front(&cur->donations), struct thread, donation_elem);
if (front->priority > cur->priority)
cur->priority = front->priority;
}
}
/* newly created thread 고려 */
void
thread_set_priority (int new_priority) {
thread_current ()->init_priority = new_priority;
refresh_priority();
test_max_priority();
}