ARM 프로세서 관점의 시스템 콜 처리

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


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


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

ARM 프로세서 모드

 


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

 Supervisor
 FIQ
 IRQ
 ABORT
 UNDEF
 USER

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

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

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

ARM 프로세서 관점에서 소프트웨어 인터럽트를 발생시키는 슈퍼바이저 콜의 흐름은 다음과 같습니다.

 


그림 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 프로세서에서 슈퍼바이저 콜로 시스템 콜 익셉션 벡터로 분기하는 과정
ARM 프로세서 기반으로 구동하는 리눅스 커널에서는 슈퍼바이저 콜로 소프트웨어 인터럽트가 발생하기 전의 시스템 콜 번호를 r7 레지스터에 저장합니다.

 

 

      
그림 11.4 ARM 프로세서 관점의 리눅스 시스템 콜 실행 흐름

USER 모드에서 ARM r7 레지스터에 POSIX 규약에서 정의한 시스템 콜 번호를 지정하고 ‘svc’ 명령어로 슈퍼바이저 콜을 실행해 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 프로세서는 유저 공간에서 'svc 0x0' 명령어를 실행하면 해당 벡터인 vector_swi 레이블을 브랜치하기만 할 뿐입니다. 다른 관점에서 보면 리눅스 커널에서 ARM 프로세서의 '익셉션 동작' 원리를 활용해 시스템 콜을 구현한 것입니다.

그렇다면 다른 CPU 아키텍처 입장에서 생각해볼까요? x86, PowerPC, ARMv8(64비트) 프로세서는 시스템 콜을 알고 있을까요? 마찬가지로 앞에서 언급한 프로세스들도 시스템 콜이 무엇인지 모릅니다. 

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

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

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


다음 절에서는 시스템 콜 종류별로 시스템 콜 핸들러를 분기하는 시스템 콜 테이블을 살펴봅시다. 

시스템 콜의 특징

이번 절에서는 시스템 콜의 특징을 알아보겠습니다.

앞서 알아봤듯이 시스템 콜은 유저 모드에서 커널 모드로 진입하는 관문입니다. 소프트웨어 구조 관점에서 보면 시스템 콜은 유저 공간과 커널 공간 사이의 가상 계층으로 볼 수도 있습니다. 이 계층은 다음과 같은 특징이 있습니다.

1. 시스템 콜 계층으로 시스템 안정성과 보안을 지킬 수 있습니다. 유저 모드에서 애플리케이션이 커널 공간에 아무런 제약 없이 접근한다고 가정해 봅시다. 실수로 애플리케이션이 커널 코드 영역의 메모리를 오염시키면 시스템이 오동작할 가능성이 높습니다. 그래서 유저 모드에서 시스템 콜로만 커널 모드에 진입해서 제한된 메모리 공간에 접근하는 것입니다.

2. 유저 애플리케이션에서 추상화된 하드웨어 인터페이스를 제공합니다. 유저 모드에서 구동 중인 애플리케이션 입장에서 하나의 파일 시스템 위에서 구동 중인 것으로 착각하게 합니다.

3. 시스템 콜 구현으로 유저 애플리케이션의 호환성과 이식성을 보장할 수 있습니다. 대부분의 리눅스 배포판은 시스템 콜 인터페이스를 POSIX(Portable Operating System Interface)라는 유닉스 표준 규약에 맞게 구현합니다. 이를 통해 유저 애플리케이션 코드를 라즈베리 파이, 안드로이드 등 리눅스 계열의 시스템과 유닉스 운영체제에서도 구동할 수 있습니다.

4. 유저 공간에서 실행되는 애플리케이션은 커널과 독립적으로 구동됩니다. 유저 애플리케이션 입장에서는 파일 시스템과 프로세스 생성과 같은 내부 동작에 신경 쓸 필요가 없습니다. 

또한 시스템 콜은 ARM 아키텍처와 연관이 깊은 동작입니다. ARM 프로세서는 시스템 콜을 익셉션의 한 종류인 소프트웨어 인터럽트로 실행하기 때문입니다. ARM 프로세스 관점에서 시스템 콜을 어떻게 구현했는지 함께 살펴봅시다.

이전 절에서 시스템 콜을 구성하는 주요 개념을 알아봤습니다. 이번에는 시야를 넓혀 전체 리눅스 시스템에서의 시스템 콜 실행 흐름을 살펴보겠습니다.  


시스템 콜의 전체 흐름 파악하기

 


다음 그림은 이번 장에서 다룰 시스템 콜의 전체 흐름입니다.

 

 
그림 11.2 시스템 콜의 전체 흐름

먼저 위 그림에서 유저 공간이라고 표시된 부분을 눈으로 따라가 봅시다.

