본문 바로가기
DPC나, APC, 그리고 Bottom Half

이전에 ISR을 너무 길게 짜면 안 된다는 발언에 대한 디펜스. ISR을 너무 길게 짜면 안 된다는 발언을 한 적이 있는 데 그럼 할 일이 많은 ISR은 어쩌라는 말이냐! 라는 항의에 너무 무책임 한 듯 하여, 이런 긴 ISR에 대한 처리를 어떻게 하면 좋을 까 하는 데에 대한 얘기입니다요.
 
대부분 이런 Interrupt는 휴대전화가 그 좋은(?) 예이죠. 친구랑 얘기하던 중에도 전화가 오면 그거부터 받고 짧은 얘기면 곧바로 얘기를 모두 끝내고, 길고 중요하지 않은 얘기면 이따가 다시 전화할께라던가 하죠. 요 길고 중요하지 않아 나중에 처리하겠다고 하는 부분이 바로 문제의 그 부위 입니다. ㅋ
 
Deferred Procedure Call이나, Asynchronous Procedure Call, 또는 Linux에서 말하는 Bottom half라는 거 들어보신 적이 있으실랑가 모르겠네요. 그다지 주목 받지도 못하고, 아름답지도 못한 이런 수상한 편법은 실은 정말로 아름다운 방법으로 구현 가능하게 되어 있답니다. 이런 아름다운 얘기를 하게 되어 약간은 긁적긁적이지만, 재미있는 여행이라고 생각해 주시면 영광굴비입니다.
 
용어를 한번 정리하자면, Bottom Half = APC + DPC라고 보시면 되요. APC는 지금 처리하지 말고, 나중에 Task Level로 처리할 수 있도록 처리하는 부분이고요, DPC는 APC를 직접 처리하는 부분이라고 보시면 무리 없어요. 보통은 APC는 API로, DPC는 Task로 구성된다고 보심 됩니다요. 일단은 ISR을 너무 길게 짜면 System에 문제가 있을 테니, ISR중에 너무 긴 녀석은 두 가지 Stage로 나누어 생각해 보도록 하죠. 지금 당장 처리해야 하는 부분과 나중에 Task Level로 처리해도 되는 부분. 이건 ISR을 만들 때 각자 잘 생각해서 나누셔야 해요.
 
이번에 다루려는 건 이미 다루었던 clk_register (key_polling, 100) 같은 녀석을 다뤄볼까 합니다. Callback Function을 등록해 놓았는데, 이 녀석을 그냥 IRQ level에서 실행하려고 보니까, 이런 녀석이 한두개가 아니더란 말이죠. 그럼 이 녀석을 Task Level에서 실행할 수 있도록 해줬으면 좋겠는데..라고 생각이 들 때 사용하는 게 이런 Bottom half라는 거죠 머.
 
이번에도 이용하는 건 Queue인데요, call back function을 APC를 처리할 DPC task의 Queue Command로 보내서 처리하면 되는 것이지요. 먼저, ISR routine에서 또또또 Queue를 이용해서 DPC를 처리할 Task의 Queue에 Callback function을 넣어주는 거죠.
 
queue_apc() 라는 APC API가 있다고 치죠 머. Clock Tick ISR이 계속 진행 되면서 Timer 등록 된 녀석 중에 expire 된 녀석이 있을 경우에! 곧바로 Clock Tick ISR에서 직접 Call back을 실행하지 않고요, queue_apc ()를 이용해서 DPC를 담당하는 Task에 callback을 넘겨 준다고 생각해 보죠.
 
queue_apc(key_polling);
send_signal (DPC_WORK_SIG);
 
