Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

75235
1036
103645


4.7.1 프로세스를 식별하는 멤버/ 4.7.2 프로세스 상태 저장 4장. 프로세스 관리

4.7.1 프로세스를 식별하는 멤버
프로세스를 식별하는 멤버들을 살펴 보겠습니다.

char comm[TASK_COMM_LEN];

comm은 TASK_COMM_LEN 크기 배열이며 프로세스 이름을 저장합니다.

"ps -ely"란 명령어를 입력하면 다음과 같이 systemd, kthreadd 그리고 kworker/0:0H가 보입니다.
root@raspberrypi:/home/pi # ps -ely
1 S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
2 S     0     1     0  0  80   0  5964  7007 SyS_ep ?        00:00:02 systemd
3 S     0     2     0  0  80   0     0     0 kthrea ?        00:00:00 kthreadd
4 I     0     4     2  0  60 -20     0     0 worker ?        00:00:00 kworker/0:0H

이 프로세스 이름들은 태스크 디스크립터의 comm이란 멤버에 접근해서 출력한 겁니다.
프로세스 이름은 set_task_comm() 이란 함수를 호출해서 지정할 수 있습니다.

현재 실행 중인 프로세스의 태스크 디스크립터 구조체인 struct task_struct에 접근하는 current 매크로와 프로세스 이름을 저장하는 comm 멤버를 조합하면 다양한 디버깅 용 코드를 작성할 수 있습니다.

한 가지 디버깅용 코드를 작성해볼까요? 이번에 kthreadd 프로세스만 깨울 때 함수 호출 흐름을 출력하는 디버깅 코드를 작성해 보겠습니다.
1 [https://elixir.bootlin.com/linux/v4.14.70/source/kernel/sched/core.c]
2 static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags,
3    struct rq_flags *rf)
4 {
5 check_preempt_curr(rq, p, wake_flags);
6 p->state = TASK_RUNNING;
7 trace_sched_wakeup(p);
8
9  + if (strcmp(p->comm, "kthreadd")) {
10 + printk("[+][%s] wakeup kthreadd process \n", current->comm);
11 + dump_stack();
12 + }
13
14 #ifdef CONFIG_SMP
15 if (p->sched_class->task_woken) {
16 /*

ttwu_do_wakeup() 함수 인자인 struct task_struct *p가 깨우려는 프로세스의 태스크 디스크립터입니다. 프로세스 이름은 struct task_struct 구조체 comm 필드에 저장된 프로세스 이름이 “kthreadd” 인 경우 dump_stack() 함수 호출로 함수 호출 흐름을 커널 로그로 출력합니다.

pid_t pid;

pid는 Process ID의 약자로 pid는 프로세스마다 부여하는 정수형 값입니다.
pid 상수는 프로세스를 생성할 때 마다 증감하므로 pid 값 크기로 프로세스가 언제 생성됐는지 확인할 수 있습니다.

pid_t tgid;

pid와 같은 타입의 멤버로 스레드 그룹 아이디를 표현하는 정수형 값입니다.
해당 프로스세가 스레드 리더인 경우는 tgid 와 pid 가 같고, child thread 인 경우는 tgid 와 pid 는 다릅니다.

4.7.2 프로세스 상태 저장
이번에는 프로세스 상태를 알 수 있는 멤버를 알아봅시다. 프로세스 실행 상태는 저장하는 state와 프로세스 세부 동작 상태와 속성을 알 수 있는 flags를 눈여겨봅시다.

volatile long state;

프로세스 상태를 저장하는 멤버로 다음 매크로 중 하나입니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/include/linux/sched.h#L69]
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002

대부분 프로세스들은 TASK_INTERRUPTIBLE 상태입니다.
런큐에 큐잉되서 실행하거나 런큐에서 실행을 기다리는 프로세스들은 state가 TASK_RUNNING 이고 특정 조건에서 깨어나는 프로세스들은 state가 TASK_UNINTERRUPTIBLE 를 저장합니다.

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

프로세스 상태 별 세부 동작은 프로세스 스케줄러 시간에 상세히 다룹니다.

unsigned int flags;

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

PF_* 매크로들을 저장하는데 PF_* 매크로 중 눈여겨볼 만한 코드는 다음과 같습니다.
[https://elixir.bootlin.com/linux/v4.14.70/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 */

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 번째 줄 코드를 보면 종료할 프로세스가 커널 스레드이면 continue 문을 실행해 다른 프로세스를 실행하는 코드입니다.
10 if (tsk->flags & PF_KTHREAD)
11 continue;

이 방식으로 프로세스 동작 상태를 읽어서 예외 처리를 수행합니다.

int exit_state;

프로세스가 종료 상태를 저장합니다. 다음 매크로 중 하나 값을 저장합니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/include/linux/sched.h#L76]
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)

int exit_code;

프로세스를 종료 코드를 저장하는 멤버 변수입니다. do_exit() 함수 3번째 줄 코드에서 종료 코드를 저장합니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/exit.c]
1 void __noreturn do_exit(long code)
2 {
...
3 tsk->exit_code = code; 

do_exit() 함수를 호출할 때 인자로 다음과 같은 시그널이나 프로세스를 종료하는 옵션을 지정할 수 있습니다.
1 static void
2 __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
3   struct pt_regs *regs)
4 {
...
5 do_exit(SIGKILL);
6 }

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

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

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

#Reference(프로세스 관리)
프로세스 디버깅
   glibc fork 함수 gdb 디버깅



    핑백

    덧글

    댓글 입력 영역