Task의 상태 전이 |
- Task가 생성되면 TASK_RUNNING(ready) 상태.
- 스케줄러에 의해서 CPU를 점유하게 되면 TASK_RUNNING(running) 상태.
- Task가 종료할 때는 EXIT_ZOMBIE 상태.
- EXIT_ZOMBIE 상태에서는 Task가 유지하던 자원을 대부분을 Kernel에 반납한 상태임.
- 이 상태에서는 자신의 소멸 이유를 부모 Task로 알려주기 위해서 유지되고 있는 상태임.
- 부모 Task가 모든 정보를 가져가면 EXIT_DEAD 상태로 진입한 뒤 모든 자원 반납.
- EXIT_ZOMBIE와 EXIT_DEAD는 Kernel 2.6에 새롭게 추가된 state로 복수개의 Task가 동 일한 Task에게 wait을 호출할 겨웅에 발생하는 Deadlock을 피하기 위해 기존 TASK_EXIT 상태를 세분화 한 것임.
- TASK_RUNNING(running) 상태에서 Task가 자신에게 할당된 시간을 모두 소모하면 TASK_RUNNING(ready) 상태로 전환.
- 실행상태의 Task는 특정 Signal(Event)를 기다려야 할 필요가 있으면 TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE 로 전이 한다.
- Task가 lock이 걸려 있는 자원에 대한 lock을 가지려고 시도하거나, disk 같은 주변장치에 요청을 보내고 완료될때까지 기다려야 하는 것등이 대표적인 예임.
- 대기하는 동안은 Task가 기다리는 Event외에 다른 Event에 대한 방해를 받지 않는 경우가 TASK_INTERRUPTIBLE, 그렇지 않은 경우가 TASK_UNINTERRUPTIBLE.
- 대기 상태의 Task는 기다리는 Event에 따라 특정 Queue에 매달려서 대기함.
- Task가 기다리던 Event가 발생하면 대기상태에 있던 Task는 다시 TASK_RUNNING(ready) 상태로 전이.
* Kernel level running & User level running
- User level running(사용자 수준 실행)은 CPU에서 User가 만든 Application이나 Library 함수를 수행하고 있는 상태임. 이 경우, 당연히 User level로 동작함.
- Kernel level running(커널 수준 실행)은 CPU에서 Kernel program의 일부가 동작하는 상태임.
- User level running에서 Kernel level running으로 전이방법은 2가지가 존재.
- 첫 번째, System call의 사용.
- System call을 요청하면 linux kernel에 트랩(?)이 걸리게 되고, 그 결과 Task의 상태가 Kernel level running 상태로 전이되며 Kerenl의 System call 처리 루틴으로 진입함.
- 두 번째, Interrupt의 발생.
- interrupt가 발생하면 linux kernel에 interrupt가 걸리고, 실행중이던 Task는 Kernel level running 상태로 전이되고(??), Kernel은 interrupt 처리 루틴으로 제어가 넘어가게 된다.
- User level running과 Kernel level running은 복잡한 stack 관리를 요구함.
- 사용자 수준의 프로그램은 0~3GB까지의 메모리 영역을 사용. stack은 3GB를 시작으로 아래로 증가됨.
- 그렇다면, Kernel level running 상태에서의 stack은?
- linux kernel은 Task가 실행될때마다 Task별로 stack을 할당함(책에는 8K라고 하면서, 설정과 kernel 버전별로 달라질 수 있다고 함).
- Task가 system call 요청시 task별로 할당된 stack을 사용함.
- 결국, Task가 생성되면 kernel은 task_struct 구조체와 stack을 할당받게 됨.
- 할당받은 stack은 thread_union이라고 하며, thread_info 구조체도 포함하고 있음.
- thread_union
- thread_info 구조체를 포함. thread_info는 process descriptor라고 부리기도 함.
- thread_info 구조체 내에는 task_struct를 가르키는 pointer, scheduling 여부를 나타내는 flag, task 수행 도메인을 나타내는 exec_domain등의 field가 존재.
- task가 system call등을 통해 kernel level running 상태로 진입하고 수행해야 할 일을 모두 마친 후에는 다시 user level running 상태로 복귀해야 함. 이것을 위해서 kernel level running 상태 진입시점에 stack안에 현재 상태의 register값들을 모두 구조체를 이용하여 일목요연하게 저장함. 이것이 pt_regs라는 이름의 공간에 저장됨.
- Kernel은 System call이나 interrupt 에 대한 처리를 완료하 후에 몇가지 중요한 일을 처리함.
- 첫째, Kernel은 현재 실행중인 Task가 signal을 받았는지 확인하고 받았다면 필요한 경우에 한해서 signal 처리 핸들러를 호출.
- 둘째, 다시 스케줄링 해야할 필요가 있따면(현재 Task의 need_resched 플래그가 1로 설정) 스케줄러를 호출.
- 셋째, kernel 내에 연기된 루틴들이 존재하면 이들을 수행한다.
* Runqueue
- liunx scheduler는 kernel version 2.6 부터 O(1)이라는 새로운 Scheduler를 사용.
- 2.6.23 부터는 CFS(Completely Fair Scheduler)라는 새로운 Scheduler가 도입됨.
- Runqueue
- 수행 가능한 상태의 Task를 queue 자료구조로 연결.
- linux에서는 이 자료구조를 Runqueue라고 함.
- Task가 생성되면 init_task를 header로 하는 이중 연결 list에 삽입됨. 이것을 앞으로 task_list로 적도록 하겠음.(정리의 편의를 위해)
- 결국, linux에 존재하는 모든 task는 task_list에 연결되어 있음.
- TASK_RUNNING 상태의 Task는 시스템에 존재하는 Runqueue에 존재함.
- Runqueue는 CPU별로 하나씩 생성됨.
- Runqueue가 여러개라면 task_list에서 task가 Runqueue에 삽입될 때 어떤 Runqueue에 삽입이 될까?
- 새로 생성되는 Task는 부모 Task가 존재하던 Runqueue에 삽입.
- 자식과 부모가 같은 CPU에서 수행할 때 더 높은 cache 친화력을 가지기 때문 (--);
- 대기상태에서 깨어난 Task는 이전에 수행하던 Runqueue에서 수행.
- 이 또한 Cache 친화력...
- task_struct의 cpus_allowed field에 자신이 수행하던 CPU number가 들어 있음.
- Scheduler는 해당 CPU의 Runqueue에서 다음에 실행시킬 Task를 골라낸다. 이때 두가지 고려사항이 있음.
- 첫째, 가장 Priority가 높은 Task를 선택하기 위한 overhead를 어떻게 줄일 것인가?
- 이것을 위해 linux는 O(1) Scheduler를 제공함.
- 둘째, Runqueue가 비어 있으면 어떻게 할 것인가?
- 이것을 위해 linux는 load balancing 기법을 제공함.
- 위 두가지 사항은 SMP(Symmetric Multi Processor) 구조의 시스템에만 해당
- SMP는 각 CPU가 동등한 System이다.
- NUMA(Non-Uniform Memory Access)구조의 시스템에서는 load balancing을 수행할 때 CPU 부하뿐만 아니라 메모리 접근 시간의 차이등도 고려하여 부하균등을 시도한다.
- Scheduler는 언제, 어떻게 호출될까?
- 첫째, 직접 호출.
- 둘째, task의 thread_info 구조체 내부에 존재하는 flags 필드중에 need_resched 필드를 설정.
- 그럼, 언제?
- 첫째, 주기적인 clock interrupt가 발생할 때, 현재 수행되고 있는 task의 need_resched 필드를 살펴보고 스케줄링이 필요하면 스케줄러를 호출
- 둘째, 새롭게 생성된 task가 현재 수행되고 있는 task를 선점해야 하면 스케줄러를 호출.
- 셋째, 현재 수행되고 있던 task가 자신의 타임 슬라이스를 모두 사용했을 때.
- 넷째, 현재 task가 sched_setscheduler() 같은 system call를 호출할 때.
- Scheduling의 동작 원리는?
- 다음에....
댓글 없음:
댓글 쓰기