xSemaphoreTake не возобновит задачу после вызова xSemaphoreGive - PullRequest
0 голосов
/ 01 сентября 2018

Я хотел реализовать FreeRTOS Queue в C ++, чтобы иметь возможность использовать классы C ++ с очередью, что невозможно при стандартной реализации очередей FreeRTOS.

Класс выглядит так:

template <typename T> class os_queue
{
  public:
    explicit os_queue(unsigned size);

    ~os_queue();

    template <typename U> bool send(U &&u);

    T receive(TickType_t timeout);

  private:
    std::vector<T> m_queue;
    const unsigned m_size;
    SemaphoreHandle_t m_mux;
    SemaphoreHandle_t m_queue_num_elems_sem;
};

Конструктор и деструктор:

template <typename T> os_queue<T>::os_queue(unsigned size) : m_size(size)
{
    m_queue.reserve(size);
    m_mux = xSemaphoreCreateMutex();
    m_queue_num_elems_sem = xSemaphoreCreateCounting(size, 0);
}

template <typename T> os_queue<T>::~os_queue()
{
    vSemaphoreDelete(m_mux);
    vSemaphoreDelete(m_queue_num_elems_sem);
}

А методы send и receive реализованы таким образом:

template <typename T> template <typename U> bool os_queue<T>::send(U &&u)
{
    static_assert(is_equiv<T, U>::value, "The argument must be of the same type as a queue element");

    bool is_full = false;

    {
        os_lockguard guard(m_mux);
        if (m_queue.size() == m_size)
            is_full = true;
        else
            m_queue.emplace_back(std::forward<U>(u));
    }

    if (!is_full)
        assert(xSemaphoreGive(m_queue_num_elems_sem) == pdTRUE);

    return !is_full;
}

template <typename T> T os_queue<T>::receive(TickType_t timeout)
{
    if (xSemaphoreTake(m_queue_num_elems_sem, timeout) == pdFALSE)
        throw std::runtime_error("Timeout when receiving element from the queue");
    else
    {
        os_lockguard guard(m_mux);
        auto res = std::move(m_queue[m_queue.size() - 1]);
        m_queue.pop_back();
        return res;
    }
}

os_lockguard при построении использует мьютекс / семафор, используя xSemaphoreTake, а при уничтожении вызывает xSemaphoreGive, чтобы освободить его.

Счетный семафор используется для управления количеством элементов в очереди.

Я хотел протестировать это решение и использовал порт FreeRTOS для Linux .

Чтобы передать любую функцию в виде кода задачи, я создал такой класс:

class os_task
{
  public:
    template <typename TaskFuncType>
    os_task(TaskFuncType &&task_code, std::string name, unsigned short stack_size, UBaseType_t priority)
        : task_code(std::forward<TaskFuncType>(task_code))
    {
        if (xTaskCreate(
                [](void *p) {
                    auto os_task_p = static_cast<os_task *>(p);
                    os_task_p->task_code();
                    os_wait_indefinitely();
                },
                name.c_str(),
                stack_size,
                this,
                priority,
                &task_handle) != pdPASS)
            throw std::runtime_error("Could not create a task");
    }

    ~os_task() { vTaskDelete(task_handle); }

  private:
    TaskHandle_t task_handle;
    std::function<void(void)> task_code;
};

Используя этот класс, я хотел протестировать простой случай, когда есть один читатель и один писатель:

static void test_single_writer_single_reader()
{
    constexpr unsigned size = 16;
    os_queue<int> q(size);
    bool result_reader = false, result_writer = false;

    auto curr_task_handle = xTaskGetCurrentTaskHandle();
    os_task(std::bind(reader, std::ref(q), std::ref(result_reader), curr_task_handle), "reader", 128, 1);
    os_task(std::bind(writer, std::ref(q), std::ref(result_writer), curr_task_handle), "writer", 128, 1);

    try
    {
        wait_for_notifs(2);
    }
    catch (std::exception &e)
    {
        TEST_ASSERT_MESSAGE(false == true, e.what());
    }

    TEST_ASSERT(result_reader == true);
    TEST_ASSERT(result_writer == true);
}

Этот тест вызывается из задачи FreeRTOS.

Читатель и писатель используют очередь в качестве ссылки для вызова send и receive методов и логической переменной для сохранения результата операции где-либо:

static void reader(os_queue<int> &queue, bool &result, TaskHandle_t task_to_notify_at_end)
{
    int r;
    result = true;
    try
    {
        r = queue.receive(max_wait_time_ms);
    }
    catch (std::exception &e)
    {
        result = false;
    }
    xTaskNotifyGive(task_to_notify_at_end);
}

static void writer(os_queue<int> &queue, bool &result, TaskHandle_t task_to_notify_at_end)
{
    int v = 0;
    result = queue.send(v);
    xTaskNotifyGive(task_to_notify_at_end);
}

Наконец, wait_for_notifs используется для синхронизации задачи с читателем и писателем:

static void wait_for_notifs(BaseType_t notifs_num)
{
    while (notifs_num)
    {
        auto notifs = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        if (notifs)
            notifs_num -= notifs;
        else
            throw std::runtime_error("Timeout when waiting for notifications");
    }
}

Основная идея заключается в том, что я хочу проверить, может ли приложение хорошо обрабатывать гонки.

Проблема заключается в том, что когда читатель сначала выполняет и приостанавливается на xSemaphoreTask, тогда писатель записывает в очередь, так что он увеличивает счетчик семпахора, считыватель не возобновляет работу.

Разве это не должно работать? Тайм-аут в xSemaphoreTake существует для хорошей обработки таких ситуаций: когда семафор берется (его счет равен 0), функция ожидает и надеется, что другая задача может дать (увеличить счетчик) семафор.

Может ли это быть связано с реализацией или настройкой порта?

Я использую FreeRTOS в версии 9.0.0 с включенным вытеснением и разделением по времени.

1 Ответ

0 голосов
/ 04 сентября 2018

Проблема здесь:

os_task(std::bind(reader, std::ref(q), std::ref(result_reader), curr_task_handle), "reader", 128, 1);
os_task(std::bind(writer, std::ref(q), std::ref(result_writer), curr_task_handle), "writer", 128, 1);

Созданный объект удаляется сразу после его создания. Должно быть

os_task r(std::bind(reader, std::ref(q), std::ref(result_reader), curr_task_handle), "reader", 128, 1);
os_task w(std::bind(writer, std::ref(q), std::ref(result_writer), curr_task_handle), "writer", 128, 1);
...