반응형

선점과 컨텍스트 전환

 

실행중인 한 작업에서 다른 작업으로 전환하는 것을 뜻하는 컨텍스트 전환 (Context-Switching)은 kernel/sched.c에 정의된 context_switch() 함수를 통해 처리     

 

context_switch() 함수

  1. <asm/mmu_context.h>에 정의된 switch_mm() 함수를 호출해 프로세스의 가상 메모리 매핑(virtual memory mapping)을 새 프로세스의 것으로 바꾼다
  2. <asm/system.h>에 정의된 switch_to() 함수를 호출해 이전 프로세스의 프로세서 상태를 현재 프로세스의 프로세서 상태를 바꾸고 이과정에서 프로세서 단위로 관리가 필요한 스택정보, 프로세서 레지스터 등의 특정 하드웨어 관련 정보를 저장하고 복원하는 일이 포함

※ 커널은 언제 schedule()함수를 호출할 것인가를 알고 있어야 한다.

※ 만일 코드에 명시된 경우에만 schedule()함수를 호출한다면, 사용자 프로그램이 영원히 실행 될 수 도 있다.

 

선점(preemption)

 

유저 선점 : 커널이 사용자 공간으로 돌아가는 순간 need_resched

  플래그가 설정되어 있어 스케줄러가 호출되면 발생한다

  따라서, 커널은 인터럽트 처리를 끝내거나 시스템 호출을 마치고

  사용자 공간으로 돌아갈 때마다 need_resched 값을 확인

 

  < 유저 선점 발생 조건> 

-시스템 호출에서 사용자 공간으로 돌아갈 때
-인터럽트 처리를 끝내고 사용자 공간으로 돌아갈 때

 

커널 선점 : 리눅스 커널은 완벽한 선점형 커널

  실행중인 작업이 잠금을 설정하고 있지 않은 상태라면 커널은 선점

  -> 선점 불가능한 영역을 표시 하는데 잠금을 사용

 

  < 커널 선점 발생 조건>    

-인터럽트 처리를 마치고 커널 공간으로 돌아갈 때
-커널 코드가 다시 선점 가능한 상태가 되었을 때
-커널 내부 작업이 명시적으로 schedule() 함수를 호출
-커널 내부 작업이 중단돼 대기 상태일 때
·schedule() 함수를 호출하게 되는 경우
반응형
반응형

휴면 & 깨어남

 

  1. 휴면(Sleep)중이거나 대기(Blocked) 상태인 작업은 실행 불가능한 상태이다.
  2. 어떤 작업이 휴면 상태가 되는데는 여러가지 유이가 있지만 결국 특정 조건이 발생하는 것을 기다리는 상황으로 요약
  3. 이 조건은 일정시간이 지나는 거 일수도 파일 입출력으로 데이터를 읽어 들인 경우나 다른 하드웨어 이벤트가 발생한 경우, 사용중인 커널의 세미포어를 얻으려는 경우에 대기상태가 됨

 

대기열(Wait queues)

  • 특정 조건이 일어나기를 기다리는 단순한 프로세스 목록
  • 커널에서 wake_queue_head_t 형으로 표현
  • DECLARE_WAITQUEUE()를 이용해 정적을 생성하거나 init_waitqueue_head()함수를 이용해 동적으로 생성
  • 대기열과 과련된 조건이 발생하면 해당 대기열에 있는 프로세스를 깨우게 된다.
  • 휴면과 깨우기 작업을 올바로 구현하지 못하면 경쟁 조건 (Race Condition)이 발생한다.

 

휴면 처리작업

  1. DEFINE_WAIT() 매크로를 이용해 대기열에 추가할 항목 선택
  2. add_wait_queue() 함수를 이용해 작업을 대기열에 추가한다. 대기열이 기다리고 있는 조건이 발생하면 대기열에 있는 프로세스를 깨운다. 조건이 발생했을 때 대기열의 wake_up 함수를 호출하는 코드가 있어야 한다.
  3. prepare_to_wait() 함수를 호출해 프로세스 상태를 TASK_(UN)INTERRUPTIBLE 상태로 바꾸고 필요할 경우 작업을 다시 대기열에 넣어 루프의 후속 작업이 진행 될 수 있게 하다.
  4. 작업상태가 TAKS_INTERRUPTIBLE이면  시그널에 의해 프로세스가 깨어날 수 있으며 이럴 경우 의사각성(spurious wake up)이라고 함고 깨어나서 시그널을 확인하고 적.절히 처리한다
  5. 작업이 깨어 나면 조건이 만족되었는지 확인하고 만족됐으면 루프를 벗어나고 그렇지 않다면 schedule()을 호출해 앞의 과정을 반복
  6. 조건이 만족되면 작업 상태를 TASK_RUNNING으로 변경하고 finish_wait()함수를 호출해 대기열에서 작업을 제거

