이번 포스트에서는 반도체 개발 과정에 대해서 알아보겠습니다.
 
1. 스팩 확정, RTL 설계(베릴로그), 합성 검증
 
먼저 반도체 회사의 마케팅 부서에서 반도체의 스팩을 정합니다. 이후 RTL이란 툴을 사용해 C 코딩과 같이 로직을 코딩합니다. RTL로 반도체 로직을 설계하는 것이라 볼 수 있죠. 이어서 설계한 내용을 바탕으로 툴을 돌립니다. 이를 Synthesis 혹은 합성이라고 부르기도 합니다. 합성한 결과를 갖고 PC 환경에서 시뮬레이터를 돌려서 검증을 시도합니다. 이 과정을 거쳐서 반도체 기능을 수행하는 반도체 모듈이 만들어지게 되는 것이죠.
 
2. FPGA 검증/Back-End 개발
 
이렇게 충분히 시뮬레이션을 한 다음에, 그 결과를 FPGA에 넣어서 시스템 반도체에 대한 검증을 시도합니다. FPGA는 RTL을 반도체와 비슷하게 동작시키는 하드웨어인데, 자일링스가 가장 유명합니다. 일반적인 칩은 설계된데로만 일정하게 동작하지만 FPGA는 내부 회로를 프로그래밍해서 바꾼다고 보시면 됩니다. 
 
이 과정을 거쳐 반도체 설계에 대한 검증을 수행하는데요. 여기까지 개발된 내용들은 모두 디지틀 로직의 세계입니다. 이어서 백앤드(Back-End) 개발을 통해 로직을 실제 물리적으로 바꾸는 과정을 진행합니다. 백앤드를 구성하는 여러 기법들이 있는데, 가장 대표적인 기법이 하드닝(Hardening)입니다. 똑같은 로직이라도 하드닝을 어떻게 했는가에 따라 반도체 성능이 천차만별입니다. 따라서 하드닝을 잘하는 반도체 업체가 실력이 있다고 봐야 하고, 하드닝을 잘하는 개발자는 몸 값이 상당합니다.
 
3. DB Out
 
팹리스 업체에서 백앤드 개발을 끝내면 이제 "DB Out"을 수행합니다. DB OUT이란 퀄컴이나 엔디비아와 같은 팹리스 업체에서 백앤드 개발을 통해 검증을 마무리한 반도체 설계 프로그램을 TSMC나 삼성전자와 같은 파운드리 업체에 넘기는 것을 의미합니다.
 
4. 공정 개발/웨이퍼(Wafer) 작업
 
파운드리 업체는 팹리스 업체에서 설계한 내용(반도체 모듈)을 바탕으로 공정을 만듭니다. 공정이라면 16나노, 8나노라는 이야기를 들어봤을 꺼에요. 이런 공정을 설계한 반도체 모듈에 맞게 만든 다음에, 웨이퍼로 된 샘플을 만듭니다. 그 다음에 패키징을 과정을 거치게 됩니다. TV나 유튜브를 보면 흰색 작업복을 입고 웨이퍼를 뚫어지게 쳐다보는 장면을 볼 수 있죠? 파운드리에서 작업하는 개발자들의 모습입니다.
 
5. 브링업/특성 평가
 
패키징된 샘플을 가져오면 개발실에서는 브링업을 수행하고 반도체에 대한 특성 평가를 합니다. 온도나 전류를 다르게 전달하면서 시퀀스가 제대로 나오는지에 대한 특성 평가를 수행하는 것이죠. 반도체 특성 평가를 진행하는 개발자들은 "시간을 갈아 넣는 느낌"이라고 말하기도 합니다. 반도체 특성이란 정확한 답이 없기 때문에 끊임없는 테스팅과 튜닝 과정을 거친다고 하네요. 
 
6. BSP 개발/시스템 인테그레이션
 
그 다음 SoC 벤더 업체에서 BSP 개발을 수행합니다. 어느 정도 BSP가 크래시가 나지 않을 정도로 기본 동작이 되게 작업한 다음에, 소스를 체계적으로 정리해 커스터머(제품 개발)업체에게 ES(Engineering Sample) 버전 소스를 전달합니다.
 
이 과정으로 반도체 개발이 이뤄지게 됩니다. ARM에 대해 배우기 전에 먼저 반도체의 특성을 알 필요가 있기 때문에 반도체를 개발하는 과정을 알 필요가 있습니다.
 

 

< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 유튜브 소개 영상
ARM 아키텍처는 운영체제에서 다양한 구조로 시스템을 설계할 수 있게 부가 기능(Extensions)을 지원합니다. 그 중 대표적인 기능이 트러스트 존(Trustzone)입니다. 
 
트러스트 존(Trustzone) 소개
 
트러스트 존은 ARM 아키텍처에서 지원하는 Security Extention(보완 확장) 기능으로, 여러 보안 애플리케이션이 ARM의 트러스트 존을 활용해 구현돼 있습니다. 다른 관점으로 보완을 위한 추가 기능이라고 볼 수 있습니다.
 
ARM 아키텍처에서 보완을 위한 트러스트 존을 지원하는 이유는 무엇일까요? 바로 해킹으로부터 시스템을 보호하기 위해서인데요, 사실 예전부터 소프트웨어 분야에서 하드웨어 제조 분야까지 보안을 위협하는 해킹의 위험이 항상 존재했습니다. 컴퓨터의 기술이 발전함에 따라 시스템을 해킹할 수 있는 기법도 업그레이드 되고 있어, 개인정보와 국가 및 기업의 기밀 유출 방지를 위한 보완의 필요성이 계속 강조되고 있습니다. 그래서 해킹과 같이 보안을 위협하는 공격에 대응하기 위해 ARM 프로세서는 신뢰할 수 있는 실행 환경을 위해 소프트웨어 구조를 지원 하는데, 이를 트러스트 존(Trustzone)이라고 합니다. 
노멀 월드와 세큐어 월드란
 