open(), write(),read() 함수는 파일을 열거나 읽고 쓰는 파일 입출력 동작이고, fork()와 exit() 함수는 프로세스 생성 및 종료와 연관된 동작을 실행합니다. 이를 리눅스 저수준 함수라고 부릅니다. 다른 관점에서 GNU C 라이브러리로 진입하는 함수이며, API(Application Programming Interface)라고도 합니다.


리눅스 시스템에는 390여 개의 표준 함수가 있는데 위 그림에서 대표적인 함수 5개를 표현한 것입니다.

라즈베리 파이에서 다음 파일을 열어보면 시스템 콜 번호를 확인할 수 있습니다.

/usr/include/arm-linux-gnueabihf/asm/ unistd-common.h
#define __NR_restart_syscall  (__NR_SYSCALL_BASE+  0)
#define __NR_exit (__NR_SYSCALL_BASE+  1)
#define __NR_fork (__NR_SYSCALL_BASE+  2)
...
#define __NR_pkey_mprotect (__NR_SYSCALL_BASE+394)
#define __NR_pkey_alloc (__NR_SYSCALL_BASE+395)
#define __NR_pkey_free (__NR_SYSCALL_BASE+396)

 


시스템 콜의 세부 실행 단계

 

 


시스템 콜을 제대로 이해하려면 시스템 콜을 발생시키는 유저 공간부터 시스템 콜 핸들러를 실행하는 커널 공간 계층까지 전체 흐름을 살펴볼 필요가 있습니다. 시스템 콜의 동작 흐름은 크게 4단계로 나눌 수 있습니다.

1단계: 리눅스 저수준 표준 함수 호출


유저 애플리케이션에서 파일 시스템에 접근해서 파일을 열고 읽고 쓰려면 open(), write(), read() 함수를 각각 호출해야 합니다. 혹은 프로세스를 생성하거나 종료할 때 fork()나 exit() 함수를 호출합니다. 이 함수들을 API라고 하며, 유저 애플리케이션에서 리눅스 커널에서 제공하는 기능을 사용하기 위해 만든 인터페이스를 의미합니다. 이 인터페이스는 모두 리눅스 시스템에서 제공하는 GNU C 라이브러리 내부에 구현돼 있습니다.

2단계: 유저 공간에서 시스템 콜 실행


리눅스 시스템의 저수준 함수를 호출하면 GNU C 라이브러리 내부에 있는 어셈블리 코드가 실행됩니다. 이때 시스템 콜이 발생합니다. 이 과정을 제대로 이해하려면 어떤 ARM 어셈블리 명령어로 시스템 콜을 발생시키는지 살펴볼 필요가 있습니다.

3단계: 커널 공간에서 시스템 콜 실행

 

시스템 콜이 실행되면 커널 공간으로 이동해 시스템 콜 테이블에 접근합니다. 이 시스템 콜 테이블로 시스템 콜 번호에 해당하는 시스템 콜 핸들러 함수로 분기됩니다. 시스템 콜 동작에 따라 호출되는 시스템 콜 핸들러 함수는 다음과 같습니다.

 가상 파일 시스템: sys_open()/sys_write()/sys_read() 함수
 프로세스 생성 및 종료: sys_clone()/sys_exit_group() 함수


시스템 콜 핸들러 함수는 리눅스 저수준 함수 앞에 sys_ 접두사가 붙는 경우가 대부분입니다. write() 함수의 시스템 콜 핸들러는 sys_write() 함수이고, read() 함수의 시스템 콜 핸들러는 sys_read() 함수입니다. 하지만 모든 시스템 콜 핸들러 함수가 이 규칙을 따르지는 않습니다. 리눅스 저수준 함수인 fork()의 경우 sys_clone() 시스템 콜 핸들러가 실행됩니다.

4단계: 시스템 콜 핸들러 실행

 

시스템 콜 핸들러에서는 유저 공간에서 전달한 인자에 오류가 있는지 체크합니다. 이후 시스템 콜의 종류에 따라 가상 파일 시스템 계층이나 프로세스 관리 함수에 접근합니다.

여기까지 유저 공간에서 커널 공간까지 시스템 콜의 처리 과정을 알아봤습니다.

- 시스템 콜은 누가 언제 실행할까요?


시스템 콜은 유저 모드에서 실행 중인 어플리케이션에서 커널에게 어떤 서비스를 요청할 때 실행합니다. 유저 어플리케이션에서 파일 시스템에 접근해서 파일을 읽고 쓰거나 PID와 같은 프로세스 정보를 얻으려 할 때 주어진 규약에 맞게 커널에 서비스를 요청을 하는 것입니다. 이를 위해 시스템 콜을 발생해서 유저 공간에서 커널 공간으로 실행 흐름을 이동합니다.


