2013-08-07 2 views
0

저는 AVR 플랫폼을위한 타이밍 루프에서 ISR 내부의 단일 바이트를 계산하고 있습니다. 이 작업은 내 프로그램의 주요 기능이기 때문에 영구적으로 프로세서 레지스터를 예약하여 평소 코드 경로가 감소하고 0과 비교할 때 ISR이 메모리 장벽에 도달 할 필요가 없도록하고 싶습니다. reti.휘발성 레지스터를 gcc가 처리하지 않는 이유는 무엇입니까?

The avr-libc docs 변수를 레지스터에 바인딩하는 방법을 보여줍니다. 문제없이 작동합니다. 그러나이 변수는 주 프로그램 (타이머 카운트 다운 시작)과 ISR (실제로 계산 및 신호 완료)간에 공유되므로 컴파일러가 최적화에 너무 영리하지 않도록하기 위해 volatile이어야합니다 .

이런 맥락에서 (전체 모 놀리 식 빌드에서 레지스터를 예약하는 것), 에이 변수를 영구적으로 저장하지만 레지스터가 수정 될 수 있기 때문에 체크를 최적화하지 않기 때문에 volatile register 조합은 의미 상 의미가 있습니다. 외부 적으로 ". GCC는 이것을 좋아하지 않지만 어쨌든 변수 접근을 최적화하기 위해 emits a warning을 사용할 것입니다.

GCC에서의이 조합의 버그 기록은 컴파일러 팀이 설명하는 시나리오의 유형을 고려하지 않으려 고하고 제공하기에는 무의미한 것이라고 생각합니다. volatile register 접근 자체가 나쁜 아이디어 인 이유 또는 의미 론적으로 이해하는 경우이지만 컴파일러 팀이 처리에 관심이없는 근본적인 이유가 누락 되었습니까?

+0

있습니까? – Beryllium

+0

정상적인 HW 타이머를 사용하고 있습니다. AVR의 저전력 타이머는 최저 속도 설정 (내부 8MHz 시스템 클록, 1024 비트, 8 비트 폭의 프리스케일러)에서 32.768ms 후에 오버플로하며, 표준 시간 확장 방법은 카운트 다운 ISR을 사용하는 것입니다. – chrylis

답변

1

완전한 컴파일 단위에 대해 하나의 변수에 대한 레지스터를 예약하는 것은 컴파일러의 코드 생성기에 너무 제한적일 수 있습니다. 즉, 모든 C 루틴은 해당 레지스터를 사용하지 않아야합니다.

코드가 범위를 벗어나면 다른 호출 된 루틴이 해당 레지스터를 사용하지 않는다는 것을 어떻게 보장합니까? 직렬 I/O 루틴 같은 것들도 그 예약 된 레지스터를 사용하지 않아야합니다. 컴파일러는 사용자 프로그램의 데이터 정의를 기반으로 런타임 라이브러리를 다시 컴파일하지 않습니다.

응용 프로그램이 실제로 너무 민감하여 L2 또는 L3에서 메모리를 가져 오는 추가 지연이 감지 될 수 있습니까? 그렇다면 ISR이 너무 자주 실행되어 필요할 메모리 위치를 항상 사용할 수 있습니다 (예 : 캐시를 통해 페이지되지 않음). 따라서 메모리 장벽에 부딪치지 않습니다 (메모리 장벽에 의해 cpu의 메모리가 실제로 어떻게 작동하는지, 캐싱 등을 통해). 그러나 실제로 이것이 사실이려면 L1 캐시가 상당히 커야하고 ISR은 매우 높은 주파수에서 실행되어야합니다.

마지막으로 애플리케이션 요구 사항에 따라 ASM에서 코드를 작성해야하는 경우가 있습니다.

+0

필자가 링크 된 문서는 영구 등록 예약과 관련된 문제를 설명했으며 임베디드 프로그래밍의 일반적인 관용구입니다. 라이브러리 루틴은 특정 레지스터 범위 (AVR에서 'r8'에서 'r15'까지)를 사용하고 다른 레지스터는 (r2에서 r7까지) 수정하지 않아야합니다. 표준'avr-libc' 라이브러리는 실제로 이러한 관행을 따르고 있습니다. 여기서 문제는 시간이 아니라 힘입니다. 컨트롤러를 일반적인 코드 경로에서 대략 10 개 명령어로 다시 잠자기 상태로 만들 수 있지만로드/저장소는 몇 개 더 추가합니다. AVR에는 캐시가 없으며 'asm'을 사용할 수 있다는 것을 알고 있지만 * * 작동해야합니다. – chrylis

+0

몇 가지 더 많은 명령어로 소모되는 전력이 실제로 그렇게 중요한 것입니까? 그렇다면 컴파일 된 언어를 사용하는 이유는 무엇입니까? – JackCColeman

