2

gcc를 사용하는 단일 프로세서 32 비트 마이크로 컨트롤러 용 코드를 작성했습니다.중요한 섹션에서 메모리 액세스에 volatile 키워드를 사용해야합니까?

링크 된 목록에서 타임 스탬프 개체를 사용해야합니다. 비동기적일 수있는 코드의 다른 부분 (아마도 ISR에서)이이를 목록에 추가합니다.

임계 영역은 인터럽트를 해제하고 barrier() 기능을 사용하여 구현됩니다.

gcc 최적화가 목록 항목 (제거 할 다음 항목, 목록 헤드 또는 무료 목록)에 대한 포인터를 캐싱하여 코드를 손상시킬 수있는 곳에서는 혼란 스럽습니다. while 루프 안의 어떤 것도 루프 이전의 시간에서 캐시되기를 원하지 않습니다. 메모리 장벽은 컴파일러가 함수를 시작할 때 한 번 포인터를로드하고 결코 다시로드하지 않기로 결정할 수 있습니까? 이러한 모든 목록 포인터는 생산자 코드의 중요 섹션에서 수정할 수 있습니다 (표시되지 않음). 예를 들어, pqueue_first이 휘발성 포인터 여야하는지 이해하려고합니다.

아마도 루프가없는 경우 (목록에 추가하는 경우), 함수의 모든 코드가 중요한 섹션에 있다면 괜찮습니까?

휘발성 또는 중요한 섹션에 대한 일반적인 기사를 가리 키지 말아주세요. 그 중 일부는 읽었으므로이 특정 코드에 적용하는 방법을 보면서 문제가 있습니다. 휘발성 변수는 참조 될 때마다 컴파일러가 변수를 다시로드하도록 보장합니다. 그러나 최적화 가능성과 메모리 장벽과의 상호 작용 가능성을 이해하지 못합니다.

typedef struct { 
    EV_EventQueueEntry_t *pqueue_alloc; // allocation (never changes) 
    EV_EventQueueEntry_t *pqueue_head; // head of active queue (ISR can change it) 
    EV_EventQueueEntry_t *pqueue_free; // head of free list (ISR can change it) 
    EV_EventQueueEntry_t *pqueue_first; // soonest item in queue (ISR can change it) 
    EV_EventQueueEntry_t *pqueue_first_prev; // back pointer from soonest item (ISR can change it) 
    EV_UInt_t max_event_count; 
} EV_EventQueue_t; 

void RunLoop(EV_EventQueue_t *pev) 
{ 
    while(not timeout) 
    { 
     // Enter critical section 
     disable_interrupts(); 
     barrier(); 

     // item with most recent timestamp 
     // this can be changed by ISR add to queue operation 
     EV_EventQueueEntry_t *pfirst = pev->pqueue_first; 

     if(pfirst!=NULL && EV_PortIsFutureTime(pfirst->event.timestamp, EV_PortGetTime())) 
     { 
      // Copy out message 
      EV_Event_t e = pfirst->event; 

      // Remove event from queue 
      if(pev->pqueue_first_prev != NULL) 
       pev->pqueue_first_prev->pnext = pfirst->pnext; 
      else 
       pev->pqueue_head = pfirst->pnext; 

      // Put event back on free list 
      pfirst->pnext = pev->pqueue_free; 
      pev->pqueue_free = pfirst; 
      pfirst->event.message.type = EV_MESSAGE_NULL; 

      // Find next soonest message to process after this one 
      pev->pqueue_first = ...; 
      pev->pqueue_first_prev = ...; // back pointer 

      // Exit critical section 
      barrier(); 
      enable_interrupts(); 

      // Dispatch message 
      ... 
     } 
     else 
     { 
      // Exit critical section 
      barrier(); 
      enable_interrupts(); 

      // waste some time 
      ... 
     } 
    } 
} 
+0

첫 번째 질문은 'barrier'기능이 모든 프로세서와 운영 체제에 대해 정의되고 표준화되었는지 여부입니다. 그렇지 않은 경우 두 번째 질문은 다음과 같습니다. 사용중인 특정 프로세서 및 운영 체제에 대한 공식 설명서는 어디에 있습니까? – user3386109

+0

barrier 함수는 아래 답변에서와 같이'__asm__ __volatile__ ("": : "memory")'을 출력하는 것으로 보입니다. 이것은 AVR32입니다. – Robotbugs

+0

참고 : 장벽 내에서 오래 걸리는 것처럼 보입니다. – chux

답변

4

C++ (11)는이 표준 기능이 있습니다 : std::atomic_signal_fence. C11은 네임 스페이스 한정자가없는 비슷한 기능을 가지고 있습니다. 프로그램이 단일 쓰레드만을 사용하고 컴파일러가 펜스를 가로 질러로드/스토어를 움직이는 것을 막으려 고하는 경우에 적합합니다. 임계 구역 앞에는 std::atomic_signal_fence(memory_order_acquire)을 사용하고 임계 구역 뒤에는 std:atomic_signal_fence(memory_order_release)을 사용하십시오.

C++ 11 또는 C11을 사용하지 않고 gcc 또는 gcc asms를 인식하는 컴파일러를 사용하는 경우 __asm__ __volatile__ ("": : :"memory")을 컴파일러 장벽으로 사용할 수 있습니다. asm은 제거 할 수없고 신비한 방식으로 메모리를 변경하겠다고 위협하기 때문에 컴파일러는로드/스토어를 이동할 수 없습니다.

2

volatile은 컴파일러가 캐시가 아닌 메모리에서 값을 검색하고 메모리에 값을 저장하고 캐시에 저장하지 않도록 컴파일러에 지시합니다. 이 작업은 여러 코어가 동일한 메모리에서 작동 할 때 수행되므로 캐시가 최신 상태인지 반드시 확인할 필요는 없습니다.

ISR은 메모리를 수정하여 액세스하는 모든 변수에 휘발성 레이블이 지정되어야합니다. 그것은 문제를 야기 할 컴파일러 최적화가 아닙니다. 이 문제는 ISR이 발생할 때 발생하는 컨텍스트 전환으로 인해 발생합니다. 프로세서의 상태 (레지스터 값)가 저장되고 ISR 후에 복원됩니다. 즉 레지스터의 값은 ISR 이전의 값이됩니다.