Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

20113
1478
166890


[리눅스커널] 프로세스: struct thread_info 구조체 소개 4. 프로세스(Process) 관리

thread_info 구조체는 프로세스의 실행 흐름을 관리하는 중요한 정보를 저장합니다. 프로세스 스케줄링을 실행할 때 이전에 실행했던 레지스터 세트 정보와 프로세스 컨텍스트 정보를 이 구조체 필드에서 확인할 수 있습니다.

thread_info 구조체 선언부 분석

thread_info 구조체 선언부를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value[2]; /* TLS registers */
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
};

---
구조체 파일의 위치가 arch/arm/include이므로 ARM 아키텍처에 의존적인 코드임을 짐작할 수 있습니다.
---

이제부터 세부 필드를 분석하겠습니다.
먼저 프로세스 동작을 저장하는 flags 필드를 소개합니다.

unsigned long flags;

프로세스 동작을 관리하는 필드이며 다음 플래그를 저장합니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h
#define _TIF_SIGPENDING    (1 << TIF_SIGPENDING) 
#define _TIF_NEED_RESCHED  (1 << TIF_NEED_RESCHED) 
...
#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)  

위에서 정의된 매크로별 동작은 다음과 같습니다.

_TIF_SIGPENDING: 프로세스에 시그널이 전달된 경우 
_TIF_NEED_RESCHED: 프로세스가 선점될 조건 
_TIF_SYSCALL_TRACE: 시스템 콜 트레이스 설정

위에서 선언된 플래그는 맨 오른쪽에 선언된 플래그를 왼쪽으로 비트 시프트 연산한 결과입니다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h
#define TIF_SIGPENDING 0  
#define TIF_NEED_RESCHED 1
...
#define TIF_SYSCALL_TRACE 8

즉, 각 정수별로 왼쪽 비트 시프트 연산을 수행한 결괏값으로서 플래그 값을 확인하면 다음과 같습니다.

#define _TIF_SIGPENDING 1   /* 1 = (1 << 0) = (1 << TIF_SIGPENDING) */
#define _TIF_NEED_RESCHED   /* 2 = (1 << 1) = (1 << TIF_NEED_RESCHED) */
...
#define _TIF_SYSCALL_TRACE/* 256 = (1 << 8) = (1 << TIF_SYSCALL_TRACE) */

프로세스는 자신의 thread_info 구조체 중 flags 필드를 수시로 체크하면서 다음 내용을 체크합니다.

시그널이 자신에게 전달됐는지
선점 스케줄링될 조건인지
시스템 콜 트레이싱 조건인지

이번에는 프로세스의 컨택스트 정보를 저장하는 preempt_count 필드를 소개합니다.

int preempt_count;

프로세스 컨텍스트(인터럽트 컨텍스트, Soft IRQ 컨텍스트) 실행 정보와 프로세스가 선점 스케줄링될 조건을 저장합니다. thread_info 구조체에서 가장 중요한 필드입니다.

---
다음 절에서는 리눅스 커널 코드에서 이 preempt_count 필드를 어떻게 접근하고 이 필드를 어떻게 활용하는지 점검합니다.
---

다음은 프로세스가 실행 중인 CPU 번호를 저장하는 cpu 필드입니다.

__u32 cpu;
프로세스가 실행 중인 CPU 번호를 저장하는 필드입니다. 보통 raw_smp_process_id() 함수를 호출하면 CPU 번호를 알 수 있습니다.

이어서 프로세스의 일반적인 속성 정보를 저장하는 task 필드입니다.

struct task_struct *task;

실행 중인 프로세스의 태스크 디스크립터 주소를 저장합니다. thread_info 구조체의 주소만 알면 task 필드를 통해 프로세스의 태스크 디스크립터의 주소를 알 수 있습니다.

마지막으로 프로세스가 실행된 레지스터 세트 정보를 저장하는 cpu_context 필드를 보겠습니다.

struct cpu_context_save cpu_context;

프로세스 컨텍스트 정보입니다. 스케줄링되기 전 실행했던 레지스터 세트를 저장하는 필드입니다. 프로세스가 스케줄링되고 다시 실행될 때 cpu_context 필드에 저장된 레지스터를 프로세스 레지스터 세트로 로딩합니다.

cpu_context_save 구조체의 선언부를 보겠습니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h
struct cpu_context_save {
__u32 r4;
__u32 r5;
__u32 r6;
__u32 r7;
__u32 r8;
__u32 r9;
__u32 sl;
__u32 fp;
__u32 sp;
__u32 pc;
__u32 extra[2]; /* Xscale 'acc' register, etc */
};

cpu_context_save 구조체 선언부에 있는 r4부터 pc 필드는 ARM 코어의 레지스터들을 의미합니다. 프로세스가 컨텍스트 스위칭을 수행할 때 마지막에 실행 중인 레지스터 세트를 위 구조체 필드에 저장합니다. 


thread_info 구조체의 cpu_context 필드는 프로세스가 컨텍스트 스위칭을 수행할 때 접근합니다. 그러면 어느 함수에서 이 필드에 접근할까요?

컨텍스트 스위칭을 수행하는 핵심 함수는 switch_to()이며 세부 동작은 __switch_to 레이블에 어셈블리 코드로 구현돼 있습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/switch_to.h
#define switch_to(prev,next,last) \
do { \
__complete_pending_tlbi(); \
last = __switch_to(prev,task_thread_info(prev), task_thread_info(next)); \
} while (0)