작업이 휴면상태가 되기전에 조건이 발생하면 루프가 종료되므로 작업 휴면상태가 되는 경우는 발생하지 않는다. 루프 안에 여러가지 많은 작업을 처리하는 커널 코드가 있음을 주의 해야 한다.

 

 

깨어남 (wake up)

  • 작업을 깨우는 일은 wake_up() 함수를 통해 처리
  • 주어진 대기열에 있는 모든 작업을 깨움
  • try_to_watke_up() 함수를 호출해 작업상태를 TASK_RUNNING 변경
  • enqueue_task() 함수를 호출해 작업을 다시 레드블랙트리에 추가
  • 깨어난 작업의 우선순위가 지금 실행 중인 작업의 우선순위보다 높으면 need_resched 값을 설정

 

반응형
반응형

리눅스 스케줄링 알고리즘

 

스케줄러 클래스

 

1. 리눅스 스케줄러는 모듈화돼어 있으며 여러유형의 프로세스를 각기 다른 알고리즘을 통해 스캐줄링이 가능하며 모듈화된 형태를 스케줄러 클래스라고 한다.

 

2. 스케줄러 클래스를 이용해 교체 가능한 여러 알고리즘을 동시에 사용하면서 클래스별로 독자적인 방식으로 프로세스를 스케줄링이 가능

 

3. kernel/sched.c 에 정의된 기본 스케줄러 코드는 각 스케줄러 클래스를 우선순위에 따라 차례대로 실행

 

4. 실행가능한 프로세스가 있는 가장 우선순위가 높은 스케줄러가 다음에 실행할 프로세스를 선택

 

5. 완전 공정 스케줄러(CFS)는  SCHED_NORMAL로 정의된 리눅스의 일반 프로세스용 스케줄러 클래스

 

 

유닉스 시스템의 프로세스 스케줄링

 

증요한 2가지의 개념 : 프로세스 우선순위와 타임슬라이스

 

타임 슬라이스는 프로세스가 얼마나 오랫동안 실행되는가이고 높은 우선순위 프로세스는 더 자주 실행되고 더 긴 타임슬라이스 값을 할당 받는다.

→ 이 시스템의 방법론적인 문제발생

 

1. 작업전환의 최적화 문제 발생

2. 우선순위(나이스 값)가 타임슬라이스와 묶이는 것과 나이스 값의 상대적인 차이 발생

3. 나이스값에 따라 타임슬라이스를 할당 하려면 타임슬라이스의 절대값을 할댕해야 됨

4. 우선순위 기반 스케줄러가 대화형작업을 최적화 하고자 할 경우 프로세스 깨우기 처리와 관련된 문제 발생

 

 

공정 스케줄링 (CFS)

 

CFS는 이상적이고 완벽한 프로세서를 가지고 있는 시스템의 프로세스 스케줄링을 추구

 

1. CFS는 각 프로세스를 순차적으로 일정시간동안 실행하고 가장 실행이 될 된 프로세스를 다음에 실행할 프로세스를 선택

2. 실행가능한 전체 프로세스 개수와 관련된 함수를 이용해 프로세스를 얼마동안 실행해야 하는지 계산

3. 프로세스에 할당할 프로세서 시간 비율의 가중치로 나이스값을 활용

4. CFS는 실제 타임슬라이스 값을 계산하기 위해 완전 멀티태스킹의 '무한히 작은' 스케줄링 단위를 근사 할 수 있는 목표치를 정해 놓고 이를 목표 응답시간이라고 한다.

 

 

리눅스 스케줄링 구현

 

CFS의 4가즈 구성요소

- 시간 기록

- 프로세스 선택

- 스케줄러 진입 위치

- 휴면 및 깨어남

 

시간 기록

 

1 모든 프로세스 스케줄러는 각 프로세스의 실행 기록을 기록

2 대부분 유닉스 시스템은 프로세스에 타임슬라이스를 할당하면서 기록

3. 시트메 클럭 한틱이 지날때마다 그틱에 해당하는 시간만큼 타임슬라이스가 줄어든다

 

스케줄러 단위 단위 구조체

 

