인프런 커뮤니티 질문&답변

아이스님의 프로필 이미지
아이스

작성한 질문수

FreeRTOS 프로그래밍

소스코드 분석-printf 와 fflush

fflush질문

작성

·

189

1

강사님 안녕하세요

틱 인터럽트 기준 1ms동안 버퍼에 문자1000개가 들어가는 속도라 하고 버퍼는 100개까지 채울수 있다는 가정시

fflush함수가 없을 때는

a 100개가 1*10번 / b 100개가 1*10번

번갈아 출력되는거죠?

그런데, fflush함수가 있으면

a 1개가 100*10번나오지 못하다 끝나고

b 1개가 100*10번나오지 못하다 끝나야 하는데

말씀하신 uart로직이 결부되었으나

aaaaaabaaaaaabaaaaaab 이런 패턴은

b 태스크에만 영향을 받는것 처럼 보이는데

제가 질문한 예시로 답변 가능할까요??..

답변 1

1

홍영기님의 프로필 이미지
홍영기
지식공유자

답변을 여기 올려드렸어요 <-- 클릭!

감사합니다.^^

아이스님의 프로필 이미지
아이스
질문자

image수업처럼 딜레이 부분은 주석처리 한 상태에서 그 결과를 물어봤는데

저의 전달이 미약한것 같습니다~

수업내용처럼 vTaskDelay 없이

fflush유무 에 따른 결과가 이상해서 질문 드렸습니다~

딜레이가 없으니 tick인터럽트 주기만큼(1ms전제) tast1 실행, task2실행 이것을 반복하잖아요~

 

image

 

이것은 aaabbbaaabbb 동일하게 나오는데

fflush를 두 task에 추가하면 aaaaaabaaaaaab 나오는 현상이요~

말씀하신 uart로직이 결부되었으나

uart로 인한 하드웨어 지연현상은 tast1 , task2 둘다에 나타나니 결과적으로 패턴은 규칙적이어야 하는데 어느 한 태스크에만 영향을 받는것 처럼 보이는 문제요!

 

홍영기님의 프로필 이미지
홍영기
지식공유자

일꾼1과 일꾼2가 있습니다.

두사람은 목재를 옮기는 일을 하고 있습니다.

주인장이 와서 시장에 가서 여기 메모지에 적혀있는 물건을 사오라고, 일꾼2에게 말합니다.

일꾼2는 심부름때문에 그날 끝냈어야 할 일을 다하지 못했습니다.

태스크1(화면에 a 을 출력하고 있다) . . .일꾼1에 비유... a 는 목재를 옮기는 일

태스크2(화면에 b 을 출력하고 있다). . .일꾼2에 비유... b 는 목재를 옮기는 일

 

아래 그림을 보시죠. T1 과 T2 는 라운드로빈으로 시간을 공평하게 나눔받아 실행중입니다.

image

그런데, T1 은 압도적으로 T2보다 더 많이 작업을 실행한 것처럼 보입니다. 왜 그럴까요?

아까의 비유를 보죠. 일꾼1이 오늘 한 일은 (1)목재를 옮기는 일이 전부입니다

하지만, 일꾼2가 오늘 한 일은 이 두가지입니다 (1)목재를 옮기는 일, (2)심부름

목재를 옮기는 일을 일꾼의 주된 일(work) 로 본다면, 심부름은 사이드 일로 볼 수 있어요.

하지만 심부름도 일이 아닌것은 아니죠.

 

b 을 화면에 찍고 있는 T2 는 T1 이 하지 않는 허드렛일을 조용히 안보이는데서 처리하고 있는 것입니다. 바로 UART 장치( HAL_UART_Transmit )를 이용해서 화면에 실질적인 출력을 하도록 하는 일인 것이죠.

 

image

하지만, 일꾼2는 다음에 비슷한 일이 있을 때 자리를 피합니다. 결국 심부름은 일꾼1이 하게됩니다.

다음의 코드를 보세요. vTaskDelay(1); 기존의 코드에 함수를 한 줄 끼워 넣습니다.

	vTaskDelay(1); // ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️ 이부분을 잘 보세요.
while(1) {
	/* TODO #3:
	코드를 실행 하여 보고
	vTaskDelay() 코드를 주석 처리한 후 그 결과를 설명한다 */
#if 1 // No comment
//vTaskDelay (pdMS_TO_TICKS (1000));
printf("a"); fflush(stdout);	// 문자 'a' 출력
#endif // TODO #3

 

그러면 다음과 같은 현상으로 나타납니다.

image

왜 이런 현상이 나타날까요.

vTaskDelay(1)을 T1 코드에 끼워 넣었기 때문에

T2가 T1 보다 while 루프를 먼저 실행하게되죠. 1밀리초 동안 버퍼에 'b' 문자들이 채워지기 시작합니다. 그리고, 운이 나쁘면 T2 는 계속 UART 장치에 글자를 출력하는 일만 도맡아하게 될 수 있습니다. 이것이 위 출력 결과의 해석입니다.

 

자, 이제 결론으로 가보죠

제가 강의에서도 충분히 지적하였지만, printf 사용하실 땐 주의하셔야 합니다. 화면에 나오는 출력이 실시간이라고 믿으시면 안되는 이유가 여기 있습니다.

아래 함수에서 __HAL_LOCK(), __HAL_UNLOCK() 을 보시면 됩니다.

일종의 임계구역이 형성되어 있습니다. T1이 임계구역으로 들어가서 실행하는 동안 선점되어 T2가 동일한 임계구역으로 들어갈 수 없도록 설계되어 있는 것이죠. 결국, 운이 나쁘면 어떤 태스크는 죽어라 이 작업을 다른 태스크에 비해 과도하게 해야 할 수 있습니다. 그렇다면 printf 을 사용하지 않고 uart_printf() 을 이용하는 방법도 고려해 볼 수 있겠지만, 서로 일장 일단이 있어서 이 방법이 저 방법보다 확실히 우월하다고 단정지을수는 없습니다.

printf 라는 것이 본시 주 용도가 디버깅이기 때문에 기기의 디버깅시 실시간으로 동작하는 것을 항시 병행해서 테스트해야 합니다. 다시말하면 printf 을 온오프시켜가면서 테스트해야 한다는 것이죠. 개발자가 그만큼 주의를 기울여야 한다는 뜻이기도 합니다.

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint8_t  *pdata8bits;
  uint16_t *pdata16bits;
  uint32_t tickstart = 0U;

  /* Check that a Tx process is not already ongoing */
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);   // ⭐️⭐️⭐️⭐️⭐️

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();

    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

    /* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer */
    if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
    {
      pdata8bits  = NULL;
      pdata16bits = (uint16_t *) pData;
    }
    else
    {
      pdata8bits  = pData;
      pdata16bits = NULL;
    }

    /* Process Unlocked */
    __HAL_UNLOCK(huart);  // ⭐️⭐️⭐️⭐️⭐️

    while (huart->TxXferCount > 0U)
    {
      if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
      {
        return HAL_TIMEOUT;
      }
      if (pdata8bits == NULL)
      {
        huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU);
        pdata16bits++;
      }
      else
      {
        huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU);
        pdata8bits++;
      }
      huart->TxXferCount--;
    }

    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    {
      return HAL_TIMEOUT;
    }

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}
아이스님의 프로필 이미지
아이스

작성한 질문수

질문하기