인터럽트를 처리하고 WFI risc-v cpu 명령어를 사용하는 의도 된 / 올바른 방법은 무엇입니까?
저는 베어 메탈 프로그래밍을 처음 접했으며 이전에 인터럽트를 사용한 적이 없지만 RISC-V FE310-G002 SOC 기반 개발 보드에서 배우고 있습니다.
나는 RISC-V WFI (Wait for interrupt) 명령에 대해 읽었고 매뉴얼에서 실제로 코어를 잠자기 위해 의지 할 수있는 것처럼 들리지 않습니다. 대신 시스템에서 실행을 중지 할 수 있으며 명령이 NOP처럼 처리되어야 함을 나타냅니다. 그러나 이것은 나에게 다소 쓸모없는 것 같습니다. 다음 ASM 프로그램 스 니펫을 고려하십시오.
wfi_loop:
WFI
J wfi_loop
이것은 WFI에 의존 할 수 없기 때문에 수행되어야합니다. 그러나 인터럽트 처리기에서 MRET를 사용하면 여전히 루프에 빠질 수 있습니다. 따라서 인터럽트 핸들러에서 값이 업데이트되는 전역 변수에 대해 조건부로 만들어야합니다. 이것은 매우 지저분 해 보입니다.
또한 구현이 실제로 WFI 명령을 따르고 인터럽트가 WFI 명령 실행 직전에 트리거되는 경우 WFI 명령 이전에 반환되므로 다른 인터럽트가 트리거 될 때까지 전체 코어가 중단됩니다.
수행 할 작업이 없을 때 명령의 올바른 사용법은 커널 스케줄러 내부에있는 것 같습니다. 그러나 그럼에도 불구하고 인터럽트 처리기에서 그러한 코드로 돌아가고 싶지는 않지만 스케줄러 알고리즘을 처음부터 다시 시작하십시오. 그러나 스택 등을 롤백해야하므로 그것도 문제가 될 것입니다.
나는 이것을 내 머릿속으로 계속 빙빙 돌고 있으며 안전한 사용법을 알아낼 수없는 것 같습니다. 원자 적으로 CSRRS로 인터럽트를 활성화 한 다음 즉시 다음과 같이 WFI를 호출 할 수 있습니다.
CSRRSI zero, mie, 0x80
wfi_loop:
WFI
J wfi_loop
NOP
NOP
그런 다음 인터럽트 처리기에서 MRET를 호출하기 전에 mepc 레지스터를 8 바이트 씩 증가시켜야합니다. 인터럽트는 또한 반환하기 전에 인터럽트 처리기 내부의 mie 레지스터에서 다시 비활성화되어야합니다. 이 솔루션은 압축 된 명령어 사용 여부에 관계없이 WFI, J 및 NOP가 모두 4 바이트 명령어로 인코딩 된 경우에만 안전합니다. 또한 CSRRSI 명령에 의해 활성화 된 후 인터럽트가 트리거되기 전에 WFI 명령에 도달하는 프로그램 카운터에 따라 다릅니다. 그러면 인터럽트가 코드의 안전한 위치에서 트리거되고이를 기다리고 있던 루프에서 벗어나는 방식으로 반환 될 수 있습니다.
하드웨어에서 어떤 동작을 기대할 수 있는지 이해하려고 노력하고 있으므로 인터럽트를 올바르게 호출하고 반환하고 WFI 명령을 사용하는 방법은 무엇입니까?
답변
유휴 상태를위한 하나의 작업 / 스레드 / 프로세스가 있어야하며 첫 번째 코드와 같아야합니다.
유휴 스레드가 가장 낮은 우선 순위를 갖도록 설정되었으므로 유휴 스레드가 실행 중이면 실행할 다른 스레드가 없거나 다른 모든 스레드가 차단됨을 의미합니다.
다른 스레드의 차단을 해제하는 인터럽트가 발생하면 인터럽트 서비스 루틴은 인터럽트 된 유휴 스레드 대신 차단 된 스레드를 다시 시작해야합니다.
IO에서 차단하는 스레드 자체도 중단 ecall
됩니다. 자체 사용을 통해 중단됩니다 . 이 예외는 IO에 대한 요청이며이 스레드가 차단되도록합니다. IO 요청이 충족 될 때까지 재개 할 수 없습니다.
따라서 IO에서 차단 된 스레드는 인터럽트 된 것처럼 일시 중단됩니다. 클럭 인터럽트 또는 IO 인터럽트는 즉시 중단 된 프로세스와 다른 프로세스를 재개 할 수 있습니다. 이는 유휴 프로세스가 발생하는 경우에 발생합니다. 실행 중이고 프로세스가 기다리고 있던 이벤트가 발생했습니다.
내가하는 일은 scratch
csr을 사용 하여 현재 실행중인 프로세스 / 스레드의 컨텍스트 블록을 가리키는 것입니다. 인터럽트시 인터럽트 서비스를 시작하는 데 필요한 최소한의 레지스터를 저장합니다. 인터럽트로 인해 다른 프로세스 / 스레드가 실행 가능 해지면 interupt에서 다시 시작할 때 프로세스 우선 순위를 확인하고 중단 된 항목을 다시 시작하는 대신 컨텍스트 전환을 선택할 수 있습니다. 중단 된 것을 다시 시작하면 빠른 복원이됩니다. 컨텍스트를 전환하기 위해 인터럽트 된 스레드의 CPU 컨텍스트 저장을 완료 한 다음 다른 프로세스 / 스레드를 다시 시작하여 scratch
레지스터를 전환합니다 .
(중첩 된 인터럽트의 경우 재개시 컨텍스트 전환을 허용하지 않지만 현재 컨텍스트를 저장 한 후 scratch
인터럽트에서 우선 순위가 더 높은 인터럽트를 다시 활성화하기 전에 컨텍스트 블록의 인터럽트 스택에 csr을 설정합니다 . 또한 매우 사소한 최적화 우리는 사용자 정의 작성된 유휴 스레드가 PC 저장 / 복원 외에는 아무것도 필요하지 않다고 가정 할 수 있습니다.)
따라서 인터럽트 핸들러에서 값이 업데이트되는 전역 변수에 대해 조건부로 만들어야합니다.
wfi
어떤 이벤트로 인해 하트가 깨어 났는지 모르기 때문에 구현에 관계없이 그렇게해야합니다.
당신은 할 수 있습니다 N 실행시 인터럽트 사용 가능 wfi
하며 하나라도 제기되었을 수 있습니다.
wfi
입니다 최적화 무언가가 일어날 때까지 전력을 절약 할 수 있습니다. 언급했듯이 OS 스케줄러는 예약 할 수있는 스레드가없는 상태 (예 : 모두 IO를 기다리거나 단순히 아무것도없는 경우)에서 다음과 같은 작업을 수행해야합니다 (필요한 모든 가시성 및 원 자성 의미 체계 포함).
while ( ! is_there_a_schedulable_thread());
그것은 단지 기다리고 있습니다.
그러나 빡빡한 루프 (성능과 전력을 손상시킬 수 있음)를 돌리는 대신 스케줄러는 다음을 사용할 수 있습니다.
while ( ! is_there_a_schedulable_thread())
{
__wfi();
}
최악의 경우에는 타이트 루프와 같으며, 외부 인터럽트가 발생할 때까지 하트를 일시 중지합니다 (잠재적으로 IO가 완료되어 스레드가 자유롭게 실행될 수 있음을 의미 함).
스레드가없는 경우에도 (타이머 인터럽트로 인해) x 마이크로 초 마다 깨어나 는 것이 전력 루핑을 낭비하는 것보다 낫습니다.
wfi
인터럽트 핸들러에 대한 모든 작업을 수행하는 경우 (예 : 버튼을 눌렀거나 이와 유사한 경우) 프로그래밍 임베딩에 유용 할 수도 있습니다.
이 경우 main
함수는 스케줄러와 마찬가지로 종료 조건없이 영원히 반복됩니다. 명령은 크게 배터리 수명을 향상시킬 수 있습니다.wfi
wfi
어디에서나 사용할 수 없거나 결코 발생하지 않는 인터럽트를 기다리고있는 자신을 발견 할 수 있습니다 (사실 권한이있는 명령입니다).
하드웨어와의 조정을위한 최적화라고 생각하십시오.
특히 인터럽트가 발생했는지 확인하는 방법으로 설계되지 않았습니다.
void wait_for_int(int int_num)
{
//Leave only interrupt int_num enabled
enable_only_int(int_num);
__wfi();
restore_interrupts();
}
RISC-V 의 특정 구현이 주어지면 그렇게 사용할 수 있지만 의사 코드에서 볼 수 있듯이 실제로는 그렇게 편리하지 않습니다.
하나를 제외한 모든 인터럽트를 비활성화하는 것은 일반적으로 OS가 감당할 수없는 것입니다.
하지만 임베디드 애플리케이션은 가능합니다.