1 CFS에는 타임슬라이스 개념이 없지만 공정하게 할당된 몫만큼을 사용하기 때문에 프로세스의 실행시간을 기록해 두어야 한다.

2 <linux/sched.h>에 정의된 struct sched_entity라는 스케줄러 단위 구조체를 사용해서 정보를 저장

 

가상실행 시간

 

1. vruntime 변수에는 프로세스의 가상 실행시간이 저정되며 가상 실행시간은 실행 가능한 프로세스 개수에 따라 정규화한 실제 실행시간을 의미한다.

2. 가상실행시간을 이용해 CFS가 지행하는 '이상적인 멀티태스킹 프로세서'를 근사적으로 구현 가능

이상적인 프로세서에서는 우선순위가 같은 프로세스의 가상 실행시간은 3. 모두 같으며 CFS는 vruntime을 통해 프로세서가 얼마나 오랫동안 실행 됬는지 앞으로 얼마나 더 실행되어야 하는지를 기록

4. 이작업은 kernel/sched_fair.c에 정의된 update_curr()에서 수행

 

프로세스 선택

 

1. 다음 실행할 프로세스를 선택할 때 CFS는 vruntime이 가장 작은 프로세스를 선택

2. CFS는 이를 효율적으로 하기 위해 레드블랙트리를 사용한다 (rbtree)

3. 레드블랙트리는 어떤 데이터를 노드에 저장하는 자료구조로, 특정키로 각 노드를 식별할 수 있으며, 키값이 주어졌을 때 그 키값을 가진 데이터를 효율적으로 탐색할 수 있는 자료구조

 

다음 작업 선택

 

1. 시스템의 실행가능한 모든 프로세스에 대해 각 프로세스별로 가상 실행시간을 키값으로 하는 노드를 만들어 레드블랙를 구성했을 때,

2. 주어진 트리에서 vruntime값이 가장 작은 CFS가 다음에 실행하고자 하는 프로세스는 트리의 가장 왼쪽에 있는 노드에 해당하는 프로세스

3. 트리의 루트노드에서부터 말단 노드에 이를 때 까지 계속 왼쪽 노드를 다라 가면 vruntime 값이 가장 작은 프로세스를 찾을 수 있다.

4. 따라서, CFS의 프로세스 선택 알고리즘은 "레드블랙트리의 가장 왼쪽노드에 해당하는 프로세스를 실행한다"로 요약

( kernel/sched_fair.c __pick_next_entity() 가 수행)

 

스케줄러 진입 위치

 

1. 프로세스 스케줄링 작업을 시작하는 곳은 kernel/sched.c 파일의 schedule()함수에 정의

2. 커널의 다른 부분에서 이 함수를 통해 프로세스 스케줄러를 호출함으로서 다음에 실행할 프로세스를 선택하고 실행

3. schedule()함수는 더 일반적인 형태의 스케줄러 클래스이고 이 함수는 가장 우선순위가 높은 실행 가능한 프로세스를 가진 스케줄러 클래스를 찾고 다음에 실행할 프로세스를 해당 스케줄러에게 물어본다.

4. kernel/sched.c 파일의 pick_next_task()함수는 가장 높은 우선순위의 스케줄러 클래스부터 돌아가면서 우선순위가 가장 높은 클래스의 우선순위가 가장 높은 프로세스를 찾아낸다.

반응형
반응형

멀티태스킹 :

하나 이상의 프로세스를 동시에 중첩형태로 실행할수 있는 운영체제

 

멀티태스킹 운영체제:

협동형 멀티태스킹 (Cooperative Multitasking) & 선접형 멀티태스킹프로세스 (Preemptive Multitasking)

 

선점형 멀티태스킹 :

 

- 프로세스 실행을 언제 중단하고 다른 프로세스를 실행할지리를 스케줄러가 결정

- 실행중인 프로세스를 강제로 중지시키는 작업을 선점이라고 정의

- 프로세스가 선점되기 전까지 주어지는 시간은 보통 미리 정해져 있으며 이것을 Timeslice(타임슬라이스)라고 함

타임슬라이스 : 프로세스가 선점되기 전까지 프로세스에 주어지는 시간

- 스케줄러는 타임슬라이스를 관리하는 방식으로 전체적인 시스템 스케줄링을 결정

 

협동형 멀티태스킹 :

- 프로세스가 자발적으로 실행을 중단하지 않는 한 프로세스를 중지 불가