트러스트 존의 개념을 이해하려면 먼저 노멀 월드와 세큐어 월드에 대해 이해를 할 필요가 있습니다. 노멀 월드는 일반적인 운영체제나 커널이 동작하는 실행 환경이며, 세큐어 월드는 트러스트 존이 동작하는 실행 환경입니다. 여기서 월드는 실행 환경 혹은 모드와 유사한 개념으로 사용됩니다.
 
다음 그림을 보면서 노멀 월드와 세큐어 월드에 대해 알아봅시다.
 

 
그림 1.8 트러스트 존의 노멀월드와 세큐어 월드의 개념
 
노멀 월드에서 보안을 필요로 하는 모듈에서 "smc"라는 ARM 어셈블리 명령어를 실행하면 노멀 월드에서 세큐어 월드로 실행 흐름이 바뀝니다. 이어서 오른쪽 박스 아랫 부분을 모니터 모드가 보이는데, 모니터 모드는 노멀 월드에서 세큐어 월드로 진입하거나 반대로 세큐어 월드에서 노멀 월드로 다시 복귀할 때의 게이트 키핑(Gatekeeping)과 같은 역할을 수행합니다.
 
그림을 보면서 트러스트 폰의 주요 개념을 살펴봤는데요, 노멀 월드와 세큐어 월드의 주요 특징은 다음과 같습니다.
 
   ● 노멀월드와 세큐어 월드 별로 각각 서로 다른 페이지 테이블이 있어, 페이지 테이블을 쉽게 해킹할 수 없도록 설정해줍니다.
 
   ● 노멀 월드에서 세큐어 월드가 실행되는 메모리 공간에 직접 엑세스할 수 없습니다. 노멀월드에서 세큐어 월드로 진입하기 위해서는 세큐어 모니터 콜(Secure Monitor Call)을 발생시켜야 하며, 이 밖에 다른 방법으로 세큐어 월드의 리소스(메모리 주소, 실행 코드)에 접근하려고 시도하면 시스템이 리셋되도록 설정할 수 있습니다.
 
실전 프로젝트에서는 ARM의 트러스트 존을 어떻게 활용할까?
 
SoC 벤더나 제품 업체에서는 ARM의 트러스트 존을 다양한 방식으로 활용해 보안을 유지합니다. 이 중에 가장 대표적인 방식을 소개합니다.
 
첫째, 노멀 월드에서 "smc" 명령어를 실행할 때 아큐먼트를 지정합니다. 만약 레지스터 r0에 아큐먼트를 전달해 smc 명령어를 실행하면 세큐어 월드에서 이에 따라 각기 다른 기능을 수행하도록 구현할 수 있습니다.
 
둘째, 특정 메모리 영역은 세큐어 월드에서만 접근할 수 있도록 지정할 수 있습니다. 만약 노멀 월드에서 세큐어 월드에서만 접근할 수 있는 메모리 주소 공간에 엑세스를 하면 FIQ를 트리거해 시스템을 리셋 시키거나 다른 예외 처리 동작을 수행할 수 있습니다.
 
셋째, 세큐어 월드에서 실행되는 코드는 별도의 파티션에서 다운로드돼 실행되도록 설계할 수 있습니다.
 
---
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. "혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
---
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
* 유튜브 소개 영상
이번 포스트에서는 캐시의 기본 개념에 대해 소개하고 ARM 프로세서에 적용된 캐시의 개념에 대해 설명하겠습니다.
 
CPU 캐시(Cache)란
 
캐시란 컴퓨터 용어로 캐시는 데이터나 값을 미리 복사해 놓는 임시 장소를 의미합니다. 캐시는 자주 사용되는 데이터를 임시 저장해 데이터에 접근하는 속도를 최적화하기 위해 사용됩니다. 다양한 소프트웨어 분야에서 캐시가 사용되는데 CPU를 설계할 때도 캐시를 사용해 CPU의 실행 속도를 최적화합니다. 그렇다면 CPU에서 캐시는 어떤 방식으로 사용이 될까요? 캐시는 어떤 형태일까요? 의문이 계속 생깁니다.
 
먼저 CPU에서 사용되는 캐시에 대해 알아봅시다. 사실 처음 CPU를 연구했던 과학자들은 처음부터 캐시를 CPU에 적용한 것은 아니었습니다. 오랫동안 CPU를 설계하는 개발자들은 CPU를 디자인하면서 다음과 같은 사실을 알게 됐습니다.
 
    ● CPU 메모리로부터 데이터를 읽어 오는데 많은 시간을 소비한다.
 
일반적으로 메모리는 CPU보다 100배 정도 느리다고 알려졌는데, CPU입장에서 100 사이클 동안 다른 동작을 하지 않고 기다려야 메모리에서 데이터를 읽어올 수 있게 됩니다. 차를 몰다가 신호등을 만나면 기다려야 하듯이 메모리에 있는 데이터를 읽는 동작이 CPU 입장에서는 일종의 병목 현상이 됐습니다. 
 
만약 메모리의 특정 영역에 있는 데이터를 엑세스할 경우 CPU 근처의 임시 저장 공간에 미리 읽어오면 메모리로부터 데이터를 읽어 오는 시간을 줄일 수 있다는 사실을 발견하게 됐습니다. 여기서 말하는 CPU 근처의 임시 저장 공간을 캐시라고 말하며 CPU의 성능을 키우기 위한 핵심 기능으로 사용되고 있습니다.
 
CPU 캐시는 메모리에 데이터를 읽어 오는 시간을 줄이기 위해 사용되는 메모리이며, 주 메모리에 비해 크기는 작지만 속도는 매우 빠릅니다. 그렇다면 캐시는 어떻게 사용될까요? 캐시가 사용되는 기본 원칙을 캐시 정책이라고 하며 이를 캐시 읽기 정책과 쓰기 정책으로 분류할 수 있습니다.
캐시(Cache)의 정책
 
캐시의 정책으로 크게 “캐시 읽기”와 “캐시 쓰기” 정책으로 분류될 수 있습니다.
 
캐시 읽기 정책
 
