2010년 7월 22일 목요일

2010/07/22

* Task's state transition


Task의 상태 전이
























  1. Task가 생성되면 TASK_RUNNING(ready) 상태.
  2. 스케줄러에 의해서 CPU를 점유하게 되면 TASK_RUNNING(running) 상태.
  3. 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 상태를 세분화 한 것임.
  4. TASK_RUNNING(running) 상태에서 Task가 자신에게 할당된 시간을 모두 소모하면 TASK_RUNNING(ready) 상태로 전환.
  5. 실행상태의 Task는 특정 Signal(Event)를 기다려야 할 필요가 있으면 TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE 로 전이 한다.
    • Task가 lock이 걸려 있는 자원에 대한 lock을 가지려고 시도하거나, disk 같은 주변장치에 요청을 보내고 완료될때까지 기다려야 하는 것등이 대표적인 예임.
    • 대기하는 동안은 Task가 기다리는 Event외에 다른 Event에 대한 방해를 받지 않는 경우가 TASK_INTERRUPTIBLE, 그렇지 않은 경우가 TASK_UNINTERRUPTIBLE.
    • 대기 상태의 Task는 기다리는 Event에 따라 특정 Queue에 매달려서 대기함.
  6. Task가 기다리던 Event가 발생하면 대기상태에 있던 Task는 다시 TASK_RUNNING(ready) 상태로 전이.
* Kernel level running & User level running
  1. User level running(사용자 수준 실행)은 CPU에서 User가 만든 Application이나 Library 함수를 수행하고 있는 상태임. 이 경우, 당연히 User level로 동작함.
  2. Kernel level running(커널 수준 실행)은 CPU에서 Kernel program의 일부가 동작하는 상태임. 
  3. 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 처리 루틴으로 제어가 넘어가게 된다.
  4. 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 구조체도 포함하고 있음.
  5. 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라는 이름의 공간에 저장됨.
  6. Kernel은 System call이나 interrupt 에 대한 처리를 완료하 후에 몇가지 중요한 일을 처리함.
    • 첫째, Kernel은 현재 실행중인 Task가 signal을 받았는지 확인하고 받았다면 필요한 경우에 한해서 signal 처리 핸들러를 호출.
    • 둘째, 다시 스케줄링 해야할 필요가 있따면(현재 Task의 need_resched 플래그가 1로 설정) 스케줄러를 호출.
    • 셋째, kernel 내에 연기된 루틴들이 존재하면 이들을 수행한다.
* Runqueue
  1. liunx scheduler는 kernel version 2.6 부터 O(1)이라는 새로운 Scheduler를 사용.
  2. 2.6.23 부터는 CFS(Completely Fair Scheduler)라는 새로운 Scheduler가 도입됨.
  3. Runqueue
    • 수행 가능한 상태의 Task를 queue 자료구조로 연결.
    • linux에서는 이 자료구조를 Runqueue라고 함.
  4. Task가 생성되면 init_task를 header로 하는 이중 연결 list에 삽입됨. 이것을 앞으로 task_list로 적도록 하겠음.(정리의 편의를 위해)
  5. 결국, linux에 존재하는 모든 task는 task_list에 연결되어 있음.
  6. TASK_RUNNING 상태의 Task는 시스템에 존재하는 Runqueue에 존재함.
    • Runqueue는 CPU별로 하나씩 생성됨.
  7. Runqueue가 여러개라면 task_list에서 task가 Runqueue에 삽입될 때 어떤 Runqueue에 삽입이 될까?
    • 새로 생성되는 Task는 부모 Task가 존재하던 Runqueue에 삽입.
      • 자식과 부모가 같은 CPU에서 수행할 때 더 높은 cache 친화력을 가지기 때문 (--);
    • 대기상태에서 깨어난 Task는 이전에 수행하던 Runqueue에서 수행.
      • 이 또한 Cache 친화력...
    • task_struct의 cpus_allowed field에 자신이 수행하던 CPU number가 들어 있음. 
* Scheduling
  1. 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 부하뿐만 아니라 메모리 접근 시간의 차이등도 고려하여 부하균등을 시도한다.
  2. Scheduler는 언제, 어떻게 호출될까?
    • 첫째, 직접 호출.
    • 둘째, task의 thread_info 구조체 내부에 존재하는 flags 필드중에 need_resched 필드를 설정.
    • 그럼, 언제?
      • 첫째, 주기적인 clock interrupt가 발생할 때, 현재 수행되고 있는 task의 need_resched 필드를 살펴보고 스케줄링이 필요하면 스케줄러를 호출
      • 둘째, 새롭게 생성된 task가 현재 수행되고 있는 task를 선점해야 하면 스케줄러를 호출.
      • 셋째, 현재 수행되고 있던 task가 자신의 타임 슬라이스를 모두 사용했을 때.
      • 넷째, 현재 task가 sched_setscheduler() 같은 system call를 호출할 때.
  3. Scheduling의 동작 원리는?
    •  다음에....

댓글 없음:

댓글 쓰기