+0

이 특별한 루프 동안에 만, 문자 그대로 1 바이트 카운터를 감소시키고, 0인지 확인하고, 잠자기로 돌아가서 나머지 프로그램이 C++에있게됩니다. 나는 오늘 오후에 얼마나 바보 같은 GCC의 어셈블리 출력이 어찌 되었든 발견했기 때문에 ISR을 손으로 다시 작성할 수는 있지만 설치 루틴과 ISR 사이의 통신을 확보하는 것은 어려울 것입니다. C99에 따르면, * * 작동해야합니다. – chrylis

1

AVR 변수에 volatile 키워드를 사용하는 이유는 컴파일러가 변수에 대한 액세스를 최적화하지 않기 위해서입니다. 이제 문제는 어떻게됩니까?

변수에는 상주 할 수있는 두 개의 위치가 있습니다. 범용 레지스터 파일이나 RAM의 일부 위치에 있습니다. 변수가 RAM에있는 경우를 고려하십시오. 변수의 최신 값에 액세스하려면 컴파일러에서 ld 명령어의 일부 양식 (예 : lds r16, 0x000f)을 사용하여 RAM에서 변수를로드합니다. 이 경우 변수는 RAM 위치 0x000f에 저장되고 프로그램은 r16에이 변수의 복사본을 만들었습니다. 인터럽트가 활성화되면 흥미로운 부분이 생깁니다. 변수를로드 한 후 inc r16이 발생하면 인터럽트 트리거와 해당 ISR이 실행됩니다.ISR 내에서 변수도 사용됩니다. 그러나 문제가 있습니다. 변수는 RAM에 하나, r16에 하나씩 두 가지 버전으로 존재합니다. 이상적으로 컴파일러는 r16의 버전을 사용해야하지만,이 버전은 존재하지 않을 것이므로 대신 RAM에서로드하므로 필요에 따라 코드가 작동하지 않습니다. volatile 키워드를 입력하십시오. 변수가 여전히 RAM에 저장됩니다, 그러나, 컴파일러는 다른 어떤 일이 발생하기 전에 변수에 따라서 다음과 같은 어셈블리가 생성 될 수있다, RAM으로 업데이트되어 있는지 확인해야합니다 :

cli 
lds r16, 0x000f 
inc r16 
sei 
sts 0x000f, r16 

첫째, 인터럽트를 사용할 수 없습니다. 그런 다음 변수가 r16에로드됩니다. 변수가 증가하고 인터럽트가 활성화 된 다음 변수가 저장됩니다. 변수가 RAM에 다시 저장되기 전에 전역 인터럽트 플래그가 활성화 되기는 어렵지만 명령어 세트 수동 :

보류중인 인터럽트보다 먼저 명령어가 실행됩니다.

이것은 모든 인터럽트가 다시 트리거되기 전에 sts 명령이 실행되고 가능한 최소 시간 동안 인터럽트가 비활성화됨을 의미합니다.

이제 변수가 레지스터에 바인드 된 경우를 생각해보십시오. 변수에 대해 수행 된 모든 작업은 레지스터에서 직접 수행됩니다. 이러한 작업은 RAM의 변수에 수행 된 작업과 달리 원자 적으로 간주 할 수 있습니다. 말할 것도없이 읽기 - 수정 -> 쓰기 사이클이 없기 때문입니다. 변수가 업데이트 된 후에 인터럽트가 트리거되면 변수에 바인딩 된 레지스터에서 변수를 읽을 것이므로 변수의 새 값을 가져옵니다.

또한 변수가 레지스터에 바인딩되어 있기 때문에 모든 테스트 명령어는 레지스터 자체를 활용할 것이고 컴파일러가 정적 인 값을 갖는 이유 때문에 최적화되지 않을 것입니다. 그들의 본질은 휘발성이다.

이제 경험상 AVR에서 인터럽트를 사용할 때 전역 가변 변수가 RAM에 절대로 도달하지 않는다는 것을 종종 눈치 챘습니다. 컴파일러는 레지스터에서 항상 읽기 -> 수정 -> 쓰기 사이클을 건너 뛰었습니다. 그러나 이것은 컴파일러 최적화에 기인 한 것이므로 의존해서는 안됩니다. 다른 컴파일러는 동일한 코드 조각에 대해 다른 어셈블리를 생성 할 수 있습니다. avr-objdump 유틸리티를 사용하여 최종 파일 또는 특정 오브젝트 파일의 디스 어셈블리를 생성 할 수 있습니다.

건배. volatile

+0

