Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

175162
807
85243


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() 함수를 호출합니다. 


"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
혹시 글을 읽고 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려 드리겠습니다!"


Reference(프로세스 관리)
4.9 프로세스 컨택스트 정보는 어떻게 저장할까?
 4.9.1 컨택스트 소개
 4.9.2 인터럽트 컨택스트 정보 확인하기
 4.9.3 Soft IRQ 컨택스트 정보 확인하기
 4.9.4 선점 스케줄링 여부 정보 저장
4.10 프로세스 디스크립터 접근 매크로 함수
 4.10.1 current_thread_info()
 4.10.2 current 매크로란
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 


#Reference 시스템 콜

Reference(워크큐)
워크큐(Workqueue) Overview


핑백

덧글

댓글 입력 영역