crash64> struct task_struct.thread
struct task_struct {
[6656] struct thread_struct thread;
}
crash64> struct thread_struct
struct thread_struct {
struct cpu_context cpu_context;
...
crash64> struct cpu_context
struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21; // 19~21 제너럴 퍼포즈 레지스터
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp;
unsigned long sp; // 스택 포인터
unsigned long pc; // 프로그램 카운터
}
https://elixir.bootlin.com/linux/v5.15.30/arch/arm/include/asm/thread_info.h
struct cpu_context_save { // 구조체 선언부,
__u32 r4;
__u32 r5;
__u32 r6;
__u32 r7; // 범용 레지스터 r0~r14
__u32 r8;
__u32 r9;
__u32 sl;
__u32 fp;
__u32 sp;
__u32 pc;
__u32 extra[2]; /* Xscale 'acc' register, etc */
}; // 위의 값들을 통해 컨텍스트 스위칭을 한다.
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 =>
...
struct cpu_context_save cpu_context; /* cpu context */
vruntime이라는 가상실행시간으로 프로세스의 우선순위 관리
prev 프로세스
next 프로세스
컨텍스트 스위칭 코드 분석
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/sched/core.c
static void __sched notrace __schedule(unsigned int sched_mode)
{
struct task_struct *prev, *next;
...
if (likely(prev != next)) {
...
trace_sched_switch(sched_mode & SM_MASK_PREEMPT, prev, next); // ftrace 이벤트 트레이싱
/* Also unlocks the rq: */
rq = context_switch(rq, prev, next, &rf); // 컨텍스트 스위칭 실행
...
}
}
컨텍스트 스위칭 코드 분석
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/sched/core.c
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
prepare_task_switch(rq, prev, next);
...
rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);
prepare_lock_switch(rq, next, rf);
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev); // 첫번째 인자로 컨텍스트 스위칭 됨, 새롭게 실행할 태스크 디스크립터 주소,
return finish_task_switch(prev);
}
out/kernel/sched/core.i
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
prepare_task_switch(rq, prev, next);
...
rq->clock_update_flags &= ~(0x02|0x01);
prepare_lock_switch(rq, next, rf);
do { ((prev) = __switch_to((prev), (next))); } while (0); //
__asm__ __volatile__("": : :"memory");
return finish_task_switch(prev);
}
컨텍스트 스위칭 코드 분석
https://elixir.bootlin.com/linux/v5.15.30/source/include/asm-generic/switch_to.h
#define switch_to(prev, next, last) \
do { \
((last) = __switch_to((prev), (next))); \
} while (0)
https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/process.c
__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
struct task_struct *next)
{
struct task_struct *last;
...
/* the actual thread switch */
last = cpu_switch_to(prev, next); // 어셈블리 명령어로 구현되어 있음, prev는 x0 . next는 x1
return last;
}
- 03번째 줄: 스케줄링되는 프로세스: thread 필드의 cpu_context 필드를 x8에 저장
- 05~07번째 줄: 레지스터 세트를 스케줄링되는 프로세스의 thread의 cpu_context 필드에 저장
- 09번째 줄: 다음에 실행될 프로세스: thread의 cpu_context 필드를 x8에 저장
- 10~13번째 줄: 다음에 실행될 프로세스: thread의 cpu_context 필드에 저장된 레지스터 세트 값을
Arm 코어의 레지스터에 로딩
컨텍스트 스위칭 코드 분석
https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/entry.S
01 SYM_FUNC_START(cpu_switch_to) // x0는 prev ,
02 mov x10, #THREAD_CPU_CONTEXT
03 add x8, x0, x10 // x0 + x10
04 mov x9, sp
05 stp x19, x20, [x8], #16 // store callee-saved registers
06 stp x21, x22, [x8], #16 //
...
07 stp x29, x9, [x8], #16
08 str lr, [x8]
09 add x8, x1, x10 // x1 + x10, 넥스트 프로세스에
10 ldp x19, x20, [x8], #16 // restore callee-saved registers
11 ldp x21, x22, [x8], #16
12 ldp x23, x24, [x8], #16
13 ldp x25, x26, [x8], #16