__switch_to 레이블 코드를 보겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/kernel/entry-v7m.S
ENTRY(__switch_to)
1 .fnstart
2 .cantunwind
3 add ip, r1, #TI_CPU_SAVE
4 stmia ip!, {r4 - r11} @ Store most regs on stack
5 str sp, [ip], #4
6 str lr, [ip], #4
7 mov r5, r0
8 add r4, r2, #TI_CPU_SAVE
9 ldr r0, =thread_notify_head
10 mov r1, #THREAD_NOTIFY_SWITCH
11 bl atomic_notifier_call_chain
12 mov ip, r4
13 mov r0, r5
14 ldmia ip!, {r4 - r11} @ Load all regs saved previously
15 ldr sp, [ip]
16 ldr pc, [ip, #4]!
17 .fnend
ENDPROC(__switch_to)

컨텍스트 스위칭 과정에서 thread_info 구조체의 cpu_context 필드에 접근하는 코드를 보겠습니다. 4번째 줄 코드를 볼까요?

4 stmia ip!, {r4 - r11} @ Store most regs on stack

현재 실행 중인 프로세스 레지스터 세트를 thread_info 구조체의 cpu_context를에 저장합니다.

다음으로 14번째 줄 코드를 보겠습니다.

14 ldmia ip!, {r4 - r11} @ Load all regs saved previously

스케줄링 실행으로 다시 실행할 프로세스는 thread_info 구조체의 cpu_context에 저장된 레지스터 세트값을 ARM 레지스터로 로딩합니다.  

---
컨텍스트 스위칭을 수행할 때 실행 중인 레지스터를 저장하는 코드는 10장 ‘프로세스 스케줄링’의 10.1.3절에서 상세히 다룹니다.
---

thread_info 구조체 필드 확인

이번에는 앞에서 소개한 thread_info 구조체를 TRACE32 프로그램을 사용해 좀 더 자세히 확인해 보겠습니다.
1   (struct thread_info *) (struct thread_info*)0x9E4F8000 = 0x9E4F8000 -> (
2   (long unsigned int) flags = 2 = 0x2,
3    (int) preempt_count = 0x00010003,
4    (mm_segment_t) addr_limit = 3204448256 = 0xAF000000,
5    (struct task_struct *) task = 0x82FB2B80,
6    (struct exec_domain *) exec_domain = 0x81A1AFDC,
7    (__u32) cpu = 2 = 0x2,
8    (__u32) cpu_domain = 0 = 0x0,
9    (struct cpu_context_save) cpu_context = (
10      (__u32) r4 = 4056868736 = 0x4,
11      (__u32) r5 = 3539676032 = 0x92FB2B80,
12      (__u32) r6 = 3664491200 = 0x8A6BB2C0,
13      (__u32) r7 = 3688475136 = 0x8BD9AA00,
14      (__u32) r8 = 3248547128 = 0x0,
15      (__u32) r9 = 3496897024 = 0x6A00,
16      (__u32) sl = 3539677168 = 0x82FB2FF0,
17     (__u32) fp = 3461324268 = 0x8E4F9DEC,
18      (__u32) sp = 3461324200 = 0x9E4F9DA8,
19      (__u32) pc = 3237955160 = 0x80FF4658,
20      (__u32 [2]) extra = ([0] = 0 = 0x0, [1] = 0 = 0x0)),

2번째 줄을 봅시다.

2   (long unsigned int) flags = 2 = 0x2,

다음 매크로 선언부와 같이 flags가 2이니 _TIF_NEED_RESCHED입니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h
#define _TIF_NEED_RESCHED   /* 2 = (1 << 1) = (1 << TIF_NEED_RESCHED) */

이 정보로 현재 프로세스가 선점 스케줄링될 조건이라는 사실을 알 수 있습니다.

이어서 3번째 줄을 봅시다.

3    (int) preempt_count = 0x00010003,

preempt_count 필드의 6번째 바이트가 1이니 인터럽트 컨텍스트입니다. 또한 3이라는 값으로 현재 프로세스가 선점 스케줄링을 지연한 상태임을 알 수 있습니다. 

5번 줄은 태스크 디스크립터의 주소를 저장하는 task 필드입니다.

5    (struct task_struct *) task = 0x82FB2B80,

7번째 줄은 현재 프로세스가 실행 중인 CPU 번호를 나타냅니다.

7    (__u32) cpu = 2 = 0x2,

마지막으로 9~20번째 줄을 봅시다.

9    (struct cpu_context_save) cpu_context = (
10      (__u32) r4 = 4056868736 = 0x4,
11      (__u32) r5 = 3539676032 = 0x92FB2B80,
12      (__u32) r6 = 3664491200 = 0x8A6BB2C0,
13      (__u32) r7 = 3688475136 = 0x8BD9AA00,
14      (__u32) r8 = 3248547128 = 0x0,
15      (__u32) r9 = 3496897024 = 0x6A00,
16      (__u32) sl = 3539677168 = 0x82FB2FF0,
17     (__u32) fp = 3461324268 = 0x8E4F9DEC,
18      (__u32) sp = 3461324200 = 0x9E4F9DA8,
19      (__u32) pc = 3237955160 = 0x80FF4658,

cpu_context 필드는 컨텍스트 스위칭 전에 실행했던 레지스터 세트 정보를 저장합니다.

18      (__u32) sp = 3461324200 = 0x9E4F9DA8,
19      (__u32) pc = 3237955160 = 0x80FF4658,

19번째 줄을 보면 프로그램 카운터는 0x80FF4658이고 18번째 줄을 통해 스택 주소가 0x9E4F9DA8임을 알 수 있습니다.

다음 절에서는 thread_info 구조체가 어느 주소 공간에 있는지 살펴보겠습니다.

#프로세스

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


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

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


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

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

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





핑백

덧글

댓글 입력 영역