-
Notifications
You must be signed in to change notification settings - Fork 0
07. Mapping Aperture address to userspace pointer
GPU instance 사이에서 direct GPU memory access를 하려면 nvidia_p2p_get_pages() 를 통해 얻은 physical address를 userspace virtual address에 매핑해야 합니다. GDRCopy에서는 mmap() 을 사용해서 매핑했지만, 그렇게 얻은 virtual address를 GPU 커널에서 접근하면 Illegal Address 라는 에러가 발생합니다.
CUDA 프로세스는 하나의 virtual address space를 CPU 전용 영역과 GPU 전용 영역으로 나누어서 관리합니다 (Unified Virtual Addressing). 그래서 GPU 전용 영역의 virtual address에 매핑하는 방법을 찾아보는 중입니다.
CUDA driver API 중에 cuMemAddressReserve() 라는 함수가 있습니다. GPU 전용 virtual address range를 예약하는 기능을 합니다. 이걸 써서 얻은 virtual address에 mmap() 을 시도해봤지만 마찬가지로 Illegal Address 에러가 발생합니다.
정상적으로 GPU memory를 할당하고 그에 해당하는 userspace pointer를 얻는 과정에서 어떤 kernel module이 어떻게 관여하는지 확인해봤습니다. cuMemAlloc() 함수가 실행되는 동안 ftrace를 확인해보면 아래와 같이 nvidia_ioctl 과 uvm_ioctl이 호출됨을 알 수 있습니다. 이 둘은 각각 nvidia.ko 와 nvidia_uvm.ko 모듈에 구현된 ioctl 함수입니다. CUDA Runtime이 커널 모듈과 상호작용하는 채널인 것 같습니다.
root@a1:/sys/kernel/debug/tracing# cat trace_pipe
va-706148 [050] .... 81497.599045: nvidia_frontend_unlocked_ioctl <-do_vfs_ioctl
va-706148 [050] .... 81497.599062: nvidia_ioctl <-nvidia_frontend_unlocked_ioctl
va-706148 [050] .... 81497.599261: nvidia_frontend_unlocked_ioctl <-do_vfs_ioctl
va-706148 [050] .... 81497.599280: nvidia_ioctl <-nvidia_frontend_unlocked_ioctl
va-706148 [050] .... 81497.599345: uvm_unlocked_ioctl <-uvm_unlocked_ioctl_entry
va-706148 [050] .... 81497.599348: uvm_ioctl <-uvm_unlocked_ioctl
va-706148 [050] .... 81497.599358: uvm_unlocked_ioctl <-uvm_unlocked_ioctl_entry
va-706148 [050] .... 81497.599360: uvm_ioctl <-uvm_unlocked_ioctl조금 더 자세히 확인해보니 nvidia_ioctl 에서는 GPU memory를 할당하는 코드가, uvm_ioctl 에서는 할당된 memory를 userspace의 virtual address에 매핑하는 코드가 실행되는 것으로 보입니다. 아래는 uvm_ioctl 함수의 일부입니다. cmd 를 로깅해보면 UVM_MAP_EXTERNAL_ALLOCATION 에 해당합니다. 즉, 이어서 uvm_api_map_external_allocation 함수가 호출됩니다.
static long uvm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
pr_info("uvm_ioctl cmd: %u\n", cmd);
switch (cmd)
{
case UVM_DEINITIALIZE:
return 0;
UVM_ROUTE_CMD_STACK_NO_INIT_CHECK(UVM_INITIALIZE, uvm_api_initialize);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_PAGEABLE_MEM_ACCESS, uvm_api_pageable_mem_access);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_PAGEABLE_MEM_ACCESS_ON_GPU, uvm_api_pageable_mem_access_on_gpu);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_REGISTER_GPU, uvm_api_register_gpu);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_UNREGISTER_GPU, uvm_api_unregister_gpu);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_CREATE_RANGE_GROUP, uvm_api_create_range_group);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_DESTROY_RANGE_GROUP, uvm_api_destroy_range_group);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_ENABLE_PEER_ACCESS, uvm_api_enable_peer_access);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_DISABLE_PEER_ACCESS, uvm_api_disable_peer_access);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_SET_RANGE_GROUP, uvm_api_set_range_group);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_CREATE_EXTERNAL_RANGE, uvm_api_create_external_range);
// ------------ 여기에 해당됨 ------------
UVM_ROUTE_CMD_ALLOC_INIT_CHECK(UVM_MAP_EXTERNAL_ALLOCATION, uvm_api_map_external_allocation);
// ------------------------------------
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_MAP_EXTERNAL_SPARSE, uvm_api_map_external_sparse);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_FREE, uvm_api_free);아래와 같이 mmap() 으로부터 얻은 buf_ptr 에 cudaHostRegister() api를 호출하면 Invalid argument 에러가 발생합니다.
cudaHostRegister((void *)buf_ptr, info.mapped_size, cudaHostRegisterIoMemory);해당 에러만으로는 정보가 부족해서 커널 단에서 무슨 일이 일어났는지 보기 위해 dmesg를 확인해봤습니다.
dtb05045@a1:~$ dmesg
[182124.925194] nvidia_ioctl cmd: 39
[182124.925211] NVRM RmIoctl: RmIoctl cmd: 0x27
[182124.925215] NVRM RmIoctl: hClass: 113
[182124.925218] NVRM RmIoctl: RmAllocOsDescriptor
[182124.925552] NVRM osCreateMemFromOsDescriptor: descriptorType: 2
[182124.925555] NVRM osCreateOsDescriptorFromIoMemory: dbg_tag 0
[182124.925564] NVRM osCreateOsDescriptorFromIoMemory: osCreateOsDescriptorFromIoMemory(): phys range 0x000020d400400000-0x000020d4013fffff overlaps with GPU BARsNVRM rmapiAllocWithSecInfo: allocation failed; status: Address not valid [NV_ERR_INVALID_ADDRESS] (0x0000001e)
[182124.925595] NVRM rmapiAllocWithSecInfo: client:0xc1d00067 parent:0x5c000001 object:0x5c000072 class:0x71
[182124.925600] NVRM RmCreateOsDescriptor: arg func: 27매핑을 요청한 physical address range가 BAR range와 겹친다는 에러 메시지가 있습니다. 모듈 코드에서 해당 메시지를 출력하는 예외처리문을 찾아봤습니다. BAR1 영역과 겹치는 physical address에 대한 registration은 금지한다는 내용의 주석이 있습니다.
//
// There is an architectural deadlock scenario involved when full-duplex P2P
// enabled over BAR1. See #3 in the description of bug 1571948 which explains
// the classic deadlock. So, make sure to error out usermode's memory
// registration if a memory range falls within any of the available GPU's
// BAR window.
//
physAddrRange.min = *base;
physAddrRange.max = *base + *pLimit;
rmStatus = osCheckGpuBarsOverlapAddrRange(physAddrRange);
if (rmStatus != NV_OK)
{
NV_PRINTF(LEVEL_ERROR,
"%s(): phys range 0x%016llx-0x%016llx overlaps with GPU BARs",
__FUNCTION__, physAddrRange.min, physAddrRange.max);
return rmStatus;
}cudaHostRegisterIoMemory 가 아닌 다른 flag도 사용해봤지만 위의 예외처리보다 선행되는 다른 예외처리에 걸립니다.
선행 예외처리를 해결한다고 하더라도 결국 위의 예외처리에 걸리게 됩니다.