본문 바로가기

무언가 만들기 위한 지식/ARM Processor

Context Switching & Saving Convention


Context Switching이란 문맥교환을 의미한다. 그리고 그 의미는 다음과 같다.

문맥 교환(Context Switch)이란 하나의 프로세스가 CPU를 사용 중인 상태에서 다른 프로세스가 CPU를 사용하도록 하기 위해, 이전의 프로세스의 상태(문맥)를 보관하고 새로운 프로세스의 상태를 적재하는 작업을 말한다.
- 위키피디아

여기서 문맥교환(Context Switching)이란,
1. 다중 프로그래밍(Multi-Programming)환경에서 다른 프로그램의 실행을 재게할때 실행중이던 프로그램의 환경을 저장하는 것.
2. 인터럽트(Interrupt)의 기법중 하나로, 인터럽트 발생과 동시에 레지스터군을 전환하는 방법.

을 의미한다.

※ 1.의 환경저장도 결국 Register 보존을 의미한다. 그런 과정면에서 1, 2는 같으나 지칭하는 분야가 살짝 다르다.
    (필자의 경우, 결국 과정 및 규칙은 비슷하다고 본다.)

1.의 경우 OS상에서의 성격이 강하고, 필자가 여기서 다룰 이야기는 2.의 내용으로 인터럽트 발생시의 문맥교환을 말하고 있는 것이며, 그 사이에 이뤄지는 Register보존, Saving Convention에 대해 이야기 할 것이다.
(Saving Convention이란 Register를 보존할 의무가 있는 주체를 말하는데, 정확한 용어는 모르겠다. Saving Convention또한 공식적으로 사용하는 것 같지는 않고, 어떤 분이 설명하는 곳에서 보고 필자도 그렇게 인식하고 있다.)

고려해야할 문맥교환은 크게 두 가지로 나뉜다.
1. 함수호출(Function Call)간의 문맥교환(Context Switching)
2. 예외(Exception) 발생시의 문맥교환(Context Switching)


필자의 경험상으로는 이 문맥교환이 발생할때 Caller 또는 Callee 어느 부분에서 보존 및 복구의 책임이 있는지가 가장 헷갈려고 개념잡는데 오래 걸렸다. (필자가 좀 무식해서 여러번 봐야 한다 ㅠ.ㅜ;)
※ 함수를 호출한쪽이 Caller, 호출당한 쪽이 Callee이다. main에서 어느 함수를 호출하였으면 main이 Caller, 함수가 Callee다.

1.의 경우를 보자. 1.의 특징은 다음과 같다.
▷ 일단 함수일 경우에는 동일한 ARM Preocessor의 Mode상에서 이루어진다. 즉 같은 Stack을 사용한다.
▷ 함수가 호출되는 시점을 인지할 수 있다. (즉 프로그래머가 호출시점을 알고 있다는 의미.)


그리고 필자가 만들어 낸 공식은 아래와 같다.
현재 코드상에서 사용할 Register는 무조건 현재코드 처음 부분에 보존을 한다. 그리고 현재코드 마지막에서는 복구를 해준다. (코드 상에서 함수 호출등이 존재함, 현재 코드상이라 함은 하나의 함수의 루틴을 말한다.)
뭐 간단하게 말하면 "사용할 Register는 사용한 곳에서 미리 보존하고 복구 책임이 있다." 이다.
용어상으로는 Calle에 보존의 책임이 있다라는 의미이기도 하다.
APCS에 의하여 일반 범용 Register, Scratch Register를 제외한 Register는 보존/복구의 책임이 Callee에게 있다.
하지만 Scratch Register의 경우 보존/복구의 책임이 Caller에게 있다.

Scartch Register의 경우 Function Return시에 홰손될 수 있는 Register이다.
그렇기 때문에 Callee에서 보존해도 결국 반환시 홰손될 가능성이 있기 때문에 의미가 없다.
그래서, 반드시 Caller에서 보존을 해줘야 한다. a1~a4 의 경우는 Caller에서 인자로 넘겨준 값이 홰손되도 상관이 없다고 판단되면 보존/복구할 필요가 없고, 해당 레지스터를 인자값뿐만아니라 연산에도 사용할 경우 등에는 반드시 Caller에서 보존해 주어야 한다.
LR Register의 경우에도 같은 맥락이다. 단일 함수가 아닌 연속으로 중첩된 함수일 경우에는 BL명령등에 의해서 LR값이 파손된다. 그렇기 때문에 r14=LR Register에서는 반드시 Caller에서 보존 및 복구를 해주어야 순차적으로 해당 함수들을 거쳐 원점으로 루틴이 돌아 올 수 있다.


CallerCallee의 책임에 대해 확실히 알기 위해서는 스스로 생각을 여러번 해보면 된다. 글로만 봐서는 이해가기 힘들 듯하다. (필자는 무식에서 계속 생각해봤었다.)
Nested Function(함수 내에서 함수가 수행될 경우) or Nested Interrupt의 경우를 위해,
Saving Convention 의무를 명확하게 지켜주어야 된다.!


2.의 경우를 보자. 2.의 특징은 다음과 같다.(Exception)
▷ 일단 예외(Exception)일 경우에는 ARM Processor 상의 MODE가 바뀐다. 이는 곧 다른 Stack을 사용한다는 의미이다.
▷ Exception은 종류에 따라 시점을 인지할 수 있는 것과 인지 할 수 없는 경우가 있다. Interrupt에 의한 발생등은 사용자 프로그램이 호출한 것이 아니기 때문에 반드시 사용되는 모든 Register를 보존(Saving)해 주어야 한다.
(일반 함수와 다른점은 Scratch의 경우도 모두 보존해야한다는 것이다.)


Exception이 발생하면 벡터테이블을 거쳐서 해당 처리 루틴으로 BL 또는 B(branch)를 하게 된다.
이때 해당 처리루틴에서 사용되는 모든 레지스터와 상태레지스터(SPSR)을 현재 Mode의 Stack에 Saving을 해 주어야 한다.
※ 상태레지스터는 Nested된 Interrupt발생시를 위해서 보존해야한다.

Exception이 발생되면, 인지되는 시점이 각각 다르다.


인지시점이 다르기 때문에 위에서 처럼 원래 위치로 복귀하는 명령어가 다르다.

Exception 처리 루틴상에서 호출되는 함수는 위의 1.의 지침을 따르면 되고,
인터럽트가 중복해서 호출되는 경우에는 해당 처리루틴에서 각 레지스터 및 상태레지스터를 보존/복구해야한다.
SWI(Software Interrupt)의 경우에는 중복해서 호출될 가능성이 농후하기 때문에 확실하게 각 상태를 보존하는 루틴이 존재하여야 한다.

이후 포스팅에서 SWI 내용 구현에 대해 언급될 것이다. (인자 4개이상 고려, Nested 고려)