DMA는 Direct Memory Access의 약자로, 하드웨어 서브시스템이 CPU와 독립적으로 메모리에 읽기나 쓰기를 하는 기능을 뜻합니다. DMA는 디스크 드라이버 컨트롤러, 그래픽 카드, 네트워크 카드, 사운드 카드 등 아주 여러가지의 하드웨어 시스템에서 사용됩니다. 이게 안 된다면 대용량 바이트스트림을 전송하는 비디오 재생, 사운드 출력, 네트워크 트래픽 송수신 등으로 인해 CPU를 혹사시키게 됩니다. 대비되는 개념으로는 PIO (Programmed input/output)가 있습니다. 여기까지는 누구나 잘 아는 내용이고 이하에서는 더 자세한 내용을 다루겠습니다.
캐시 일관성 문제
DMA는 캐시 일관성 문제를 야기할 수 있습니다. CPU와 외부 메모리 사이에 일반적으로 캐시 메모리가 존재하기 때문에 일어나는 현상입니다. CPU가 데이터를 메모리에 쓰더라도 캐시에만 반영되어 있고, 이 상태에서 디바이스 드라이버가 DMA 전송을 초기화하게 되면 과거의 데이터가 장치로 전송될 수 있습니다. 이 때문에 하드웨어적으로 캐시를 무효화 시키는 신호를 구현하거나, 소프트웨어적으로 운영체제 수준에서 DMA 영역의 메모리를 동기화 시키는 두 가지 방법을 일반적으로 사용하고 있습니다.
리눅스에서 Consistent DMA Mapping을 사용하는 경우 캐시 일관성 문제는 알아서 해결되니 걱정할 필요가 없습니다. (대신 스트리밍 매핑 방식보다 느립니다.) pci_alloc_consistent 함수를 사용해서 CPU 주소와 DMA 핸들 주소를 얻을 수 있습니다. 해제할 때는 pci_free_consistent 함수를 사용합니다. 만약 많은 수의 적은 메모리 영역을 필요로 한다면, pci_pool API를 이용할 수 있습니다. 아래 4개 함수를 이용합니다. pci_pool_create, pci_pool_alloc, pci_pool_free, pci_pool_destroy
리눅스에서 Streaming DMA Mapping을 사용하는 경우 일일이 싱크를 명령해주어야 합니다. 일단 메모리 영역을 잡아야 되는데, 통으로 크게 잡거나 (pci_map_single), scatter 목록으로 잘게 쪼개어 (pci_map_sg) 잡을 수 있습니다. 한 번 스트리밍 DMA 영역을 잡아놓고 여러 번 전송을 수행할 경우, 캐시 일관성 문제를 해결하기 위해 sync 함수를 호출해야 합니다.
가령 CPU에서 읽기 전에 동기화 하려면 pci_dma_sync_single_for_cpu 함수를 호출합니다. CPU쪽에서 뭔가 수정을 한 다음 장치에서 DMA 영역을 읽어가려고 한다면 pci_dma_sync_single_for_device 함수를 호출합니다. scatter list를 사용하는 경우라면 single 대신 sg로 바꿔서 동일한 함수를 사용할 수 있습니다. 이렇게 스트리밍 DMA 매핑을 한 경우에는 전송할 때마다 동기화를 해주어야 합니다.
PCI 버스 마스터링
PCI 아키텍처는 ISA와 달리 중앙 DMA 컨트롤러라는 것이 없습니다. 어떤 PCI 컴포넌트라도 버스 제어권을 요청하고 시스템 메모리에 읽고 쓸 수 있도록 되어있습니다. PCI 버스 컨트롤러(보통 사우스브릿지)에게 버스 소유권을 받아서 작업을 하게 되는 것이지요. 동시에 여러 컴포넌트가 소유권을 요청하게 되면, PCI 버스 컨트롤러가 적절히 중재하게 되어있습니다. 이론적으로는 PCI 버스 마스터링을 통해 임의의 장치와 통신할 수 있지만 대부분 DMA 하는 용도로만 사용하고 있습니다.
SAC와 DAC
최근의 CPU는 PAE (Physical Address Extension)를 이용해서 4기가 이상의 메모리(36비트 주소 모드)를 사용할 수 있습니다. 그런데 이렇게 되면 DMA를 사용하는 장치는 4G 넘어가는 메모리 영역을 사용할 방법이 없게 됩니다. 이를 위해 PCI 버스와 장치가 지원하는 경우 DAC (Double Address Cycle) 방식을 사용해서 64비트 DMA 주소를 다룰 수 있습니다. 지원이 안 되는 경우 더블 버퍼 (윈도우)나 바운스 버퍼 (리눅스)를 사용해서 우회는 가능합니다만 비용이 많이 듭니다. 리눅스에서는 다음 함수를 사용해서 설정합니다. pci_set_dma_mask, pci_set_consistent_dma_mask
DMA 방향
리눅스에서 DMA 방향을 나타내는 값은 4가지로 나뉘어집니다. Consistent 매핑은 방향 지정할 수 없고 스트리밍 매핑의 경우에만 지정이 가능합니다.
캐시 일관성 문제
DMA는 캐시 일관성 문제를 야기할 수 있습니다. CPU와 외부 메모리 사이에 일반적으로 캐시 메모리가 존재하기 때문에 일어나는 현상입니다. CPU가 데이터를 메모리에 쓰더라도 캐시에만 반영되어 있고, 이 상태에서 디바이스 드라이버가 DMA 전송을 초기화하게 되면 과거의 데이터가 장치로 전송될 수 있습니다. 이 때문에 하드웨어적으로 캐시를 무효화 시키는 신호를 구현하거나, 소프트웨어적으로 운영체제 수준에서 DMA 영역의 메모리를 동기화 시키는 두 가지 방법을 일반적으로 사용하고 있습니다.
리눅스에서 Consistent DMA Mapping을 사용하는 경우 캐시 일관성 문제는 알아서 해결되니 걱정할 필요가 없습니다. (대신 스트리밍 매핑 방식보다 느립니다.) pci_alloc_consistent 함수를 사용해서 CPU 주소와 DMA 핸들 주소를 얻을 수 있습니다. 해제할 때는 pci_free_consistent 함수를 사용합니다. 만약 많은 수의 적은 메모리 영역을 필요로 한다면, pci_pool API를 이용할 수 있습니다. 아래 4개 함수를 이용합니다. pci_pool_create, pci_pool_alloc, pci_pool_free, pci_pool_destroy
리눅스에서 Streaming DMA Mapping을 사용하는 경우 일일이 싱크를 명령해주어야 합니다. 일단 메모리 영역을 잡아야 되는데, 통으로 크게 잡거나 (pci_map_single), scatter 목록으로 잘게 쪼개어 (pci_map_sg) 잡을 수 있습니다. 한 번 스트리밍 DMA 영역을 잡아놓고 여러 번 전송을 수행할 경우, 캐시 일관성 문제를 해결하기 위해 sync 함수를 호출해야 합니다.
가령 CPU에서 읽기 전에 동기화 하려면 pci_dma_sync_single_for_cpu 함수를 호출합니다. CPU쪽에서 뭔가 수정을 한 다음 장치에서 DMA 영역을 읽어가려고 한다면 pci_dma_sync_single_for_device 함수를 호출합니다. scatter list를 사용하는 경우라면 single 대신 sg로 바꿔서 동일한 함수를 사용할 수 있습니다. 이렇게 스트리밍 DMA 매핑을 한 경우에는 전송할 때마다 동기화를 해주어야 합니다.
PCI 버스 마스터링
PCI 아키텍처는 ISA와 달리 중앙 DMA 컨트롤러라는 것이 없습니다. 어떤 PCI 컴포넌트라도 버스 제어권을 요청하고 시스템 메모리에 읽고 쓸 수 있도록 되어있습니다. PCI 버스 컨트롤러(보통 사우스브릿지)에게 버스 소유권을 받아서 작업을 하게 되는 것이지요. 동시에 여러 컴포넌트가 소유권을 요청하게 되면, PCI 버스 컨트롤러가 적절히 중재하게 되어있습니다. 이론적으로는 PCI 버스 마스터링을 통해 임의의 장치와 통신할 수 있지만 대부분 DMA 하는 용도로만 사용하고 있습니다.
SAC와 DAC
최근의 CPU는 PAE (Physical Address Extension)를 이용해서 4기가 이상의 메모리(36비트 주소 모드)를 사용할 수 있습니다. 그런데 이렇게 되면 DMA를 사용하는 장치는 4G 넘어가는 메모리 영역을 사용할 방법이 없게 됩니다. 이를 위해 PCI 버스와 장치가 지원하는 경우 DAC (Double Address Cycle) 방식을 사용해서 64비트 DMA 주소를 다룰 수 있습니다. 지원이 안 되는 경우 더블 버퍼 (윈도우)나 바운스 버퍼 (리눅스)를 사용해서 우회는 가능합니다만 비용이 많이 듭니다. 리눅스에서는 다음 함수를 사용해서 설정합니다. pci_set_dma_mask, pci_set_consistent_dma_mask
DMA 방향
리눅스에서 DMA 방향을 나타내는 값은 4가지로 나뉘어집니다. Consistent 매핑은 방향 지정할 수 없고 스트리밍 매핑의 경우에만 지정이 가능합니다.
- PCI_DMA_BIDIRECTIONAL : 양방향. 가급적이면 방향 명시할 것.
- PCI_DMA_TODEVICE : 메모리에서 PCI 장치로 전송. 예를 들어 네트워크 패킷 전송.
- PCI_DMA_FROMDEVICE : PCI 장치에서 메모리로 전송. 예를 들어 네트워크 패킷 수신.
- PCI_DMA_NONE : 디버깅 용도




덧글