이 동작은 다음 그림으로 표현할 수 있습니다.



이번에 시스템 콜 세부 동작을 왜 잘 알아야 하는지 생각해봅시다.

시스템 콜은 리눅스 시스템에서 당연히 잘 동작하는데 왜 알아야 할까요? 그 이유는 문제 해결 능력을 키우기 위해서입니다. 리눅스 시스템 저수준 함수를 써서 응용 어플리케이션 코드는 누구나 작성할 수 있습니다. 하지만 시스템 콜이 유저 공간에서 커널 공간까지 어떤 흐름으로 동작하는지 모르면 어디부터 문제 원인을 분석해야 할지 알 수 없습니다.


 시스템 콜이 어떤 흐름으로 동작하는지 잘 모르고 매뉴얼에 있는 내용만 참고해서 코드 작성하는 분보다 시스템 콜 전체 흐름을 제대로 이해한 분이 더 안정적인 코드를 작성할 가능성이 높습니다. 특정 리눅스 시스템 함수를 호출했는데 갑자기 에러 코드를 음수로 반환한다고 가정합시다. 시스템 인터페이스 구조를 알면 어느 코드부터 분석을 시작할지 판단할 수 있습니다.


리눅스에서는 실행 공간을 메모리 접근과 실행 권한에 따라 유저 공간과 커널 공간으로 분류합니다. 


먼저 커널 공간이 무엇인지 알아봅시다. 

커널 코드가 실행할 때는 모든 커널 함수 호출이 가능하며 제약 없이 메모리 공간에 접근해서 하드웨어를 제어할 수 있습니다. 이런 시스템 상태와 메모리 접근을 커널 공간이라고 부릅니다. 


다음은 유저 공간을 소개하겠습니다. 유저 어플리케이션 코드가 구동하는 동작과 상태를 유저 공간이라고 합니다. 유저 어플리케이션은 유저 공간에서 실행하며 메모리 공간 접근에 제한이 있고 하드웨어에 직접 접근할 수 없습니다. 


     

유저 어플리케이션에서 권한이 없는 메모리 공간에 접근하면 커널은 오류를 감지해서 해당 프로세스를 종료시킵니다.



다음 소절에 이어 시스템 콜 전체 흐름도와 동작에 대해서 살펴보겠습니다.


- 시스템 콜(시스템 호출)은 왜 필요할까?


시스템 콜은 유저 모드에서 커널 모드로 진입하는 동작입니다. 다른 관점으로 시스템 콜은 유저 공간과 커널 공간 사이 가상 계층이자 인터페이스라고 볼 수도 있습니다. 이 계층은 다음과 같은 특징이 있습니다.

1. 시스템 안정성과 보안을 지킬 수 있습니다. 유저모드에서 어플리케이션이 커널 공간에 아무런 제약없이 접근한다고 가정합시다. 실수로 어플리케이션이 커널 코드 영역 메모리를 오염을 시키면 시스템은 오동작할 가능성이 높습니다.


2. 유저 어플리케이션에서 추상화된 하드웨어 인터페이스를 제공합니다. 유저 모드에서 구동 중안 어플리케이션 입장에서 하나의 파일 시스템 위에서 구동 중인 것으로 착각하게 합니다.


3. 시스템 콜 구현으로 유저 어플리케이션의 호환성과 이식성을 보장할 수 있습니다. 리눅스 시스템은 시스템 콜 인터페이스는 POSIX(Portable Operating System Interface) 이란 유닉스 표준 규약에 맞게 구현되어 있기 때문입니다. 이로 유저 어플리케이션 코드를 라즈베리파이, 안드로이드 등 리눅스 계열의 시스템과 유닉스 운영체제에서도 구동할 수 있습니다.


4. 유저 공간에서 실행하는 어플리케이션에서 커널 공간으로 진입하는 인터페이스를 두고 커널과 독립적으로 구동합니다. 유저 어플리케이션 입장에서 파일 시스템과 프로세스 생성과 같은 내부 동작에 신경 쓸 필요가 없습니다.


리눅스 디바이스 드라이버와 가상 파일 시스템 함수도 시스템 콜을 통해 시스템 콜 핸들러를 통해 관련 코드를 실행합니다.  


또한 시스템 콜은 ARM 아키텍처와 연관이 깊은 동작입니다. ARM 프로세서는 시스템 콜을 익셉션의 한 종류인 소프트웨어 인터럽트로 실행하기 때문입니다. ARM 프로세스 관점으로 시스템 콜을 어떻게 처리하는지 알아볼 필요가 있습니다.


- 시스템 콜 전체 흐름도 소개


이전에 소개한 시스템 콜 흐름도와 시스템 콜 동작은 그리 간단하지 않습니다. 시스템 콜 세부 동작을 알려면 다음 시스템 전체 흐름도를 이해해야 합니다.


