갑자기 생긴 의문 2

이전에 캐시 일관성에 대해 자세히 톺아봤었는데, 여기서 하나의 의문이 더 생겼다. “DMA(Direct Memory Access)같은 경우 캐시를 거치지 않고, 메모리에 직접 접근하게 되는데 이 경우 캐시일관성이 문제가 아니라 메모리자체의 일관성이 깨질수도 있지 않을까?” 란 의문이었다. 즉 하나의 메모리 공간에 대해 core가 아닌 또 다른 DMA라는 제어로직이 들어가면 어떻게 되나에 대한 질문이다. 물론 학교에서 운영체제 시간에 DMA가 목적, 동작 방식등에 대해 간단히 배웠었지만, 다시한번 되짚어보며 정리하고 의문을 해결해보려고 한다. 관련 내용은 arm 공식 도큐먼트커뮤니티의 글을 참고하여 작성하였다.


DMA란?

우선 DMA(Direct Memory Access)에 대해 간단히 알아보고 넘어가자. DMA는 CPU를 대신하여 메모리에 읽거나 쓸 수 있는 모듈을 말한다. 즉 DMA 모듈 자체는 메인 메모리와 I/O 장치 간의 데이터 교환을 제어하는 모듈이다. 이 때 CPU는 전송의 시작과 끝 시점에 어떤 데이터를 얼마나 주고받을지에 대해서만 관여를 하게 된다.

그렇다면 DMA는 왜 나온걸까? 주로 I/O 장치는 컴퓨터 주변의 입출력장치(키보드, 마우스, 모니터 등)를 의미한다. 이러한 입력장치는 인터럽트를 통해 CPU가 하던 일을 멈추고, 입력장치를 통해 입력장치의 로컬버퍼 내용을 메모리에 쓰는식으로 데이터를 입력하게 된다. 달리 말하면 인터럽트를 통해 CPU가 데이터 처리를 하기위한 시간을 뺐는다는 의미이다. 이 데이터가 적으면 큰 문제가 안되겠지만, 데이터의 양이 많으면 CPU가 입출력을 처리하느라 본래의 일을 할 수 없게 된다. 이를 해결하기 위해 나온게 DMA이다.

DMA


DMA와 캐시의 관계

하지만 DMA의 존재는 앞선 포스트에서 설명한 방법이 무효화 될 수 있다. 발생할 수 있는 문제는 cpu, DMA가 모두 데이터를 읽고 쓸때 존재한다.

  1. DMA 는 memory 에 write 할 경우, bus를 통해서 memory에 직접적으로 데이터를 쓰게 된다. 이때 CPU는 자신의 cache가 변경되지 않아 해당 데이터의 변경을 인지하지 못한다. 즉, DMA가 데이터를 변경해도 CPU는 알 수 없다.

  2. DMA의 cache에 특정 캐시라인이 저장되어있을 때, CPU가 DMA cache와 겹치는 주소공간의 내용을 수정할 경우, CPU의 cache 내용만 변경되기 때문에 DMA 는 이걸 인지하지 못한다. 결국, DMA는 memory 내부의 old data만 읽을 수 있다.

이제 문제가 파악되었으니 CPU, DMA가 모두 같은 메모리를 볼 수 있게 하여 coherency를 지킬 수 있게 하는 방법에 대해 알아보자.


DMA consistency

DMA consistency를 지키기 위해서는 대표적으로 3가지 방법이 있다. 이 방법들이 다 개별적인것은 아니고 어느정도 상호 보완적인 관계를 가지면서 consistency를 유지시키는데, 하나씩 알아보도록 하자.

Cache 비활성화

가장 간단한 방법은 CPU가 캐시를 사용하지 않는것이다. 말 그대로 CPU가 cache에 읽고 쓰는게 아니라 항상 memory에 접근하여 읽고 쓰게 만드는 것이다. 대표적으로 아래 예제와 같이 java의 volatile 식별자 등을 통해 구현 가능하다. 하지만 cache를 쓰지 않는다는것은 동일한 메모리 접근에 대해서 항상 bus를 통해 메인메모리에 접근해야 한다는 것이고 이는 성능과 전력 소모에 영향을 미칠 수 있다고 한다.

public class SharedObject
{
    public volatile int counter = 0;
}

위 예제와 같이 특정 변수를 volatile 식별자를 통해 선언하면 항상 해당 값을 메모리에서 읽고 쓰도록 보장 할 수 있다. 이러한 volatile 식별자는 C/C++에도 존재하지만 C/C++에서의 volatile은 최적화 금지의 의미도 가진다. 그렇기 때문에 대부분의 device driver와 같이 외부 데이터를 읽고 쓰는 소프트웨어 내부에서 주로 volatile을 사용한다.

Software managed coherency

두번째 방법은 소프트웨어적으로 해결하는 방법이다. device driver에서 데이터를 읽고 쓸 때 CPU 아키텍쳐에서 제공하는 캐시 제어 명령을 통해 일관성을 지켜주는것이다. 이번 포스트에서는 ARM 아키텍쳐에서의 cache관련 명령어만 간단히 알아보고, 이를 어떤식으로 사용하여 coherency를 지키는지 정리해보려 한다.

ARM 에서는 cache 관련 명령으로 invalidateclean을 사용한다. 가장 먼저 invalidate 명령은 특정 cache block을 invalid 하게 만든다. 그리고, clean 명령어는 CPU에 의해 수정된 cache block공식 문서에서는 dirty 표시가 되었다고 한다 을 memory로 write-back 한다.

이 방법은 소프트웨어에서 특정 데이터에 접근, 수정하기 전에 cache를 직접 업데이트 하여 coherency를 지키는 방식으로, 위의 캐시 비활성화와 마찬가지로, bus와 cpu를 추가적으로 사용해야 한다.

Hardware managed coherency

마지막 방법은 하드웨어적으로 Software managed coherency를 단순화 시켜주는 방식이다. 이는 앞선 포스트에서 다뤘던 bus snooping을 이용하여 구현된다. 간단히 말해 DMA나 Cache에서 서로 공유되는 데이터에 대해 bus를 확인하여 알아서 캐시를 무효화 한다는 뜻이다.

이렇게 하드웨어적으로 coherency를 지원하는 아키텍쳐들도 존재하지만, 지원 안하는 경우가 존재한다. 그렇기 때문에 결국에는 각 운영체제 단에서 하드웨어의 기능 제공여부에 따라 volatile 식별자를 구현 방법이 달라지게 된다.


후기

이렇게 DMA가 포함된 구조에서도 coherency를 지킬 수 있다는 것을 알 수 있었다. 해당 내용에 대해 자세히 알아보면서 내가 아무생각 없이 쓰던 소켓, 마우스, GPU같은 곳에도 이런식으로 하나하나 어떻게 해야 더 빠르고 효율적으로 처리할 수 있을지에 대한 고민이 들어있는 것을 보면서 재미있었던것 같다. 앞으로도 “그냥 있으니까 가져다 써야지”가 아닌, “이건 왜 이렇게 동작할 수 있을까”라는 의문을 갖고 이런 지식을 하나하나 쌓아가며 더 효율적이고, 더 정밀한 프로그램을 만들수 있는 사람이 될 수 있도록 노력하려 한다.