게스트 커널 진입과 호스트 복귀를 나누어 봅니다
가상 머신에서 프로그램이 getpid를 호출하면 보통 그 가상 머신의 Linux 커널이 처리합니다. 시스템 호출마다 호스트 커널이나 QEMU가 대신 getpid를 실행하는 구조가 아닙니다. 게스트 안에서 사용자 모드와 커널 모드가 바뀌는 사건과, 게스트 실행을 멈추고 호스트가 개입하는 사건을 구분해야 합니다.
| 경계 | 어떤 사건입니까? | 이번 예의 처리 주체 |
|---|---|---|
| 게스트 EL0 → 게스트 EL1 | 사용자 프로그램의 SVC로 커널 서비스를 요청합니다. | 게스트 Linux 커널입니다. |
| 게스트 실행 → EL2 처리 | Stage-2 fault나 설정된 trap 등의 사건으로 하이퍼바이저 처리가 필요합니다. | EL2 진입 코드와 KVM입니다. |
| KVM_RUN → 호스트 사용자 공간 | KVM이 사용자 공간의 도움을 받거나 실행을 중단해야 합니다. | QEMU 같은 VMM입니다. VMM은 가상 머신을 관리하는 프로그램입니다. |
이 글은 중첩 가상화와 보호 VM을 제외하고, ARM64 Linux를 실행하는 일반 VM을 설명합니다. 예제의 SVC에는 추가 trap·debug 설정이 없으며, 신호나 추적 도구가 시스템 호출의 결과를 바꾸지 않는다고 가정합니다. 게스트가 VM을 다시 실행하는 중첩 가상화에서는 SVC를 가로채는 경로도 있습니다. v6.18.37의 KVM handle_svc도 그 경우를 처리합니다. KVM의 SVC·예외 처리
세 줄로 getpid를 요청해 봅니다
아래는 게스트의 64비트 사용자 프로그램 안에 있다고 가정한 설명용 코드입니다. 앞의 주소는 각 명령의 위치를 보이기 위한 값이며 어셈블리 문법의 일부가 아닙니다. 실제 프로그램의 배치 주소와 PID는 실행할 때 달라집니다.
0x00400000: mov x8, #172
0x00400004: svc #0
0x00400008: mov x19, x0| 줄 | 이 줄에서 하는 일 | 이 값은 어디서 옵니까? |
|---|---|---|
| mov x8, #172 | 정수 172를 x8에 넣고 x8의 기존 값을 덮어씁니다. 메모리에 172바이트를 복사하는 명령이 아닙니다. | 이 버전의 AArch64 Linux getpid 시스템 호출 번호입니다. 주소나 PID가 아닙니다. |
| svc #0 | 동기 예외를 발생시켜 게스트 커널의 시스템 호출 경로로 진입합니다. | 이 ABI에서 서비스 선택은 x8의 번호로 합니다. #0을 getpid 번호로 해석하지 않습니다. |
| mov x19, x0 | 커널에서 돌아온 뒤 x0의 정숫값을 x19에 복사합니다. x0는 그대로이고 x19는 덮어씁니다. | x0에는 이 호출의 결과인 게스트 프로세스의 PID가 들어옵니다. x0가 가리키는 메모리를 읽는 동작은 없습니다. |
getpid는 이 예에서 x0~x5의 인자를 요구하지 않습니다. 따라서 호출 전 x0의 값은 이 서비스의 입력으로 사용하지 않습니다. 다른 시스템 호출은 x0~x5를 인자로 사용할 수 있으므로 같은 결론을 모든 호출에 적용하면 안 됩니다. getpid의 결과는 게스트의 PID namespace에서 보이는 프로세스 ID이며, 호스트 QEMU 프로세스나 vCPU thread의 ID가 아닙니다. getpid 번호 정의 x8을 읽는 do_el0_svc getpid의 반환값
명령을 실행하면서 무엇이 바뀝니까?
1. 호출 번호를 넣습니다
| 실행 위치·레지스터 | mov x8, #172 실행 뒤 |
|---|---|
| 현재 실행 | 게스트 EL0입니다. 다음 명령의 PC는 0x00400004입니다. |
| x8 | 172: getpid를 선택할 번호입니다. |
| x0 | 호출 전 코드가 남긴 값입니다. 이 getpid의 입력으로 쓰지 않습니다. |
PC는 다음에 실행할 명령의 주소입니다. 명령을 실행하기 전 x8의 실제 값은 앞선 코드에 달려 있지만, 이 mov 뒤에는 172로 정해집니다.
2. SVC 예외를 받습니다
| 위치 | 보관하거나 처리하는 값 |
|---|---|
| 실행 위치 | 게스트 EL1의 예외 처리 경로입니다. |
| ELR_EL1 | 돌아갈 다음 명령 주소 0x00400008을 보관합니다. |
| SPSR_EL1 | 예외 전의 게스트 EL0 실행 상태를 보관합니다. |
| 커널의 pt_regs | 어셈블리 진입 코드가 사용자 레지스터와 복귀 상태를 메모리에 저장합니다. |
SVC 자체가 PID를 계산해 x0에 넣는 것은 아닙니다. 커널 코드가 x8로 서비스를 고른 뒤 결과를 준비합니다. 커널 내부의 현재 x0와 pt_regs에 저장된 사용자 x0도 구분해야 합니다.
3. 게스트 커널에서 돌아옵니다
| 실행 위치·레지스터 | 일반적인 정상 복귀 뒤 |
|---|---|
| 현재 실행 | 게스트 EL0, PC = 0x00400008입니다. |
| x0 | 이 예의 PID 42입니다. |
| QEMU | 이 시스템 호출을 대신 처리하러 돌아온 상태로 가정하지 않습니다. |
게스트 커널이 사용자 레지스터의 반환값을 준비하고 예외 복귀 경로가 이를 복원합니다. 실제 PID가 731이라면 x0에는 731이 들어옵니다. 42는 고정 규칙이 아닙니다.
4. 결과를 x19에 보관합니다
| 레지스터 | mov x19, x0 실행 뒤 |
|---|---|
| x19 | 42로 바뀝니다. x19의 호출 전 값은 덮어씁니다. |
| x0 | 42가 유지됩니다. |
| PC | 0x0040000c로 진행합니다. |
이 마지막 mov는 레지스터 값 복사입니다. 메모리를 가리키는 화살표나 다른 CPU로의 전달로 읽지 않습니다.
단계는 설명용 명령의 실행과 정상 복귀 순서입니다. EL0 → EL1은 같은 게스트 안의 실행 권한 변화입니다. PID 42와 코드 주소는 예제 값이며 실제 장치에서 측정한 결과가 아닙니다.
v6.18.37의 entry-common.c는 SVC64를 el0_svc로 연결합니다. syscall.c는 저장된 x8로 호출을 선택하고, syscall_set_return_value는 저장된 사용자 x0를 갱신합니다. entry.S는 복귀 주소·상태와 레지스터를 복원합니다. 따라서 “예외가 생겼다”는 말만 보고 EL2 또는 QEMU로 갔다고 판단하지 않습니다. 게스트 커널의 SVC 진입 사용자 x0에 반환값 저장 예외 진입·복귀 어셈블리
호스트 커널이 EL2에 있으면 무엇이 다릅니까?
| 일반적인 구성 | 호스트 Linux 커널 | 게스트 Linux 커널 | 게스트 사용자 프로그램 |
|---|---|---|---|
| nVHE | EL1에서 실행하고 별도의 EL2 처리 코드를 이용합니다. | EL1 | EL0 |
| VHE | EL2에서 실행합니다. | EL1 | EL0 |
같은 EL1이라는 이름을 써도 nVHE의 호스트 커널과 게스트 커널이 같은 문맥을 공유하는 것은 아닙니다. KVM은 실행 대상을 바꿀 때 레지스터·주소 변환·예외 처리 상태를 전환합니다. 반대로 호스트가 VHE로 EL2에서 실행한다고 해서 게스트의 모든 커널 코드까지 EL2에서 실행되는 것도 아닙니다. nVHE 문맥 전환 VHE 문맥 전환
VHE에서 HCR_EL2의 E2H와 TGE는 호스트용 실행 환경과 EL0 예외 경로를 구분하는 데 관여합니다. Linux의 HCR_HOST_VHE_FLAGS에는 두 비트가 포함됩니다. 이 매크로는 빌드할 때 정해지는 비트 조합이고, 실제 상태 변화는 실행 중 그 값을 HCR_EL2에 기록할 때 일어납니다. 게스트로 들어갈 때와 호스트로 돌아올 때의 설정을 같은 것으로 읽으면 안 됩니다. HCR host·guest 값 trap 활성화·해제
예전 EL2 설명에서 “가상 주소 영역이 하나뿐”이라고 읽었다면 VHE 적용 전후를 확인해야 합니다. VHE는 호스트 커널을 EL2에서 실행하기 위해 두 영역과 ASID 지원을 추가합니다. ASID는 주소 공간을 구별하는 식별자입니다. Arm의 해당 입문 문서 8절도 이 차이를 따로 설명합니다. Arm의 VHE 설명 · 8절
EL2로 나온 뒤에도 QEMU로 돌아오지 않을 수 있습니다
QEMU의 vCPU thread가 KVM_RUN을 호출하면 호스트 커널 안에서 게스트를 실행합니다. 실행 도중 EL2 처리가 필요해져도 KVM이 해결한 뒤 같은 KVM_RUN 안에서 게스트로 다시 들어갈 수 있습니다. 따라서 guest exit 횟수와 사용자 공간으로 돌아온 횟수는 같지 않습니다.
- 호스트의 vCPU thread가 KVM_RUN을 호출합니다.
vcpu_fd는 실행할 가상 CPU를 식별하는 파일 descriptor입니다.
커널에 실행을 요청합니다.
- KVM이 게스트 실행 상태를 준비합니다.
일반 레지스터와 주소 변환, 가상 interrupt 등의 상태를 준비하고 진입합니다.
게스트가 직접 명령을 실행합니다.
- 처리가 필요한 사건으로 게스트에서 나옵니다.
KVM은 원인을 살펴봅니다. 커널 안에서 해결되면 다음 반복에서 다시 진입할 수 있습니다.
사용자 공간 처리가 필요한 경우에만 아래로 진행합니다.
- KVM_RUN이 VMM으로 반환합니다.
예를 들어 사용자 공간 장치 모델이 MMIO 응답을 만들어야 합니다.
화살표는 제어가 넘어가는 순서입니다. 커널에서 해결한 경우에는 세 번째 단계에서 두 번째 단계로 돌아갑니다. 모든 guest exit가 마지막 단계까지 간다는 뜻은 아닙니다.
/* v6.18.37 arm.c의 반복 조건과 handler 호출입니다.
* 사이의 준비·진입·복원 코드는 생략했습니다. */
while (ret > 0) {
/* ... */
ret = handle_exit(vcpu, ret);
}| 이 내부 handler의 결과 | 호출자가 하는 일 |
|---|---|
| ret > 0 | 실행 loop를 이어갑니다. 다음 반복에서 요청·신호 등을 다시 확인하므로 무조건 즉시 진입한다는 뜻은 아닙니다. |
| ret == 0 | 사용자 공간에 반환할 사건을 준비한 정상 경로입니다. VMM은 exit_reason을 확인합니다. |
| ret < 0 | 오류 경로입니다. 사용자 공간 ioctl의 실패와 errno로 이어집니다. |
이 ret 규칙은 handle_exit와 실행 loop 사이의 내부 반환 규칙입니다. 사용자 프로그램에서 ioctl(KVM_RUN)이 1을 돌려주면 계속 실행된다는 뜻이 아닙니다. 사용자 공간은 ioctl의 성공·실패를 먼저 확인해야 합니다. 또한 kvm_io_bus_read 같은 helper의 성공값 0도 위 handler의 0과 역할이 다릅니다. 실행 loop handler 반환 규칙
같은 load라도 어느 주소 변환에서 막혔는지 봅니다
일반 RAM 읽기를 예로 들어 보겠습니다. 두 변환 모두 4 KiB 페이지를 사용하고, 권한·메모리 속성·정렬 조건이 맞으며, 필요한 페이지 표도 접근 가능하다고 가정합니다. 게스트 가상 페이지 0x00400000은 IPA 페이지 0x80000000에, 그 IPA는 호스트 물리 페이지 0x120000000에 대응한다고 정했습니다. IPA는 이 VM이 물리 주소로 취급하는 주소입니다.
- 게스트 VA: 0x00400128
게스트 명령이 사용하는 가상 주소입니다. 페이지 안의 offset은 0x128입니다.
게스트 Stage-1 표의 대응을 적용합니다.
- IPA: 0x80000128
게스트가 관리하는 물리 주소 공간의 주소입니다. 호스트의 실제 물리 주소와 구별합니다.
KVM이 관리하는 Stage-2 대응을 적용합니다.
- 호스트 PA: 0x120000128
두 변환의 조건을 만족했을 때 읽을 최종 RAM 위치입니다.
화살표는 주소 변환 관계입니다. CPU가 세 주소를 일반 레지스터 세 개에 차례로 넣거나 데이터를 두 번 복사한다는 뜻은 아닙니다. 주소는 설명용 값이며 TLB가 변환을 보관하면 매번 모든 표를 읽지 않을 수 있습니다.
| 상황 | 누가 먼저 처리합니까? | 이후 가능한 결과 |
|---|---|---|
| 게스트 Stage-1에 매핑이 없습니다. | 보통 게스트 커널이 자신의 page fault를 처리합니다. | 게스트 RAM 할당·매핑 등으로 해결하거나 잘못된 사용자 접근에 signal을 보낼 수 있습니다. |
| 유효한 게스트 RAM이지만 Stage-2 매핑이 아직 없습니다. | EL2로 나와 KVM이 memslot과 backing memory를 확인합니다. | 매핑을 준비한 뒤 fault를 일으킨 명령을 재시도할 수 있습니다. |
| 사용자 공간 모델이 담당하는 가상 장치 주소입니다. | KVM이 접근 정보를 준비합니다. | 커널에서 처리하지 못한 접근은 KVM_EXIT_MMIO로 VMM에 전달할 수 있습니다. |
| 범위·권한·접근 종류를 허용하지 않습니다. | 해당 fault 경로가 정책과 원인을 판단합니다. | 예외 주입 또는 오류가 될 수 있습니다. 매핑이 없다는 이유만으로 RAM을 새로 주지는 않습니다. |
게스트 페이지 테이블 자체도 메모리에 있으므로, Stage-1 표를 읽는 도중 Stage-2 fault가 생기는 경우도 있습니다. 위 그림은 최종 데이터 주소를 이해하기 위한 단순한 예입니다. “Stage-2 fault이면 Stage-1은 언제나 완전히 끝났다”는 일반 규칙으로 읽지 않습니다. v6.18.37의 kvm_handle_guest_abort도 표 탐색 중 fault인지를 별도로 검사합니다. 게스트 abort와 S1PTW 처리
memslot은 KVM에 등록한 게스트 물리 메모리 범위입니다. 일반 사용자 메모리 기반 VM에서는 그 범위와 호스트 사용자 주소 HVA를 연결합니다. HVA는 KVM이 RAM의 backing page를 찾는 데 쓰며, 게스트의 하드웨어 변환을 VA → IPA → HVA → PA라는 세 단계로 만드는 것은 아닙니다. 실제 AArch64 구현 경로도 v6.18.37에서는 arch/arm64/kvm/mmu.c입니다.
가상 장치에서 읽은 값은 언제 x2에 들어갑니까?
이번에는 게스트 커널이 가상 장치의 32비트 상태 register를 읽는다고 가정합니다. 게스트와 호스트는 little-endian이고, 단일 AArch64 LDR의 정상 MMIO 처리를 예로 듭니다. x1은 장치로 매핑된 게스트 VA이며, 대응하는 IPA는 0x09000000입니다. 이 주소는 사용자 공간의 설명용 장치 모델이 담당합니다. 실제 보드의 register 주소나 장치 기능을 지정한 것이 아닙니다.
ldr w2, [x1]x1의 숫자를 w2에 복사하는 mov가 아닙니다. x1을 주소로 사용해 4바이트를 읽고 결과를 w2에 넣으려는 명령입니다. 완료되면 x2의 상위 32비트는 0이 됩니다. 이 예에서 접근 전 x2는 0xa5, 장치 모델이 돌려줄 값은 0x12345678이라고 정했습니다.
1. 게스트가 LDR을 시도합니다
| 항목 | 아직 접근이 완료되지 않은 상태 |
|---|---|
| x1 | 가상 장치 register를 가리키는 게스트 VA입니다. |
| 저장될 결과 | 아직 없습니다. 게스트의 저장된 x2는 이 예에서 0xa5입니다. |
| 예외 원인 | Stage-2 경로에서 이 접근을 장치 emulation으로 처리하게 됩니다. |
예제에서는 KVM이 syndrome에서 접근 폭 4바이트, 읽기 방향, 대상 register 2번을 해석할 수 있다고 가정합니다. 모든 abort에 이 정보가 있는 것은 아닙니다.
2. KVM이 VMM에 읽기를 요청합니다
| kvm_run의 항목 | 이 예의 값 |
|---|---|
| exit_reason | KVM_EXIT_MMIO: 사용자 공간의 장치 처리가 필요합니다. |
| mmio.phys_addr | 0x09000000: fault IPA입니다. 호스트 RAM의 PA가 아닙니다. |
| mmio.len / mmio.is_write | 4 / 0: 4바이트 읽기입니다. |
kvm_run은 커널과 VMM이 공유하는 메모리 구조체입니다. CPU의 하드웨어 register 이름이 아닙니다. 이때 VMM이 게스트 x2를 직접 덮어쓰는 방식으로 설명하지 않습니다.
3. VMM이 응답 바이트를 채웁니다
| 위치 | VMM의 처리 뒤 |
|---|---|
| mmio.data의 처음 4바이트 | 78 56 34 12: 예제 값 0x12345678의 little-endian 바이트입니다. |
| 게스트 저장 register x2 | 응답 buffer를 채운 것만으로 갱신이 끝난 것은 아닙니다. |
VMM은 같은 vCPU에 KVM_RUN을 다시 호출해 미완료 MMIO 처리를 커널이 끝내도록 합니다. 장치 모델의 요청·응답과 실제 외부 장치의 작업 완료도 별개의 개념입니다.
4. KVM이 게스트 상태에 결과를 반영합니다
| 항목 | 정상 MMIO 완료 뒤 |
|---|---|
| 게스트 x2 | 0x0000000012345678입니다. w2의 32비트 읽기 결과와 상위 0이 반영됩니다. |
| 게스트 PC | emulation이 끝난 LDR 다음 명령을 가리키도록 조정됩니다. |
| 다음 실행 | 실행 조건을 다시 확인한 뒤 게스트를 진행할 수 있습니다. |
일반 RAM의 지연 매핑은 원래 load를 재시도하지만, 이 MMIO 경로는 이미 emulation한 load를 다시 수행하지 않도록 진행시킵니다. 그렇지 않으면 읽을 때 값이 사라지는 장치 register를 두 번 읽는 등의 문제가 생깁니다.
단계는 MMIO 요청과 결과 반영 순서입니다. data buffer → x2는 KVM이 응답을 게스트 register 상태에 반영하는 관계입니다. 예제의 주소·0xa5·0x12345678은 임의로 정한 값이며 실행 기록이 아닙니다.
io_mem_abort는 먼저 커널 안의 MMIO 처리 가능 여부를 확인합니다. 성공하면 커널 안에서 결과를 완료하고 계속 실행할 수 있습니다. 사용자 공간 처리가 필요한 경우에만 KVM_EXIT_MMIO를 준비합니다. 따라서 “MMIO는 항상 QEMU가 처리한다”도 맞지 않습니다. MMIO 해석·커널 처리·사용자 공간 반환
다시 KVM_RUN을 호출하면 arm.c의 시작 부분에서 kvm_handle_mmio_return을 먼저 처리합니다. 읽기 값의 폭과 확장 규칙을 적용하고 게스트 register에 반영한 뒤 PC를 조정합니다. KVM API 문서도 MMIO 같은 요청은 재진입하여 남은 처리를 끝내야 게스트 상태가 일관된다고 설명합니다. MMIO 완료를 먼저 처리하는 위치 KVM_RUN과 MMIO 완료 규칙
관찰한 현상을 어디에 연결해야 합니까?
| 관찰·질문 | 먼저 나누어 볼 것 |
|---|---|
| 게스트에서 시스템 호출이 많습니다. | 게스트 EL0·EL1의 작업과 host guest-exit 횟수를 따로 봅니다. 호출 수가 곧 QEMU 복귀 횟수는 아닙니다. |
| Stage-2 fault가 반복됩니다. | RAM의 첫 접근·쓰기 추적·권한 문제·MMIO·페이지 표 탐색 중 fault를 구분합니다. |
| VMM이 MMIO 응답을 썼는데 값이 반영되지 않았습니다. | 동일 vCPU의 KVM_RUN 재진입과 커널의 완료 처리까지 확인합니다. |
| KVM_RUN이 반환했습니다. | ioctl의 반환값·errno부터 확인하고, 성공 반환에서 exit_reason과 대응하는 자료를 해석합니다. |
| 실행 위치를 EL1이라고 들었습니다. | 게스트 커널인지 nVHE 호스트 커널인지, 현재 어떤 문맥이 적재되어 있는지 함께 확인합니다. |
기존 KVM·Stage-2 글에서는 함수 연결과 매핑 코드를 이어서 읽으실 수 있습니다. 이 글의 숫자는 그 코드가 바꾸는 상태를 설명하기 위해 정한 예이며, 실제 KVM을 실행하거나 trace를 수집한 측정 결과는 아닙니다.
Bao의 정적 분할을 KVM의 시간 분할과 구분합니다
Bao의 기본 정적 분할 구조에서는 VM을 만들 때 CPU·메모리·장치를 배정합니다. 공식 설명은 가상 CPU와 물리 CPU의 1:1 대응, 2단계 주소 변환으로 배정한 메모리, 장치 직접 할당과 물리 인터럽트 대응을 명시합니다. 위의 KVM_RUN과 QEMU 장치 모델 경로를 그대로 Bao의 실행 순서라고 설명하지 않습니다. Bao 공식 구조 설명
자원을 나눴다는 사실만으로 공유 캐시·메모리 버스 등 모든 하드웨어 간섭이 없어지거나 응용의 마감 시간이 보장되는 것은 아닙니다. VM 사이에 메모리와 알림을 공유한다면 생산자·소비자의 소유권, 데이터 공개 순서, 접근 권한과 한쪽 재시작 시 처리 규칙을 별도로 정해야 합니다. CPU 주소 변환과 장치 DMA의 격리도 각각 확인합니다. 특정 보드 데모의 주소·지연 수치를 다른 구성의 보장값으로 옮기지 않습니다.
Verdin 데모에서는 자원과 IPC를 어떻게 나눕니까?
일반 구조를 실제 구성에 연결하려면 VM별 배정표와 게스트 코드를 함께 읽어야 합니다. 아래는 공식 bao-demos의 고정 커밋 f4d55d8044e1e17e6e54261e1da7db7e18d34725에 있는 torizonos+freertos / imx8mp-verdin 사례입니다. 다른 보드에 그대로 적용할 주소표나 설치 절차가 아니라 이 소스의 배정 관계를 분석합니다.
| 자원 | Torizon 게스트 | FreeRTOS 게스트 |
|---|---|---|
| CPU | cpu_num=3. README는 FreeRTOS에 준 한 코어를 제외한 나머지 코어를 사용한다고 설명합니다. | cpu_num=1, cpu_affinity=0x8. affinity의 bit 3을 지정합니다. |
| RAM | 0x40000000에서 1GiB. place_phys=1이고 물리 시작 주소도 동일하게 지정합니다. | 게스트 주소 0x0에서 128MiB. 여기에는 고정 물리 배치 지정이 없으므로 호스트 물리 주소 0이라는 뜻이 아닙니다. |
| UART3 | 직접 장치 매핑에서 0x30880000의 4KiB 구간을 제외합니다. | 해당 물리 구간을 게스트 주소 0xff000000에 매핑하고 물리 IRQ 60을 배정합니다. |
| 공유 메모리 | 하나의 shmem_id=0 객체가 64KiB입니다. 두 VM의 IPC 설정은 각각 게스트 주소 0xf0000000에 같은 객체를 연결합니다. 같은 게스트 주소값 자체가 공유의 근거인 것은 아닙니다. | |
| IPC 알림 | IPC 항목에 IRQ 60을 지정합니다. | IPC 항목은 IRQ 52입니다. 위 UART의 물리 IRQ 60과 다른 용도이며, 번호만 보고 같은 사건으로 합치지 않습니다. |
고정 VM 구성에서 Torizon 쪽 초기 이미지 이름은 u-boot.bin입니다. Bao가 이 VM을 시작한 순간을 곧바로 Torizon 커널 실행으로 읽지 않습니다. 함께 제공된 게스트 U-Boot 패치도 UART3 관련 사용을 제거하고 DRAM 크기 정의를 1GiB로 조정합니다. 하이퍼바이저 배정과 게스트가 전제하는 장치·메모리 구성이 맞아야 합니다.
공유 영역, 메시지 구획, 알림을 구분합니다
FreeRTOS 예제는 공유 시작점과 +0x2000에 각각 8KiB 메시지 구획을 둡니다. 이는 전체 64KiB를 하나의 메시지 크기로 사용한다는 뜻이 아닙니다. UART 수신 handler는 공유 문자열을 갱신하고, IPC IRQ 52의 handler는 반대편 문자열을 읽습니다. strchr()로 개행을 찾은 뒤 반환 포인터를 바로 쓰므로, 개행이 없는 입력에 대한 NULL 검사는 이 함수에 없습니다. 정상 데모 문자열 처리와 임의 입력에 안전한 메시지 프로토콜은 구분해야 합니다.
공식 데모 README는 /dev/baoipc0를 통한 통신을 설명하지만, Torizon 쪽이 FreeRTOS 메시지에 비동기로 반응하여 출력하도록 구성된 것은 아니라고 명시합니다. 마지막 메시지는 장치를 읽어 확인하는 예입니다. IRQ 설정의 존재만으로 양쪽 응용에 비동기 수신 완료가 보장되거나, 이 장치 경로가 모든 Bao 시스템의 공통 ABI가 되는 것은 아닙니다.
빌드 인자 2MiB와 실제 IPC 매핑 64KiB는 다릅니다
같은 커밋의 빌드 설정은 SHMEM_SIZE=0x200000, 즉 2MiB를 전달합니다. 반면 VM 구성의 공유 객체와 양쪽 IPC 크기는 0x00010000, 즉 64KiB입니다. 이 두 설정값을 같은 증거로 취급하면 안 됩니다. 전처리 인자만으로 하이퍼바이저가 2MiB 접근을 허용했다고 결론 내릴 수 없으며, 실제 생성물·접근 범위와 각 계층의 설정을 별도로 대조해야 합니다. 이 차이로 특정 실행 장애가 발생했다고 재현한 것은 아닙니다.
위 분석은 고정 소스의 자원·통신 관계 확인입니다. Bao나 게스트를 빌드하거나 보드에서 실행하지 않았고, 지연·최악 실행 시간·DMA 격리·안전성을 측정하지 않았습니다. 공유 버스 간섭, 데이터 공개 순서, 메시지 길이와 재시작 시 소유권 같은 일반 한계는 앞의 정적 분할 설명을 함께 봅니다. 이 예제 구성만으로 제품 수준의 실시간성이나 격리를 보증하지 않습니다.
English summary
This pinned Verdin example gives Torizon three CPUs and 1GiB of fixed physical RAM, and FreeRTOS one CPU with affinity mask 0x8 and a 128MiB guest region. A guest address of zero does not imply physical address zero. UART3 and IPC interrupts serve different purposes.
The shared object and both IPC mappings are 64KiB. The FreeRTOS application uses two 8KiB message areas. Torizon is not configured to asynchronously display incoming messages; the documented device read retrieves the last message. The FreeRTOS receive handler does not check the newline search result for NULL.
The build argument SHMEM_SIZE=0x200000 is 2MiB, not the configured 64KiB IPC mapping. A build argument is not proof of hypervisor access permission. This is source analysis, not a reproduced failure, hardware benchmark, DMA-isolation test or production-readiness guarantee.