본문 바로가기

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

SWI_Exception_Implement(System Call-1)

이 전 포스팅에서 SWI와 System Call에서 언급했었다.


이제 부터는 SystemCall을 구현하기 위해 SWI를 어떻게 구현하는지 알아보자.

소프트웨어 인터럽트는 다른 예외처리와는 다르게 사용자의 요청에 의하여 발생된다. IRQ나 FIQ등은 사용자가 어느시점에서 발생할지 모르기 때문에 각 스크레치 레지스터등을 보존하고 파이프 라인을 통해 전해오는 LR값을 고려해야하지만 소프트웨어는 그렇지 않다.

SWI 예외처리는 명령어 파이프 라인의 디코드(DECODE) 단계 에서 인식된다. 따라서 예외처리 후 되돌아갈 때에는 SWI 예외처리가 발생한 시점의 다음 명령어 위치로 돌아가면 된다. 결국 SWI 예외 발생후 모드가 바뀌면서 LR값에는 발생한 시점+4 의 위치가 저장된다.


SWI 호출 시 발생(User Mode에서 호출했다고 가정)하는 일.

- User Mode의 CPSR값이 SVR Mode의 SPSR로 복사

- CPSR값이 SVR의 값으로 변경(기타 CPSR 레지스터의 내부 bit 변경) 
- SVR의 LR 값에 User모드의 PC값(pc+4)이 복사

- PC에는 해당 Vector Table 내의 주소값이 복사

위의 4가지 동작은 하드웨어적으로 발생한다.
그 후의 처리 동작이나 복귀부분은 exception Handler에서 해주어야 한다.

 

아래 그림은 개괄적인 SWI 예외처리 이용한 System Call 흐름을 보여준다. 처음 main.c나 기타 프로그램 실행중 프로그래머에 의해 SWI를 발생하게 된다. SWI가 발생하면 위에서 언급한 하드웨어적 동작과 함께 자동으로 SVR Mode로 전환이 되며 CRT0.s(C-RunTime.s) 코드에 존재하는 Vector Table로 이동하게 된다. SWI의 경우 Table의 3번째에 위치하여 있다. Vector Table에는 다시 같은 소스 내 존재하는 특정 루틴으로 분기 하게 된다. 분기하는 곳은 흔히 SWI_Handler라고 지칭하는데, 이곳에서는 각 Register를 스택에 보존하거나 복구하고, 최종적으로 수행할 Device Driver 함수로 인자를 전달하며 분기한다. 최종적인 Device Driver 내에서 함수 처리가 끝나면 Return 값을 가지고 다시 거쳐온 곳을 다시 돌아오면, 최종적으로 swi가 호출되었던 다음 명령위치로 돌아온다. 이 때 Return값도 각 위치에서 보존되고, 기타 사용된 Register들도 규칙에 맞게 보존한다.


[SWI 호출 시 인자의 전달과 Return 값 보존]

SWI 호출 자체는 함수와 같아야 한다. 인자가 필요한 경우 전달해야 하고, Return값이 존재하는 경우 Callee가 Caller에게 무사히 전달해주어야 한다. Swi 호출은 Assembly 언어 상에서 [swi 인터럽트번호;] 형태로 호출된다. 이 인터럽트 번호에 의해서 이미 정의해놓았던 함수가 실행된다. 즉 Device Driver 함수와 main에서 사용하는 함수를 연결해주는 것이 System Call의 역할이다. 이를 모드 변경을 통해 연결하기 위해서 고려해야할 사항이 크게 두 가지가 존재한다.

1. 인자를 전달할 때, 5개 이상의 인자의 경우 어떻게 전달할 것인가?
2. SWI가 연속적으로 호출되거나, 여러모드에서 중첩(Nested)되어 호출될 때, 각 범용 Register 및 상태 Register를 어떻게 보존할 것인가?


ARM 컴파일러에서는 함수의 인자와 결과 값을 전달할 때 어떤 레지스터를 사용할 것인지, 변수는 어떤 레지스터를 사용할 것인지를 규정하고 있으며 프로그래머는 이 규칙에 따라 함수를 구현하여야 한다. 이 표준을 APCS(ARM Procedure Call Standard)라고 하는데 ARM의 범용 레지스터가 어떻게 사용되어야 하는지를 규정한다.


APCS에 대한 조금은 자세한 설명 : http://shinlucky.tistory.com/292


 

[SWI 호출 시 복귀 책임 ]

SWI가 호출되거나 아니면 함수 호출시 Caller 또는 Callee에서는 각자 사용한 Register를 복귀 해야할 책임이 있다. 이 책임을 회피하거나 지지 않는다면 안전하게 인자 전달도 못할 뿐더라 다시 호출한 시점으로 돌아 올 수도 없다.

 

위 그림은 일반함수에서의 호출 내용이다. 그림에서와 같이 Callee나 Caller에서는 현재 코드에서 사용될 Register를 미리 보존한 후에 사용하여야 한다. 그리고 복귀전에는 반드시 복구한 후 Return을 한다. 저장과 복구는 Stack을 이용하여 PushPop을 이용한다. APCS에 의하여 R0~R3, R12Scratch Register로 언제나 파손되어 복구된다. 그렇기 때문에 만약 인자로 전달한 R0~R3가 현재 코드 내에서 함수 호출후 리턴되고 나서 다시 사용되지 않는다면(재사용등의 이유로 보존할 이유가 있다면) 보존할 필요는 없다. 
 


위 그림은 SWI의 복귀처리 의무이다. SWI의 경우 사용자가 미리 위치를 인식하고, 발생하는 예외처리이기 때문에 일반 함수와 비슷하다. 추가적으로 상태 Register를 보존해야한다. 함수와 SWI의 경우 모두 LR Register는 필수적으로 보존해야 한다. 같은 모드가 두 번 호출되거나 같은 함수가 두 번 호출될 경우 LR Register가 덮어 씌워지기 때문에 반드시 보존 및 복구를 해야 한다.


지금까지는 이해를 위한 개념도 위주 설명이었고,
다음 포스팅에서는 직접 작성된 코드를 통해 System Call구현을 위한 SWI부분을 확인할 것이다.