프로세서가 어셈블리 명령어가 실행될 때 메모리에 존재하는 데이터를 읽는 동작을 자주 수행합니다. 데이터를 읽을 때 먼저 캐시에 접근합니다. 요청한 데이터가 캐시에 있으면 캐시에 있는 데이터를 즉시 가져와 사용합니다. 이런 동작을 캐시 히트라고 부릅니다. 반대로 캐시에 요청한 데이터가 존재하지 않으면 이를 캐시 미스라고 하며, 요청한 데이터는 메모리에서 읽어서 캐시로 가져와야 합니다. 조금 어려운 이야기지만 한 개 데이터를 읽어 오지 않고 캐시 라인을 읽어와야 합니다.
 
캐시 읽기 정책을 반영하면 다음과 같은 문제에 직면하게 됩니다.
 
   ● 캐시 미스가 발생했는데 만약 캐시가 가득 차 있는 경우 어떻게 이를 처리해야 할까?
 
위와 같은 요구 사항은 다양한 방식으로 구현할 수 있으나 대표적으로 LRU(Least Recently Used) 방식을 적용할 수 있습니다. 최근에 액세스한 캐시는 남아 있고 대신 오래된 엔트리를 삭제하는 방식입니다. 
 
캐시 쓰기 정책
 
이번에는 캐시 쓰기 정책에 대해 알아 봅시다. 
프로세서가 메모리에 어떤 데이터를 쓰려고 할 때 "캐시 쓰기 정책"이란 기준에 따라 데이터를 캐시에 먼저 저장합니다. 프로세서가 캐시에 데이터를 쓰면 연산 속도를 높힐 수 있는데 빠른 시간 내에 캐시에 써진 데이터를 메모리에 업데이트해야 합니다. 이 때 어떤 방식으로 기준으로 캐시에 써진 데이터를 메모리에 업데이트해야 하는지 결정해야 하는데, 이는 라이트 쓰루(Write Through)와 라이트 백(Write-back)으로 방식으로 분류될 수 있습니다.
 
라이트 쓰루 정책에 따라 데이터를 쓰면, 데이터가 캐시에 써지면 동시에 메모리에도 업데이트됩니다. 만약 캐시에 데이터를 쓰고 난 다음 캐시에서 같은 데이터를 읽을 때는 캐시에서 읽어 오게 됩니다.
 
라이트 백 정책은 라이트 쓰루 정책과는 달리 캐시에 존재하는 "라이트"라는 버퍼에 먼저 데이터를 쓴 다음에 더티(Dirty)라고 체크합니다. 이후 "라이트" 버퍼가 다 차면 "라이트" 버퍼를 메모리에 업데이트합니다.
 
ARM 프로세서의 캐시
 
ARM 프로세서가 실행할 때 처리되는 데이터와 어셈블리 명령어와 메모리에 엑세스하는 데이터와 같은 패턴으로 분류할 수 있습니다. ARM 명령어와 같이 명령어에 대한 데이터를 저장하고 관리하는 캐시를 인스트럭션 캐시(Instruction Cache)라고 부르며 메모리에 저장되는 데이터를 관리하는 캐시는 데이터 캐시(Data Cache)라고 정의합니다.
 
ARMv7 아키텍처에의 메모리 계층 구조를 나타내는 다음 그림을 보면서 캐시에 대해 알아봅시다. 
 

 
그림 1.7 ARM의 캐시 계층 구조
 
그림의 가장 왼쪽 아랫 부분에 보이는 R0, R15는 ARM 코어에 있는 레지스터를 의미합니다. 오른쪽을 보면 Level1 Cache와 Level2 Cache가 보이는데 이는 이번 절에서 설명한 CPU 캐시를 의미합니다.
 
여기서 Level이란 단어가 보이는데 이 용어의 의미는 무엇일까요? Level은 단계와 수치를 의미하며, Level1 캐시는 1단계 캐시, Level2 캐시는 2단계 캐시라고 해석하면 됩니다. 보통 Level 1 캐시는 L1 캐시, Level 2 캐시는 L2 캐시라고 부르는데, CPU에서는 먼저 L1 캐시에 접근해 데이터를 찾는데 만약 데이터가 없다면 순서대로 L2 캐시에 접근해 찾는 데이터가 있는지 체크합니다. 
 
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. "혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
 
 
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
* 유튜브 소개 영상
ARM 프로세서는 익셉션을 지원하며 운영체제의 ARM 프로세서가 지원하는 익셉션을 활용해 세부 기능을 구현합니다. 익셉션의 타입에 대해 알아보고 익셉션의 세부 동작에 대해 살펴보겠습니다.
 
ARMv7과 ARMv8 아키텍처 별로 익셉션 벡터 테이블의 종류가 상이하지만, 익셉션이 동작하는 방식은 유사하므로 ARMv7 아키텍처 기준으로 익셉션에 대해 소개합니다. 
 
익셉션의 종류
 
익셉션의 세부 동작에 대해 설명드리기 전에 먼저 익셉션의 타입에 대해 알아봅시다. 다음은 ARM의 익셉션의 종류입니다.
 
표 1.3 익셉션의 타입과 종류
 
먼저 메모리 어보트 타입으로 분류되는 익셉션에 대해 살펴봅시다. 다음은 메모리 어보트 타입으로 분류되는 익셉션입니다.
 
   ❑ Undefined Instruction: ARM 코어가 어셈블리 명령어를 디코딩을 할 때 해석할 수 없는 경우 발생하는 익셉션으로 대부분 메모리가 오염(Memory Corruption)됐을 때 발생
   ❑ Prefetch Abort: ARM 코어가 제대로 명령어를 패치하지 못할 경우 발생하는 익셉션
   ❑ Data Abort: 접근하는 메모리 주소가 유효하지 않아 엑세스할 수 없어 발생하는 익셉션
 
메모리 어보트 타입으로 분류되는 익셉션은 대부분 널 포인터에 엑세스하거나 유효하지 않은 함수에 접근하다가 발생합니다.
 
두 번째 타입의 익셉션은 IRQ/FIQ 익셉션입니다. 각각 익셉션의 특징은 다음과 같습니다.
 
   ❑ IRQ: 하드웨어적인 인터럽트가 발생하면 ARM 코어는 IRQ 익셉션 발생
   ❑ FIQ: 하드웨어적으로 FIQ(Fast Interrupt Request)인터럽트가 발생하면 FIQ 익셉션 발생
 
