Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[라즈베리파이] 시스템 콜 - ARM 프로세서 관점 시스템 콜 처리 11. System Call

리눅스 시스템에서 시스템 콜을 관련 코드를 읽다보면 어셈블리 코드를 만나게 됩니다. 

보통 어셈블리 코드는 ARM 프로세서 입장에서 실행하는 동작입니다. 어셈블리 코드로 구현돼 있다는 것은 시스템 콜이 아키텍처(ARM, x86) 동작과 연관이 있다는 볼 수 있습니다. 

라즈베리파이는 ARM 아키텍처에서 구동하므로 ARMv7(Aarch32, ARM 32비트) 프로세서 기준으로 시스템 콜 세부 동작을 알아보겠습니다.

ARM 프로세서 모드 소개하기
ARM 프로세서에서 시스템 콜이 어떻게 동작하는지 알려면 ARM 프로세스 모드에 대해 알아야 합니다. ARM 프로세서는 다음과 같이 6가지 모드를 지원하며 각 모드 별 레지스터 세트를 저장합니다. 
-   Supervisor
-   FIQ
-   IRQ
-   ABORT
-   UNDEF
-   USER

ARM 기반 리눅스 커널에서 리눅스 커널은 Supervisor 모드, 유저 어플리케이션은 USER 모드에서 실행합니다. 리눅스 커널에서 보통 커널 모드는 ARM의 Supervisor 모드에서 실행하고, 유저 어플리케이션은 ARM의 USER 모드에서 실행합니다. 
-   ARM Supervisor 모드: 커널 공간
-   ARM USER 모드: 유저 공간

여기서 다음과 같은 의문이 생깁니다.
     
     "그렇다면 ARM 프로세스 관점에서 USER 모드에서 Supervisor 모드로 스위칭하려면 
      어떻게 해야 할까?"

ARM 프로세서에서 각 모드를 전환시키려면 익셉션을 발생시켜야 합니다.
리눅스 커널의 유저 모드에서 커널 모드로 진입하려면 USER 모드에서 Supervisor 모드로 실행 모드 변환을 해야 합니다. 이를 위해 익셉션을 유발해야 하며 이 과정에서 Supervisor Call이란 어셈블리 명령어를 실행해야 합니다.

ARM 프로세서 관점으로 소프트웨어 인터럽트를 발생하는 Supervisor Call 흐름도는 다음과 같습니다.
 
[그림 11.3] ARM 프로세서 관점 시스템 콜 실행 흐름도 

위 그림에서 보이듯 유저 공간에서 'svc' 어셈블리 명령어를 실행하면 커널 공간에 있는 'vector_swi' 벡터로 실행 흐름이 바뀝니다. 그렇다면 ARM 아키텍처 관점으로 시스템 콜은 어떻게 처리할까요?

     "ARM 아키텍처 입장에서 시스템 콜은 소프트웨어 인터럽트로 처리합니다." 

즉, ARM 프로세서에서 소프트웨어 인터럽트는 익셉션의 한 종류로 처리하는 것입니다. ARM 프로세서에서 익셉션이 발생하면 이미 정해놓은 주소로 ARM 프로그램 카운터를 브랜치하고 정해진 동작을 합니다. 

     "그렇다면 익셉션으로 소프트웨어 인터럽트만 있을까?"

소프트웨어 인터럽트는 익셉션 종류 중 하나입니다. 대표적인 익셉션으로 인터럽트를 예로 들 수 있습니다. 인터럽트가 발생하면 인터럽트 벡터인 irq_svc 로 브랜치 합니다. 마찬가지로 소프트웨어 인터럽트가 발생하면 이미 정해진 주소인 vector_swi 레이블로 브랜치합니다.

ARM 프로세서에서는 소프트웨어 인터럽트를 다음 2번째 줄과 같은 명령어로 실행합니다.
1 0x76f01170 <__libc_fork+276>    mov    r7, #120        ; 0x78                                                                                    
2 0x76f01174 <__libc_fork+280>    svc    0x00000000

각 아키텍처별로 커널 모드에서 유저 모드로 변환시키는 방식은 다릅니다.
ARM 프로세스 입장에서는 Supervisor Call 을 실행하면 모드 전환을 처리합니다. 이 동작이 시스템 콜로 어떤 동작을 하는지 모릅니다.

수많은 운영체제들이 ARM 프로세서를 CPU로 쓰고 있습니다. 리눅스는 ARM 프로세서에서 탑재된 수많은 운영체제 중 하나일 뿐입니다.

ARM 프로세서에서 Supervisor Call로 시스템 콜 익셉션 벡터로 분기하는 과정
ARM 프로세서 기반으로 구동하는 리눅스 커널은 Supervisor Call로 소프트웨어 인터럽트 발생 전 시스템 콜 번호를 r7 레지스터에 저장합니다.
        
[그림 11.4] ARM 프로세서 관점 리눅스 시스템 콜 실행 흐름도 

USER 모드에서 ARM r7 레지스터에 POSIX 규약에서 정의한 시스템 콜 번호를 지정하고 ‘svc’ 명령어로 Supervisor Call을 실행해 Supervisor 모드로 전환합니다. Supervisor 모드는 커널 코드가 실행하는 커널 공간임을 기억합시다. Supervisor(커널) 모드에서 vector_swi 레이블을 실행할 때 USER 모드에서 저장한 r7 레지스터를 읽습니다. 

