컴퓨터는 수많은 코드의 흐름으로 돌아간다.
이 코드 흐름 하나하나를 우리는 스레드(Thread)라고 부른다.
정확히 말하면,
CPU가 명령어를 순서대로 실행해나가는 경로(흐름)이다.
예를 들어보자.
print("동생 깨우기")
print("세수하기")
print("아침 먹기")
이 코드는 아침에 일어나서 행동하는 순서를 나타낸 것이다.
이렇게 순서대로 처리되는 흐름이 바로 하나의 스레드이다.
print("노래 재생")
print("다음 곡 준비")
이건 음악을 재생하는 코드이다.
만약 컴퓨터가 아침 루틴과 노래 재생을 동시에 처리한다면?
→ 이렇게 서로 다른 일을 하는 두 개의 스레드가 있는 것이다.
PintOS Project 1에서는
이 스레드라는 실행 흐름을 직접 만들어보고,
그 흐름을 운영체제가 어떻게 관리하는지 구현해보는 프로젝트이다.
컴퓨터에는 실행해야 할 코드들이 정말 많다.
즉, 수많은 스레드들이 존재한다.
PintOS에서는 CPU가 단 하나만 있다고 가정한다.
한 번에 딱 하나의 스레드만 실행할 수 있다.
나머지 스레드들은 기다려야 한다.
줄을 서서 "내 차례야!"라고 외치고 있는 셈이다.
누가 그 줄을 세워주고, 차례를 정해줄까?
바로 운영체제다.
운영체제는 마치 감독 선생님😎처럼 스레드들을 관리한다.
역할 | 설명 |
---|---|
스레드 만들기 | 새로운 스레드를 생성하고 대기 목록에 넣음 |
스케줄링 | 누가 CPU를 사용할지 결정 |
양보 처리 | 실행 중인 스레드가 자발적으로 CPU를 내놓음 |
멈추기 | 스레드가 자원 기다릴 때 잠시 대기시킴 |
깨우기 | 자원이 생기면 다시 줄 세움 |
이 과정을 한마디로 부르면?
스케줄링(Scheduling)이다.
운영체제가 스레드를 관리하기 위해
스레드를 한 구조체(struct)로 정의해 놓았다.
이 구조체에는 다양한 정보가 담겨 있지만,
스케줄링 관점에서 특히 중요한 두 가지는 아래와 같다.
ready_list
에 들어간 스레드는 모두 THREAD_READY
상태이고,
CPU를 받을 기회를 기다리고 있는 중이다.
운영체제는 스레드들을 단순히 먼저 온 순서로 실행하지 않는다.
대신, 우선순위(priority)를 기준으로 스레드를 줄을 세운다.
"더 급한 스레드 먼저 실행시켜줘!"
이런 방식이다.
PintOS Project 1에서 우리가 한 일은?
우리는 이 스레드 관리 기능 전체를 직접 구현했다.
thread_create()
tid_t thread_create(const char *name, int priority, thread_func *function, void *aux);
새로운 스레드를 만든다.
이 스레드는 당장 실행되는 게 아니라,
CPU 대기열인 ready_list
에 들어간다.
스레드 하나 만들고, 대기 줄에 세운다!
ready_list
와 schedule()
ready_list
는 말 그대로 “누가 다음으로 CPU 쓸까?” 하는 줄이다.
여기엔 스레드들이 우선순위 순으로 정렬되어 있다.
schedule()
함수는 ready_list
를 보고
가장 우선순위 높은 스레드를 골라서 실행시킨다.
줄에서 1등 스레드를 골라 CPU를 준다!
thread_yield()
스레드가 자발적으로 CPU를 양보하고 싶을 때가 있다.
예를 들어,
“지금보다 더 급한 스레드가 생겼네? 난 좀 쉬자~” 이런 느낌이다.
thread_yield();
스레드는 다시 ready_list
에 들어가고
schedule()
이 다른 스레드를 실행시킨다.
내가 먼저였지만, CPU 잠깐 줄게!
thread_block()
스레드가 무조건 CPU를 계속 갖고 있을 순 없다.
자원을 기다려야 할 수도 있고,
시간이 되기 전까진 실행되면 안 될 수도 있다.
thread_block();
실행 중이던 스레드를 멈추고 blocked
상태로 만든다.
ready_list
에서도 제외된다.
난 지금 못 움직여 .. 자원이 올 때까지 멈출게!
thread_unblock()
thread_unblock(thread *t);
기다리던 자원이 생기거나,
타이머가 끝나면
스레드를 다시 ready_list
에 넣어준다.
자원 생겼어! 이제 나도 줄 설게 ~
처음엔 스레드(thread)가 정말 추상적으로 다가왔는데,
CPU가 명령어를 실행해가는 하나의 흐름이라는 것의 의미를
이번 프로젝트를 통해 비로소 제대로 이해하게 된 것 같다.
중간에 알 수 없는 오류들로 여러번 당황했지만,
팀원이 gdb 사용법을 알려줘서 디버깅하는 방법도 익혀갈 수 있었다.
오류를 마주해도 당황하지 않고 하나씩 들여다보는 힘이 생긴 것 같아 뿌듯하다.