struct thread_info 구조체는 프로세스 실행 흐름을 관리하는 중요한 정보를 저장합니다. 프로세스 스케줄링 실행 시 이전에 실행했던 레지스터 정보와 프로세스 컨택스트 정보를 이 구조체 필드에서 확인할 수 있습니다.
struct thread_info 구조체 선언부 분석하기
struct thread_info 구조체 선언부를 볼까요?
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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 이므로 CPU 아키텍처에 의존적인 코드임을 짐작할 수 있습니다.
이제부터 세부 필드를 분석하겠습니다.
unsigned long flags;
프로세스 동작을 관리하는 필드이며 다음 플래그를 저장합니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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://github.com/raspberrypi/linux/blob/rpi-4.19.y/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) */
프로세스는 자신의 struct thread_info 구조체 flags 필드를 수시로 체크하면서 다음 내용을 체크합니다.
시그널이 자신에 전달됐는지
선점 스케줄링 될 조건인지
시스템 콜 트레이싱 조건인지
int preempt_count;
프로세스 컨택스트(인터럽트 컨택스트, Soft IRQ 컨택스트) 실행 정보와 프로세스가 선점 스케줄링될 조건을 저장합니다. struct thread_info 구조체에서 가장 중요한 필드입니다.
다음 소절에서 이 필드를 리눅스 커널 코드에서 어떻게 접근하고 활용하는지 점검합니다.
__u32 cpu;
프로세스가 실행 중인 CPU 번호를 저장하는 필드입니다. 보통 raw_smp_process_id() 함수를 호출하면 CPU 번호를 알 수 있습니다.
struct task_struct *task;
실행 중인 프로세스의 태스크 디스크립터 주소를 저장합니다.
struct cpu_context_save cpu_context;
프로세스 컨택스트 정보입니다. 스케줄링되기 전 실행했던 레지스터 세트를 저장하는 필드입니다. 프로세스가 스케줄링되고 다시 실행할 때 cpu_context 필드에 저장된 레지스터를 프로세스 레지스터 세트로 로딩합니다.
struct cpu_context_save 구조체 선언부를 보면 각각 필드는 레지스터 세트를 의미합니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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 */
};
struct cpu_context_save 구조체 선언부를 보면 필드들은 ARM 프로세스 레지스터들을 의미합니다. 프로세스가 컨택스트 스위칭을 수행할 때 마지막에 실행 중인 레지스터 세트를 위 구조체 필드에 저장합니다.
struct thread_info 구조체 cpu_context 필드는 프로세스가 컨택스트 스위칭을
수행할 때 접근합니다. 그러면 어느 함수에서 이 필드에 접근할까요?
컨택스트 스위칭을 수행하는 핵심 함수는 switch_to() 인데 세부 동작은 __switch_to 레이블에 어셈블리 코드로 구현되어 있습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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://github.com/raspberrypi/linux/blob/rpi-4.19.y/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)
컨택스트 스위칭 과정에서 struct thread_info 구조체 cpu_context 필드에 액새스하는 코드를 보겠습니다.
4번째 줄 코드를 볼까요?
4 stmia ip!, {r4 - r11} @ Store most regs on stack
현재 실행 중인 프로세스 레지스터 세트를 struct thread_info 구조체 cpu_context를 저장합니다.
다음 14번째 줄 코드를 보겠습니다.
14 ldmia ip!, {r4 - r11} @ Load all regs saved previously
스케줄링 실행으로 다시 실행할 프로세스는 struct thread_info 구조체 cpu_context에 저장된 레지스터 세트값을 ARM 레지스터로 로딩합니다.
컨택스트 스위칭할 때 실행 중인 레지스터를 저장하는 코드는 10장 스케줄링(10.10.3)에서 상세히 다룹니다.
struct thread_info 구조체 필드 확인해보기
이번에는 위에서 소개한 struct thread_info 구조체를 더 자세히 확인해 보겠습니다.
1 (struct thread_info *) (struct thread_info*)0x9E4F8000 = 0x9E4F8000 -> (
2 (long unsigned int) flags = 1 = 0x1,
3 (int) preempt_count = 2097155 = 0x00100003,
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://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/include/asm/thread_info.h]
#define _TIF_NEED_RESCHED /* 2 = (1 << 1) = (1 << TIF_NEED_RESCHED) */
이 정보로 다음 사실을 알 수 있습니다.
현재 프로세스가 선점 스케줄링될 조건이다.
이어서 3번째 줄을 봅시다.
3 (int) preempt_count = 2097155 = 0x00100003,
preempt_count 필드가 0x00100003이니 인터럽트 컨택스트이란 사실을 알 수 있습니다. 마지막 3이란 값으로 현재 프로세스가 선점 스케줄링을 지연한 상태임을 알 수 있습니다.
5번 줄을 볼 차례입니다.
5 (struct task_struct *) task = 0xD2FB2B80,
태스크 디스크립터 주소를 저장하는 task 필드입니다.
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 임을 알 수 있습니다.
다음 절에서는 struct thread_info 구조체가 어느 주소 공간에 있는지 살펴보겠습니다.
최근 덧글