CPU에서 실행 중인 프로세스를 비우고 새로운 프로세스를 CPU에서 실행시키는 과정을 컨택스트 스위칭이라고 합니다.
컨택스트 스위치란
다음 그림을 보면서 컨택스트 스위칭란 용어에 대해서 살펴봅시다.

CPU에서 E란 프로세스가 실행 중이라고 가정합시다. 어떤 프로세스가 CPU에서 실행 중이란 의미는 CPU 레지스터 세트에 프로세스 실행 정보가 채워져 있다는 의미입니다. 어떤 코드가 실행 중이란 의미는 ARM 코어 PC(프로그램 카운터)에 현재 실행 중인 주소를 가르키고 있다는 것입니다.
E란 프로세스는 계속 ARM 코어에서 실행을 하려고 하지만 A, B, C, D 프로세스들이 실행 요청을 합니다. 스케줄러는 ARM 코어에서 실행 중인 E란 프로세스와 A, B, C, D 프로세스들과 우선 순위를 비교합니다.
만약 E 프로세스보다 A란 프로세스가 우선 순위가 높으면 스케줄러는 어떻게 할까요?
이 경우 E란 프로세스를 ARM 코어에서 빼낸 후 A란 프로세스를 ARM 코어에 채워줍니다.
조금 더 구체적으로 설명을 드리면 ARM 코어에서 실행 중인 E란 프로세스 실행 정보로 채워져 있는 ARM 코어 레지스터 세트를 비운 다음 A 프로세스 레지스터 세트를 다시 ARM 코어 레지스터에 채워 줍니다.
이 과정을 컨택스트 스위칭이라고 말합니다.
컨택스트 스위치 자료구조란
리눅스 커널에서 컨택스트 스위칭 세부 동작을 알기 위해서 context_switch() 함수를 분석할 필요가 있습니다. context_switch() 함수를 분석하기 전 컨택스트 스위치란 용어의 의미를 조금 더 생각해 봅시다.
스케줄링 동작의 핵심인 컨택스트 스위칭 세부 동작을 알아봅시다.
컨택스트 스위치란 용어는 컨택스트 + 스위치란 단어가 합쳐져 있습니다.
컨택스트를 바꾼다는 의미입니다. 여기서 컨택스란 용어는 무슨 뜻일까요?
컨택스트는 프로세스가 실행 중인 그 자체를 의미합니다. 이 표현은 추상적이라 이해하기 어려울 수도 있습니다. 프로세스 실행 그 자체를 어떻게 표현할까요? 그것은 레지스터 세트입니다. CPU 레지스터 세트가 프로세스 실행 자체를 표현하기 때문입니다.
어떤 프로세스가 CPU에서 실행을 하면 CPU 레지스터 세트에 프로세스가 실행 중인 코드와 함수 정보가 채워지게 됩니다.
다음 정보는 ARM 코어에서 실행 중인 프로세스의 레지스터 세트입니다.
pc : [<80f65224>] lr : [<80f65bcc>] psr: 0x80f655ac
sp : 0x9b7dfce8 ip : 0x9b7dfd74 fp : 0x9b7dfd64
r10: 0x0 r9 : 0x9f89ea00 r8 : 0x9db8b200
r7 : 0x81709294 r6 : 0x9eb07000 r5 : 0x828ea000 r4 : 0xa6b46780
r3 : 0x00000000 r2 : 0x036db918 r1 : 0x00000089 r0 : 0x72fbe0b8
ARM 코어 레지스터 세트에는 ARM 코어에서 실행 중인 프로세스의 코드 정보로 가득차 있습니다.
그럼 위 레지스터 세트는 어떤 코드를 실행 일 때 정보일까요?
위 레지스터 세트를 채우면서 실행 중인 주인공은 라즈베리파이에서 볼 수 있는 "lxpanel" 프로세스이며 다음 콜스택으로 실행 중이었습니다.
1 crash> bt 718
2 PID: 718 TASK: 9eb07000 CPU: 2 COMMAND: "lxpanel"
3 #0 [<80f65224>] (__schedule) from [<80f65bcc>]
4 #1 [<80f65b80>] (schedule) from [<801e059c>]
5 #2 [<801e0480>] (futex_wait_queue_me) from [<801e11b8>]
6 #3 [<801e10a0>] (futex_wait) from [<801e2e14>]
7 #4 [<801e2cf4>] (do_futex) from [<801e3a58>]
8 #5 [<801e3938>] (sys_futex) from [<80108f20>]
위 콜스택은 리눅스 코어 덤프를 로딩해서 프로세스 정보를 디버깅할 수 있는 크래시 유틸리티 프로그램에서 확인한 내용입니다. "bt [pid]" 명령어를 입력하면 프로세스의 콜스택을 확인할 수 있습니다.
레지스터 세트에서 프로그램 카운터는 80f65224이며 __schedule() 함수 내 주소를 의미합니다.
ARM 코어 프로그램 카운터 레지스터에 어떤 함수 주소가 저장돼 있다는 것을 무엇을 의미할까요?
이는 현재 ARM 코어에서 해당 주소에 있는 어셈블리 코드를 실행 중이라고 봐야 합니다.
프로그램 카운터에 코드 주소를 지정하면 ARM ALU에서 어셈블리 코드에 대한 사칙연산을 수행합니다.
컨택스트란 CPU에서 실행 중인 프로세스의 레지스터 세트를 의미합니다.
컨택스트란 실행 그 자체이면 실행 중인 프로세스 정보가 채워진 CPU 레지스터 세트라고 설명을 드렸습니다. 이 내용을 조금 더 확장하면, 컨택스트 스위칭이란 CPU 레지스터 세트를 바꾼다고 볼 수 있습니다.
컨택스트 스위칭 자료구조는 무엇일까?
그러면 컨택스트 스위칭으로 CPU 레지스터 세트를 어딘가에 저장할 것입니다.
이 과정에서 어떤 자료구조가 필요한 것입니다. 그러면 컨택스트, 즉 CPU 레지스터 세트를 표현한 자료구조는 무엇일까요?
다음 8 번째 줄 코드와 같이 struct thread_info 구조체에서 cpu_context 필드를 볼 수 있습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/arch/arm/include/asm/thread_info.h]
1 struct thread_info {
2 unsigned long flags; /* low level flags */
3 int preempt_count; /* 0 => preemptable, <0 => bug */
4 mm_segment_t addr_limit; /* address limit */
5 struct task_struct *task; /* main task structure */
6 __u32 cpu; /* cpu */
7 __u32 cpu_domain; /* cpu domain */
8 struct cpu_context_save cpu_context; /* cpu context */
커널은 프로세스가 생성될 때 함수를 호출하고 실행할 수 있는 스택 공간을 부여하는데,
프로세스 스택 최상단 주소 공간에 struct thread_info 필드 데이터가 있습니다.
8 번째 줄 코드 오른쪽 주석문을 보면 역시 /* cpu context */ 이란 주석문을 볼 수 있습니다.
struct cpu_context_save 구조체인 cpu_context 필드가 컨택스트인 레지스터 세트를 표현합니다.
이번에는 struct cpu_context_save 구조체 코드를 조금 더 분석해 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/arch/arm/include/asm/thread_info.h]
1 struct cpu_context_save {
2 __u32 r4;
3 __u32 r5;
4 __u32 r6;
5 __u32 r7;
6 __u32 r8;
7 __u32 r9;
8 __u32 sl;
9 __u32 fp;
10 __u32 sp;
11 __u32 pc;
12 __u32 extra[2]; /* Xscale 'acc' register, etc */
13 };
struct cpu_context_save 구조체 필드를 보겠습니다.
2~7번째 줄 코드를 보면 r4, r5, r6, r7, r8, r9 이란 필드를 볼 수 있습니다.
또한 8~11번째 줄 코드에서 sl, fp, sp, pc 란 필드가 있습니다.
각각 필드들은 CPU에서 실행했던 ARM 레지스터 세트를 의미합니다.
코어 덤프에서 ARM 레지스터 세트를 어디에 저장하는지 확인해 봅시다.
1 crash> ps 718
2 PID PPID CPU TASK ST %MEM VSZ RSS COMM
3 > 718 1 2 9eb07000 WA 0.1 13540 1704 lxpanel
3 번째 줄 정보로 보아 프로세스 태스크 디스크립터 주소가 9eb07000 이라는 사실을 알 수 있습니다. 참고로, TASK란 필드는 프로세스 태스크 디스크립터를 의미합니다.
이번엔 다음 명령어를 입력해서 9eb07000 주소를 태스트 디스크립터 구조체로 캐스팅해보겠습니다.
1 crash> struct task_struct 9eb07000
2 struct task_struct {
3 state = 256,
4 stack = 0x9b7de000,
5 usage = {
6 counter = 3
7 },
4 번째 줄 정보로 보아 프로세스 스택 최상단 주소는 0x9b7de000이란 정보를 알 수 있습니다.
이번엔 프로세스 스택 최상단 주소인 0x9b7de000 를 struct thread_info.cpu_context 필드로 캐스팅해보겠습니다.
1 crash> struct thread_info.cpu_context 0x9b7de000
2 cpu_context = {
3 r4 = 0xa6b46780,
4 r5 = 0x828ea000,
5 r6 = 0x9eb07000,
6 r7 = 0x81709294,
7 r8 = 0x9db8b200,
8 r9 = 0x9f89ea00,
9 sl = 0x0,
10 fp = 0x9b7dfd64,
11 sp = 0x9b7dfce8,
12 pc = 0x80f655ac,
13 extra = {0x0, 0x0}
14 }
출력 메시지를 보면 cpu_context 세부 필드에 레지스터 세트 정보를 확인할 수 있습니다.
r4~pc 필드가 해당 프로세스가 실행 중일 때 ARM 레지스터 세트 값입니다.
달리 보면 이 프로세스가 컨택스트 스위칭을 실행할 때 저장한 ARM 레지스터 세트 값입니다.
그런데 pc 필드를 보면 0x80f655ac 주소를 확인할 수 있습니다.
0x80f655ac 주소의 정체는 무엇일까요? 0x80f655ac 주소 근처 코드를 보면 확인할 수 있습니다.
1 crash> dis 0x80f6559c 100
2 0x80f6559c <__schedule+0x37c>: ldr r2, [r5, #4]
3 0x80f655a0 <__schedule+0x380>: mov r0, r6
4 0x80f655a4 <__schedule+0x384>: ldr r1, [r6, #4]
5 0x80f655a8 <__schedule+0x388>: bl 0x8010f96c <__switch_to>
6 0x80f655ac <__schedule+0x38c>: bl 0x80158eec <finish_task_switch>
5 번째 줄 코드를 보면 __switch_to() 함수를 호출하는 어셈블리 코드입니다.
즉, 컨택스트 스위칭될 때 함수 주소라 할 수 있습니다.
5 번째 줄 코드는 C 코드로는 어떤 형태일까요?
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/sched/core.c]
1 static __always_inline struct rq *
2 context_switch(struct rq *rq, struct task_struct *prev,
3 struct task_struct *next, struct rq_flags *rf)
4{
...
5 /* Here we just switch the register state and the stack. */
6 switch_to(prev, next, prev);
7 barrier();
8
9 return finish_task_switch(prev);
10 }
위 코드 기준으로 context_switch() 함수 6 번째 줄 코드입니다.
다시 컨택스트 스위칭이란 용어 의미를 정리합시다. 먼저 prev와 next 프로세스에 대해 소개합니다.

테이블에 소개된 내용을 정리하면 prev 프로세스는 CPU를 비울 프로세스, next는 다음에 CPU를 점유하면서 실행할 프로세스입니다.
prev와 next 프로세스 용어를 써서 컨택스트 스위칭에 대한 용어를 정리하면 다음과 같습니다.

컨택스트 스위칭이란 다음과 같은 문장으로 정리할 수 있겠습니다.
CPU에서 실행 중인 프로세스 정보로 채워진 CPU 레지스터 세트를 프로세스 스택 공간에 저장하고 다음에 실행할 프로세스의 레지스터 세트를 스택 공간에서 로딩해 CPU 레지스터 세트에 채우는 동작입니다.
CPU에서 실행 중인 프로세스 정보로 채워진 CPU 레지스터 세트를 프로세스 스택 공간에 저장하고 다음에 실행할 프로세스의 레지스터 세트를 스택 공간에서 로딩해 CPU 레지스터 세트에 채우는 동작입니다.
context_switch() 함수 분석
이번에는 current_thead_info()->cpu_context 필드 중심으로 컨택스트 스위칭 코드 동작을 알아보겠습니다. 이를 위해 context_switch() 함수에 전달하는 인자를 살펴볼 필요가 있습니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/core.c#L2750]
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
2~3번째 인자인 struct task_struct *prev와 struct task_struct *next를 살펴보겠습니다.
struct task_struct *prev
+ 현재 실행 중인 프로세스
+ 스케줄링으로 CPU를 비우고 휴면에 진입할 프로세스
struct task_struct *next
+ 다음에 실행할 프로세스
+ 스케줄러가 다음에 실행할 프로세스로 선택
이전 절에 봤던 ftrace 로그를 다시 살펴보면서 prev 인자의 의미를 확인합시다.
lxpanel-718 [002] d... 7831.739824: sched_switch: prev_comm=lxpanel prev_pid=718 prev_prio=120 prev_state=D ==> next_comm=Xorg next_pid=552 next_prio=120
먼저 가장 왼쪽 메시지를 보면 현재 CPU2에서 lxpanel(pid-718) 프로세스가 실행 중 이란 정보를 확인할 수 있습니다.
휴면에 진입할 프로세스는 lxpanel(pid=718) 이고 다음에 실행될 프로세스는 Xorg(pid=552) 인 것입니다.
struct task_struct *prev: prev_comm=lxpanel prev_pid=718 prev_prio=120
struct task_struct *next: ext_comm=Xorg next_pid=552 next_prio=120
여기서 한 가지 궁금한 점이 생깁니다.
Q: 현재 스케줄링 관련 코드는 어느 프로세스가 실행하고 있을까요?
schedule() 함수에서 prev로 지정된 함수입니다.
ftrace 로그를 봐도 마찬가지 정보를 확인할 수 있습니다.
lxpanel-718 [002] d... 7831.739824: sched_switch: prev_comm=lxpanel prev_pid=718 prev_prio=120 prev_state=D ==> next_comm=Xorg next_pid=552 next_prio=120
스케줄링을 실행하는 주체가 프로세스입니다. 프로세스는 자신에게 주어진 특정 미션을 수행하기 위해 생성됐다고 볼 수 있습니다. 그런데 프로세스가 해야 할 일에 스케줄링 동작이 추가된 것입니다. 따라서 스케줄링이 자주 실행하면 프로세스 실행에 부하를 줄 수 밖에 없습니다.
소프트웨어 세상에서 가장 어려운 과제가 최적화인 것 같습니다.
하지만 스케줄링을 적게 실행하면서 프로세스를 최대한 골고루 실행하는 것이 스케줄러의 역할이자 숙명인 것 같습니다. 트레이드 오프란 변명은 통하지 않습니다.
컨택스트 스위칭 동작을 휴면에 진입할 프로세스와 다음에 실행할 프로세스 입장에서 생각해봅시다.
먼저 휴면에 진입할 프로세스는 실행 정보를 어딘가에 저장해야 합니다. 다음에 스케줄러가 자신을 깨우면 다시 실행할 정보입니다. 이 실행 정보는 current_thread_info()->cpu_context 필드에 저장합니다. 여기서 실행 정보는 레지스터 세트를 의미합니다.
이번에 컨택스트 스위칭 동작을 휴면에 진입할 프로세스와 다음에 실행할 프로세스 입장에서 생각해봅시다.
먼저 휴면에 진입할 프로세스(struct task_struct *prev) 관점으로 생각해 봅시다.
휴면에 진입할 프로세스는 실행 정보를 어딘가에 저장해야 합니다. 다음에 스케줄러가 자신을 깨우면 다시 실행할 정보입니다. 이 실행 정보는 current_thread_info->cpu_context 필드에 저장합니다. 여기서 실행 정보는 레지스터 세트를 의미합니다.
위에서 언급한 ftrace를 예로 들겠습니다.
struct task_struct *prev: prev_comm=lxpanel prev_pid=718 prev_prio=120
lxpanel 프로세스는 자신의 current_thread_info()->cpu_context 필드에 현재 실행 중인 레지스터 세트를 저장해야 합니다.
이번에는 다음에 실행할 프로세스(struct task_struct *next) 관점으로 생각해 봅시다.
다시 실행할 프로세스는 이전에 자신이 실행했던 정보를 ARM 레지스터에 로딩해야 합니다.
어디서 이전에 자신이 실행했던 정보를 로딩할까요? current_thead_info()->cpu_context 필드에서 로딩합니다. 이전에 휴면에 진입할 때 저장했던 정보입니다.
위에서 언급한 ftrace를 예로 들겠습니다.
struct task_struct *next: ext_comm=Xorg next_pid=552 next_prio=120
Xorg 프로세스는 자신의 current_thread_info()->cpu_context 필드에 있는 레지스터 세트를 ARM 레지스터로 로딩합니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/arch/arm/include/asm/thread_info.h]
1 struct cpu_context_save {
2 __u32 r4;
3 __u32 r5;
4 __u32 r6;
5 __u32 r7;
6 __u32 r8;
7 __u32 r9;
8 __u32 sl;
9 __u32 fp;
10 __u32 sp;
11 __u32 pc;
12 __u32 extra[2]; /* Xscale 'acc' register, etc */
13 };
ARM 레지스터 중에 PC(Program Counter)가 있습니다. 이 레지스터는 현재 프로세스가 실행 중인 코드 주소를 저장합니다. 이 주소는 11번째 줄 pc 필드에 저장돼 있습니다.
PC 못지 않게 중요한 레지스터가 있습니다. 스택(Stack) 포인터 레지스터라고 하며 ARM에서 r13입니다. 이 주소는 10 번째 줄 sp 필드에서 확인할 수 있습니다.
context_switch() 함수부터 분석할 함수는 다음과 같습니다.
context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next, struct rq_flags *rf)
switch_to(prev, next, prev);
__switch_to(prev,task_thread_info(prev), task_thread_info(next));
실제 컨택스트 스위칭을 실행하는 코드는 __switch_to 이란 레이블에서 확인할 수 있습니다.
__switch_to 레이블은 어셈블리 코드로 구현돼 있습니다.
컨택스트 스위칭은 아키텍처에 의존적인 동작입니다. ARM64/x86 아키텍처 별로 프로세스 별로 실행된 레지스터를 로딩하거나 저장하는 동작이 다릅니다.
context_switch() 함수 분석을 시작하기 전에 인자와 반환값을 알아보겠습니다.
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
struct rq* 주소를 반환합니다.
각각 인자의 의미는 다음 테이블에서 확인할 수 있습니다.
struct rq *rq // 런큐 주소
struct task_struct *prev, // 휴면될 프로세스 태스트 디스크립터 주소
struct task_struct *next // 다음 프로세스로 실행할 태스크 디스크립터 주소,
struct rq_flags *rf // 런큐 플래그
컨택스트 스위칭에 실행 흐름에 초점을 맞춰 코드 분석을 하겠습니다.
다음 context_switch() 함수 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/core.c#L2750]
1 static __always_inline struct rq *
2 context_switch(struct rq *rq, struct task_struct *prev,
3 struct task_struct *next, struct rq_flags *rf)
4 {
...
5 /* Here we just switch the register state and the stack. */
6 switch_to(prev, next, prev);
7 barrier();
8
9 return finish_task_switch(prev);
10 }
context_switch() 함수에서 switch_to() 함수 호출 이전에 메모리 디스크립터는 백업하고 로딩하는 코드 분석은 생략합니다.
위 코드 6번째 줄 코드를 보겠습니다.
switch_to() 함수를 호출해서 새로운 프로세스로 실행 시작 준비를 합니다.
switch_to() 함수 구현부를 보면 다음과 같은 코드로 치환됩니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/arch/arm/include/asm/switch_to.h]
1 extern struct task_struct *__switch_to(struct task_struct *, struct thread_info *, struct thread_info *);
3
4 #define switch_to(prev,next,last)
5 do {
6 __complete_pending_tlbi();
7 last = __switch_to(prev,task_thread_info(prev), task_thread_info(next));
8 } while (0)
7 번째 줄 코드를 보면 __switch_to() 함수를 호출합니다.
여기서 __switch_to() 함수에 전달하는 인자를 정리합시다.
prev: 휴면에 진입할 프로세스 태스크 디스크립터 주소
task_thread_info(prev): 휴면에 진입할 프로세스 current_thread_info() 주소
task_thread_info(next): 다음 실행할 프로세스 current_thread_info() 주소
__switch_to() 코드를 검색하니 구현부는 어셈블리 코드입니다.
[https://elixir.bootlin.com/linux/v4.14.49/source/arch/arm/kernel/entry-armv.S]
/*
* Register switch for ARMv3 and ARMv4 processors
* r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
* previous and next are guaranteed not to be the same.
*/
ENTRY(__switch_to)
UNWIND(.fnstart )
UNWIND(.cantunwind )
add ip, r1, #TI_CPU_SAVE
ARM( stmia ip!, {r4 - sl, fp, sp, lr} ) @ Store most regs on stack
THUMB( stmia ip!, {r4 - sl, fp} ) @ Store most regs on stack
THUMB( str sp, [ip], #4 )
THUMB( str lr, [ip], #4 )
ldr r4, [r2, #TI_TP_VALUE]
ldr r5, [r2, #TI_TP_VALUE + 4]
#ifdef CONFIG_CPU_USE_DOMAINS
mrc p15, 0, r6, c3, c0, 0 @ Get domain register
str r6, [r1, #TI_CPU_DOMAIN] @ Save old domain register
ldr r6, [r2, #TI_CPU_DOMAIN]
#endif
코드를 보기 어려우니 바이너리 유틸리티로 어셈블리 코드를 봅시다.
1 80707db8 <__switch_to>:
2 80707db8: e281c018 add ip, r1, #24
3 80707dbc: e8ac6ff0 stmia ip!, {r4, r5, r6, r7, r8, r9, sl, fp, sp, lr}
4 80707dc0: e592405c ldr r4, [r2, #92] ; 0x5c
5 80707dc4: e5925060 ldr r5, [r2, #96] ; 0x60
6 80707dc8: ee1d7f50 mrc 15, 0, r7, cr13, cr0, {2}
7 80707dcc: ee0d4f70 mcr 15, 0, r4, cr13, cr0, {3}
8 80707dd0: ee0d5f50 mcr 15, 0, r5, cr13, cr0, {2}
9 80707dd4: e5817060 str r7, [r1, #96] ; 0x60
10 80707dd8: e1a05000 mov r5, r0
11 80707ddc: e2824018 add r4, r2, #24
12 80707de0: e59f000c ldr r0, [pc, #12] ; 80707df4 <__switch_to+0x3c>
13 80707de4: e3a01002 mov r1, #2
14 80707de8: ebe8d560 bl 8013d370 <atomic_notifier_call_chain>
15 80707dec: e1a00005 mov r0, r5
16 80707df0: e894aff0 ldm r4, {r4, r5, r6, r7, r8, r9, sl, fp, sp, pc}
17 80707df4: 80c77088 sbchi r7, r7, r8, lsl #1
18 80707df8: e320f000 nop {0}
19 80707dfc: e320f000 nop {0}
r0 = prev: 휴면에 진입할 프로세스 태스크 디스크립터 주소
r1 = task_thread_info(prev): 휴면에 진입할 프로세스 current_thread_info() 주소
r2 = task_thread_info(next): 다음 실행할 프로세스 current_thread_info() 주소
2단계로 나눌 수 있습니다.
1단계: 휴면에 진입할 프로세스 레지스터 세트를 cpu_context 필드에 백업
2단계: 다음 실행할 프로세스 current_thread_info()->cpu_context 필드 값을 ARM 레지스터로 로딩
1단계
2 번째 줄 코드를 보겠습니다.
2 80707db8: e281c018 add ip, r1, #24
current_thread_info() 주소에서 24만큼 더합니다. struct thread_info 구조체에서 cpu_context 필드 오프셋이 24바이트이기 때문입니다.
3 번째 줄 코드를 분석하겠습니다.
3 80707dbc: e8ac6ff0 stmia ip!, {r4, r5, r6, r7, r8, r9, sl, fp, sp, lr}
struct thread_info 구조체에서 cpu_context 필드에 r4, r5, r6, r7, r8, r9, sl, fp, sp, lr 레지스터 세트를 저장합니다.
2단계
코드 분석 전 r2 레지스터에 다음 실행할 프로세스 current_thread_info() 주소가 저장됐다는 사실을 떠올립시다.
11 번째 줄 코드를 봅시다.
11 80707ddc: e2824018 add r4, r2, #24
r2에서 24만큼 더한 결과는 r4에 저장합니다.
current_thread_info() 주소에서 24만큼 더합니다. struct thread_info 구조체에서 cpu_context 필드 오프셋이 24바이트이기 때문입니다. 이 어셈블리 명령어 연산 결과 r4는 current_thread_info()->cpu_context 필드 주소를 저장합니다.
다음 16 번째 줄 코드를 보겠습니다.
16 80707df0: e894aff0 ldm r4, {r4, r5, r6, r7, r8, r9, sl, fp, sp, pc}
r4에 저장된 레지스터 정보를 ARM 레지스터에 로딩합니다. 이 순간부터 기존에 실행했던 프로그램 카운터로 이동해 다시 실행을 재개합니다.
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
Thanks,
Austin Kim(austindh.kim@gmail.com)
Reference(프로세스 스케줄링)
스케줄링 소개
프로세스 상태 관리
어떤 함수가 프로세스 상태를 바꿀까?
스케줄러 클래스
런큐
CFS 스케줄러
CFS 관련 세부 함수 분석
선점 스케줄링(Preemptive Scheduling)
프로세스는 어떻게 깨울까?
스케줄링 핵심 schedule() 함수 분석
컨택스트 스위칭
스케줄링 디버깅
스케줄링 프로파일링
CPU에 부하를 주는 테스트
CPU에 부하를 주지 않는 테스트
# Reference: For more information on 'Linux Kernel';
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
최근 덧글