이후 시스템 콜 테이블인 sys_call_table 변수 주소에 접근해 r7에 저장된 시스템 콜 번호에 따라 시스템 콜 핸들러로 분기합니다.

     
소프트웨어 인터럽트에 대해서 조금 더 배워 볼까요? 
우선 소프트웨어 인터럽트는 인터럽트가 아닙니다. 여기서 말하는 ‘인터럽트’는 하드웨어서 올려주는 전기 신호로 언제 발생할지 모르는 비동기적인 이벤트이나 통지입니다.

그런데 소프트웨어 인터럽트는 ARM 프로세서에서 제공하는 “svc” 어셈블리 명령어를 실행하면 동작합니다. 소프트웨어 인터럽트를 발생시키는 주인공은 누구일까요?

     "프로세스입니다." 

소프트웨어 인터럽트란 용어의 인터럽트는 하드웨어 디바이스에서 비동기적으로 전달하는 신호는 아닙니다.

실제 인터럽트가 발생하면 ARM 프로세서는 인터럽트를 익셉션의 한 종류로 처리합니다. 
처리 과정을 조금 더 세분화해서 보면 다음과 같습니다. 
1. 익셉션 발생(인터럽트는 비동기적인 신호)
2. 익셉션 벡터로 ARM 프로그램 카운터를 이동
3. 익셉션 벡터에서 기존에 실행 중인 레지스터 세트를 스택 공간에 저장
4. 익셉션 종류에 따른 서브 루틴으로 분기
5. 익셉션 처리를 마무리한 후 익셉션 서브 루틴을 실행한 주소로 복귀
6. 스택에 푸시한 레지스터를 ARM 레지스터 세트에 로딩해서 익셉션 발생 전 실행했던 주소로 이동

소프트웨어 인터럽트는 인터럽트가 아니라고 했습니다.
대신 소프트웨어 인터럽트는 ARM에서 지원하는 어셈블리 코드 "svc" 명령어를 명시적으로 실행해서 익셉션을 유발하는 동작입니다. 따라서 소프트웨어 인터럽트는 하드웨어 신호로 발생하는 비동기적인 이벤트는 아닙니다. 

위에서 언급한 인터럽트 익셉션이 발생했을때 6가지 단계로 실행 흐름을 분류했습니다. 그러면 익셉션이란 단어를 소프트웨어 인터럽트란 단어로 바꿔 볼까요?
1. 유저 모드에서 svc "0x00000000" 명령어 실행으로 커널 코드 진입
2. 소프트웨어 인터럽트 벡터로 ARM 프로그램 카운터를 이동
3. 소프트웨어 인터럽트 벡터에서 기존에 실행 중인 레지스터 세트를 스택 공간에 저장
4. 소프트웨어 인터럽트 종류에 따른 서브 루틴으로 분기
5. 소프트웨어 인터럽트 처리를 마무리한 후 소프트웨어 인터럽트 서브 루틴을 실행한 주소로 복귀
6. 소프트웨어 인터럽트에 푸시한 레지스터를 ARM 레지스터 세트에 로딩해서 소프트웨어 인터럽트 전 실행했던 주소로 이동(유저 모드 복귀)

소프트웨어 인터럽트를 유발하는 소스가 다른 것이지 ARM 프로세서에서 인터럽트 벡터를 실행해서 인터럽트를 처리하는 방식은 같습니다.

ARM 프로세서에서 인터럽트 벡터는 __irq_svc입니다. 마찬가지로 소프트웨어 인터럽트 벡터는 vector_swi 입니다.

즉, 다음과 같은 과정으로 실행 흐름이 바뀝니다.
 
[그림 11.5] ARM 프로세서 관점 소프트웨어 인터럽트 발생 흐름도

위 동작은 순수히 ARM 프로세서 익셉션 관점으로 설명을 한 것입니다.

이번 소절에서는 ARM 프로세서 입장에서 시스템 콜 동작을 설명 드리고 있습니다. 여기서 한 가지 의문이 생깁니다. 

     "과연 ARM 프로세서는 시스템 콜을 알고 있을까?"

ARM 프로세서 입장에서는 시스템 콜이 무엇인지 모릅니다. ARM 프로세서는 유저 공간에서 'svc 0x0' 명령어를 실행하면 해당 벡터인 vector_swi 레이블을 브랜치만 할 뿐입니다. 다른 관점으로 보면 리눅스 커널에서 ARM 프로세서의 '익셉션 동작' 원리를 활용해 시스템 콜을 구현한 것입니다.

그렇다면 다른 CPU 아키텍처 입장에서 생각해볼까요?

     "x86, PowerPC, ARMv8(64비트) 프로세서는 시스템 콜을 알고 있을까?"

마찬가지로 위에서 언급한 프로세스들도 시스템 콜이 무엇인지 모릅니다. 

위에서 설명드린 내용을 종합하면 시스템 콜 세부 구현은 방식을 다음과 같다고 결론을 내릴 수 있습니다.

     "해당 CPU 아키텍처의 특징을 활용해 시스템 콜을 구현한다."

ARM 프로세서 입장에서 지금 실행 중인 운영체제가 리눅스인지 모릅니다. 단지 소프트웨어 인터럽트를 발생한 다음 vector_swi 레이블로 분기할 뿐입니다. 


다음 절에서는 시스템 콜 실행 과정에서 시스템콜 별로 시스템 콜 핸들러를 분기해주는 시스템콜 테이블을 살펴봅시다. 

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


#Reference: 시스템 콜
시스템 콜 주요 개념 소개
유저 공간에서 시스템 콜은 어떻게 발생할까
시스템 콜 핸들러는 어떤 동작을 할까? 
시스템 콜 실행 완료 후 무슨 일을 할까?
시스템 콜 관련 함수  
시스템 콜 디버깅  






핑백

덧글

댓글 입력 영역