외부 장치에서 인터럽트라는 전기 신호를 보낼 때 인터럽트 익셉션이 발생합니다. 메모리 어보트나 IRQ/FIQ 타입 익센션은 하드웨어적으로 ARM 프로세서가 유발합니다. 즉, 일부러 ARM 어셈블리 명령어를 실행해 메모리 어보트나 IRQ/FIQ 타입 익셉션을 유발할 수 없습니다.
 
마지막 타입으로는 소프트웨어 타입 익셉션입니다. 
 
   ❑ SVC: User 공간에서 "svc"라는 명령어를 실행할 때 익셉션을 유발
   ❑ HVC: "hvc" 명령어를 실행하면 하이퍼바이저 모드로 진입
 
ARM 어셈블리 명령어를 실행하면 인터럽트가 발생하는 것입니다. 리눅스에서 유저 공간에서 svc 명령어를 실행하면 소프트웨어 인터럽트가 발생하며 이를 활용해 시스템 콜을 구현합니다.
 
익셉션 벡터 테이블
 
익셉션의 동작 원리를 파악하려면 익셉션 벡터와 이를 구성하는 익셉션 벡터 테이블을 이해할 필요가 있습니다. 그렇다면 익셉션 벡터와 익셉션이란 무엇일까요? 익셉션이 발생하면 ARM 프로세서는 익셉션의 종류별로 지정된 주소로 프로그램 카운터를 변경하는데, 익셉션의 종류별로 지정된 주소를 익셉션 벡터라고 합니다. 
 
익셉션을 ARM 프로세서가 감지하면 ARM 프로세서는 무엇을 할까요? 크게 다음과 같은 동작을 수행합니다.
 
   ❑ 프로세서의 동작 모드를 저장(SPSR 레지스터)
   ❑ 익셉션의 종류 별로 지정된 주소로 프로그램 카운터를 변경
 
그렇다면 ARM 프로세서는 익셉션이 발생했다는 사실을 감지한 다음에 지정된 주소로 프로그램 카운터를 어떻게 변경할까요? 다음 순서로 동작합니다.
 
   ❑ 익셉션 벡터 테이블의 시작 주소를 찾는다.
   ❑ 익셉션 벡터 테이블의 시작 주소에서 떨어진 주소로 프로그램 카운터를 변경한다.
 
ARM 프로세서가 익셉션을 처리할 때 참고하는 중요한 정보는 익셉션 벡터 테이블의 시작 주소입니다. 익셉션 벡터 테이블는 익셉션이 발생했을 때 ARM 프로세서가 프로그램 카운터를 익셉션의 종류 별로 지정된 주소로 변경할 수 있는 정보를 담고 있습니다. 익셉션 벡터는 익셉션이 발생하면 익셉션의 종류 별로 프로그램 카운터가 변경되는 주소인데, 익셉션 벡터 테이블은 익셉션 벡터들의 정보를 담고 있습니다.
다음 표에 각 익셉션의 종류별로 지정된 익셉션 벡터의 주소 정보는 익셉션 벡터 테이블에 명시돼 있습니다. 
 
표 1.4 ARMv7 아키텍처의 익셉션 벡터 테이블
 
표 1.4의 오른쪽 행은 오프셋을 의미하는데, 익셉션 벡터 테이블의 시작 주소 기준으로 떨어진 주소를 의미합니다. ARM 프로세서 관점에서 익셉션 벡터 테이블의 시작 주소는 시스템 레지스터인 VBAR(Vector Base Address Register)를 통해 변경할 수 있는데, 리눅스에서는 보통 0xFFFF_0000 주소로 설정합니다.
 
만약 데이터 어보트(Data Abort) 익셉션이 발생하면 익셉션 벡터 테이블의 시작 주소 기준으로 0x10 주소로 프로그램 카운터를 변경합니다. 만약 인터럽트 익셉션이 발생하면 익셉션 벡터 테이블의 시작 주소 기준으로 0x18 주소로 프로그램 카운터를 변경합니다. 여기서 프로그램 카운터를 변경하는 주인공은 ARM 프로세서입니다.
 
익셉션 벡터 테이블은 리눅스 운영체제에서 어떻게 구현됐을까
 
ARM 프로세서 입장에서 익셉션이 발생하면 다음과 같은 동작만을 수행합니다. 
 
   ❑ 익셉션 벡터 테이블이 시작 주소를 참고해 익셉션의 종류 별로 지정된 주소를 프로그램
     카운터로 변경한다. 
 
이 부분을 공부하면 자연히 다음과 같은 의문점이 생깁니다.
 
   ❑ 익셉션의 종류 별로 뭔가 소프트웨어적인 처리를 해야 하지 않을까?
 
데이터 어보트라는 익셉션이 발생하면 데이터 어보트가 발생했다는 정보를 출력하거나 일을 해야 할 것 같습니다. 또한 인터럽트라는 익셉션이 발생하면 인터럽트가 발생했다는 사실을 운영체제에 알리거나 뭔가 다른 처리를 해야 할 것 같습니다. 이렇게 익셉션의 종류별로 후속 처리를 하는 코드는 운영체제의 소프트웨어 개발자가 구현하며, 익셉션 벡터에 코드를 배치시킵니다. 
 
다음은 리눅스 운영체제에서 구현된 익셉션 벡터 테이블의 코드입니다.
 
01 NSR:FFFF0000|EA0003FF    b       0xFFFF1004       ; vector_rst 
02 NSR:FFFF0004|EA000465    b       0xFFFF11A0       ; vector_und
03 NSR:FFFF0008|E59FFFF0    ldr     pc,0xFFFF1000
04 NSR:FFFF000C|EA000443    b       0xFFFF1120       ; vector_pabt
05 NSR:FFFF0010|EA000422    b       0xFFFF10A0       ; vector_dabt
06 NSR:FFFF0014|EA000481    b       0xFFFF1220       ; vector_addrexcptn
07 NSR:FFFF0018|EA000400    b       0xFFFF1020       ; vector_irq
08 NSR:FFFF001C|EA000487    b       0xFFFF1240       ; vector_fiq
 