- 프로세스가 자주 양보함으로써 실행 중인 프로세스가 충분한 프로세서가 충분한 동작 시간을 확보할 있다

 

 

정책 : 스케줄러가 무엇을 언제 실행 할  것인지를 정하는 동작

- 스케줄러의 정책을 통해 프로세서 시간의 사용을 최적화하는 책임

 

입출력중심 / 프로세서중심 프로세스

 

입출력중신 프로세스 : 입출력 요청을 기다리는데 대부분의 시간을 사용하는 프로세스

-> 입출력을 기다리는 작업을 반복하게 되므로 실제 실행시간을 짧다

ex) GUI 어플리케이션, Word Application

 

프로세서 중심 프로세스 : 대부분의 시간을 코드를 실행하는 데 사용

-> 선점될때 까지 계속 실행 / 긴시간동안 덜 자주실행되는것이 중요

ex) ssh-keygen , MATLAB

 

 

프로세스 우선순위

1. 스케줄링 알고리즘의 일반적인 형태는 우선순위 기반의 스케줄링이다

2. 목표는 가치와 필요에 따라 프로세스의 순위를 매겨 프로세서 시간을 할당해야 한다.

 

리눅스 커널의 두가지 우선순위 단위

1. 나이스값 (-20 ~ +19, 기본값 : 0) : 클 수록 우선순위가 낮다.

2. 실시간 우선순위 (0 ~ 09) : 클수록 우선순위가 높다

-> 실시간 우선순위값은 나이스값과 별도의 값이다

 

타임슬라이스 : 선점되기 전까지 작업을 얼마나 더 실행할 수 있는지 나타내는 값이다

1. 너무 길게 잡으면 시스템의 대화형 성능이 떨어지고 실제 App이 시스템에서 실행되고 있다는 것을 느끼기 어렵다

2. 너무 짧게 잡으면 타임슬라이를 소진한 프로세스를 다음 프로세스로 전환하는데 빈번하게 시스템 시간을 사용하게 되므로 프로세스 전환에 프로세스 시간을 소비한다.

 

※ 리눅스의 CFS스케줄러는 타임슬라이스 값을 할당하지 않고 대신 프로세스별 프로세서 할당 비율을 지정하는 방법을 사용한다

→ 리눅스에서 프로세스에 할당되는 프로세스 시간은 시스템의 부하에 따른 함수로 결정되고 이는 나이스값의 영향을 받는다 

반응형
반응형

프로세스 스케줄러

 

1. 프로세스를 작동시키는 커널의 서브시스템

2. 어떤 프로세스를 얼마나 오랫동안 실행할 것인지를 결정

3. 실행중인 시스템 프로세스에 프로세서(CPU) 동작시간이란 자원 할당

 

스케줄러

 

1. 리눅스 같은 멀티태스킹 운영체제의 기본요소

2. 스케줄러의 최대 사용률을 끌어내며

3. 사용자에게 여러개의 프로세스가 동시에 실행되는 느낌을 줌

 

스케줄러의 원리

1. 프로세서 작동시간을 최대한 활용할 수 있는 실행 가능한 프로세스가 있다면, 어떤 프로세스라도 실행하고 있어야 한다.

2. 시스템의 프로세서 개수보다 실행가능한 프로세스의 개수가 많은 경우라면 특정 순간에 일부 프로세스는 실행 중이 아니며 대기하게 된다.

 

스케줄러가 해결해야하는 근본적인 문제는 실행 가능한 프로세스가 여럿 주어졌을 때 다음에 어떤 프로세스를 실행할 것 인가 이다.

 

반응형
반응형

유닉스(리눅스)시스템에서 모든 프로세스는 PID가 1인 init 프로세스의 자손이다. init 부트 과정의 최종단계에서 커널이 실행하는 프로세스다

 

시스템의 모든 프로세스는 정확히 하나의 부모 프로세스를 가지며 모든 프로세스는 하나이상의 자식 프로세스를 가질수 있다. 같은 부모 프로세스를 가지는 자식 프로세스를 형제(sibling)이라고 부른다.

 

task_struct 구조체에는 부모의 task_struct를 가리키는 parent라는 포인터와 task_struct 리스트를 가리키는 children 포인트가 있으며 결과적으로 주어진 현재 프로세스에서 부모 프로세스의 task_struct를 얻을 수 있다.

 

<부모 프로세스 찾기>

struct task_struct *my_parent = current->parent;

 

<자식 프로세스 찾기>

struct task_struct *task;

struct list_head *list;

 

