본문 바로가기

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

SWI_Exception_Implement(System Call-2)

이 포스팅에서는 작성된 파일들의 관계와 구현된 일부 코드들을 보인다.
작성된 코드가 절대 최선의 선택은 아니고, 필자가 구현한 방법중의 하나일 뿐이다. (Stack을 이용한 방법)
나중에 생각해본 것인데, 이보다 더 효율적이고 B. BL을 조금 사용하는 코드들도 생각해볼 수 있었다.

[작성된 파일]

- swi.s : 함수를 만들어 해당함수를 호출하면 호출된 함수에 맞는 Device Driver 함수를 호출한다. 내부적으로 swi 호출시 번호를 통해 호출한다. 각 번호에 따른 Device Driver 함수는 SWI_Table.c파일에 정의되어 있다. 
(어셈블러로 작성된 함수목록으로 실제 수행되는 함수와 c파일에서 사용되는 함수를 연결해 준다. - 그 과정에서 Mode가 변경)

SWI_Table.c : void *타입의 배열이 존재하고 이 배열 요소값은 함수 포인터가 존재 한다. 즉 해당 인덱스로 접근하면 함수의 첫 주소값이 들어 있다. 차후 SWI의 번호를 통해 접근하기 위해 배열로 설치하였다. Device Driver의 특정상 모든 함수에게 같은 수행 속도를 제공해야하기 때문에 인덱스값을 통해 if문 없이 바로 실행된다.


- crt0.s SWI의 Handler 부분이 정의되어 있다. 어떤식으로 인자를 전달하고, 각 레지스터를 보존 할 것인지를 결정한다. 어플리케이션에서 사용하는 함수와 DeviceDriver 함수를 연결해주는 역할을 하며 모든 swi 실행시 반드시 이 부분을 거쳐 지나가야 하기 때문에 어떤 경우에도 실행가능한 범용성이 필요하다.

(ex: 인자의 개수 및 리턴 타입, Recursive에 종속되지 않음, crt0.s가 부트로더임. c-runtime 환경구축, 첫번째 주소에 ENTRY 설정 및 벡터테이블이 존재한다.)

 

- Device Driver*.c : swi.s상의 함수들과 연결시키기 위하여 Device Driver들의 함수명을 바꾸어 준다.


도식화하면 다음과 같다.


위와 같은 순서로 전체적인 파일별로의 흐름이 진행된다.

위와 같은 흐름에서 System Call 구현을 위해 함수와 함수를 연결해줄때 고려해야할 2가지 사항이 있다. (이전 포스팅에서 언급)

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

 

1)문제해결 : 함수의 경우, 인자가 5개 이상일 경우에는 APCS에 근거하여 자동적으로 Stack영역을 이용하게 된다. 하지만 SWI의 경우에는 자동적으로 해결되지 않는다.

10개의 인자가 전달되었다고 가정해보자.

swi.s의 어셈블러로 작성된 함수 내부에는 현재 User/System Mode(둘중에 하나라고 가정)이며, R0,R1,R2,R3을 통해 인자를 전달 받은 상태이다. 나머지 6개의 인자는 User/System상에 존재하는 Stack영역에 쌓여있다. 이때 swi가 호출될 경우 SP(R13)의 Register가 SVR전용 SP로 바뀌기 때문에 User/System Mode상의 Stack에 존재하는 나머지 6개의 인자들을 얻어 올 수 없다.


- 이에 대한 해결책으로는 첫 번째로 User/System Mode의 SP의 주소를 전달받아 SVR Mode상에서 접근하여 인자를 사용할 수 있다. 그러나 이방법의 경우 인자의 개수를 알아야 일정 크기의 인자들을 받아들일 수 있다. 때문에 소스가 길어질 뿐만 아니라, 복잡해지고 swi.s에 작성되는 코드들이 함수의 인자의 개수마다 다르게 작성해야 한다.

(사실 인자의 개수는 Device Driver에서 선언된 함수정의에 의해 알 수는 있다.)


- 두 번째 해결책은 아예 SVR Mode에서 User/System Mode의 Stack을 사용하는 것이다.

인자는 User/System Mode상의 Stack에 저장되어 있기 때문에 SVR Mode상에서 CPSR을 강제로 변경하여 SVR Mode로 변경한다. 그리고 호출되는 Device Driver함수들을 User/System Mode상의 Stack에서 수행하도록 한다. 각 함수간에 보존 책임만 잘지켜주면 문제없이 Return값만 가지고 SVR Mode로 복귀가 가능하다.

단 이 경우에는 User/System Mode상의 LR Register가 문제가 된다. 함수보존에 의해 LR Register는 Stack상에 보존을 할텐데, 이 때 User/System Mode로 옮긴 상태에서 LR을 다시 Stack에서 쌓는다면, 그후에 호출되는 DeviceDriver함수에서 호출시 4byte 잘못된 값을 가져오게 된다. 그렇기 때문에 User/System Mode상의 LR값을 Stack이 아닌 다른 공간에 보존을 해야한다. 보존의 경우 함수간 보존 법칙에 의거하여 범용 Register에 저장하고, Device Driver 함수 종료시 다시 복구해주었다. 사용된 범용 Register의 복구 책임은 호출된(Callee)에게 있기 때문이다.

인자간의 전달방법은 후자, Stack을 통제로 변경하여 함수를 실행하는 방법을 이용하였다. 물론 이전 모드의 LR값은 범용 Register로 보존과 복구를 가능하도록 구현하였다.



2)문제해결 : swi가 연속으로 호출될 때는 Device Driver 내에서 다른 Device Driver를 호출할 문제가 되면, 많은 상황이 존재한다. 또한 프로그램 상 FIG에서 swi가 호출될 가능성도 있고, Undef에서 호출될 상황도 존재한다. 이 모든 상황을 고려할 때 이전 상태 레지스터를 저장하는 SPSR은 반드시 Stack에 보존되어야 한다. 같은 Exception이 돌아돌아 호출될 경우 그 값이 훼손되기 때문이다. 특히 LR값은 반드시 어느 모드에서나 해당 Stack에 보존되어야만, LR값이 덮어써져서 복귀할 주소를 찾지 못하는 문제를 방지할 수 있다.


각 Mode에 따른 System Call 구현 모습은 다른 모습과 같다.


기존 User Mode에서 SVR로 이동하는 모습은 위 그림처럼 표현된다.
중간 2~3과정 사이에 User Mode상에서의 인자를 가져오기 위하여 도중에 강제로 User Mode로 전환하여 User/System Mode의 Stack을 사용한 모습을 확인할 수 있다.

이 다음 포스팅에서는 실제 코드상의 모습을 확인할 수 있다.