이렇게 넣어주면 queue_apc는 key_polling() 함수를 DPC를 담당하는 Task (이제부터 DPC_task()라고 부를게요) DPC_task()의 Queue에 함수를 넣어두는 거에요. ISR이 끝나고 Scheduling이 될 때, DPC_task에게 순서가 왔을 때, Queue에 넣어진 Callback function들을 하나씩 꺼내서 실행하는 구조로 만들면 되겠사옵니다. 여기서 주의할 점은 DPC_task의 Priority는 꽤나 높아야 하겠죠. 아무래도 나중에 처리한다고 해도, Interrupt에 의해서 처리 되었었어야 되는 걸 처리하는 거니까, 빨리 처리하는 게 신상에 좋을 거에요. 그리고, 이런 Callback 함수를 처리할 때는 Interrupt도 안 걸리게 하는 게 좋겠죠.
 
 
void DPC_task()
{
   while (1)
   {
      wait_signal ( DPC_WORK_SIG );
      clear_signal ( DPC_WORK_SIG );
 
      Interrupt_Lock( );
         while( (queue_get())() );   /* Queue가 비워지지 않는 한 get한 함수를 처리 */
      Interrupt_free( );
   }
} /* END rex_dpc_task *
 
 
어때요. DPC 차암~ 쉽죠 잉?

 
Linked at 친절한 임베디드 시스템 개발자.. at 2009/10/01 12:45

... ⓞ DPC나 APC, 그리고 Bottom Half ... more

Commented by 김대현 at 2009/09/15 22:32
interrupt_lock된 상태에서 queue_get하고 관련 callback들을 수행한다면, 수행동안 인터럽트가 걸리지 않는 상태가 되니, 결국 interrupt내에서 처리하는것과 전혀 차이가 없겠죠. 그래서 위 코드는 인터럽트와 공유하고 있는 queue에서 get을 할때만 lock하고 callback은 free 이후에 수행해야 하는것으로 고쳐야 할듯 합니다
Commented by 히언 at 2009/09/15 22:39
홋! 일리있는 이야기십니다.
현재 사용되고 있는 구현은 위의 예와 말씀하신 예가 모두 사용되고 있는 것으로 파악하고 있습니다.

일단은 policy가 문제인데요.
ISR에서 처리할 것들이 너무 많은 System에서는 대현님께서 말씀하신 Policy를 사용하는 게 맞고요,
DPC로 많은 것을 처리하라고 맡기지 않는 경우에는 위의 예가 어울릴 듯 합니다.

Interrupt 내에서 처리하느냐 아니냐는
Mode의 문제이면서 Interrupt Mode에서 너무 많이 머물게 하지 않는다가 그 핵심인 것이지요.

Interrupt Lock을 거는 예는 그 외에도 많이 있으니 이점 참고하시면 좋을 것 같습니다아아아~
Commented by 김대현 at 2009/09/16 09:15
policy를 그런의도로 개발자가 그렇게 했다면 더이상 할말이 없겠지만, 이런용도로 왜 궂이 만들었을까 하는 의문도 있습니다. 작업량이 적은것만 DPC로 돌린다는것도 좀 이치에 안맞는것 같습니다. 어쨋든 결과적으로 인터럽트 레벨에서 수행되기 때문에 DPC를 하나마나 한 결과를 초래하기 때문이죠. DPC의 용도가 가급적 인터럽트 활성화 시간을 보다 늘리긴 위한 방안이 아닐까요 ?.. 어쨋든, 이런 용도로 아시다시피, Windows 에서는 DPC/APC, WinCE에서는 IST, Linux에서는 bottom half, Nucleus에서는 HISR등이 있죠. 이들 모두 는 DPC level에서 수행중에 인터럽트가 모두 걸릴수 있는 상황이죠. 말씀하고 계신 예는 rex OS에 한정적인듯 한데요, REX코드를 첨봤을때 왜 저런식으로 DPC를 구현했을까 하는 의문이 아직도 있습니다. 저는 개발자의 실수일것이라고 봤는데, 아니겠지요 ?
Commented by 히언 at 2009/09/17 00:26
그러게요~
일단은 DPC로 작업량이 적은 걸 돌린다기 보담은 DPC로 돌릴만한 일이 많이 않을 경우라고 봐야 하지 않을까요~?
ㅎㅎ 몇가지 Test해 봤는데 개발자 실수는 아닌거 같애요~
별 무리 없이 잘 돌아가고요~ 특히나 Timer Call back 돌릴 때 아주 좋습니다~
인터럽트 처리하는 모드에서 빠져나와 인터럽트 Lock이 걸린 상태라 해도 Task level에서 처리한다는데
큰 의의를 두시면 괜찮지 않을까 생각중 이에요~
Commented by 김대현 at 2009/09/17 09:11
네. 사실 말씀하신데로 callback을 정말 특이하게 작성하지 않는 이상은 적당한 cpu 프로세싱 파워가 있는 환경에서는 동작상 문제는 보이지 않을것으로 예상됩니다. 그런데 문제는 내제되어있죠. 설명을 위해 비정상적인 예를 하나 들어보겠습니다. 1ms마다 인터럽트가 걸리는 타이머가 있다고 하고, 그 인터럽트를 처리하는 DPC가 2ms의 시간이 걸린다고 하면, DPC가 수행중 인터럽트가 2번 발생해야 하는데, DPC수행후 인터럽트가 활성화 되면서 2개가 1개로 뜨는 경우가 생기겠죠. 다른 주변장치 인터럽트는 이게 그다지 문제되지 않을거 같습니다만, 각각의 인터럽트가 모두 중요한 타이머 인터럽트인 경우는 무시 못할 문제죠. CPU스케쥴링에 타이머인터럽트인 TickISR이 사용되는데 OS마다는 다르지만 tickCount를 이 부분에서 증가를 시키는데, 위와 같은 문제로 tickCount의 오류가 발생하겠죠. 사실 이 오차는 중요하지 않을수 있습니다만, 어쨋든 문제는 내포하고 있다는 것이죠. 꼭 타이머 DPC가 2ms 걸리지 않더라도 동시에 발생한 여러 DPC를 모두 처리하는데 2ms이상 소요되도 같은 문제가 발생할 수도 있겠죠. 제가 일하는 분야에서 사실 위와 같은 문제가 좀 치명적이어서 얘기하다보니 많이 길어졌네요. 아뭏튼, 히언님과 즐거운 토론을 할수 있어서 매우 좋았습니다. 좀 늦은감이 있지만, 정말 감탄스럽습니다. 전체 게시글에서 다방면의 다양한 경험을 보았고, 그 경험을 글로 남기는 열정과, 또 그것을 공개하는 사회적선행에 감사드립니다. 제가 기술적 토론을 좋아하는 지라 자주 들리면서 이런글을 많이 남길지도 모르니, 귀찮아 하시지는 말구요.... 그럼 또 뵙죠.
Commented by 히언 at 2009/09/19 14:15
넹~ 아주 많이 감사합니다~
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.
친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -