프로세서 2개를 돌려봤으니, 이제 간단하게 2개의 Task가 데이터를 공유하는 샘플을 한번 만들어봐야겠다 싶다. 그래서 기존 멀티태스크 예제를 수정해서 Task간 데이터를 어떻게 공유하는지 확인해보도록 하자. 필자도 리눅스에서 프로그래밍을 하다보면 여러가지 IPC(Inter Process Communication)을 이용한다. 간단하게는 기본자료형, Queue, 공유메모리등 어떤 기법이 편리한지에 따라서 적당한 방법을 사용하여 프로세스간 데이터를 주고 받는다. 그러다보면 데이터 동기등에 의해서 mutex나 semapore등 초보 개발자들이 싫어하는 그리고 필자도 그다지 좋아하지 않는 방법을 이용하는데, 본 예제에서는 그런 어려운것은 지양하고 간단한 예제를 통해서 기본 개념을 알아보도록 하자. 

     

    --- Contens ---

    1. [ESP32] VS Code 개발환경 구성 : https://makeutil.tistory.com/303

    2. [ESP32] 첫 프로젝트 생성하기 : https://makeutil.tistory.com/304

    3. [ESP32] 멀티 테스크 예제 (2 Task) : https://makeutil.tistory.com/305

    4. [ESP32] Task간 데이터 공유 (Queue, Mutex) : 현재 글

     

    이번에는 데이터 공유를 위한 2가지 예제를 만들어 보고자 한다. 

     

    1. Queue

       기존 예제에서 Task1과 Task2에서 카운터가 올라가는데, 첫번째 Task1의 카운터 출력을 삭제하고 Task2에서 둘 모두 출력하는 걸로 바꾸는 것으로 첫번째 예제를 작성해보자. 

    1.1. 소스코드

      소스코드는 다음과 같이 입력하였다. 

    #include <stdio.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    
    #include "freertos/queue.h" // Queue를 위해 추가
    
    QueueHandle_t xQueue;
    
    void task1(void *pvParameters)
    {
        int count=0;
        while (1)
        {
            count++;
            printf("[%d] Task1\n", count);
            xQueueSend(xQueue, &count, portMAX_DELAY);  // 카운터 값을 Queue에 전달
            vTaskDelay(1000 / portTICK_PERIOD_MS);  
            if(count==100000)
                count=0;
        }
    }
    
    void task2(void *pvParameters)
    {
        int count=0;
        int recvValue=0;
    
        while (1)
        {
            count++;
            if (xQueueReceive(xQueue, &recvValue, portMAX_DELAY) == pdPASS)
            {
                printf("<Task1 : [%d] - Task2 : [%d]\n", recvValue, count);
            } else {
                printf(">Task1 : [%d] - Task2 : [%d]\n", recvValue, count);
            }
            vTaskDelay(2000 / portTICK_PERIOD_MS);  
            if(count==50000)
                count=0;
        }
    }
    
    int app_main(void)
    {
        xQueue = xQueueCreate(10, sizeof(int)); // Queue 생성 
        if (xQueue == NULL)
        {
            printf("Queue Create Failed!\n");
            return 0;
        }
    
        xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
        xTaskCreate(task2, "Task2", 2048, NULL, 1, NULL);
    
        return 0;
    }

     

    1.2. 결과

      결과를 보면 이상함을 확인할 수 있다. 

     

      결과를 보면 2초 뒤에 Task 2가 출력될 때, 두 Task의 값이 출력되도록 수정했는데... 2초에 한번 출력될 거라 생각 했는데 그렇지 않다는 것을 확인할 수 있다. 원래 의도로는 Queue가 10가 이므로, Task2가 출력되는 시점에 Task1은 2씩 증가해야되는데, 딜레이 코드를 넣었음에도 불구하고 동일하게 출력됨을 볼 수 있다.

     

     

      세부적으로 라이브러리를 직접 확인하진 않았지만, task2의 출력 시퀀스는 queue가 overflow되는 시점에서 task1에 동기화되는 것으로 보인다. 오류가 발생하지 않는건 좋은데  task2의 동작 시간도 틀어진 것 같다. 이런 오류는 미리 겪어보는게 도움이 된다.  그럼 소스코드를 바꿔보도록 하자.

     

    1.3. 소스코드 수정

      소스코드에서 task2의 딜레이를 제거했다.

    #include <stdio.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    
    #include "freertos/queue.h" // Queue를 위해 추가
    
    QueueHandle_t xQueue;
    
    void task1(void *pvParameters)
    {
        int count=0;
        while (1)
        {
            count++;
            printf("[%d] Task1\n", count);
            xQueueSend(xQueue, &count, portMAX_DELAY);  // 카운터 값을 Queue에 전달
            vTaskDelay(1000 / portTICK_PERIOD_MS);  
            if(count==100000)
                count=0;
        }
    }
    
    void task2(void *pvParameters)
    {
        int count=0;
        int recvValue=0;
    
        while (1)
        {
            count++;
            if (xQueueReceive(xQueue, &recvValue, portMAX_DELAY) == pdPASS)
            {
                printf("<Task1 : [%d] - Task2 : [%d]\n", recvValue, count);
            } else {
                printf(">Task1 : [%d] - Task2 : [%d]\n", recvValue, count);
            }
     
        }
    }
    
    int app_main(void)
    {
        xQueue = xQueueCreate(10, sizeof(int)); // Queue 생성 
        if (xQueue == NULL)
        {
            printf("Queue Create Failed!\n");
            return 0;
        }
    
        xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
        xTaskCreate(task2, "Task2", 2048, NULL, 1, NULL);
    
        return 0;
    }

     

     

     

    1.4. 결과

      결과를 보면 task1의 딜레이에 맞춰서 값이 증가한다. task2는 >가 출력되지 않는 것으로 보아, Task2는 Task1의 Queue에 의해서 동기화 되는 것으로 보인다. 흠..  아직은 잘 모르겠지만, xQueueSend와 xQueueReceive는 동기화 되는 것으로 보인다. 해당 동기화 때문에 결국 task2의 delay에 문제가 발생되는 것으로 우선은 이해해야되겟다. 좀더 찾아보면 동기가 되지 않는 Queue가 있을지는 모르겠지만... 필요하면 만들어 써야 되겠다. 

     

     

    2. Mutex를 이용한 데이터 공유

      기존 예제에서 Queue가 아닌 전역변수와 Mutex를 이용해서 자료에 접근하는 예제를 만들어보자.

    2.1. 소스코드 

      뮤텍스를 이용한 예제를 만들어 보았다. 우선은 원하는 결과를 확인할 수 있었다. 뮤텍스를 사용하기 위해서 semphr.h를 추가하였다.  전역변수로 gCount를 만들고, xMutex로 뮤텍스 핸들을 선언한다. 

    #include <stdio.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/semphr.h"
    
    int gCount = 0;  // volatile int 로 설정하여 컴파일러에 변경되지 않도록 하는게 좋다.
    
    SemaphoreHandle_t xMutex;
    
    void task1(void *pvParameters)
    {
        while (1)
        {
            
            if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) // 뮤텍스 잠금 가능하면 잠금
            {
                gCount++;   // 전역 카운트 증가
                printf("[%d] Task1\n", gCount);
                xSemaphoreGive(xMutex);  // 뮤텍스 해제
            }
            vTaskDelay(1000 / portTICK_PERIOD_MS);  // 1초 대기
        }
    }
    
    // Task2: 카운터 값을 출력함
    void task2(void *pvParameters)
    {
        int count=0;
        while (1)
        {
        
            count++;
            if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) // 뮤텍스 잠금 가능하면 잠금
            {
                printf("Task1 : [%d] Task2: [%d]\n", gCount, count);
                xSemaphoreGive(xMutex);  // 뮤텍스 해제
            }
    
            vTaskDelay(2000 / portTICK_PERIOD_MS);  // 2초 대기
        }
    }
    
    void app_main(void)
    { 
        xMutex = xSemaphoreCreateMutex(); // 뮤텍스 생성
    
        if (xMutex == NULL)
        {
            printf("Mutex create failed!\n");
            return;
        }
    
        xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
        xTaskCreate(task2, "Task2", 2048, NULL, 1, NULL);
    }

     

    2.2. 실행결과

      실행 결과를 확인하면 Task1이 245, 246이 실행되고 Task2가 실행되면서 Task1의 최종값 246과 Task2의 124가 실행되는 것을 볼 수 있다. 태스크가 처음 실행될 때 생성에 따른 시차가 발생되면서 카운트는 조금 틀어딜 수 있지만, 증가값을 봤을때, 각 Task는 정상적으로 동작되고 있다는 것을 알 수 있다. 

     

    2.3. 기타 설명

    2.3.1. 사용된 함수 설명

     - xSemaphoreTake()

        BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);

       뮤택스가 사용할 수 있는 상황이면 뮤텍스를 잠그고 진행하고, 그렇지 않으면  진입을 위해 무한 대기한다.

     

     - xSemaphoreGive()

        BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

        다른 태스크가 자원을 사용할 수 있도록 뮤텍스를 해제한다.

     

    2.3.2. POSIX의 Mutex

      우선 FreeRTOS 관련하여 확인해봤는데 POSIX Thread등에 관련된 함수는 기본적으로 지원하지 않는다고 한다. OS 자체에서 해당 기능에 대해서 모든 기능을 지원하지 않기 때문에 사용을 권하지 않는다고한다. 굳이 사용하겠다면, 설정에서 포팅하여 사용할 수 있지만, 성능이나 기능에 문제가 발생될 수 있다고 한다. 

     

      - ESP-IDF 설정

        ESP32에서 POSIX API를 활성화하려면, ESP-IDF 설정에서 CONFIG_POSIX 옵션을 활성화 해야함.

      - CMakeLists.txt 설정

         ESP-IDF 프로젝트에서 POSIX을 활성화하려면 CMakeLists.txt에서 CONFIG_POSIX을 활성화 해야함.
      - POSIX 기능 사용

         pthread_mutex_t를 사용하려면 pthread 라이브러리와 mutex를 포함하는 헤더 파일을 추가합니다.

     

    필요한 시점이 오면, 더 알아보도록 하자. 

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기