Introduction · GitBook
No results matching ""
casys-kaist.github.io
GitHub - casys-kaist/pintos-kaist
Contribute to casys-kaist/pintos-kaist development by creating an account on GitHub.
github.com
Thread Life cycle
main 함수에서 thread_init () 함수를 실행하면
default thread 인 main thread가 하나 생성됩니다.
이후
thread_start () 함수를 통해 스레드 스케쥴러가 작동하기 시작합니다.
따라서 이 함수를 통해 스레드의 라이프사이클의 시작을 확인해볼 수 있습니다.
thread_start 함수는 다음과 같은 역할을 합니다.
/* Starts preemptive thread scheduling by enabling interrupts.
Also creates the idle thread. */
인터럽트를 받을 수 있도록 하여 선점가능한 스레드 스레쥴링을 시작합니다.
또한 idle thread, 즉 현재 cpu를 점유할 스레드가 없을 때 작동하는 유휴 스레드를 생성해줍니다.
먼저 유휴 스레드를 생성합니다.
유휴 스레드를 생성하는 중에는 idle thread에 대한 세마포어를 0으로 초기화 해
idle thread의 초기화 작업에 문제가 생기지 않도록
이후 idle 함수에서 sema_up(&idle_started) 하기 전까지 대기하도록 합니다.
이후 인터럽트를 받을 수 있게 설정합니다.
Pintos의 인터럽트는 아래 내용을 참고하면 좋을 것 같습니다.
인터럽트는 cpu 단위로 각각 다른 상태를 유지하는데,
pintos는 단일 cpu를 가정한, 단일 cpu 멀티 스레딩 환경이어서
시스템 전체에서 유지하는 인터럽트 상태는 하나입니다. (interrupt ON or OFF)
[링크]
threads/interrupt.c 파일 속에 x86_64 아키텍처의 인터럽트 개수가 명시되어있습니다.
intr_enable () 함수는 인터럽트를 활성화하며,
활성화 이전 인터럽트의 상태를 반환합니다.
intr_disable () 함수는 인터럽트를 비활성화하며,
비활성화 이전 인터럽트의 상태를 반환합니다.
인터럽트 상태, 즉 인터럽트 level은 interrupt.h 헤더에 존재합니다.
(포스팅 분할 요망)
idle thread의 초기화를 sema_down 함수에서 기다리게 됩니다.
ㅠㅠ 컴퓨터 구조에 대한 이해가 부족해 thread가 어떻게 작동하는 지 한참 고민하면서 찾았습니다ㅠㅠ
thread의 상태는 timer가 1번씩 tick 할 때마다 결정되게 됩니다.
timer_tick을 단위로 해서 block 하거나, 다시 ready 상태로 가져오는 등의 작업을 진행할 수 있습니다.
다시 말 해, thread의 작업 단위는 timer의 1 tick이 됩니다.
흠 근데 그러면 timer의 tick을 1번씩 증가시켜주는 건 누가 하는 일인가요?
저는 처음에 여기서 너무 헷갈렸고, 고민을 많이 했습니다.
처음에는 timer의 tick 증가를 OS가 하는 줄 알았습니다.
timer가 하드웨어일 거라는 생각을 못했고, timer에서 interrupt가 올 때마다 timer_tick이 1 씩 증가하는 구조를 가지는 걸 확인했는데요.
그래서 전부 다 OS가 관리하는 소프트웨어로 구현되어있을 줄 알았고, 소스코드단에서 찾으려고 하니 찾을 수가 없었습니다ㅠㅠ.
PintOS가 사용하는 timer는 PIT(Programmable Interval Timer)로써, 하드웨어 기반 타이머입니다.
timer_init 함수에서 timer의 tick 주기를 설정할 수 있고, 이를 참고해 timer_init 함수는 PIT와 연결된? 0x40 포트에 주기 값을 설정합니다.
또한 타이머는 하드웨어이므로 OS 외부에서 발생하는 인터럽트이고,
이를 핸들링 하기 위해 intr_register_ext 함수를 통해 "8254 Timer"로 명명한 인터럽트를 핸들러와 함께 0x20번 (10진수로는 32번) 인터럽트로 등록합니다.
이후 PIT 타이머가, 설정한 주기마다 0x20번 외부 인터럽트 "8254 Timer"를 주게 되면,
"8254 Timer"인터럽트에 등록된 timer_interrupt () 라는 핸들러를 작동시키게 됩니다.
이 때 PintOS 전체에서 바라보는 timer_tick이 1 증가하게 됩니다.
이후 thread_tick () 함수를 통해
tick의 1 증가로 인한 변화를 스레드에 반영합니다.
우선 시스템 전체의 통계량(아마 모니터링을 위해 유지할)에 변화를 반영합니다.
(idle_ticks++ 혹은 user_ticks++, kernel_ticks++)
이후 현재 스레드의 tick을 1 증가시키고, 그게 TIME_SLICE(default value = 4) 보다 커지면
intr_yield_on_return () 함수를 통해 스케쥴링을 강제합니다(선점 스케쥴링).
yield_on_return
이 boolean 변수는 인터럽트가 반환될 때 현재 스레드가 다른 스레드로 교체되어야 하는지를 나타냅니다.
다시 intr_handler 함수를 찾아보면,
모든 인터럽트 핸들러는 yield_on_return 이라는 조건을 처리하는 구문이 존재합니다.
timer interrupt는 외부 인터럽트 이며, 이런 외부 인터럽트에 대해
yield_on_return 필드가 true이면
thread_yield 함수를 호출해, 현재 스레드를 스케쥴링 대상으로 삼아 스케쥴링을 진행합니다.
(Pintos 는 single thread 프로그램이었나??)
thread.c의 파일 최상단에서, thread_ticks 변수 정의를 찾아보면
값 할당 혹은 값 초기화 과정이 없어서 이런 의문이 들었습니다.
현재 수행중인 thread에 대한 tick은 어디서 유지하고 있을까요?
(변수의 선언과 정의도 구분해서 사용하더라고요, 링크입니다.)
아래 schedule 함수에서 thread_ticks를 0으로 초기화합니다.
schedule 함수는 do_schedule 이라는 함수에서 호출됩니다.
그렇다면 do_schedule 함수는요?
스레드 스레쥴링을 하는 함수니까 뭔가 스레드가 CPU를 사용하고 나올 때와 관련이 있을 것 같습니다.
네, 위처럼 스레드가 CPU를 사용하고 물러선 이후의 내용을 담은 thread_yield () 함수에서 do_schdule 함수를 호출하네요.
(running state를 가지는 스레드를 ready state(cpu 사용할 수 있지만 대기중인 상태)로 바꾸는게 yield 과정입니다.)
음 근데 결과적으로 thread_ticks 변수를 처음 초기화 하는 건 thread_start() 함수를 통해 처음 idle 스레드를 만들 때 실행합니다.
idle 스레드는 무한히 현재 자기 자신을 block 하고 다시 스케쥴링받아오는 과정을 반복합니다.
thread_block () 과정은
현재 스레드를 블락시킨 후에
다음에 스케쥴링할 스레드를 찾고 스케쥴링을 진행하게 되는데 (스레드 변경)
이 때 schdule () 함수를 호출하여
현재 스레드가 실행된 tick을 유지할 변수 thread_ticks를 0으로 초기화 합니다!
좋습니다....!
한 번 스레드 라이프사이클에 대한 플로우 차트를 그려볼 수 있을 것 같아요
thread, interrupt, thread_ticks 간의 관계로 표현해볼 수 있을 것 같습니다!
나름대로 여러 요소로 나눠 이해한 흐름을 그려보려고 했으나...
흠... OS의 작동방식을 2차원 평면에 나타내려고 하는 게 잘못되었던 걸까요...?
어떻게 더 잘 표현할 수 있을 것 같은데 마땅한 방법이 떠오르질 않네요..
일단 PlantUML으로 다시 그려보도록 하겠습니다.
힘드네요...
정말 어렵습니다...
해설은 내일 적겠습니다...
'OS > PintOS' 카테고리의 다른 글
[Pintos] Priority Scheduling (1) | 2024.09.05 |
---|---|
[Pintos] Alarm Clock (1) | 2024.09.05 |
PintOS 프로젝트 - 프로그램 이해를 위한 플로우 차트 그리기 (1) | 2024.09.03 |