컨텍스트 스위칭이란 (Narrow Down)

  • 컨텍스트는 레지스터 세트이다
  • 레지스터 세트를 스위칭하는 것이 컨텍스트 스위칭이다.

컨텍스트 스위칭이란

  • 태스크 스케줄링의 아키텍처 관점의 동작
  • CPU 아키텍처 마다 구현 방식이 다름
  • 어셈블리 명령어로 구현

컨텍스트 스위칭 관련 자료 구조

  • Armv8 아키텍처 기반 리눅스 커널
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; // 프로그램 카운터
}

Armv7 아키텍처 기반(32비트) 리눅스 커널

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 프로세스

    • prev는 현재 CPU에서 실행 중인 프로세스를 의미하며, 컨텍스트 스위칭으로 CPU를 비
      울 프로세스
    • prev 프로세스의 current->thread.cpu_context에 실행 정보가 채워져 있는 레지스터
      세트를 저장
  • next 프로세스

    • next는 스케줄러가 다음에 실행할 프로세스로 선택한 프로세스
    • next 프로세스의 current->thread.cpu_context에 접근해 레지스터 세트 정보를 CPU
      코어의 레지스터로 로딩

__schedule() 함수 구현부

  • 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); // 컨텍스트 스위칭 실행
...
}
}

context_switch() 함수 구현부

컨텍스트 스위칭 코드 분석
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);
}

switch_to와 cpu_switch_to 레이블

  • X0 레지스터: prev 프로세스의 태스크 디스크립터 주소
  • X1 레지스터: next 프로세스의 태스크 디스크립터 주소
컨텍스트 스위칭 코드 분석
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;
}

cpu_switch_to 레이블 구현부 // 어셈블리 명령어

- 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

0개의 댓글