owned this note
owned this note
Published
Linked with GitHub
# Zephyr 스터디 노트
<details markdown="1">
<summary>스터디 소식</summary>
## 시작하기
### 아이스브레이킹
* 돌아가면서 간단히 자기소개
- 본명이든 별명이든 불리고 싶은 이름으로
- 나이는 공개하지 않는 편이
- 호칭은 님으로 통일
- 자기소개의 목적은 모두 짧게라도 한마디씩 해서 보다 깊이 인볼브되도록 하는 것
- 능동적인 모임, 활발한 토론
- 참석자들의 영상을 여는 게 친밀감 높이는 데 도움이 될 지 모르겠음
* 주로 사용하는 MCU 그리고 개발환경 공유
* 매주 코드 드라이버 뽑기
* 공부하고 싶은 분야 그리고 스터디에서 다루고 싶은 부분
- 빌드 시스템
- 스케줄러
- 파일 시스템
- 디바이스 트리
- 디바이스 드라이버
- 토이 프로젝트
* 지향점은 조바심내지 않고 차곡차곡 한발씩 함께 밟아나가는 것
### 1화: Hello, reset.S!
* 일시: 2021년 8월 8일, 20시부터 22시까지
* 참석자(6): blackbird, Kye-Hyun Park, Albert, Chae Lee, 허정훈, 권경환
- 드라이버: 권경환
* 매주 일요일 20시부터 22시까지 2시간 동안 커널 소스를 분석하기로 함
- ARM Cortex-M 아키텍처 기반
* 매주 랜덤으로 뽑힌 사람이 소스 화면을 공유, 다함께 라인바이라인으로 읽은 코드 의미를 주석으로 남김. 이를 드라이버라 부르기로
- 드라이버는 화면을 공유하고 코드를 쫓아가는 역할을 할 뿐, 해석은 다함께 하므로 부담가질 필요 없음
- 다른 사람은 어떤 개발환경과 도구를 사용하는 지 보고 배울 수 있음
- 그날 읽은 코드에 오너쉽을 갖고 능동적으로 참여하게 됨
* reset.S 의 `z_arm_reset` 부터 C 도입 부분인 `z_arm_prep_c` 전까지 어셈블리 살펴봄
### 2화
* 일시: 2021년 8월 15일, 20시부터 22시까지
* 참석자(8): blackbird, Kye-Hyun, Albert, 허정훈, 최봉진, Stephanos, 송종훈, 권경환
- 드라이버: blackbird
* 3명이 새로 합류. 스터디 신청자 중 1명만 아직 합류하지 않음(spirit cha)
* `z_arm_prep_c` 부터 `z_cstart` 의 `arch_kernel_init` 함수까지 살펴봄
- 디바이스트리와
- Security extension(TrustZone)
- Logging subsystem
- MPU 관련 코드는 일단 스킵하고 나중에 보기로 함
### 3화
* 일시: 2021년 8월 22일, 20시부터 22시까지
* 참석자(7): blackbird, Kye-Hyun, 허정훈, Stephanos, yunsik, Chae Lee, 권경환
* 드라이버: 권경환(Kye-Hyun님 화면공유가 안되서 대타)
* 자발적 참여로 모임 시작 전 60분 가량 문서읽기로
* oyskwu 님 새로 합류
### 4화
* 일시: 2021년 8월 29일, 20시부터 22시까지
* 문서읽기는 19시부터
* 참석자(8): Kye-Hyun, 허정훈, yunsik, HyunYoung, 박종한, 최봉진, inhyun, 권경환
* 드라이버: Kye-Hyun
* 문서읽기
* 참석자(5): HyunYoung, yunsik, Kye-Hyun, 허정훈, 권경환
* 허정훈 - [external modules](https://docs.zephyrproject.org/latest/guides/modules.html)
* Kye-Hyun - [posix](https://docs.zephyrproject.org/latest/guides/portability/index.html)
* 권경환 - [kernel timing](https://docs.zephyrproject.org/latest/reference/kernel/timing/clocks.html)
* 스터디 소식
* inhyun 님 새로 합류
### 5화
* 일시: 2021년 9월 5일, 20시부터 22시까지
* 참석자(6): 최봉진, inhyun, Kye-hyun, yunsik, Stephanos, 권경환
* 드라이버: Stephanos
* 문서읽기
* 참석자():
* 허정훈 - [드라이버](https://docs.zephyrproject.org/latest/reference/drivers/index.html)
* Kye-Hyun - [스레드](https://docs.zephyrproject.org/latest/reference/kernel/threads/index.html)
* 권경환 - [커널 오브젝트](https://docs.zephyrproject.org/latest/reference/usermode/kernelobjects.html)
* 스터디 소식
* blackbird 님 개인사정으로 하차
### 6화
* 일시: 2021년 9월 12일, 20시부터 22시까지
* 참석자(7): BongJin, Inhyun, Kye-Hyun, Stephanos, yunsik, 허정훈, 권경환
* 드라이버: 허정훈
* 문서읽기
* 참석자(3): 허정훈, Kye-Hyun, 권경환
* 스터디 소식
* nucleo_l073rz 보드 기반으로 adc 드라이버 예제 읽기로 함
### 7화
* 일시: 2021년 9월 19일, 20시부터 22시까지
* 참석자(6): Kye-Hyun, BongJin, Inhyun, 김준철, yunsik, 권경환
* 드라이버: BongJin
* 문서읽기
* 참석자(2): Kye-Hyun, 권경환
* 스터디 소식
* 준철님 새로 합류
### 8화
* 일시: 2021년 9월 26일, 20시부터 22시까지
* 참석자(7): Kye-Hyun, BongJin, Inhyun, 김준철, yunsik, 허정훈, 권경환
* 드라이버: yunsik
* 문서읽기
* 참석자(2): Kye-Hyun, 권경환
* kobject 문서 훑어봄
### 9화
* 일시: 2021년 10월 3일, 20시부터 22시까지
* 참석자(7): Kye-Hyun, BongJin, Inhyun, 김준철, yunsik, 허정훈, 권경환
* 드라이버: 김준철
* 문서읽기
* 참석자(3): Kye-Hyun, 김준철, 권경환
* 후보:
* https://docs.zephyrproject.org/latest/guides/arch/arm_cortex_m.html
* https://docs.zephyrproject.org/latest/guides/porting/index.html
* https://docs.zephyrproject.org/latest/guides/test/index.html
* https://docs.zephyrproject.org/latest/guides/tfm/index.html
* https://docs.zephyrproject.org/latest/guides/build/index.html
### 10화
* 일시: 2021년 10월 10일, 20시부터 22시까지
* 참석자(5): Kye-Hyun, BongJin, Inhyun, 허정훈, 권경환
* 드라이버: Inhyun
* 문서읽기
* 참석자(2): Kye-Hyun, 권경환
### 11화
* 일시: 2021년 10월 17일, 20시부터 22시까지
* 참석자(6): Kye-Hyun, BongJin, Inhyun, 허정훈, yunsik, 권경환
* 드라이버: Kye-Hyun
* 문서읽기
* 참석자(3): Kye-Hyun, 허정훈, 권경환
### 12화
* 일시: 2021년 10월 24일, 20시부터 22시까지
* 참석자(5): Kye-Hyun, Inhyun, 허정훈, yunsik, 권경환
* 드라이버: 허정훈
* 문서읽기
* 참석자(3): Kye-Hyun, 허정훈, 권경환
### 13화
* 일시: 2021년 10월 31일, 20시부터 22시까지
* 참석자(4): Kye-Hyun, Inhyun, 허정훈, 권경환
* 드라이버: 허정훈
* 문서읽기
* 참석자(3): Kye-Hyun, 허정훈, 권경환
### 14화
* 일시: 2021년 11월 7일, 19시30분부터 21시30분까지
* 참석자(4): Kye-Hyun, Inhyun, 허정훈, 권경환
* 드라이버: 권경환
* 문서읽기
* 참석자(3): Kye-Hyun, 허정훈, 권경환
### 15화
* 일시: 2021년 11월 14일, 20시부터 22시까지
* 참석자(4): Kye-Hyun, Inhyun, 허정훈, 권경환
* 드라이버: 권경환
* 문서읽기
* 참석자(3): Kye-Hyun, 허정훈, 권경환
### 16화
* 일시: 2021년 11월 21일, 20시부터 22시까지
* 참석자(5): Kye-Hyun, Inhyun, yunsik, 허정훈, 권경환
* 드라이버: Kye-Hyun
### 17화
* 일시: 2021년 11월 28일, 20시부터 22시까지
* 참석자(4): Kye-Hyun, yunsik, 허정훈, 권경환
* 드라이버: 허정훈
### 오프라인 식사
* 일시: 2021년 12월 3일, 19시30분부터 22시까지
* 참석자(5): Kye-Hyun, Inhyun, yunsik, 허정훈, 권경환
* 위치: https://www.diningcode.com/profile.php?rid=jAqpJTgD2b61
### 18화
* 일시: 2021년 12월 5일, 20시부터 22시까지
* 참석자(4): Kye-Hyun, Inhyun, yunsik, 허정훈, 권경환
* 드라이버: yunsik
### 19화
* 일시: 2021년 12월 12일, 20시부터 22시까지
* 참석자(4): Kye-Hyun, Inhyun, yunsik, 허정훈, 권경환
* 드라이버: 권경환
* 19시에 cmake 관련 자료 살펴봄
* https://www.youtube.com/watch?v=bsXLMQ6WgIk
* https://cliutils.gitlab.io/modern-cmake/
* https://hsf-training.github.io/hsf-training-cmake-webpage/
* https://riptutorial.com/cmake
* https://www.internalpointers.com/post/modern-cmake-beginner-introduction
* https://cmake.org/documentation
* IPv6 사양: https://datatracker.ietf.org/doc/html/rfc2460
</details>
## 의문점들
* 초기화에서 dummy thread 가 필요한 이유?
* SMP(xtensa)에서의 tick 업데이트
* `drivers/timer/xtensa_sys_timer.c:36` `ccompare_isr()` 에서 스핀락 걸고 announce 함
* 코어별 타이머는 `z_smp_init()` -> `arch_start_cpu()` -> `smp_init_top()` -> `smp_timer_init()` 에서 초기화 됨
* 모든 코어에서 announce하게 되어 있는데 한 곳에서만 해야 하지 않나?
* `timeout_q`는 `_kernel` 전역변수에서 관리하는게 좀 더 구조적?이지 않나? 지금 구현은 모듈에서 static 으로 사용하고 있음. 나라면 큐랑 스핀락 모두 전역 커널 자료구조에 넣었을 것 같은데. 모듈화를 선호해서? 설계관점이 궁금하다
* 디바이스 핸들 용도?
## TODOs
- [ ] Security extension
- [ ] logging subsystem
- [ ] 런타임 커버리지
- `z_cstart()` 첫번째 라인: `gcov_static_init()`
- [ ] 문서 읽기
- [ ] [User and Developer Guides](https://docs.zephyrproject.org/latest/guides/index.html)
- [ ] [Security](https://docs.zephyrproject.org/latest/security/index.html)
- [ ] [Build and Configuration Systems](https://docs.zephyrproject.org/latest/guides/build/index.html)
- [ ] [Application Development](https://docs.zephyrproject.org/latest/application/index.html)
- [ ] [API Reference](https://docs.zephyrproject.org/latest/reference/index.html)
- [ ] IPI 그리고 코어들에 대한 global timer의 인터럽트
- [ ] 디바이스 드라이버 정리
- [ ] 시스템콜 정리
- [ ] 빌드시스템
- [ ] 파이썬 스크립트
## 용어
* PE
- Processing Element
* MVE
- M-Profile Vector Extension
* CMSE
- Cortex-M Security Extensions
## 코드분석
### 시스템 리셋
* Cortex-M 실행모드에는 thread mode와 handler mode 두가지 모드가 있음
- 스레드 모드는 어플리케이션 모드
- 핸들러 모드는 시스템 자원을 관리하는 커널 모드라고 볼 수 있음
- 모든 익셉션은 핸들러 모드에서 처리됨. 즉 인터럽트 진입시 무조건 핸들러 모드
- 리셋시 스레드 모드임
* 그리고 privileged 와 unprivileged 두가지 실행 권한이 있음
- 핸들러 모드에서는 무조건 privileged 모드로 실행
- 스레드 모드에서는 설정에 따라 privileged 일수도, unprivileged 일수도 있음
- `CONTROL.nPRIV` 에서 설정
- `CONTROL.nPRIV` 디폴트 값이 0이므로, 리셋시 privileged 모드임
* MSP, PSP 두개의 기본 스택과 security extension 유/무에 따라 MSP_S, PSP_S 두개의 스택이 더 있음
- Security extension 이 없는 경우 reset 시 MSP 를 사용
- Security extension 이 있는 경우 reset 시 MSP_S 를 사용
- 핸들러 모드에서는 MSP 사용
- 스레드 모드에서는 `CONTROL.SPSEL` 설정에 따라 MSP 가 될수도 PSP 가 될수도 있음
* 유효한 MSP 를 가진 Privileged thread mode 로 reset 벡터에 진입해야 함
- 부트로더에서 분기하든 다른 어플리케이션에서 분기하든 이 조건이 만족되어야 함
- 차후 MSP 는 인터럽트 핸들러에서만 사용
* C 초기화 코드로 넘어가기 전에 인터럽트 스택을 초기화하고 임시로(초기화가 끝나기 전까지) psp가 인터럽트 스택을 가리키도록 해 인터럽트 스택을 당분간 사용
* 부트코드는 당연히 하드폴트를 일으키면 안됨. NMI와 하드폴트를 제외한 모든 인터럽트를 금지함
* 시작지점(entry point)으로 `z_arm_reset` 이 지정되어 있지만 부트로더에서 `__start` 심볼을 찾는 경우도 있기 때문에 두 심볼 모두 동일한 주소로 맵핑
* 부트로더나 다른 어플리케이션으로부터 분기했을 수도 있기 때문에 `CONFIG_INIT_ARCH_HW_AT_BOOT` 옵션이 설정되어 있는 경우 core register 상태를 리셋 상태로 초기화함
* LDR 두번째 피연산자 앞에 `=` 용도
- pseudo 명령 사용을 암시함: https://developer.arm.com/documentation/dui0041/c/Babbfdih
- `=` 뒤의 값이 어셈블러가 알 수 있는 값이고, 그 값이 “유효범위” 내에 있다면(예컨대 mov 명령어의 경우 imm8 영역) 해당 명령어가 생성됨
- 그러니까 8비트내의 값이라면, ldr은 mov 또는 mvn 명령으로 대체됨
- 유효범위를 벗어날 경우, 상수 값은 리터럴 풀에 저장되고 PC-relative LDR 명령어가 생성됨
- 그러니까 8비트를 벗어나는 값이라면, 그 값은 리터럴 풀에 저장되고 LDR 명령을 통해서 그 값을 레지스터에 읽어오게 됨
- ARM 명령셋은 남는 명령 하위 비트에 제한된 immediate 상수값만을 표현할 수 있음
- Architecture Reference Manual 의 C2 챕터 참고
- 따라서 보다 큰 상수값을 표현하기 위해서 ldr 명령으로 레지스터에 전체 상수값을 로드하는 방식을 사용함
### 초기화
#### runlevel 에 따른 동적 초기화
* 4 단계의 런레벨로 구분됨
* SMP를 위한 추가적인 런레벨이 있음(총 5단계)
* `SYS_INIT` 매크로로 초기화 함수를 등록함
* 등록된 심볼들은 `__init_start` 아래 등록됨
* 내부적으로 `Z_INIT_ENTRY_DEFINE` 매크로로 치환됨
* 어셈블러나 링커에서 alignment를 위한 패딩을 넣을 수 있기 때문에 명시적으로 alignment 를 지정함
* 패딩이 들어가면 array walk 가 쫑날 수 있기 때문
* 링커스크립트의 alignment 구문으로는 충분하지 않음. 어셈블러는 세그먼트별로 패딩을 넣을 수 있기 때문
* 커널 초기화할 때 `z_sys_init_run_level()` 함수로 등록된 초기화 함수들을 커널 컨텍스트에서 실행
##### PRE_KERNEL_1
* kheap - statics_init
* mailbox - init_mbox_module
* mem_domain - init_mem_domain_module
* mem_slab - init_mem_slab_module
* pipes - init_pipes_module
* system_work_q - k_sys_work_q_init
* userspace - app_shmem_bss_zero
##### PRE_KERNEL_2
* gdb_init
##### POST_KERNEL
* disk_init
* fs_init
##### APPLICATION
### Memory Barrier
* memory barrier 와 synchronization barrier 두가지로 분류
- memory barrier 는 compiler memory barrier와 cpu memory barrier 두가지로 분류
- DMB는 cpu memory barrier
- ISB, DSB는 synchronization barrier
* 편의상 4단계 파이프라인을 가정: fetch, decode, execution, write back
* 언급된 아래 배리어외에도 csdb, pssbb, ssbb 가 있음
#### ISB
* ISB 명령을 execute 하게 되면 파이프라인을 flush 하고(ISB 이후에 fetch 및 decode 된 명령들을 취소하고) 새로 fetch 함. 즉, ISB 이전 명령의 모든 연산 결과가 ISB 이후 명령들에 적용됨
* Control 레지스터 변경이나 인터럽트 활성/비활성 등의 명령 뒤에 사용됨. 해당 연산의 결과는 다음 명령에 바로 visible 해야 하기 때문
#### DSB
* DMB 는 reordering 만을 방지하는 반면, DSB 는 앞선 메모리 연산이 완료될 때 까지 다음 명령을 실행하지 않는다
* ISB는 flush 하는 반면 DSB는 stall 시킴
* 이를테면, 캐시 invalidate 한 경우 캐시 상태가 시스템 전반에 전달되어야 하기 때문에 DSB 명령이 사용됨
#### DMB
* 캐시나 메모리 특성에 따라 의존성이 없는 경우 효율을 위해 메모리 연산을 프로세서가 reordering 하는 경우가 생기는 데, 이를 방지하기 위해 사용
* 가령 memory mapped I/O 레지스터의 결과가 다음 메모리 접근에 영향을 줄 경우, 프로세서는 이를 모르고 reordering 할 수 있음. 이때 두 명령 사이에 DMB 명령을 넣으면 reordering 막아줌
### Memory Type
#### Normal Memory
* 일반 코드 및 데이터용 메모리
* 프로세서가 memory reordering 이나 merging 같은 메모리 최적화를 수행할 수 있음
#### Device Memory
* load 와 store 가 반드시 코드 순서대로 발생
* 다음과 같은 소속성을 지님
* G: gathering
* 여러개의 접근이 하나의 transaction으로 합쳐질 수 있음
* R: reordering
* E: early write acknowledge?
* 다음 4가지 조합만 가능
* !G!R!E = Strongly ordered memory type
* !G!RE = Device memory type
* !GRE
* GRE
#### Strongly Ordered Memory(v6 & v7)
* Device Memory 조건 + 해당 연산이 종료될때까지 기다림
### Memory Attributes
#### Shareable
* 여러 버스 마스터가 있는 시스템에서 마스터끼리 메모리 동기화
* 가령, 프로세서와 DMA는 하나의 버스를 공유하는 각각의 마스터
* 여러 버스 마스터가 non-shareable 메모리에 접근할 수 있다면, 소프트웨어에서 데이터 coherency 를 반드시 보장해야 함
* strongly-ordered memory 는 항상 shareable 임
#### Cacheable
#### Transient
* 충분한 시간적 지역성temporal locality을 갖지 못하는 경우
* 다시 사용될 일 없는 데이터에 캐시를 할당하므로써 다른 캐시 엔트리를 밀어내지 않도록
* 캐시의 LRU 위치에 집어넣어 다른 캐시 엔트리에 비해 금방 evict 될 수 있도록 할 수 있는 속성?
* 검색을 좀 해보니 사용하는 경우가 없는 듯? 구현비용에 비해 얻는 이득이 크지 않아서?
* B7.15: R~XQXW~, I~LDXP~
### Shareability Domain
* 시스템을 어떻게 설계하냐에 따라 다르겠지만, 보통 동일한 운영체제를 사용하는 프로세서들이나 하이퍼바이저는 Inner Shareable shareability domain 에 속함
- 예컨대, 시스템 내의 두 프로세서는 inner로 묶일 수도 있고 outer로 묶일 수도 있음
- 또다른 예로 big.LITTLE 시스템은 두개의 클러스터를 가지는 데, 클러스터 간은 outer, 클러스터 내는 inner로 묶임
* Non-cacheable 일 경우, 모든 observer(master)에게 coherent 해야 하므로 outer shareable 임
* synchronization barriers
- ISB는 core 별로 실행되어야 하는 명령이므로 제외
- 외부 버스에 전달되어야 하는 경우(예컨대 DMA): sy 또는 osh
- inner의 경우: ish.
### 메모리 도메인
* 여러 파티션(혹은 하나의)을 하나의 도메인으로 묶어 권한을 설정
* 스레드는 하나의 도메인에 속하고 도메인 권한에 따라 메모리 접근
* `SYS_INIT` 매크로로 메모리 도메인을 초기화하는 함수가 등록되어 있음, `init_mem_domain_module()`
* 디폴트 도메인을 초기화하고
* libc 파티션을 디폴트 도메인에 추가함
* `gen_app_partitions.py` 의해 관련 심볼들이 명시된 섹션으로 들어가고
* `K_APPMEM_PARTITION_DEFINE` 매크로로 해당 섹션주소를 파티션 주소에 할당
### Linker script
* include/arch/arm/aarch32/cortex_m/scripts/linker.ld
- common-rom.ld
- snippets-rom-start.ld
- arch/arm/core/aarch32/CMakeLists.txt 의해서 만들어짐
- arch/arm/core/aarch32/vector_table.ld
- arch/arm/core/aarch32/cortex_m/relay_vector_table.ld
* scripts/gen_relocate_app.py
- linker_relocate.ld 파일과
- code_relocation.c 파일을 생성함
- 코드 재배치를 위해 소스코드와 input section 지정을 분리한 듯?
* scripts/gen_app_partitions.py
- CMakeLists.txt 에서 invoke 됨
### 코드/데이터 메모리 재배치
* 런타임 재배치가 아니라 컴파일 타임 재배치임
* 보드별로 서로 다른 여러개의 메모리 공간region을 갖고 있기 때문에 보드마다 특정한 메모리 공간을 지정하기 위해 사용
* SRAM1, SRAM2, ITCM, DTCM 등
* MMU 가 없는 cortex-m 프로파일에서만 사용
* CMakeLists.txt:797 의 `toolchain_ld_relocation()` 매크로가 gen_relocate_app.py 를 실행
* cmake/linker/ld/target_relocation.cmake 에 정의되어 있음
* gen_relocate_app.py 의 인자는 다음에서 설정됨
* cmake/extensions.cmake:1213 의 `zephyr_code_relocate` 로 `code_data_relocation_target` 을 설정
* 사용처를 찾아보니 현재로선 XIP용 SPI 플래시를 다루는 코드에만 사용하는 듯(속도 때문?)
* kernel/init.c 의 다음 코드들은 생성되지 않음
* bss_zeroing_relocation()
* data_copy_xip_relocation()
* 비용 대비 효율이 안좋은 거 아닌지. 복잡도만 너무 올라간 거 아닌가..
### 가독성을 해치고 유지보수를 어렵게 하는 #ifdef 전처리 대안
예제:
```c=
#define _XXXX1 _YYYY,
#define __DEBRACKET(...) __VA_ARGS__
#define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
#define __COND_CODE(one_or_two_args _if_code, _else_code) \
__GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
#define Z_COND_CODE_1(_flag, _if_1_code, _else_code) \
__COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
#define COND_CODE_1(_flag, _if_1_code, _else_code) \
Z_COND_CODE_1(_flag, _if_1_code, _else_code)
```
* 참일 경우, 다음과 같이 확장됨
* `COND_CODE_1(1, ("true"), ("false"))`
* `Z_COND_CODE_1(1, "true", "false")`
* `__COND_CODE(_XXXX1, "true", "false")`
* `__GET_ARG2_DEBRAKET(_YYYY, "true", "false")`
* `__DEBRACKET "true"`
* 참이 아닐 경우, 다음과 같이 확장됨
* `COND_CODE_1(0, ("true"), ("false"))`
* `Z_COND_CODE_1(0, "true", "false")`
* `__COND_CODE(_XXXX0, "true", "false")`
* `__GET_ARG2_DEBRAKET(_XXXX0 "true", "false")`
* `__DEBRACKET "false"`
* 핵심 트릭은 매크로에 콤마를 넣거나 뺌으로써 value 값을 조정
* 매크로 인자를 반드시 괄호로 감싸주어야 함
* `__VA_ARGS__` 매크로가 마지막 단계에서 괄호를 한단계 풀어(없애)주는 듯
* `__DEBRACKET` 을 호출하는 부분에서 괄호를 씌우면 될텐데
* `__GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET(val)` 이런식으로
* 이유가 뭘까?
### 인터럽트
* Processor core exception 과 device-specific exception 으로 나눌 수 있음. 각각
* Internal interrupt
* External interrupt 라고 칭하겠음
#### 초기화
* `SHCSR` 레지스터로 각종 fault 활성화 함
* `SCR.SEVONPEND` 로 core 가 슬립상태일 때 인터럽트 발생시 wakeup 할 수 있도록 함
* `AIRCR` 레지스터의 secure 관련 설정
#### Internal interrupts
* 커널 코드 시작지점은 링커스크립트의 `ENTRY(CONFIG_KERNEL_ENTRY)`로 지정됨
* 링커스크립트 위치는 `include/arch/arm/aarch32/cortex_m/scripts/linker.ld`
* `CONFIG_KERNEL_ENTRY` 디폴트 값은 `__start`
* aarch32의 경우 `__start` 는 `arch/arm/core/aarch32/cortex_m/reset.S` 에 정의되어 있음
* 링커스크립트의 벡터테이블 관련 섹션은 `snippets-rom-start.ld` 의해 include 되는데
* 링커스크립트의 `snippets`으로 시작하는 링커 파일들은 cmake의 `zephyr_linker_sources`로 취합된 뒤 generated 디렉토리에 생성됨
* `zephyr_linker_sources` cmake 함수 정의는 `cmake/extensions.cmake` 에 있음
* `snippets-rom-start.ld` 에서 include 되는 `rom_start_offset.ld` 과 `vector_table.ld` 파일은 각각 `arch/common/CMakeLists.txt` 와 `arch/arm/core/aarch32/CMakeLists.txt` 에서 추가됨
* `arch/arm/core/aarch32/cortex_m/vector_table.S` 에 정의된 벡터 테이블은 `vector_table.ld` 에 선언한 `exc_vector_table` 섹션에 위치하게 됨
* `_IRQ_VECTOR_TABLE_SECTION_SYMS`(`.gnu.linkonce.irq_vector_table*`) 는 `exc_vector_table` 섹션 바로 다음 위치에 들어감
#### External interrupts
*`IRQ_CONNECT` 로 생성되는 서비스 루틴들
* `IDT_LIST`(`intlist.ld`) 라는 모조의 메모리 공간에 IRQ 메타 데이터(`struct _isr_list`)들이 등록됨
* `ZEPHYR_PREBUILT_EXECUTABLE` 를 먼저 빌드한 뒤 `.intList` 섹션에 등록된 위 데이터를 `isrList.bin` 파일로 만듬
* `isrList.bin` 의 헤더는 `arch/common/isr_tables.c` 파일에서 `_iheader` 를 `.irq_info` 섹션에 저장
* `gen_isr_tables.py` 스크립트를 통해 `isr_tables.c` 파일을 생성하고 이 irq 테이블은 `_IRQ_VECTOR_TABLE_SECTION_NAME` 섹션, 즉 `exc_vector_table` 바로 뒤에 위치하게 됨
* 모든 외부 인터럽트 핸들러는 `ISR_WRAPPER`를 거쳐 호출되게 됨
* `_isr_wrapper`은 `arch/arm/core/aarch32/isr_wrapper.S` 에 정의되어 있음
#### 인터럽트 우선순위
* armv6m 에서 internal interrupt 우선순위는 설정 불가능(svc, pendsv, systick 제외)
* v7에서 memmanage, busfault, usagefault, debugmonitor가 추가됐고 우선순위 설정 가능함
* v8에서 banked 된 hardfault 와 securefault 가 추가됨
* CMSIS에서 제공하는 NVIC_SetPriority 함수는 음수값일 경우 internal, 0을 포함한 양수값일 경우 external interrupt 를 가리킴
* SHPRx 레지스터의 초기값이 0이므로 internal interrupt 우선순위는 0
- `z_arm_exc_setup()` 에서 설정됨
* PendSV는 항상 가장 낮은 우선순위를 가짐
* SVC는 external interrupts 보다 항상 높은 우선순위를 가짐
- `CONFIG_ZERO_LATENCY_IRQS` 가 설정된 경우, SVC 보다 우선순위가 높은 external interrupts 를 만들 수 있음
- 즉, 스케줄러가 선점할 수 없고 스케줄러를 선점하는 문맥을 만들 수 있음
- 따라서 관련 코드는 유념해 작성해야함
- 데드락이 발생할 수도 있고
- Busy waiting으로 벽돌이 될 수도 있음
- 노르딕 nrf52 BLE 스택이 zero latency irq 우선순위에서 동작한다고 함
* `CONFIG_NUM_IRQS` 은 벤더에서 구현한 external interrupts 의 갯수
#### 인터럽트 릴레이
* arch/arm/core/aarch32/irq_relay.S
* `__vector_relay_table` -> `__vector_relay_handler` -> `_vector_table_pointer` -> `_vector_start`
* arch/arm/core/aarch32/cortex_m/vector_table.S
* ARMv6-M 나 일부 ARMv8-M baseline core의 경우 벡터 테이블 주소를 변경할 수 없음
* 따라서 부트로더등의 다른 프로그램에 의해 벡터 테이블이 이미 선점된 경우, zephyr 벡터 테이블을 사용할 수 있도록 “릴레이"함
* 예컨대 부트로더의 경우, 부트로더에 `__vector_relay_table` 심볼이 알려져있고 인터럽트 발생시 해당 테이블로 분기해야함
- https://github.com/mcu-tools/mcuboot/blob/e933e586ec179789d98d10821226533586577460/boot/zephyr/main.c#L223
- 현재 mcuboot 만 지원하고 있다는데, 근데 위 코드는 벡터 테이블 주소를 변경 가능한(VTOR) 경우에만 동작할 듯?
- VTOR 지원하지 않는 경우엔 Port 레이어에서 처리해줘야 할 듯
#### EXC_RETURN
![D1.2.95 Exception Return Payload](https://i.imgur.com/Ux4LIGT.png)
* security 도메인 또는 FPU 사용을 구분해야 할 때 `EXC_RETURN` 은 TCB에 저장되어야 함
#### 인터럽트 wrapper
* 모든 인터럽트 핸들러는 마지막에 `z_arm_int_exit` 를 호출함
* `z_arm_int_exit` 는 pendsv 를 올리고 stack sentinel 체크 후 핸들러에서 빠져나감
### FLOAT & MVE
* CP10 과 CP11 이 관련 코프로세서임
* 32개의 단정도 레지스터를 갖고 있음(S0~S31)
- 2개의 레지스터를 결합하여 16개의 배정도 레지스터로 사용할 수 있음(D0~D15)
* CPACR.CP10 에서 접근권한을 부여함
- CP11은 CP10과 동일한 값을 가져야 함
- Full access, privileged only access, no access 설정 가능
* FPCCR.ASPEN 은 exception 발생시 부동소수점 관련 레지스터도 스택에 저장하도록 함
- 그리고 `CONTROL.FPCA` 플래그를 셋 함
- 인셉션 진입시 그만큼(스택에 S0~S31 저장) latency 발생
- 스택 사용량이 그만큼 커짐
### MPU
* Cortex-M 은 unified 만 지원. instruction/data 영역을 구분하지 않음
* 즉, `MPU_TYPE.DREGION` 만 기능
* `MPU_TYPE.SEPERATE` 는 항상 0
* `MPU_CTRL.PRIVDEFENA` 로 region이 설정되지 않은 영역에 privileged 접근을 가능하게 함
* 총 8개의 region 을 제공
* v6와 v7에서는 8개의 subregion 을 제공함
* 각 영역region은 32바이트 단위로 정렬되어야 함
* cache line length?
* region 번호가 높을수록 높은 우선순위
* 비활성화되어 있는 경우, default system address map 에서 접근 속성을 가져옴
* execute-never(XN) 비트는 해당 영역의 instruction이 fetch 될 수 있는지 설정. XN이 활성화된 상태에서 해당 영역을 실행하게 되면 execute 단계에서 memory management fault 발생
* 인터럽트 벡터 어드레스 테이블은 MPU가 체크하지 않음
* 레퍼런스 매뉴얼 B10.1: R~VHHL~
* 벡터 테이블 주소가 바껴도?(VTOR)
* 임베디드 시스템에서 보통 다음과 같이 분류함
* privileged code
* user code
* privileged data
* user data
* privileged peripherals
* user peripherals
* Zephyr cortex-m 에서는 다음과 같이 초기 설정함
* non-cacheable ram area
* `__nocache` 를 키워드로 사용처를 찾아보니 이더넷과 USB 엔드포인트 버퍼에 사용됨
* 예컨대, 한편으론 DMA를 통해 수신한 이더넷 프레임을 특정 RAM에 저장하고, 다른 한편으론 프로세서에서 해당 RAM 영역에 접근할 경우
* 그 영역이 캐시 가능한 영역이라면 stale 한 값을 읽게 되기 때문
* ram code
* ram area for relocated text
* for main stack guard
* user 스레드에서 RO 코드를 읽거나 실행할 수 있도록 초기 설정
* 스레드 stack guard 는 컨텍스트 스위칭 때 설정됨
* Zephyr 에서는 memory domain 이라는 개념이 있음
* 스레드는 하나의 memory domain 에 속함
* 하나의 memory domain 은 하나 또는 여러개의 파티션으로 이루어짐
* 문맥전환할 때 `z_arm_configure_dynamic_mpu_regions` 로 스레드별 설정
* `CONFIG_USERSPACE` 가 설정되어 있는 경우, 스레드가 속한 메모리 도메인의 파티션들은 파티션당 하나의 region 으로 설정됨
* user 스레드인 경우, 스택 역시 하나의 region 으로 설정됨
* `CONFIG_MPU_STACK_GUARD` 가 설정되어 있는 경우, guard 용도로 하나의 region 이 설정됨
* supervisor 스레드일 경우: `thread->stack_info.start` 앞에
* user 스레드일 경우: `thread->arch.priv_stack_start` 앞에
#### 설정방법
1. 설정 중 폴트발생을 막기 위해 MPU 기능을 비활성화, `MPU_CTRL.ENABLE`
2. 설정하려는 region을 설정, `MPU_RNR.REGION`
3. 설정하려는 region의 시작주소를 설정, `MPU_RBAR`
4. region 속성 설정, `MPU_RBAR` & `MPU_MAIR`
5. region의 마지막주소 설정, `MPU_RLAR`
6. region 속성 연결, `MPU_RLAR.AttrIndx`
7. region 활성화, `MPU_RLAR.EN`
### WFI vs WFE
* https://developer.arm.com/documentation/ka001283/latest
* WFE 는 multi-processor 설계에 크게 관련있는 듯
* 멀티코어 환경에서의 spinlock 을 예로 들고 있음
* 인터럽트는 이벤트의 일종으로 볼 수 있을 듯(인터럽트 $\subset$ 이벤트)
* 기본 idle 함수는 wfi 를 사용하는 반면 atomic 은 wfe 를 사용함
### 스레드
* 크게 다음의 구성요소를 가짐
* 스택
* TCB(Thread Control block)
* 함수 entry
* 스케쥴링 우선순위
* 실행모드
* 디폴트는 supervisor 모드. 모든 memory address space와 peripherals 에 접근가능하고 privileged instruction 을 실행할 수 있음
* 시스템콜용 fixed-size privilege elevation stack 이 따로 있음
* 시스템 초기화시 하나의 main 스레드와 코어별 idle 스레드를 생성
* main 스레드는 커널 초기화를 수행하고 `main()` 함수를 호출
* 선점형일 경우 가장 높은 우선순위인 0
* 비선점형일 경우 가장 낮은 우선순위인 -1
* 통상 단일 스레드 어플리케이션일 경우 `main()`함수 사용
* idle 스레드는 항상 존재해야 함
* 우선순위는 항상 가장 낮은 우선순위
* 통상 슬립모드로 들어가는 역할
* 그리고 필요에 따라 시스템 workqueue 스레드를 생성
* 스레드 시작지점은 항상 `z_thread_entry`
* `arch_user_mode_enter()`
* 스레드 종료시에는 항상 `z_thread_abort()` 가 호출됨
* join 호출등의 이유로 pended 되어 있는 스레드들 다 깨움
* 자원정리 및 정보 업뎃
* `K_THREAD_DEFINE` 매크로로 정적으로 스레드를 등록할 수 있음
* 등록된 스레드는 커널 초기화 후 main 호출 전에 실행됨
#### 스레드 lifecycle
#### 스레드 상태
![](https://docs.zephyrproject.org/latest/_images/thread_states.svg)
* `_THREAD_QUEUED` 가 Ready 상태
* `_THREAD_PRESTART` 가 New 상태
#### 스레드 우선순위
* `int8_t` 형
* `CONFIG_NUM_COOP_PRIORITIES` 와 `CONFIG_NUM_PREEMPT_PRIORITIES` 로 설정할 수 있지만, `int8_t` 형 범위를 넘어설 수 없음
* 낮은 숫자일 수록 높은 우선순위
* 우선순위는 런타임에 바뀔 수 있음
* 우선순위에 따라 두가지 종류의 스레드:
* cooperative thread
* 우선순위가 음수일 경우
* 스스로 CPU 자원을 넘기지 않는 이상 계속 current 스레드가 됨
* preemptible thread
* 우선순위가 0과 같거나 큰 경우
* 우선순위가 같거나 높은 스레드에 의해 선점됨
* bottom-half 를 처리하기 위한 meta-irq 우선순위의 스레드가 ready queue에 있을 경우 cooperative 스레드일 경우라도 선점됨
* 스케쥴러 락이 걸려있더라도 선점됨
* 그러니까 어플리케이션이 실행되기 전에 무조건 meta-irq 가 실행됨
### 스케쥴링
* 우선순위 기반 스케쥴링
* cooperative 와
* preemptive 두 종류
* time slice 를 사용할 경우와
* time slice는 스레드별로 설정하는 게 아니라 시스템 전역임
* 그렇지 않은 경우
* 동일한 우선순위일 경우 가장 오래 실행되지 않은 스레드가 스케쥴링 됨
* ready queue 자료구조는 다음 중 하나가 될 수 있음:
* 링크드 리스트(`CONFIG_SCHED_DUMB`)
* memory foot print 는 가장 작지만, 적은 수의 스레드만 runnable 인 시스템에 사용
* 스레드 수가 많을수록 비확정적인 latency 발생
* red-black tree(`CONFIG_SCHED_SCALABLE`)
* 삽입/삭제에 약간의 오버헤드가 추가됨
* 최대 2KB 코드 사이즈 증가
* multi-queue(`CONFIG_SCHED_MULTIQ`)
* `k_sched_lock()`은 선점가능한 스레드를 일시적으로 선점 불가능한 cooperative 스레드로 만듬
* 실제로 우선순위를 변경하는 것 보다 효율적임
* 진입지점인 pendsv 위치는 arch/arm/core/aarch32/swap_helper.S
* vector_table.S -> swap_helper.S
* 디폴트 스케줄러는 `SCHED_DUMP`
* 런큐에 넣을 때 우선순위에 따라 정렬해서 넣으므로 O(N)
* 런큐에서 빼낼 때는 맨 앞쪽에 가장 높은 우선순위가 있으므로 O(1)
### 디바이스
* 디바이스는 `DEVICE_DEFINE` 매크로 또는 `SYS_INIT`, `SYS_DEVICE_DEFINE` 매크로로 컴파일 타임에 등록할 수 있음
#### 디바이스트리
* 네 종류의 파일로 이루어짐
* .dts
* 타겟 보드의 하드웨어를 기술하는 base 파일. 보통 board 디렉토리에 위치하며 dtsi 파일들을 include 함
* .dtsi
* 여러 보드에 공통으로 사용할 수 있는 하드웨어 기능들
* .overlay
* optional 이며 base 파일을 override 함
* 가령 디폴트로 비활성화되어 있는 기능을 KConfig 설정으로 활성화시킬 경우, 코드 수정할 일 없이 overlay 로 가능
* .yaml
* 바인딩 파일
* dts/bindings 디렉토리에 있음
* 디바이스트리 빌드 방식
* 통상 보드별로 하나의 .dts 파일이 있음. 해당 파일에서는 보드 특정 하드웨어를 기술함
* 다른 보드와 공통된 기능은 .dtsi 파일에 기술되고 위 .dts 파일에서 include 됨
* .dtsi 파일은 board->vendor->architecture->skeleton 식으로 계층적으로 include 됨
* 기존 노드를 overwrite 하고 싶을 때 노드명 앞 `&` 를 붙이고 다시 정의할 수 있음
* `#interrupt-cells` 속성은 값으로 1 또는 2 가질 수 있는데
* 첫번째 값은 인터럽트 인덱스
* 두번째 값은
* 1 = low-to-high edge triggered
* 2 = high-to-low edge triggered
* 4 = active high level-sensitive
* 8 = active low level-sensitive
* `DEVICE_DT_INST_DEFINE` -> `DEVICE_DT_DEFINE`
* 내부적으로 `Z_DEVICE_DEFINE` 호출
##### 예제: STM32 ADC 의 경우
`drivers/adc/adc_stm32.c` 파일의 `STM32_ADC_INIT` 매크로로 `POST_KERNEL` 런레벨에 드라이버가 초기화됨. `build/zephyr/include/generated/` 하단에 생성되는 헤더는 `scripts/dts/gen_defines.py` 스크립트에 의해 생성됨.
드라이버 소스(`drivers/adc/adc_stm32.c`)의 제일 하단 `DT_INST_FOREACH_STATUS_OKAY()` 매크로로 드라이버가 등록됨. 확장 순서는 다음과 같음:
* 디바이스트리의 해당 노드가 활성화(okay)되어 있다면 등록된 모든 인스턴스에 대해 `STM32_ADC_INIT` 매크로를 실행함
* `DT_FOREACH_OKAY_INST_st_stm32_adc()` 매크로는 `gen_defines.py` 로 만들어짐
* `build/zephyr/include/generated/devicetree_unfixed.h` 에서 확인할 수 있음
* 예제의 경우 인스턴스가 1개이므로 `DT_FOREACH_OKAY_INST_st_Stm32_adc(STM32_ADC_INIT(0))` 로 확장됨
`node_id` 의 경우 `DT_DRV_INST` 매크로를 통해 다음과 같이 확장된다:
1. `DT_DRV_COMPAT`은 드라이버 최상단에 `st_stm32_adc`로 정의되어 있음
2. `DT_INST(0, st_stm32_adc)`
3. `UTIL_CAT(DT_N_INST, DT_DASH(0, st_stm32_adc))`
4. `MACRO_MAP_CAT(aMacroToAddUnderscore, 0, st_stm32_adc)`
5. `UTIL_CAT(DT_N_INST, _0_st_stm32_adc)`
6. 즉, `node_id`는 `DT_N_INST_0_st_stm32_adc`에서 `DT_N_S_soc_S_adc_40012400`으로 확장됨
`MACRO_MAP_CAT` 매크로는 넘어온 인자를 특정패턴으로 반복적으로 이어붙이는 역할을 한다. 내부적으로 사용된 `NUM_VA_ARGS_LESS_1` 매크로는 실제로 전달된 인자의 갯수를 상수로 리턴하는 역할을 한다. `NUM_VA_ARGS_LESS_1_IMPL` 매크로에서 리턴값으로 사용되는 `N`의 위치가 넘어가는 인자 갯수에 따라 달라지기 때문.
1. `Z_DEVICE_DT_DEV_NAME(DT_N_S_soc_S_adc_40012400)`
2. `_CONCAT(dts_ord_, DT_DEP_ORD(DT_N_S_soc_S_adc_40012400))`
3. `DT_DEP_ORD`매크로는 `DT_N_S_soc_S_adc_40012400_ORD`로 확장되고 결국 `13` 이라는 상수값이 됨
4. 즉, `dts_ord_13` 으로 확장됨
따라서 실제 호출 인자는 다음과 같다: `Z_DEVICE_DEFINE(DT_N_S_soc_S_adc_40012400, dts_ord_13, "ADC_1", ...)`
device 자료구조는 `__device_dts_13` 형식의 이름으로 정의된 뒤 `devices` 섹션의 `__device_POST_KERNEL` 하단에 등록된다.
#### mux
* 하위 5비트 = remap
* 다음 1비트는 padding
* 그 다음 2비트는 alt func
* 그 다음 4비트는 pin
* 그 다음 4비트는 port
### 시스템콜
* `scripts/gen_syscalls.py` 스크립트로 `z_impl_` prefix를 갖는 함수로 바인딩됨
* include/generated/syscall_dispatch.c 생성
* CMakeLists.txt:623 에서 custom command로 실행
* `_k_syscall_table` 이 위 스크립트로 생성되고 `svc` -> `_do_syscall` 에서 참조됨
* syscalls.json 을 참조하고 만들어지는데
* syscalls.json 파일은 `scripts/parse_syscalls.py` 스크립트로 만들어짐
* 시스템 헤더들에서 `__syscall` 를 추출
* 인덱싱을 어떻게 하는지?
### 동기화 자료구조
* API 문서의 `isr-ok` 특성은 인터럽트 핸들러에서도 해당 함수가 호출될 수 있다(blocking 되지 않는다)일 뿐 thread-safe를 보장하지 않음
* 사용자가 동기화 책임을 가짐
* queue 가 lock-free 자료구조로 구현되어 있나 살펴보니 아니었음
### kobject
* 주목적은 여러 커널 오브젝트들을 특정 오브젝트 타입으로 분류해서 접근권한과 참조수를 관리?
* 오브젝트(심볼) 주소로 타입을 찾는데, 이때 해시(gperf)를 사용
* scripts/process_gperf.py
* 오브젝트별로 해시를 만드면 메모리 낭비 같은데?
* 관련 소스들
* scripts/gen_kobject_list.py
* linker/kobject-text.ld
* 시스템콜, 스래드 콜 등에서 `z_object_validate()` 함수로 권한 체크함
### 시스템 클럭
* `sys_clock_isr` -> `sys_clock_announce`
* `sys_clock_isr` 은 `SYS_DEVICE_DEFINE`으로 등록됨
* `sys_clock_announce` 에서 스레드 time slice 처리
* elapsed 된 시간만큼 time slice 줄임. 0 이 된 경우 문맥전환
* `z_add_timeout` 으로 등록된 타임아웃 콜백들이 `sys_clock_announce` 에서 호출됨
* timeout 은 가까운 시간순대로 정렬. O(N) 함수임
* tickless일 경우, 가장 가까운 timeout의 dt를 systick load 값으로 설정
* 등록된 timeout이 없을 경우 최대값(`MAX_WAIT`)으로 설정
* 남은 스레드 time slice가 timeout 이나 최대값보다 작을 경우 time slice 값으로 설정
* 전역변수인 `_kernel`의 `timeout_q`와는 무슨 관계 혹은 차이일까?
* timeout.c 에 static 으로 정의된 `timeout_list` 와 동일한 용도로 중복된 것 같음
* 코드를 `_kernel.timeout_q` 사용하도록 바꾸는 게?
* 그리고 spinlock을 `_kernel` 구조체에 집어넣으면, `timeout_lock`이나 `sched_spinlock`처럼 여러개의 lock을 하나로 합칠 수 있지 않나?
### SMP
이해를 돕기 위한 SMP 다이어그램:
![SMP sample diagram](https://www.intel.com/content/dam/altera-www/global/en_US/documentation/sfo1410070178831/sfo1410068290100.svg)
* ARM SMP 시스템에는 다음 세가지 종류의 인터럽트가 있음
* SGI: Software Generated Interrupt
* 대게 inter-core 통신을(IPI: Inter-Processor Interrupt) 위해 사용됨. 인터럽트 0~15번이 이 용도로 예약되어 있음
* PPI: Peripheral Private Interrupt
* 개별 core 에 종속된 peripheral 에서 발생하는 인터럽트. 인터럽트 16~31번이 이 용도로 예약되어 있음
* SPI: Shared Peripheral Interrupt
* 인터럽트 컨트롤러에서 하나 또는 여러개의 core 로 인터럽트를 발생. 인터럽트 32~ 번이 이 용도로 예약되어 있음
### 네트워크
### 기타
* 관례상 `_` 로 시작하는 이름은 c 라이브러리와 같은 c 언어에 관련된 용도로 사용
* `__`로 시작하는 이름은 컴파일러 내부에서 사용
* 스택 데이터 타입
* MPU region 설정시 alignment 제약 때문에 임의의 char 형 버퍼를 간단히 스택으로 지정하기 어려움
* 그래서 `K_THREAD_STACK()` 매크로와 opaque data type 을 만듬
* `__LP64__`
* 64비트 시스템인 경우 컴파일러에서 define 함
* long pointer 줄임말
* flagged singly linked list
* 남는 하위 2비트를 사용자 지정 플래그로 사용하려고 generic 한 API를 만들어 둔 것
* stack canary & stack sentinel
* canary 는 컴파일러에서 제공하는 stack smashing protection 기능을 사용하기 위한 것
* sentinel 은 커널(아마도 스케줄링할 때?)에서 주기적으로 체크해 corruption 을 감지하기 위한 것
#### 단계별 초기화 내용
* reset.S
* 시스템 상태(레지스터)를 known 상태로 설정
* 인터럽트를 금지
* c 코드로 분기하기 전 인터럽트 스택을 PSP 로 설정하고 전환
* `prep_c.c: z_arm_prep_c`
* zephyr 인터럽트 벡터를 사용하도록 설정
* 부트로더등으로부터 분기한 경우 다른 시스템의 인터럽트 벡터를 사용하고 있기 때문
* FPU 초기화
* bss, data 섹션 초기화
* 외부 인터럽트 우선순위 초기화
* `init.c: z_cstart`
* 커버리지 정보 초기화
* 로깅 초기화
* 인터럽트 스택 설정
* 내부 인터럽트(우선순위 포함) 초기화
* fault 활성화 시키고
* systick 및 svc, pendsv등 우선순위 설정
* MPU 초기화
* 커널 오브젝트 초기화
* 초기화 코드들 실행
* `PRE_KERNEL_1`
* `PRE_KERNEL_2`
* main 스레드 생성, 초기화, 실행
* main 스레드로 스위칭하면서 인터럽트 활성화
## 보드 포팅
* https://docs.zephyrproject.org/latest/guides/porting/board_porting.html
## 참고자료
### GNU Assembler
* https://docs.huihoo.com/redhat/rhel-4-docs/rhel-as-en-4/index.html
* https://sourceware.org/binutils/docs/as/Section.html
### ARM
#### ARM Instruction Set Architecture
* https://developer.arm.com/architectures/instruction-sets
#### ARM Architexture Reference Manual
* Armv8-M
- https://developer.arm.com/documentation/ddi0553/bp
- An Introduction to the ARMv8-M architecture
- https://developer.arm.com/documentation/100688/0200
* Armv7-M
- https://developer.arm.com/documentation/ddi0403/ee
* Armv6-M
- https://developer.arm.com/documentation/ddi0419/e
#### Procedure Call Standard for the Arm Architecture(ABI)
* https://developer.arm.com/documentation/ihi0042/latest/
* https://github.com/ARM-software/abi-aa/releases
### Zephyr Documentation
* https://docs.zephyrproject.org/latest/guides/build/index.html#build-overview
### Memory barriers
* http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.07.23a.pdf
* https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/memory-access-ordering---an-introduction
### Shareability Domain
* https://developer.arm.com/documentation/ihi0022/e/ACE-Protocol-Specification/About-ACE/Concepts-required-for-the-ACE-specification/Domains
* https://medium.com/@om.nara/arm64-system-memory-fbf71dce37ee
### C 표준
* http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
### SMP
* https://developer.arm.com/documentation/198123/0300/Arm-CoreLink-GIC-fundamentals
### 기타
* 섹션의 allocatable 과 loadable 의 의미
- https://www.avrfreaks.net/forum/lodable-allocatable-sections
* Stack smashing protection
- https://embeddedartistry.com/blog/2020/05/18/implementing-stack-smashing-protection-for-microcontrollers-and-embedded-artistrys-libc/
- canaries
- http://www.lazenca.net/display/TEC/03.Canaries
* Transient memory
* https://stackoverflow.com/questions/38636326/concept-and-advantages-of-transient-and-non-transient-memory-in-arm
* futex
* https://eli.thegreenplace.net/2018/basics-of-futexes/
* gperf
* https://www.stev.org/post/cppusinggperf
* Thread Local Storage
* https://maskray.me/blog/2021-02-14-all-about-thread-local-storage
* https://chao-tic.github.io/blog/2018/12/25/tls
* DeviceTree
* https://www.devicetree.org/