01번째 줄에 보이는 0xFFFF0000는 익셉션 벡터 테이블의 시작 주소인데, ARM 아키텍처에서 명시된 익셉션 벡터 테이블에 지정된 오프셋에 따라 코드가 구현됐습니다. 
 
01~08번째 줄은 모두 지정된 레이블로 브랜치를 하는 코드로 구성돼 있습니다. 
 
예를 들어, 데이터 어보트라는 익셉션이 발생하면 ARM 프로세서는 프로그램 카운터를 0xFFFF0010가 변경하는데, 0xFFFF0010 주소에는 vector_dabt 레이블로 브랜치하는 "b 0xFFFF10A0 ; vector_dabt"  명령어가 보입니다. 
 
마찬가지로 인터럽트 익셉션이 발생하면 ARM 프로세서는 프로그램 카운터를 0xFFFF0018가 변경하며 0xFFFF0018 주소를 실행하면 vector_irq 레이블로 브랜치합니다.
 
이처럼 익셉션 벡터의 구현부는 ARM 프로세서와 운영체제의 교차점이라고 볼 수 있습니다. ARM 프로세서에서 익셉션이 동작하는 원리와 운영체제의 기능을 알아야 익셉션에 대한 코드를 구현할 수 있습니다.
 
---
"이 포스팅이 유익하다고 생각되시면 공감 혹은 댓글로 응원해주시면 감사하겠습니다. "혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!"
 
​Thanks,
Guillermo Austin Kim(austindh.kim@gmail.com)
---
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
* 유튜브 소개 영상
호출 규약(Calling Convention)이란 무엇일까요? 어떤 함수를 호출했을 때 서브 루틴이 자신을 호출하는 함수로부터 인자를 어떤 방식으로 받아 결과를 반환하는지에 대한 규약입니다. x86, RISC-V와 같은 CPU 아키텍처마다 함수 호출 규약(Calling Convention)을 정의하는데, ARM 프로세서에서는 이를 AAPCS(Procedure Call Standard for ARM Architecture)라고 명시합니다. 앞으로, AAPCS를 함수 호출 규약이라고 명시하겠습니다.
 
연산을 수행하는 ARM 어셈블리 명령어에서 레지스터는 연산의 결과를 임시로 저장하는 역할에 그칩니다. 그래서 각각 레지스터의 역할을 세세하게 배울 필요가 없는데요. 함수 호출 규약을 배울 때는 다릅니다. 그것은 다음과 같은 이유 때문입니다. 
 
    "함수 호출 규약의 핵심은 레지스터입니다." 
 
함수 호출 규약을 정의할 때는 레지스터가 각각 어떤 역할을 수행하는지 세세하게 다룹니다.
 
ARMv7 아키텍처의 함수 호출 규약
 
다음 테이블은 ARMv7 아키텍처에서 각각 레지스터의 역할을 나타냅니다.
 

 
표 1.5 ARMv7 아키텍처의 함수 호출 규약에서 레지스터의 역할 
 
ARMv7 아키텍처 기준으로 호출 규약에서 16개의 ARM 레지스터를 다음과 같이 정의합니다. 
 
   ● r15: 프로그램 카운터 
   ● r14: 링크 레지스터 
   ● r13: 스택 포인터 
   ● r12: 스크래치 용으로 사용되는 레지스터 
   ● r4~r11: 지역 변수를 저장을 위해 사용 
   ● r0~r3: 함수로 전달되는 인수값을 저장하고 함수로부터 반환되는 결과값을 r0 레지스터에 저장
 
특히 r14, r13(sp: 스택 포인터) 레지스터의 정보는 디버깅을 할 때 자주 사용되는 레지스터로 함수 호출 규약에서 중요한 역할을 수행합니다.
 
이어서 다음 그림을 보면서 ARMv8 아키텍처에서 각각 레지스터의 역할을 알아봅시다.
 

 
그림 1.6 ARMv8 함수 호출 규약에서 레지스터의 역할
 
위 그림은 ARMv8 아키텍처의 함수 호출 규약에서 각각 레지스터의 역할을 나타내는데, 각각 레지스터의 역할은 다음과 같습니다.
 
   ● X0~X8: 함수에 아규먼트를 전달할 때 사용
   ● X9~X15: 함수를 호출할 때 지역 변수를 저장 
   ● X29: 프레임 포인터 레지스터로 이전 스택 포인터 주소를 저장
   ● X30: 링크 레지스터
 
ARMv7 아키텍처와 마찬가지로 ARMv8 아키텍처의 함수 호출 규약에서는 X30(링크 레지스터)와 X29(프레임 포인터) 레지스터가 중요한 역할을 맡습니다. 디버깅을 할 때 자주 참고하는 레지스터입니다.
 
ARMv7/ARMv8 아키텍처의 함수 호출 규약에서 레지스터는 중요한 역할을 수행합니다. 
ARM의 함수 호출 규약을 실전 프로젝트에서 어떻게 활용될까?
 
ARM의 함수 호출 규약을 배우면 생기는 의문점을 말씀 드리려 합니다.
 
   ● 도대체 ARM 아키텍처의 함수 호출 규약은 실전 프로젝트에서 어떻게 활용될까?
 
저를 포함해 많은 개발자들이 품는 의문입니다. 이런 의문을 해소하기 위해 실전 디버깅 프로그램에서 확인한 콜 스택(함수의 호출 흐름을 출력)을 소개합니다.
 
다음은 크래시 유틸리티 프로그램에서 확인한 콜 스택 출력 결과입니다.
 
