AMP & Heterogeneous IPC

Linux to FreeRTOS IPC 최적화: OpenAMP 공유 메모리 제어 방법

임베디드 친구 2026. 7. 15. 22:11
반응형

Linux Userspace와 FreeRTOS 간 대용량 데이터 전송을 위한 OpenAMP RPMsg Zero-copy 구현 배경

이기종 멀티코어(AMP) 아키텍처 환경에서 Linux 호스트 프로세서와 FreeRTOS 리모트 코어 간의 IPC(Inter-Processor Communication) 속도는 시스템 전체 성능을 결정합니다. 고해상도 카메라 프레임 버퍼 또는 고주파 센서 정밀 데이터와 같은 대용량 데이터를 전송할 때, 표준 RPMsg 통신 프레임워크는 내부적인 데이터 복사 오버헤드로 인해 심각한 처리량(Throughput) 저하를 유발합니다. 표준 rpmsg_send() 함수는 사용자 공간 메모리에서 커널 vring 버퍼로, 그리고 다시 리모트 코어의 로컬 메모리로 데이터를 다중 복사(Buffer Copy)합니다. 이번 포스팅에서는 이러한 다중 데이터 복사를 제거하고 공유 메모리 풀(Shared Memory Pool)의 포인터만을 교환하여 하드웨어 전송 오버헤드를 최소화하는 rpmsg_virtio_alloc_tx_buffer() 및 rpmsg_virtio_send_offchannel_raw() 기반의 Zero-copy 아키텍처 구현 방법을 상세히 다룹니다.

OpenAMP RPMsg Zero-copy 버퍼 제어 핵심 요약

  • 대용량 데이터 전송 시 memcpy() 오버헤드를 제거하기 위해 rpmsg_virtio_alloc_tx_buffer() API를 호출하여 vring의 공유 메모리 풀에서 가용한 버퍼 포인터를 직접 할당받아 데이터를 기록합니다.
  • 할당된 버퍼 포인터 주소를 rpmsg_virtio_send_offchannel_raw() 함수를 통해 반환함으로써 메모리 복사 연산 없이 물리 주소 포인터 매핑만으로 이기종 코어 간 초고속 데이터 전송을 완료합니다.
  • 전송 완료 후 수신 측 코어에서는 rpmsg_hold_rx_buffer() 및 rpmsg_release_rx_buffer() API 쌍을 사용하여 데이터 처리가 끝날 때까지 해당 공유 메모리 공간의 릴리즈를 제어합니다.

RPMsg Buffer Copy와 Zero-copy 아키텍처 상세 분석 및 OpenAMP API 소스 코드 구현

기본 RPMsg 통신 구조는 고정된 크기의 버퍼(RPMSG_BUFFER_SIZE, 통상 512바이트)를 기준으로 작동합니다. 이로 인해 버퍼 크기를 초과하는 대용량 데이터 전송 시 단편화(Fragmentation)와 반복적인 메모리 복사 작업이 발생하여 CPU 클록 소모와 메모리 버스 대역폭 낭비가 발생합니다. 반면 Zero-copy 메커니즘은 송신 코어가 데이터 버퍼를 가상 버스의 버퍼 풀에서 할당받아 직접 쓰고, 수신 코어는 해당 메모리 주소를 그대로 참조하여 복사 단계를 완전히 생략합니다.

표준 RPMsg와 Zero-copy 메커니즘 성능 및 구조 비교

비교 항목 (Metrics) 표준 RPMsg 버퍼 통신 (Standard Copy) OpenAMP Zero-copy 공유 메모리 제어
메모리 복사 횟수 2회 이상 (memcpy 호출 필수) 0회 (Zero-copy 포인터 전달)
최대 데이터 전송 크기 RPMSG_BUFFER_SIZE 제한 (기본 512B) 할당된 공유 메모리 풀(Pool) 크기에 비례
CPU 자원 소모율 패킷 크기 증가 시 CPU 점유율 급증 데이터 크기와 무관하게 최소 레벨 유지
주요 활용 API rpmsg_send(), rpmsg_trysend() rpmsg_virtio_alloc_tx_buffer(), rpmsg_virtio_send_offchannel_raw()
캐시 동기화 관리 커널 드라이버 내부에서 자동 수행 개발자가 명시적으로 Cache Flush/Invalidate 관리 필요