list_for_each(list, current->children)

{

task = list_entry(list, struct task_struct, sibling)

// 이제 task는 현재 프로세스의 자식 프로세스 중 하나를 가르킨다

}

 

 

프로세스 생성

 

대부분의 운영체제는 스폰(Spawn)방식을 사용하여 새로운 주소공간에 새 프로세스를 만들고, 실행 파일을 읽은 다음 그 코드를 실행한다. 유닉스&리눅스는 이 과정을 fork(), exec()라는 두 함수로 나누어 실행한다

 

먼저 fork()는 현재 태스크를 복제해 자식 프로세를 만든다. 이렇게 만들어진 프로세스는 PID(프로세스의 고유값), PPID(부모 프로세스의 PID), 상속되지 않은 지연된 시그널과 같은 일부자원, 통계수치를 제외하고는 부모와 동일하다. 다음으로 exec()는 새로운 실행파일을 주소공간에 불러오고 실행하게 된다.

 

Copy-on-Write (기록사항 발생시 복사, COW)

 

리눅스에서는 copy on write 페이지를 이용해 fork() 함수를 구현했다. COW기능은 데이터 복사를 지연 또는 방지하는 기능이다. 프로세스 주소공간을 복사하는 대신 부모와 자식 프로세스가 같은 공간을 공유한다.

 

그러나 기록 사항이 발생해 데이터 변경이 필요하면 그 순간 사본이 만들어 지고 각 프로세스가 별도의 내용을 가지게 된다. 따라서 리소스 복사는 해당 리소스에 대한 기록이 발생하는 경우에만 발생한다.

 

이 방법은 주소공간에 실제 기록작업이 일어 날때가지 각 페이지의 복사 작업을 지연시키며 프로세스가 절대 기록을 하지 않는 경우 복사가 필요 없어 진다.

 

리눅스의 스레드

 

스레드를 이용해 메모리 주소공간을 공유하는 같은 프로그램 여러개를 동시에 실행 할 수 있다. 스레드를 통해 동시 프로그래밍(Concurrent Programming)이 가능하고, 다중 프로세서 시스템에서는 진정한 병렬처리를 구현 할 수 있다.

 

리눅스는 기본적인 프로세스로 모든 스레드를 구현한다.  별도의 자료구조나 스케줄링 기법없이 특정 자원을 다른 프로세스와 공유하는 특별한 프로세스로 다루게 된다. 각 스레드는 task_struct 구조체를 가지고 있으며 커널 입장에서는 주소공간과 같은 자원을  다른 프로세스와 공유하고 있는 프로세스이다.

 

스레드의 생성

 

정상적인 프로세스(태스크)와 같은 방식으로 만들어지지만 clone() 시스템에서 특정 자원을 공유할 수 있도록 Flag를 지정해 준다.

 

커널 스레드

 

커널의 일부 동작을 백 그라운드에서 실행하는 것이 좋을 때가 있다 . 커널 공간에서만 존재하는 표준 프로세스인 커널 스레드를 이용해 백그라운드 작업을 수행 할 수 있다.

 

정상 프로세스와의 차이점은 커널 스레드에는 주소공간이 없다는 점이다. (mm포인터 값이 NULL) 커널 스레드는 커널 공간에서만 동작하며 사용자 공간에서만 동작하며 사용자 공간으로 컨텍스트 전환이 일어나지 않는다. 하지만 커널 스레도도 스케줄링이 되며 선점이 가능하다.

 

대표적인 예로는 flush 및 ksoftirq 작업을 예로 들 수 있다. 리눅스는 kthreadd 커널 프로세스가 모든 커널 스레드를 만드는 방식으로 커널 스레드를 관리한다.

 

struct task_struct *kthread_create(int (*threadfn)(void *data),
                                             void *data,
                                                 const char namefmt[], ...);

 

kthread_create()는 date 매개변수를 통해 전달된 threadfn 함수를 실행하며 namefmt문자열에 해당하는 이름을 갖게 된다.

 

 

처음 프로세스는 실행할 수 없는 상태로 만들어지기 때문에 wake_up_process()함수를 통해 명시적으로 깨워주지 않으면 실행되지 않는다.

 

커널 스레드는 한번 시작되면 스스로 do_exit() 함수를 호출하거나, 커널의 다른 부분에서 kthread_create()가 반환한 task_struct 구조체의 주소와 함께 kthread_stop() 함수를 호출할 때 까지 계속 실행된다
 

 

 

반응형

+ Recent posts