crash> bt  
PID: 4944   TASK: d110e040  CPU: 0   COMMAND: "sh"
 #0 [<c01003ac>] (do_DataAbort) from [<c0fde6a0>]
    pc : [<c0524b88>]    lr : [<c0fdde2c>]    psr: 60010013
    sp : c59c3f10  ip : c1920700  fp : bee5765c
    r10: 00000000  r9 : acf2a284  r8 : 00000000
    r7 : 00000063  r6 : 00000000  r5 : c191ff18  r4 : c195cd70
    r3 : 00000000  r2 : 00000001  r1 : 40010013  r0 : 00000000
    Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM
 #1 [<c0fde6a0>] (__dabt_svc) from [<c0fdde2c>]
 #2 [<c0524b88>] (sysrq_handle_crash) from [<c0525034>]
 #3 [<c0525034>] (__handle_sysrq) from [<c0525468>]
 #4 [<c0525468>] (write_sysrq_trigger) from [<c028a310>]
 #5 [<c028a310>] (proc_reg_write) from [<c0237b70>]
 #6 [<c0237b70>] (vfs_write) from [<c0238078>]
 #7 [<c0238078>] (sys_write) from [<c0106b00>]
 
#7번째 줄에서 #1번째 줄 방향으로 함수가 호출된 것을 나타내는데, 함수가 호출된 흐름이 깔끔하게 출력됩니다.
 
크래시 유틸리티는 리눅스 커널를 디버깅하기 위한 오픈 소스 프로젝트인데, 실전 프로젝트에서 많이 활용됩니다.
 
이번에는 GDB 프로그램으로 본 콜 스택 출력 결과입니다.
 
(gdb) bt
#0  0xb6fd7d5c in do_lookup_x (undef_name=0x7c9c7b11) at dl-lookup.c:541
#1  0xb6fd88b4 in _dl_lookup_symbol_x (undef_name=0x1023b "puts") at dl-lookup.c:814
#2  0xb6fdda54 in _dl_fixup (l=0xb6fff978) at dl-runtime.c:112
#3  0xb6fe3b44 in _dl_runtime_resolve () at ../sysdeps/arm/dl-trampoline.S:57
#4  0x00010414 in main () at main.c:5
 
보다시피 함수가 호출된 흐름을 깔끔하게 보여줍니다. 함수의 호출 흐름은 #4번째 줄에서 #0번째 줄 방향입니다.
 
이처럼 크래시 유틸리티와 GDB와 같은 프로그램을 사용할 때 당연히 함수가 호출된 흐름을 잘 표현해줄 것이라 예상합니다. GDB 프로그램이 함수의 콜 스택을 정확히 표현해주는 이유는 무엇일까요? 
 
   ● 크래시 유틸리티와 GDB 프로그램이 ARM의 함수 호출 규약에 따라 함수의 실행 흐름을
      표현하기 때문입니다.
 
사실 대부분 개발자들은 C 언어로 코드를 작성할 때 함수를 호출합니다. 단지 함수를 호출하지 않고 인자를 전달하면서 호출합니다. 그리고 호출된 함수가 반환하는 값으로 제어하는 코드를 입력합니다. 코드를 입력해 컴파일을 하면 당연히 원하는 동작을 수행하는데, 그 이유는 다음과 같습니다.
 
   ● 모두 ARM 프로세서의 함수 호출 규약에 따라 코드가 작동하기 때문이다.
 
소프트웨어 개발자로 함수 호출 규약은 반드시 잘 익혀둬야 할 핵심 개념입니다. 함수 호출 규약에 대한 상세한 내용과 실습은 5장 ARM 함수 호출 규약을 참고하시기 바랍니다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
* 유튜브 소개 영상
ARM 아키텍처 문서를 보면 가장 먼저 보이는 용어가 ARM 동작 모드입니다. ARM 아키텍처를 이해하기 위해 꼭 알아야 하는 개념이니 자주 보면서 익혔으면 합니다. ARM 동작 모드를 잘 배워야 시스템을 디자인하거나 디버깅을 할 때 유용하게 활용할 수 있습니다. 
 
User 모드를 시작으로 각각 동작 모드에 대해 소개하겠습니다.
 
User 모드
 
휴대폰에서 사용하는 브라우저, 카카오톡와 같은 애플리케이션이 구동되는 모드입니다. 라즈베리 파이에서 다음과 같은 유저 애플리케이션 코드를 빌드해 애플리케이션을 동작하는 모드입니다. 
 
#include <stdio.h>
 
int main() 
{
    printf("hello, world\n");
    return 0;
}
 
유저 모드에서 실행되는 코드는 메모리와 같은 리소스를 접근할 때 제약이 있습니다.
 
Supervisor 모드
 
User 모드에서 슈퍼바이저 콜(Supervisor Call)을 수행하면 진입되는 모드입니다. 운영체제의 커널이 동작하는 모드로 시스템 전체 메모리 공간에 접근할 수 있는 권한이 있습니다.
 
시스템 모드
 
시스템 모드에서 실행되는 코드는 PL1 레벨을 갖습니다. 시스템 전체 메모리 공간에 접근할 수 있는 권한이 있는 모드입니다.  
 
어보트 모드
 
데이터 어보트나 Prefetch 어보트와 같은 메모리 어보트(Memory Abort)가 발생할 때 진입하는 모드입니다. 운영체제에서 어보트 모드에 진입하면 어보트가 발생할 때의 정보를 로그로 출력하면서 시스템이 리셋됩니다.
 
Undefined 모드
 
ARM 코어는 ARM 어셈블리 명령어를 실행하기 위해 명령어를 디코딩하는데, ARM 코어가 해석할 수 없는(UNDEFINED) 명령어를 실행할 때 진입되는 모드입니다. 운영체제에서는 어보트 모드와 마찬가지로 시스템이 리셋됩니다. 대부분 메모리나 하드웨어 부품에 문제가 생겼을 경우 Undefined 모드로 진입합니다. 일부러 유효하지 않는 명령어를 실행해 Undefined 모드의 익셉션 벡터가 실행되도록 해 디버깅 목적으로 활용하는 경우도 있습니다.
 
IRQ 모드
 
인터럽트 요청이 발생하면 진입하는 모드입니다. IRQ 모드에서는 IRQ가 발생하기 직전의 ARM 모드를 저장한 후 인터럽트에 대한 처리를 수행합니다. 대부분 운영체제에서는 IRQ 모드로 진입하면 인터럽트에 대한 후속 처리를 수행하며 이를 ISR(Interrupt Service Routine)이라고 부릅니다.
 
FIQ 모드
 
