ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

203239
1625
172600


[리눅스커널] 프로세스: 태스크 디스크립터(task_struct 구조체) - 프로세스 상태 4. 프로세스(Process) 관리

태스크 디스크립터에는 프로세스 상태를 관리하는 다음과 같은 두 가지 필드가 있습니다. 

state: 프로세스 실행 상태 
flags: 프로세스 세부 동작 상태와 속성 정보 

먼저 프로세스의 상태를 저장하는 state 필드를 소개합니다.

volatile long state;

프로세스 상태를 저장하는 필드로 다음 매크로 중 하나를 저장합니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/sched.h
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002

위 매크로에서 정의한 프로세스의 상태는 다음과 같습니다.

TASK_RUNNING: CPU에서 실행 중이거나 런큐에서 대기 상태에 있음
TASK_INTERRUPTIBLE: 휴면 상태 
TASK_UNINTERRUPTIBLE: 특정 조건에서 깨어나기 위해 휴면 상태로 진입한 상태

리눅스 시스템에서 확인되는 프로세스들은 대부분 TASK_INTERRUPTIBLE 상태입니다. TASK_RUNNING 혹은 TASK_UNINTERRUPTIBLE 상태인 프로세스가 비정상적으로 많으면 시스템에 문제(데드락, 특정 프로세스 스톨)가 있는 경우가 많습니다. 

---
프로세스 상태별 세부 동작 방식은 10장 ‘프로세스 스케줄링’에서 상세히 다룹니다.
---

다음으로 flags 필드를 소개합니다.

unsigned int flags;

프로세스 종류와 프로세스 세부 실행 상태를 저장하는 필드입니다. flags 필드는 PF_*로 시작하는 매크로 필드를 OR 연산한 결과를 저장합니다.

PF_* 매크로 가운데  눈여겨볼 만한 코드는 다음과 같습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/sched.h
#define PF_IDLE 0x00000002 /* I am an IDLE thread */
#define PF_EXITING 0x00000004 /* Getting shut down */
#define PF_EXITPIDONE 0x00000008 /* PI exit done on shut down */
#define PF_WQ_WORKER 0x00000020 /* I'm a workqueue worker */
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */

각 매크로의 의미는 다음과 같습니다.

PF_IDLE: 아이들 프로세스
PF_EXITING: 프로세스 종료 중인 상태
PF_EXITPIDONE: 프로세스 종료를 마무리한 상태
PF_WQ_WORKER: 프로세스가 워커 스레드인 경우
PF_KTHREAD: 프로세스가 커널 스레드인 경우

flags 필드에 저장된 값으로 프로세스의 세부 실행 상태를 알 수 있습니다. 커널은 flags에 저장된 프로세스의 세부 동작 상태를 읽어서 프로세스를 제어합니다. 그래서 이번에는 flags 필드로 프로세스의 실행 흐름을 제어하는 한 가지 예를 들어 보겠습니다. 

https://elixir.bootlin.com/linux/v3.7/source/drivers/staging/android/lowmemorykiller.c
1 static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
2 {
3 struct task_struct *tsk;
4 struct task_struct *selected = NULL;
5 int rem = 0;
...
6 for_each_process(tsk) {
7 struct task_struct *p;
8 int oom_score_adj;
9
10 if (tsk->flags & PF_KTHREAD)
11 continue;
12
13 p = find_lock_task_mm(tsk);
14 if (!p)
15 continue;
...
16 lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
17      p->pid, p->comm, oom_score_adj, tasksize);
18 }

위 코드는 안드로이드에서 메모리 회수를 위해 특정 프로세스를 종료하는 lowmem_shrink() 함수의 일부분입니다. 이 코드에서 10~11 번째 줄을 봅시다.

10 if (tsk->flags & PF_KTHREAD)
11 continue;

10번째 줄에서는 태스크 디스크립터의 flags 필드와 PF_KTHREAD 매크로를 대상으로 AND 연산을 수행합니다. 이 조건을 만족하면 11번째 줄을 실행해 아래 부분의 코드를 실행하지 않습니다. 이 코드의 의도는 프로세스 타입이 커널 스레드이면 프로세스를 종료하지 않겠다는 것입니다. 이 같은 방식으로 프로세스 실행 정보를 읽어서 예외 처리를 수행합니다.

이어서 exit_state 필드를 확인해봅시다.

int exit_state;

프로세스 종료 상태를 저장합니다. 다음 매크로 중 하나 값을 저장합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/sched.h
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)

마지막으로 exit_code 필드를 확인해봅시다.

int exit_code;

프로세스의 종료 코드를 저장하는 필드입니다. do_exit() 함수의 3번째 줄에서 종료 코드를 저장합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/exit.c
1 void __noreturn do_exit(long code)
2 {
...
3 tsk->exit_code = code; 

do_exit() 함수를 호출할 때 인자로 다음과 같은 시그널이나 프로세스를 종료하는 옵션을 지정할 수 있습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/mm/fault.c
01 static void
02 __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
03   struct pt_regs *regs)
04 {
...
05 do_exit(SIGKILL);
06 }

5번째 줄을 보면 SIGKILL 인자와 함께 do_exit() 함수를 호출합니다.

* 유튜브 강의 동영상도 있으니 같이 들으시면 좋습니다. 




#프로세스

프로세스 소개 
프로세스 확인하기  
프로세스는 어떻게 생성할까?  
유저 레벨 프로세스 실행 실습  
커널 스레드  
커널 내부 프로세스의 생성 과정   
프로세스의 종료 과정 분석  
태스크 디스크립터(task_struct 구조체)  
스레드 정보: thread_info 구조체  
프로세스의 태스크 디스크립터에 접근하는 매크로 함수  
프로세스 디버깅  
   * glibc의 fork() 함수를 gdb로 디버깅하기  


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

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

# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2


 


핑백

덧글

댓글 입력 영역