Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[라즈베리파이] 스케줄링: 프로세스 상태 관리 10. Process Scheduling

프로세스를 효율적으로 관리하기 위해서 커널은 프로세스에게 프로세스 상태를 부여합니다. 
커널에서 정의한 프로세스 상태는 다음과 같습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/tools/perf/builtin-sched.c]
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2

커널에서 구동 중인 프로세스는 위에서 정의한 상태 정보를 갖고 있으며 커널은 이 정보를 기준으로 스케줄링을 수행합니다.

그러면 프로세스 상태는 어디에 저장할까요?
프로세스를 관리하는 태스크 디스크립터 필드 중 state는 위에서 언급한 정수형 상태 정보를 저장합니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/include/linux/sched.h]
struct task_struct {
...
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;

프로세스 실행 상태를 분류하는 이유는 무엇일까요? 그것은 프로세스 관리를 효율적으로 할 수 있기 때문입니다.

이번 절에서는 리눅스 커널에서 프로세스 상태를 어떻게 관리하는지 살펴보겠습니다.

프로세스 상태 소개
프로세스 상태는 다음 프로세스 상태 다이어그램으로 표현할 수 있습니다.


다음 테이블에서 위 프로세스 상태를 확인할 수 있습니다.

프로세스는 생성된 후 위 테이블에서 언급된 상태 중 하나로 실행합니다. 커널 스케줄러는 프로세스 상태 정보를 읽고 어떤 프로세스를 다음에 실행할지 결정합니다.

TASK_RUNNING 상태에 대한 이야기를 조금 더 하겠습니다. 
런큐에 Enqueue되어 실행을 기다리는 실행 대기(TASK_RUNNING) 상태 프로세스와 CPU를 점유하면서 실행 중인 CPU실행(TASK_RUNNING) 상태 프로세스 둘 다 TASK_RUNNING 상태입니다. 리눅스 커널에서는 이 두 가지 상태를 어떻게 식별할까요?
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/sched/sched.h]
1 struct rq {
2 /* runqueue lock: */
3 raw_spinlock_t lock;
...
4 struct task_struct *curr, *idle, *stop;

런큐 구조체인 struct rq 중 curr 필드는 현재 CPU를 점유하면서 실행 중인 프로세스 태스크 디스크립터 주소를 저장합니다. 커널은 CPU를 점유하면서 실행 중인 프로세스를 확인할 때 반드시 런큐 구조체 curr 필드에 접근합니다.

struct rq 구조체 필드 중 curr에 접근하는 예제 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/sched/sched.h]
1 static inline int task_current(struct rq *rq, struct task_struct *p)
2 {
3 return rq->curr == p;
4 }

함수 인자를 조금 더 자세히 살펴보겠습니다. 첫 번째 인자는 런큐 구조체인 struct rq *rq이고, 두 번째 인자로 태스크 디스크립터 주소를 저장하는 struct task_struct *p 가 보입니다.

위 task_current() 함수 3 번째 줄 코드를 보면 struct rq 구조체 curr 필드와 두 번째 인자로 전달한 태스크 디스크립터와 비교합니다.

두 번째로 전달한 프로세스가 현재 CPU에서 실행 중인 프로세스인지 점검하는 동작입니다.

위 함수는 두 번째 인자인 struct task_struct *p가 현재 CPU에서 실행 중인지 점검합니다. 만약 현재 CPU에서 실행 중이면 1, 아니면 0을 반환합니다.

이번에는 task_current() 함수를 호출하는 코드를 소개합니다. 
 [https://elixir.bootlin.com/linux/v4.14.70/source/kernel/sched/core.c]
1 void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
2 {
3 struct rq *rq = task_rq(p);
4 bool queued, running;
5
6 lockdep_assert_held(&p->pi_lock);
7
8 queued = task_on_rq_queued(p);
9 running = task_current(rq, p);

3번째 줄 코드를 보면 런큐 주소를 읽습니다. 이후 9 번째 줄에서 task_current() 함수를 호출해서 p란 프로세스(태스트 디스크립터)가 현재 CPU에서 실행 중인지 점검합니다.

이렇게 CPU를 점유하면서 현재 실행 중인 프로세스를 current 프로세스라고 부릅니다.


프로세스가 휴면에 진입하면 TASK_INTERRUPTIBLE와 TASK_UNINTERRUPTIBLE 중 하나 상태로 변경합니다. 두 상태의 차이는 무엇일까요?

보통 프로세스가 휴면에 진입하면 TASK_INTERRUPTIBLE 상태로 변경합니다. ps -ely 명령어를 입력하면 모든 프로세스 목록을 볼 수 있습니다. 대부분 프로세스가 TASK_INTERRUPTIBLE 상태입니다.

그런데 프로세스가 휴면에 진입하면서 프로세스가 특정 조건에서 깨워나고 싶을 때가 있습니다. 이 때 스스로 깨어날 조건을 설정한 다음에 자신을 TASK_UNINTERRUPTIBLE 상태로 변경합니다.

뮤텍스을 얻지 못하거나 I/O 동작 중에 TASK_UNINTERRUPTIBLE 상태로 변경합니다.

예를 들어 프로세스가 뮤텍스를 획득 못했을 때 자신을 TASK_UNINTERRUPTIBLE 상태로 바꾼 다음 휴면에 진입합니다. 이 후 뮤텍스를 해제하는 프로세스가 자신을 깨우면 TASK_RUNNING(실행 대기) 상태로 변경하는 경우가 대부분입니다.
커널에서 구동 중인 전체 프로세스 목록 중 TASK_UNINTERRUPTIBLE 혹은 TASK_RUNNING 상태 프로세스가 너무 많은(20개 이상) 경우가 있습니다. 이럴 때 시스템에 뭔가 문제가 있을 확률이 높으니 차근차근 프로세스들의 콜스택이나 ftrace를 점검할 필요가 있습니다.

TASK_UNINTERRUPTIBLE 상태 프로세스가 비정상으로 많은 경우
1. 다수의 프로세스들이 뮤텍스를 획득하지 못해 자신을 TASK_UNINTERRUPTIBLE 상태로 변경하고 휴면에 진입함
2. I/O 동작 중에 외부 저장 장치와 인터페이싱에 문제가 있음

TASK_RUNNING 상태 프로세스가 비정상으로 많은 경우
1. 특정 프로세스가 CPU를 계속 점유하고 실행 중
2. 인터럽트가 비정상적으로 많이 발생해서 프로세스 선점 스케줄링이 제대로 수행되지 못함






Reference(프로세스 관리)
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

Reference(프로세스 스케줄링)

스케줄링 소개
프로세스 상태 관리
   어떤 함수가 프로세스 상태를 바꿀까?
스케줄러 클래스
런큐
CFS 스케줄러
   CFS 관련 세부 함수 분석  
선점 스케줄링(Preemptive Scheduling)   
프로세스는 어떻게 깨울까?
스케줄링 핵심 schedule() 함수 분석
컨택스트 스위칭
스케줄링 디버깅
   스케줄링 프로파일링
     CPU에 부하를 주는 테스트   
     CPU에 부하를 주지 않는 테스트

핑백

덧글

댓글 입력 영역