Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

123199
1107
135851


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

프로세스의 속성 정보를 표현하는 가장 중요한 자료구조는 무엇일까요? 정답은 태스크 디스크립터를 나타내는 task_struct 구조체입니다. 리눅스 커널에서 가장 중요한 자료구조이니 잘 알아두면 좋습니다.

---
TCB(Task Control Block)라는 용어를 들어본 적이 있나요? 임베디드 시스템에서 태스크 혹은 프로세스 정보를 표현하는 자료구조입니다. 

그럼 리눅스 커널에서 프로세스 정보를 표현하는 자료구조는 무엇일까요? 여기서 임베디드 시스템에서 말하는 태스크와 리눅스 커널의 프로세스는 같은 개념으로 봐야 합니다. 
---

그럼 task_struct 구조체에 접근해서 프로세스 정보를 출력하는 한 가지 예를 들겠습니다. 라즈베리 파이에서 터미널을 열고 다음와 같이 “ps –ely”라는 명령어를 입력하면 프로세스 목록을 볼 수 있습니다.

root@raspberrypi:/home/pi/dev_raspberri # ps -ely
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S     0     1     0  0  80   0  5964  7007 SyS_ep ?        00:00:02 systemd
S     0     2     0  0  80   0     0     0 kthrea ?        00:00:00 kthreadd
I     0     4     2  0  60 -20     0     0 worker ?        00:00:00 kworker/0:0H
I     0     6     2  0  60 -20     0     0 rescue ?        00:00:00 mm_percpu_wq
S     0     7     2  0  80   0     0     0 smpboo ?        00:00:00 ksoftirqd/0
I     0     8     2  0  80   0     0     0 rcu_gp ?        00:00:00 rcu_sched
I     0     9     2  0  80   0     0     0 rcu_gp ?        00:00:00 rcu_bh

맨 오른쪽에 systemd, kthreadd 순서로 프로세스 이름을 볼 수 있고, PID는 1, 2, 4번으로 확인할 수 있습니다. 이 프로세스 정보는 태스크 디스크립터 필드에 저장된 값을 읽어서 이를 표현하는 것입니다.

리눅스 시스템에서 구동 중인 프로세스 목록은 어떻게 출력할까요? 프로세스를 관리하는 태스크 디스크립터의 연결 리스트(init_task.tasks)에 접근해서 등록된 프로세스를 출력합니다.

처음 리눅스 커널의 소스코드를 분석하기 시작할 때 어떤 코드부터 분석해야 할지를 많이 고민합니다. 소스코드 분석을 시작할 때 태스크 디스크립터를 구성하는 각 필드가 리눅스 커널의 어느 코드에서 바뀌게 되는지 점검하는 것도 좋습니다. 리눅스 커널은 프로세스를 중심으로 중요한 데이터를 저장하고 로딩하기 때문입니다. 처음부터 태스크 디스크립터의 필드들을 모두 무리해서 외우지 말고 각 필드가 무슨 의미인지 코드를 이해하면서 자연히 체득하는 것이 좋습니다.

태스크 디스크립터를 각 항목별로 살펴봅시다. 

프로세스를 식별하는 필드

먼저 프로세스를 식별하는 필드를 살펴보겠습니다.

char comm[TASK_COMM_LEN];

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

터미널에서 "ps -ely" 명령어를 입력해 볼까요? 

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

출력 결과에서 맨 오른쪽 부분에서 systemd, kthreadd, kworker/0:0H가 보입니다. 그럼 프로세스 이름은 어떤 자료구조에 접근해 출력하는 것일까요? 프로세스 이름들은 태스크 디스크립터를 나타내는 task_struct 구조체의 comm 필드에 접근해서 출력하는 것입니다(참고로 프로세스 이름은 set_task_comm() 함수를 호출해서 지정할 수 있습니다).

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


여기서 배운 내용을 활용해 디버깅 코드를 하나 작성해 봅시다. 다음 코드에서 + 기호로 표시된 코드가 추가되는 패치 코드입니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/sched/core.c
1 static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags,
2    struct rq_flags *rf)
3 {
4 check_preempt_curr(rq, p, wake_flags);
5 p->state = TASK_RUNNING;
6 trace_sched_wakeup(p);
7
8  + if (!strcmp(p->comm, "kthreadd")) {
9  + printk("[+][%s] wakeup kthreadd process \n", current->comm);
10 + dump_stack();
11 + }
12
13 #ifdef CONFIG_SMP
14 if (p->sched_class->task_woken) {
15 /*

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

위 코드는 kthreadd 프로세스를 깨울 때 함수 호출 흐름을 커널 로그로 출력하기 위한 코드입니다.

이어서 프로세스를 식별하는 정수형 값인 pid를 확인하겠습니다.

pid_t pid;

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

이번에는 스레드의 그룹 아이디를 살펴봅시다.

pid_t tgid;

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


#프로세스

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


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

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



    핑백

    덧글

    댓글 입력 영역