FIQ(Fast Interrupt Request) 요청이 발생하면 진입하는 모드입니다. 일부 운영체제에서는 FIQ를 디버깅 용도로 사용합니다.
 
Hyp 모드
 
멀티 운영체제를 동작시키는 하이퍼바이저 환경에서만 진입하는 모드입니다. 일반적으로 운영체제의 커널이 동작하는 Supdervisor 모드에서 “hvc” 명령어를 실행하면 진입하는 모드입니다. 한 개의 컴퓨터에서 한 개의 운영체제로 구성된 시스템에서는 Hyp 모드를 사용하지 않습니다.
 
모니터 모드
 
보통 트러스트 존에 진입할 때 실행되는 모드로 Supervisor 모드에서 "SMC" 명령어가 실행되는 진입됩니다. 시스템에서 보완 관련 기능을 구현할 때 진입하는 모드입니다.
 
여기까지 ARMv7 아키텍처의 동작 모드에 대해 소개 했습니다.
 
ARMv7 아키텍처의 동작 모드로 진입하는 유발자는 익셉션이기 때문에 ARMv7 아키텍처의 동작 모드는 익셉션의 원리와 함께 배우는 경우가 많습니다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 유튜브 소개 영상
이번에는 ARMv8 아키텍처의 64비트 명령어 기준으로 레지스터 세트에 대해 알아봅시다. 다음은 ARM 스팩 문서에서 발췌한 내용입니다.
 
출처: DDI0487A_g_armv8_arm.pdf
B1.2.1 Registers in AArch64 state
 
In the AArch64 application level view, an ARM processing element has:
R0-R30 31 general-purpose registers, R0 to R30. Each register can be accessed as:
  • A 64-bit general-purpose register named X0 to X30.
  • A 32-bit general-purpose register named W0 to W30.
 
See the register name mapping in Figure B1-1.
---
 
R0~R30 레지스터는 범용 레지스터로 사용된다는 내용인데, 레지스터 저장하는 데이터의 크기에 따라 다음과 같은 방식으로 표기합니다.
 
   ● 64비트 크기 데이터를 저장하는 레지스터는 X0~X30로 표기
   ● 32비트의 데이터를 저장하는 경우 W0~W30으로 표기
 
대신 프로세스의 실행 흐름을 나타내는 스페셜 기능 레지스터는 PC와 SP입니다.
 
ARMv8 아키텍처(Aarch64)에서 각 익셉션 레벨 별로 사용되는 레지스터 목록은 다음과 같습니다.
 

 
그림 1.5 ARMv8(Aarch64) 아키텍처에 접근되는 레지스터 목록
 
위 그림에서 표시된 각 레지스터의 주요 기능은 다음과 같습니다.
 
   ● XZR, WZR(Zero register): 항상 0이 저장되고 0이 읽히는 레지스터입니다.
 
   ● SP_ELn: 모든 익셉션 레벨 별로 존재하는 레지스터입니다. SP_EL0은 익셉션 레벨0(EL0)에서 접근 가능한 스택 포인터 레지스터, SP_EL1은 익셉션 레벨1(EL1)에서 사용되는 스택 포인터 레지스터입니다.
 
   ● SPSR(Saved Process Status Register): 익셉션 발생시 현재의 상태 정보를 저장하는 레지스터인 PSTATS 백업하는 기능의 레지스터입니다. EL0을 제외하고 각 EL 레벨별로 존재하며, 각각의 이름은 SPSR_ELn입니다.
 
   ● ELP(Exception Link Register): 익셉션이 발생한 다음에 다시 복귀할 코드의 주소를 저장하는 레지스터입니다. EL0을 제외하고 각 EL별로 존재하며 각각의 이름은 ELR_ELn입니다. 즉, EL1에서 접근하는 ELP 레지스터는 ELP_EL1, EL2에서 접근되는 ELP 레지스터는 ELP_EL2입니다.
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 유튜브 소개 영상
ARMv7 아키텍처에서 사용되는 레지스터는 범용과 특별 용도 레지스터로 구분할 수 있습니다. 먼저 범용 레지스터에 대해 살펴봅시다.
 
다음은 ARM 스팩 문서에서 발췌한 내용입니다.
 
출처: DDI0406C_arm_architecture_reference_manual.pdf
 
A2.3 ARM core registers
 
In the application-level view, an ARM processor has:
•  thirteen general-purpose 32-bit registers, R0 to R12
•  three 32-bit registers with special uses, SP, LR, and PC, that can be described as R13 to R15.
 
위 내용은 "R0~R12 레지스터는 범용 용도로 사용되며 SP, LR, PC와 같이 (R13~R15) 레지스터는 특별한 용도로 사용된다"라고 해석할 수 있습니다.
 
범용 레지스터는 어셈블리 명령어를 실행할 때 연산에 주로 사용되며, 주로 데이터를 저장합니다. 대신 스페셜 레지스터는 프로세스의 실행 흐름을 나타내는 역할을 수행합니다. SP, LR, PC 레지스터를 알면 프로세스가 어느 코드까지 실행됐는지 파악할 수 있습니다.
 
다음은 ARMv7 코어에서 사용되는 레지스터 세트를 나타낸 그림입니다.
 

 
그림 1.4 ARM 모드 별로 사용되는 레지스터의 목록
 
위 그림에서 가장 윗 부분에 보이는 User~FIR는 ARMv7에서 지원하는 ARM 동작 모드를 나타냅니다. 이 내용으로 ARM 동작 모드 별로 사용되는 레지스터가 있다는 사실을 추정할 수 있습니다.
 
가장 왼쪽 행을 보면 R0부터 PC까지 흰색 배경으로 표시된 부분이 있는데, 이는 범용으로 사용되는 레지스터 세트를 나타냅니다. 어떤 ARM의 작동 모드에서도 사용할 수 있는 레지스터를 뜻합니다.
 
회식 음영으로 나타낸 레지스터들은 해당 작동 모드에서만 엑세스할 수 있는 레지스터입니다. 다음 표에서 Supervisor 모드, IRQ 모드 그리고 FIQ모드에서 엑세스할 수 있는 레지스터를 확인할 수 있습니다.
 
 
표 1.2 각 모드별 접근 가능한 레지스터의 목록
 
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
 