다음 그림은 이번에 다룰 전체 시스템 콜 흐름도입니다.




open(), write() 그리고 read() 함수는 파일을 열고 읽어서 쓰는 파일 입출력 동작이고, fork()와 exit() 함수는 프로세스 생성과 종료와 연관된 동작을 실행합니다. 이를 리눅스 저수준 함수라고 부릅니다. 다른 관점으로 GNU C 라이브러리로 진입하는 함수이며 이를 API(Application Programming Interface) 라고 부릅니다.


리눅스 시스템에서는 390여 개의 표준 함수들이 있는데 위 그림에서 대표적인 함수 5개를 표현한 것입니다.


라즈베리파이에서 다음 파일을 열어보면 시스템 콜 번호를 확인할 수 있습니다.

[/usr/include/arm-linux-gnueabihf/asm/unistd.h]

#define __NR_restart_syscall (__NR_SYSCALL_BASE+  0)

#define __NR_exit (__NR_SYSCALL_BASE+  1)

#define __NR_fork (__NR_SYSCALL_BASE+  2)

...

#define __NR_pkey_mprotect (__NR_SYSCALL_BASE+394)

#define __NR_pkey_alloc (__NR_SYSCALL_BASE+395)

#define __NR_pkey_free (__NR_SYSCALL_BASE+396)


시스템 콜을 제대로 이해하려면 시스템 콜을 발생하는 유저 공간부터 시스템 콜을 실행하는 커널 공간 계층까지 전체 흐름도를 살펴볼 필요가 있습니다.


시스템 콜 실행 흐름은 4단계로 나눌 수 있습니다.


1 단계: 리눅스 저수준 표준 함수 호출

유저 어플리케이션에서 파일시스템에 접근해서 파일을 열고 읽고 쓰려고 할 때 open(), write(), read() 함수를 호출해야 합니다. 혹은 프로세스를 생성하거나 종료할 때 fork() 나 exit() 함수를 호출합니다. 이 함수들은 API(Application Programming Interface)라고 말합니다. 유저 어플리케이션에서 리눅스 커널에서 제공하는 기능을 쓰기 위해 만든 인터페이스를 의미합니다. 이 인터페이스는 모두 리눅스 시스템에서 제공하는 GNU C 라이브러리 내부에 구현돼 있습니다.


2 단계: 유저 공간에서 시스템 콜 실행

리눅스 시스템 저수준 함수를 호출하면 리눅스 시스템에서 제공하는 GNU C 라이브러리 내 코드가 실행합니다. 라이브러리 내부 ARM 어셈블리 코드 실행으로 시스템 콜을 발생합니다. 이 과정을 제대로 이해하려면 ARM에서 시스템 콜을 어떻게 처리하는지 살펴볼 필요가 있습니다.


3 단계: 커널 공간에서 시스템 콜 실행

시스템 콜이 실행하면 커널 공간으로 이동해서 시스템 테이블에 접근한 후 각 리눅스 저수준 함수(API) 종류별로 대응하는 시스템 콜 핸들러 함수로 분기합니다. sys_open(), sys_write() 그리고 sys_read() 함수들은 가상 파일 시스템을 통해 파일 시스템에 접근합니다. sys_clone() 그리고 sys_exit() 함수들은 프로세스 생성과 종료와 연관된 커널 드라이버에 있는 계층에 접근합니다.

시스템 콜 핸들러 함수는 리눅스 저수준 함수 앞에 sys_ 접두사가 붙는 경우가 대부분입니다. write() 함수는 sys_write() 함수, read() 함수는 sys_read() 함수에 대응합니다. 하지만 모든 시스템 콜 핸들러 함수가 이 규칙을 따르지는 않습니다. 리눅스 저수준 함수 fork()는 sys_clone() 시스템 콜 핸들러가 실행합니다.


4단계: 커널 공간에서 시스템 콜 핸들러 실행

시스템 콜 핸들러에서는 유저 공간에서 전달한 매개 인자에 오류를 점검 후 시스템 콜 종류에 따라 가상 파일 시스템 계층이나 프로세스 관리 함수에 접근합니다.


시스템 콜 핸들러에서는 유저 공간에서 전달한 매개 인자에 오류를 점검 후 시스템 콜 종류에 따라 가상 파일 시스템 계층이나 프로세스 관리 함수에 접근합니다.


여기까지 유저 공간에서 커널 공간까지 시스템 콜 처리 과정입니다.


시스템 콜 인터페이스 동작을 더 정확하게 이해하려면 ARM 프로세스에서 시스템 콜을 어떻게 처리하는지 알아야 합니다. 이 내용은 다음에 다룹니다.

+ Recent posts