?>

Hard Copy World

HCW

Arduino advanced

Home > Learning >

Arduino advanced

아두이노 volatile 키워드와 reordering

페이지 정보

작성자 최고관리자 쪽지보내기 메일보내기 홈페이지 자기소개 아이디로 검색 전체게시물 작성일16-04-19 15:14 조회1,796회 댓글0건

본문

 

아두이노에서 인터럽트를 사용할 경우 메인 루틴(loop() 반복 함수의 동작에 의한)은 언제든 ISR(인터럽트 서비스 루틴)에 의해 중단될 수 있습니다. 그리고 ISR 동작이 모두 끝나면 다시 원래 동작으로 복귀합니다.

이때 메인 루틴에서 사용하던 전역 변수들이 ISR에 의해 완전히 변경될 수 있기 때문에, 메인 루틴과 ISR이 공유하는 변수들은 volatile 키워드를 이용해서 선언하도록 되어 있습니다. 

인터럽트를 사용할 경우 아래와 같은 원칙에 맞게 volatile 키워드를 전역 변수에 사용하세요.

  • ISR 내부에서만 사용되는 변수 : volatile 불필요
  • ISR 외부에서만 사용되는 변수 : volatile 불필요
  • ISR 내외부에서 사용되는 변수 : volatile 필요

 

 

volatile 의 역할

 

아두이노 개발환경에서 작성한 코드는 컴파일 할 때 일련의 statement 로 변환됩니다. 한 줄의 코드는 수행하는 동작에 따라 다수의 statement 로 구성됩니다. 그런데 컴파일러는 프로그래머가 짠 코드의 순서 그대로 statement 로 변환하는 대신 효율적인 동작을 위해 "reordering" 을 하게됩니다. (컴파일러에 의한 최적화 작업, optimizing)

이 과정을 컴파일러가 자유롭게 수행하는 대신 프로그래머의 코드 진행과 같은 결과가 나올 수 있도록 판단(measure of the net effect) 합니다. 하지만 인터럽트를 사용하는 경우 의도치 않게 명령 수행 과정에서 ISR에 의한 변수 변형이 일어날 수 있습니다. 컴파일러가 바뀌지 않을 것이라 생각한 변수가 바뀔 수도 있는 것입니다. 컴파일러가 이런 상황을 미리 알지 못한다면, 코드 최적화 작업에 의해 변형된 statement 가 전혀 엉뚱한 결과를 초래할 수도 있습니다. 

volatile 키워드는 이런 상황에서 컴파일러의 reordering 을 제한하는 역할을 합니다. statement 수행 도중 외부의 영향이 있을 수 있으니 주의하라는 경고를 보내는 것입니다.

 

 

Critical section

 

하지만 volatile 키워드 만으로는 동작의 무결성을 유지할 수 없는 경우가 있습니다. 메인 루틴의 statements 들이 volatile 변수를 다루는 중간에 ISR 에 의해 변형이 있을 수 있기 때문입니다. 

이 때문에 PC 처럼 멀티태스킹 OS를 사용하는 경우 memory barriers 라는 기법을 사용합니다. 이 기법은 변수를 조작하는 statements 를 수행하기전에 사용되는 변수들이 반드시 레지스터(register)에서 메모리(memory)로 반영되도록 합니다. statements 가 끝나면 변수는 메모리에서 다시 읽어옵니다. 즉, 레지스터 안에 임시 저장된(cached) 변수가 없도록 만들어 statements 수행 시점의 변형을 최대한 방지합니다. 하지만 이런 동작은 아두이노 같은 AVR에는 해당되지 않습니다. 그래서 아두이노에서는 다른 방식으로 이걸 구현합니다.

 

예를 들어 아래와 같은 아두이노 코드를 보겠습니다.

volatile int aaa = 0;
volatile int bbb = 0;

loop() {
  int ccc = (aaa + bbb);
  Serial.println(ccc);
}

 

ISR에 의해서 aaa, bbb 변수가 바뀐다고 하면 ccc 는 변경된 aaa, bbb 변수의 합이 들어가야 합니다. 하지만 경우에 따라 ccc 값이 의도치 않게 찍힐 수 있습니다. aaa, bbb 를 계산하는 도중에 ISR 에 의한 변형이 있을 수 있기 때문입니다.

이럴 때는 aaa, bbb 값을 읽기 전 강제로 인터럽트를  종료함으로써 memory barriers 와 유사한 효과를 낼 수 있습니다.

loop() {
  cli();
  int a1 = aaa;
  int b1 = bbb;
  sei();
  
  int ccc = (a1 - b1);
  Serial.println(ccc);
}

cli() 는 인터럽트를 종료하는 함수이고 sei()는 인터럽트를 활성화하는 함수입니다. 따라서 위 코드는 cli() 함수로 인터럽트를 종료해서 외부 영향을 없애고 aaa, bbb 변수를 읽어와 저장해 두는 겁니다. 그리고 다시 sei() 함수로 인터럽트를 활성화 합니다. 이렇게 하면 적어도 aaa, bbb 변수의 값의 읽어오는 동안은 외부의 영향을 없앨 수 있습니다. 

이렇게 cli(), sei() 를 이용한 코드를 critical section 이라 부릅니다. Critical section 은 프로그래머가 작성한 코드 블록이 외부 영향에서 자유롭도록 해주기 때문에 volatile 키워드의 역할과는 다릅니다.

 

 

참고자료:

 

 

 

 

추천 0
  • 페이스북으로 보내기
  • 트위터로 보내기
  • 구글플러스로 보내기

댓글목록

등록된 댓글이 없습니다.