글에 틀린 부분이 있을 수도 있습니다. 피드백은 언제나 환영입니다. 감사합니다.
사전지식
포스트를 작성하기 전에 미리 공부했던 내용들입니다.
1. Asynchronous, Synchronous, Blocking, Non-blocking
상황 : 함수 A와 함수 B,C,D가 있다고 가정. 함수 A는 CPU bound, 함수 B,C,D는 I/O bound 라고 가정. request1~3과 그에 따른 response1~3이 있다고 가정.
Blocking
A 함수가 B 함수를 호출 할 때, B 함수가 자신의 작업이 종료되기 전까지 A 함수에게 제어권을 돌려주지 않는 것
Non-blocking
A 함수가 B 함수를 호출 할 때, B 함수가 제어권을 바로 A 함수에게 넘겨주면서, A 함수가 다른 일을 할 수 있도록 하는 것.
Synchronous
어떤 작업에 대한 response가 들어오기전까진 다른 request를 내보낼 수 없다. request1에 대한 response1이 들어오기전까진 request2나 request3를 내보낼 수 없다. 설명을 이렇게 하니까 Blocking과 비슷해보인다. 하지만 둘은 다른 개념이다. Blocking은 "제어권", Sync는 "response" 와 관련있다. 세마포어를 가지고 두 태스크가 acquire, release를 하는 상황을 생각해봄으로써 synchronous가 어떤 것인지 생각해볼 수 있다.
Asychronous
어떤 request에 대한 response가 들어오지 않더라도 다른 request를 내보낼 수 있는 것이라고 이해할 수 있다. request1에 대한 response1이 들어오기 전에도 request2나 request3를 내보낼 수 있다. 설명을 이렇게 하니까 non-blocking과 비슷해보인다. Non-blocking은 "제어권", Async는 "response"와 관련있다.
○ 어떤 request에 대한 response가 들어오기 전에 여러개의 request를 내보낼 수 있다는 건, non-blocking 상황이여야 가능한 것이다. 적어도 c언어에서는 blocking api 호출하면, 다른 request 호출할 틈도 없이, 바로 해당 라인에서 block 되기 때문이다. 다른 언어에서는 어떤지 잘 모르겠다
○ 그러므로 일단 non-blocking 상황을 가정해야, sync일 때와 async일 때의 차이를 구별할 수 있다.
○ blocking 상황에서는 sync일 때와 async일 때의 차이를 구별하기 힘들다.
Blocking/Synchronous
함수 A에서 함수 B를 호출하면, 함수 B가 끝날 때까지 cpu제어권을 넘기지 않는다. 그리고 당연한 말이지만, 이 request에 대한 response가 오기 전까지 다른 request를 호출할 수 없다.
Non-blocking/Synchronous
함수 A에서 함수 B를 호출하면, flag 같은 것만 설정하고 바로 제어권을 함수 A로 넘긴다. 이전 request에 대한 response가 오지 않으면, 다음 request를 호출할 수 없다. 제어권은 (완료됬는지 request보내고 response 받을 때 빼고는) 함수 A한테 계속 있지만, 중간중간 함수B가 완료됬는지 계속 request를 보내고 response를 받는다.
Non-blocking/Asynchronous
함수 A에서 함수 B를 호출하면, flag 같은 것만 설정하고 바로 제어권을 함수 A로 넘긴다. 그림엔 나와있지 않지만, 함수B 뿐만 아니라 함수 C와 함수 D에게도 request를 한다고 생각해보자. flag 같은 것만 set 하고 즉시 제어권을 반환하므로 함수 B, C, D는 다 background에서 실행되다가 각자 맡은 작업이 끝나면 call back 함수를 호출한다. 함수 B, 함수 C, 함수 D에 대한 request를 각각 request1, request2, request3라고 해보자. request1, request2, request3 순서대로 호출되었다 하더라도 꼭 request1에 대한 call back 함수가 가장 먼저 오지 않아도 된다.
Blocking/Asynchronous
c언어에서는 blocking api 호출하면 바로 해당 라인에서 block 되므로 한번에 여러개 request 못한다. request1을 보냈다고 해보자. call back 함수에 함수A에서 필요한 데이터가 들어있다고 가정하자. call back 함수가 호출되면 함수 A는 그 데이터를 이용하여 필요한 작업을 진행할 수 있을 것이다. 그런데 누가 굳이 이렇게 프로그램을 설계할까 싶다. 더 익숙한 Blocking/Synchronous 방법이 있는데 Blocking/Asynchronous로 설계할 필요는 없다고 생각한다.
2. CPU bound, I/O bound
non-blocking, asynchronous 방식이 효과를 보이려면, 호출되는 함수들이 CPU 바운드 작업이 아니라 I/O 바운드 작업이어야 한다. CPU 바운드 작업은 CPU 연산이 주를 이루는 작업 (예: 계산, 데이터 처리)을 말하고, IO 바운드 작업은 I/O (입출력) 작업이 주를 이루는 작업을 말한다. 호출되는 함수들이 CPU 바운드 작업이라면, non-blocking 방식이 큰 의미가 없을 수도 있다.
예를 들어 함수A와 함수B가 있다고 하자. 그리고 둘 다 CPU바운드 작업이라고 하자. 함수A는 함수B의 결과가 필요한데 함수 B의 완료에도 CPU가 필요하기 때문에, 함수A가 함수B의 결과를 받기 위해선 CPU 제어권을 B에게 양보해야 한다. 따라서 전체 수행시간이 blocking 방식과 큰 차이가 나지 않을 것이다.
그러나 함수B가 I/O 바운드 작업이라면, 함수B는 CPU 제어권을 일부만 필요로 하고 외부장치와의 입출력이 주요 작업이므로( = cpu를 그렇게 많이 필요로 하지 않으므로 = cpu없어도 일할 수 있으므로) 함수A와 함수B가 동시에 실행되는 효과가 나타난다.
Question.
non-blocking 방식을 이용하는 HAL_UART_Transmit_IT 함수에 대해 알아보던 중 위의 글을 발견하게 되었다. 오실로스코프로 측정한 파형 같은데, 정말로 non-blocking 방식처럼 동작하는게 신기했다.
non-blocking과 관련한 많은 자료들을 찾아본 결과, 모두 kernel이 있는 상황을 전제로 설명이 되어있었다.
그러면 os가 없는 baremetal 환경인 firmware에서는 어떻게 이런 non-blocking 동작이 가능한 것일까?
Answer.
결론부터 말하자면, uart interrupt가 이러한 non-blocking, asynchronous 동작을 가능하게 한다.
uart 송신 인터럽트를 예로 들어 설명하겠다. 실행순서는 아래와 같다. 사진과 함께 보면 이해가 더 쉬울 것이다.
1. HAL_UART_Transmit_IT 함수 호출
HAL_UART_Transmit_IT 함수가 호출되면 huart->TxISR 안에다가 UART_TxISR_16BIT 또는 UART_TxISR_8BIT 함수를 등록한 후 USART_CR1레지스터의 TXEIE 비트만 set 하고 제어권을 다시 main.c로 넘기게 된다. flag만 set 하고 제어권을 main.c로 넘기기 때문에, main.c의 코드들은 uart 송신 완료까지 기다리지 않고도 바로 실행될 수 있다.
2. uart 하드웨어에서 송신 작업 완료
uart 하드웨어는 송신버퍼, 수신버퍼 등으로 이루어져 있는데, 여기서 송신 작업을 하는 것은 cpu의 개입을 필요로 하지 않는다. 따라서 main.c의 코드와 병렬적으로 실행될 수 있다.
3. uart 하드웨어가 송신작업을 완료하면 IRQ를 보냄
4. ISR(=HAL_UART_IRQHandler) 이 실행됨
ISR안에서 huart->TxISR(huart) 로 인해 1번 단계에서 등록한 함수가 실행되고, UART_EndTransmit_IT 함수가 호출 -> HAL_UART_TxCpltCallback 호출된다. HAL_UART_TxCpltCallback 함수는 사용자가 따로 정의해야 한다. ISR 이 main.c의 코드들보다 우선순위가 높으므로, 이때는 main.c의 코드들이 실행되지 못하겠지만 ISR이 아주 금방 끝나므로 main.c의 코드들이 delay하는데 큰 영향을 미치지 않는다.
os가 없지만, os가 있는 환경에서의 kernel의 기능을 interrupt가 담당하고 있다. 이를 이용하여 non-blocking 동작이 가능하게 되는 것이다.
출처 :
https://velog.io/@codemcd/Sync-VS-Async-Blocking-VS-Non-Blocking-sak6d01fhx