* 유튜브 소개 영상
우리가 외국인이 어떤 사람인지 잘 알려면 외국어를 배워야 합니다. 외국어를 직접 말하면서 외국인과 대화를 해야 그 사람의 성격을 제대로 알 수 있습니다.
 
그렇다면 프로그래머 입장에서 ARM 프로세서가 어떤 동작을 하는 지 파악하려면 무엇을 배워야 할 까요? 바로 ARM 어셈블리 명령어입니다.
 
미국인과 대화를 하려면 영어를 말하듯 ARM 프로세서와 대화를 하려면 ARM 어셈블리 명령어를 사용해야 합니다. 즉, ARM 어셈블리 명령어는 ARM 프로세서와 소통하는 대화하는 언어라고 말할 수 있습니다.
 
ARM 어셈블리 명령어를 사용하면 ARM 프로세서와 어떻게 대화를 할 수 있을까요?
이번에는 프로그래머 입장에서 조금 더 구체적으로 ARM 프로세서와 대화하는 패턴을 예로 들겠습니다.
 
쉽게 설명을 하면 ARM 프로세서와는 대화하는 패턴을 2가지로 요약할 수 있습니다. 
 
   ● ARM 프로세서에게 일을 시킨다.
   ● ARM 프로세서의 상태 정보를 읽어 오고 싶다.
   
프로그래머 입장에서 위에서 언급한 패턴을 벗어나는 대화를 하는 경우는 드뭅니다. 
 
ARM 어셈블리 명령어의 포멧 
 
ARM 어셈블리 명령어는 명령어와 명령어를 수행한 결과를 저장하는 레지스터로 구성돼 있습니다.
다음 그림을 보면서 ARM 어셈블리 명령어의 포멧을 설명하겠습니다.
 

 
그림 1.3 어셈블리 명령어의 포멧
 
위 그림에서 표기된 필드 중 왼쪽 부분부터 살펴봅시다.
 
   ● OP Code: mov, add, sub, str, ldr와 같이 어셈블리 명령어의 기능을 나타내는 필드입니다. 
   ● cond: 어셈블리 명령어를 조건부로 실행하기 위해 OP Code 뒤에 붙여서 사용되는 필드입니다. 명령문의 추가 옵션입니다.
   ● Rd: 연산을 수행한 다음의 결과를 저장하는 레지스터인 목적 레지스터가 위치하는 필드입니다. 이 필드에는 R0~R15 레지스터가 올 수 있습니다. 
   ● Rn: Operand1로 레지스터가 위치하는 필드이며 R0~R15 레지스터가 사용됩니다. 
   ● Rm: Operand2로 레지스터뿐만 아니라 상수, 주소, 비트 시프트 연산식과 같은 등 다양한 값이 위치하는 필드입니다.
 
이번에는 다음과 같이 간단한 명령어를 예를 들어 어셈블리 명령어를 소개합니다. 
 
   ●  mov r1, #0x1
   
위 명령어는 mov라는 명령어인데 레지스터에 데이터를 저장하는 역할을 합니다. 
mov란 명령어를 실행하면 실행 결과는 어디에 저장될까요? 바로 레지스터 r1입니다.
   
처음에 ARM 어셈블리 명령어를 보고 공포에 질려 공부를 포기하는 분들이 많지만 ARM 어셈블리 명령어의 포멧은 다음과 같이 정말로 간단합니다.  
 
ARM 어셈블리는 ARM의 기본 개념과 더불어 배울 수 있는 개념이므로  어셈블리 명령어의 의미와 ARM 프로세서의 동작 원리를 확인할 수 있습니다.
< '시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 저자>
 
* 유튜브 소개 영상 
ARM 프로세서를 배울 때 가장 혼돈되는 게 용어입니다. 먼저 ARM과 관련된 용어를 소개합니다.
 
ARM 아키텍처
 
아키텍처는 ARM 프로세서를 설계하는 디자인을 의미하며, 프로그래머 모델을 의미합니다. ARM 아키텍처는 레지스터, 메모리 구조, ARM 어셈블리 명령어, 함수 호출 규약과 같이 소프트웨어 개발자가 알아야 하는 주요 기능입니다. 이 책에서는 ARMv7(32비트)와 ARMv8(64비트: Aarch64) 아키텍처 기반의 프로그래머 모델을 설명합니다.
 
ARM 프로세서
 
ARM 프로세서는 용어 그대로 디바이스를 뜻합니다. ARM 프로세서는 ARM 아키텍처에 의존적인데, 같은 아키텍처 버전 기반의 ARM 프로세서들은 같은 명령어를 사용합니다.
 
다음 표는 ARM 아키텍처와 ARM 프로세서의 관계를 나타냅니다.
 
표 1.1 ARM 아키텍처와 ARM 다바이스의 종류
 
예를 들어 Cortex-A7 ARM 프로세서와 Cortex-A9 프로세서는 같은 ARM 아키텍처(ARMv7)에 포함되며 둘 다 ARMv7 아키텍처에서 제공하는 같은 명령어를 사용할 수 있습니다.
 
하지만 ARM 프로세서를 설계하는 마이크로프로세서 혹은 하드웨어 관점으로 같은 아키텍처에 속한 ARM 프로세서의 내부 구성은 다를 수 있습니다. 예를 들어 캐시의 구성이나 파이프라인이 다른 방식으로 구현할 수 있습니다.  
 
ARM 코어
 
ARM 프로세서를 'ARM 코어'라고 부르는 경우가 있는데, ARM 코어는 ARM 아키텍처의 주요 기능을 활용해 구현된 ARM 프로세서의 핵심 부분을 뜻합니다. ARM 코어의 '코어'는 CPU 코어의 '코어'와 비슷한 의미를 지닙니다.
 
이 블로그의 포스트에서는 'ARM 코어의 레지스터'와 같이, 'ARM 코어'란 용어를 자주 사용하는데, 이는 ARM 아키텍처의 핵심 부분을 의미합니다.
 
  
 

+ Recent posts