'volatile'은 업데이트의 원 자성을 보장하기 위해 사용되지 않는다.'cli '/'sei' 시퀀스가 ​​제안하는 것처럼 보입니다. ISR과 비 ISR 코드간에 변수를 공유 할 때 프로그래머는 경쟁 조건이 발생하지 않도록해야합니다. '휘발성 '을 추가하는 것만으로는 충분하지 않습니다. –

+0

사실,이 경우입니다. 자세한 내용은 [이 링크] (http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_volatile)를 참조하십시오. – TRON

+0

당신의 링크는'flag'가'uint8_t'이고, 아마'uint8_t'에 대한 쓰기가 항상 (통상적 인 것처럼) 원자 적이라는 것을 보장하는 프로세서/컴파일러를 사용하는 예를 보여줍니다. 'volatile'은'while'-loop이 어느 시점에서 종료 될 수 있도록 보장하며 모든 반복마다'flag'를 다시 읽습니다. 'flag '가'uint64_t' 인 경우,'volatile'은 대부분/모든 환경에서 그 자체로 충분하지 않았을 것입니다. –

3

의미는 당신이 "레지스터는 외부에서 변경 될 수 있으므로 검사를 멀리 최적화하지 않는다"설명하지만, 실제로는 더 좁은 정확히 하지 있습니다 : "변수의 캐시하지 않는으로 생각하려고 RAM의 값은 레지스터 "입니다.

이런 식으로 보면 레지스터 자체를 '캐싱'할 수 없으므로 변수의 '실제'값과 불일치 할 수 없기 때문에 레지스터를 volatile으로 선언하는 것은 의미가 없습니다. 판독

사실 휘발성 변수 하지 얻어 optimzed 단지 상기 의미론 부작용 보통에 액세스하지만, 보장 아니다.

GCC는 기본적으로 레지스터의 값이 '휘발성과 비슷하다'고 가정해야한다고 생각하지만 실제로 그렇게하는 지 확인하지 않았습니다.

편집 : 난 그냥 작은 테스트를하고 발견

:

  1. AVR-GCC 4.6.2이 하지가 읽기 접근과 관련하여 휘발성 물질과 같은 글로벌 레지스터 변수를 처리 않으며,
  2. Atmel Studio의 Naggy 확장 프로그램이 내 코드에서 "전역 레지스터 변수가 지원되지 않습니다"라는 오류를 감지합니다.

글로벌 레지스터 변수는 실제로 나는 놀라지하지 않다 "지원되지 않는"것으로 간주됩니다 가정하면 GCC 취급 그들에게 알려진 의미와 단지 같은 지역 변수, 그.

내 테스트 코드는 다음과 같습니다 : 당신이 정상적인 HW 타이머를 사용할 수없는 이유는

uint8_t var; 

volatile uint8_t volVar; 

register uint8_t regVar asm("r13"); 

#define NOP asm volatile ("nop\r\n":::) 

int main(void) 
{ 
    var = 1; // <-- kept 
    if (var == 0) { 
     NOP; // <-- optimized away, var is not volatile 
    } 

    volVar = 1; // <-- kept 
    if (volVar == 0) { 
     NOP; // <-- kept, volVar *is* volatile 
    } 

    regVar = 1; // <-- optimized away, regVar is treated like a local variable 
    if (regVar == 0) { 
     NOP; // <-- optimized away consequently 
    } 

    for(;;){} 
} 
+0

해체로 확인했습니다. 그 GCC는 정상적으로 레지스터 변수를 처리하지 않습니다 : C 코드에서 액세스 한 레지스터 변수는 먼저 다른 레지스터에 복사 된 다음 조작되고 필요한 경우 다시 기록됩니다. 전역 레지스터 변수는 내가 말할 수있는 한 지원되지 않습니다. – chrylis

+0

해석 관련 휘발성 "에 대한 모든 접근은 추상 상태 머신과 정확하게 일치해야한다고 명시되어있다."모든 expr 에센스가 평가된다 "(5.2.1.3.3). 루프 테스트를 제거하는 등 멀리 평가를 최적화하면이 서신이 중단됩니다. 평가 결과가 추상 상태 시스템에 영향을 미치지 않는 경우에만 액세스를 최적화 할 수 있습니다. – chrylis

+0

@chrylis 귀하는 어떤 최적화 수준으로 시험을 보았습니까? 내 설정은'-O2'로 이루어졌지만, 예를 들어'-O0'는 최적화되지 않을 가능성이 큽니다. - 그리고 네, GCC 자체는 내 전역 변수 (또는 전역 변수)에 대해서 불평하지 않았습니다. 그러나 Naggy는 여하튼이 인용문이 인용 된 이유에 대해 잘못된 것이라고 결론을 내렸고, 분해 된 종류는 적어도 적어도 기대했던대로 작동하지 않는다는 것을 확인했습니다. – JimmyB

관련 문제