FreeRTOS 단 OpenAMP Zero-copy 송신 구현 소스 코드

아래 소스 코드는 FreeRTOS 환경에서 가상 버스 풀로부터 직접 TX 버퍼를 할당받아 복사 연산 없이 물리 포인터 스왑 형식으로 데이터를 송신하는 펌웨어 파트입니다.

#include "rpmsg_virtio.h"

#define COMPONENT_CHANNEL_NAME "rpmsg-raw-channel"
#define COMPONENT_ENDPOINT_ADDR 30

void vZeroCopyTransmitTask(void *pvParameters)
{
    struct rpmsg_virtio_device *vdev = (struct rpmsg_virtio_device *)pvParameters;
    struct rpmsg_endpoint ept;
    void *tx_buffer = NULL;
    uint32_t buffer_size = 0;
    int status;

    /* Initialize rpmsg endpoint */
    status = rpmsg_create_ept(&ept, &vdev->rvdev.rdev, COMPONENT_CHANNEL_NAME,
                              COMPONENT_ENDPOINT_ADDR, RPMSG_ADDR_ANY, NULL, NULL);
    if (status != 0) {
        /* Error handling: Endpoint creation failed */
        vTaskDelete(NULL);
    }

    for (;;) {
        /* Allocate a buffer directly from the virtio vring shared pool for zero-copy */
        tx_buffer = rpmsg_virtio_alloc_tx_buffer(vdev, &buffer_size, RPMSG_TRUE);
        if (tx_buffer == NULL) {
            /* Error handling: No available tx buffer in the pool */
            vTaskDelay(pdMS_TO_TICKS(1));
            continue;
        }

        /* Application writes large high-frequency sensor or camera data directly to tx_buffer */
        uint32_t *data_ptr = (uint32_t *)tx_buffer;
        data_ptr[0] = 0xDEADBEEF; /* Example header */
        data_ptr[1] = 0xCAFEBABE; /* Example payload */

        /* Perform cache clean operation to ensure visibility to Linux host core */
        Xil_DCacheFlushRange((INTPTR)tx_buffer, buffer_size);

        /* Transmit the buffer address using the raw offchannel API without copying data */
        status = rpmsg_virtio_send_offchannel_raw(vdev, &ept, ept.addr, ept.dest_addr,
                                                  tx_buffer, buffer_size, RPMSG_TRUE);
        if (status < 0) {
            /* Error handling: Transmit operation failed */
        }

        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

Linux Kernel Zero-copy 버퍼 수신 및 보존 구조

리눅스 커널 커스텀 드라이버 수신 콜백 함수 내부에서 가상 링 버퍼 공간이 오버라이트(Overwrite)되지 않도록 락을 걸어야 합니다. rpmsg_hold_rx_buffer()를 사용하여 리모트 core가 보낸 원본 주소 공간의 소유권을 완전하게 유지한 뒤 후처리를 진행해야 합니다.

static int custom_rpmsg_rx_callback(struct rpmsg_device *rpdev, void *data,
                                    int len, void *priv, u32 src)
{
    /* Protect the shared memory buffer from being recycled by the virtio ring */
    rpmsg_hold_rx_buffer(rpdev, data);

    /* Enqueue the raw data pointer to the background processing workqueue */
    struct custom_work_struct *work = kmalloc(sizeof(*work), GFP_ATOMIC);
    if (work) {
        work->rx_data_ptr = data;
        work->data_length = len;
        work->rpdev_handle = rpdev;
        INIT_WORK(&work->real_work, custom_deferred_processing_worker);
        queue_work(custom_wq, &work->real_work);
    } else {
        /* Instantly release buffer if allocation fails */
        rpmsg_release_rx_buffer(rpdev, data);
    }

    return 0;
}

static void custom_deferred_processing_worker(struct work_struct *work)
{
    struct custom_work_struct *custom_work = container_of(work, struct custom_work_struct, real_work);

    /* Invalidate cache range before reading the shared buffer space */
    clflush_cache_range(custom_work->rx_data_ptr, custom_work->data_length);

    /* Process the data without copy overhead */
    process_large_payload(custom_work->rx_data_ptr, custom_work->data_length);

    /* Release the buffer back to the OpenAMP free buffer pool */
    rpmsg_release_rx_buffer(custom_work->rpdev_handle, custom_work->rx_data_ptr);
    kfree(custom_work);
}

가상 링 버퍼 및 데이터 메모리 맵 분석을 위한 디버깅 팁

  • vring 디바이스 속성 실시간 모니터링: Linux 런타임 환경에서 /sys/kernel/debug/remoteproc/remoteprocX/ 가상 파일 시스템 경로로 이동하십시오. 내부의 vring0 및 vring1 디버그 노드를 cat 명령어로 조회하면 avail_idx와 used_idx 인덱스의 전이 상태를 바이트 레벨로 파악할 수 있습니다.
  • 메모리 덤프 검증: devmem2 유틸리티 툴을 활용하여 리소스로 할당된 공유 RAM 주소 영역을 강제로 덤프하여 송신 코어의 데이터 작성 내용과 물리 RAM 영역의 불일치 여부를 로우 데이터 레벨에서 직접 대조 검증하십시오.

OpenAMP Zero-copy 연동 과정에서 흔히 하는 실수와 예외 해결책

캐시 미동기화로 인한 수신측 메모리 오염 및 데이터 왜곡 현상

  • 발생 원인: Cortex-A와 Cortex-M 간 데이터 캐시(Data Cache) 구조가 활성화된 상태에서, 송신 코어가 버퍼에 데이터를 작성한 후 캐시 플러시(Cache Flush)를 생략하거나 수신 코어가 무효화(Cache Invalidate) 처리를 생략한 경우입니다. 물리 RAM 영역이 아닌 CPU L1/L2 캐시 라인 상에만 가상 데이터가 머물게 되어 상대 코어는 쓰레기 값(Garbage/Stale Data)을 읽게 됩니다.
  • 해결 방법: 송신 API rpmsg_virtio_send_offchannel_raw() 호출 직전에 Xil_DCacheFlushRange() 또는 ARM Architecture의 DCCMVAC 어셈블리 명령어를 사용하여 캐시 라인을 물리 RAM으로 강제 이주시키십시오.

rpmsg_hold_rx_buffer 누락에 따른 vring 링 버퍼 데드락 에러

  • 발생 원인: 수신 콜백 바디 내부에서 비동기 워커 스레드로 포인터를 넘겼으나, rpmsg_hold_rx_buffer() 처리를 누락하여 OpenAMP 코어가 해당 버퍼가 비어 있는 것으로 오인하고 가상 링 인덱스를 재사용(Recycle)하면서 데이터 가상 버스가 충돌하는 현상입니다. 이로 인해 virtqueue_add_inbuf 함수 실행 시 ENOSPC 또는 Invalid argument 영문 에러 메시지가 커널에 출력됩니다.
  • 해결 방법: 가상 링 버퍼의 포인터를 콜백 함수 외부의 독립 태스크로 이관할 때는 반드시 rpmsg_hold_rx_buffer()를 선행 호출하고, 연산 처리가 종료되는 시점에 rpmsg_release_rx_buffer()를 명확하게 호출하도록 매칭 구조를 구현하십시오.

이기종 코어 간 대용량 데이터 고속 처리를 위한 RPMsg Zero-copy 아키텍처 결론

OpenAMP 기반의 Zero-copy 기법은 대용량 멀티미디어 및 고속 센서 패킷 전송을 위해 필수적인 최적화 아키텍처 기법입니다. rpmsg_virtio_alloc_tx_buffer()와 rpmsg_virtio_send_offchannel_raw()를 바인딩하여 복사 연산 오버헤드를 물리적으로 제거함으로써 시스템 통신 대역폭을 극대화할 수 있습니다. 메모리 캐시 코히런시 정책과 링 버퍼의 릴리즈 라이프사이클을 완벽히 동기화 제어하여 안정적인 임베디드 이기종 통신 모듈을 빌드하십시오.

반응형