‮Сдвиг по фазе (kincajou) wrote,
‮Сдвиг по фазе
kincajou

Category:

worklog: FreeRTOS; придумал хероту.

Осваиваю потихоньку премудрости риальне систем.

Многие вещи делаются совсем не так, как в более привычных системах (вернее, в их отсутствии -- никогда раньше никакие РТОСы не использовал).

Возникла вполне практическая задачка

1) Допустим, есть некая железяка, которую все хотят использовать. В одну единицу времени железяка может обслуживать ровно один запрос. Если запросов больше одного, то каждый следующий должен подождать выполнения предыдущего -- это очень обычная ситуация, встречается сплошь и рядом.

2) Допустим, есть как минимум два процесса, которые хотят иметь доступ к этой железяке. И ещё предположим, что нет требования безусловной строгости выполнения запроса на доступ ровно в тот момент, когда зачесалось. И это, опять же, естественное ограничение - например, если мы подчинимся требованию "СРОЧНО ПРЕДОСТАВИТЬ" в случае какого-нибудь последовательного порта, то на его выходе будет каша из символов, переданных одновременно двумя процессами. Т.е. нужна некоторая более крупногабаритная гранулярность, неделимая порция данных. И пока одна порция выводится, второй процесс (в самом простейшем случае) должен подождать. Тут, канешн, могут быть разные другие тонкости - бывает так, что нам вообще не важен результат выполнения запроса, а значит, ждать вовсе не нужно его выполнения. Например, если мы просто вываливаем строчки на экран, то их достаточно напихать в какой-нибудь отдельный буфер, из которого они будут постепенно изыматься по мере сил. Но если результат выполнения операции безусловно важен для процесса, запросившего её, то надо терпеливо дожидаться завершения.

Как одно соединить с другим? Придумал вот что.
Отдельный процесс, который имеет абсолютный доступ к этой железяке - и больше никто. Назовём его "СЕРВЕР". Он получает запросы, обрабатывает их и шлёт ответы.
И пара процессов, каждому из которых позарез нужно к этой железяке обратиться и что-то от неё специфическое получить. Назовём их "КЛИЕНТ1" и "КЛИЕНТ2".

Предположим, что эта железяка - это лампочка.
Первый клиент каждую секунду хочет включить её ровно на так же одну секунду и чтобы ему оттудова пришла буква "А". А второй клиент хочет, чтобы лампочка включилась на 100 миллисекунд, причём чтобы это происходило так же каждые 100 миллисекунд, но он ждёт, что ему придёт буква "Б".

Делаем вот что:
1) выделяем по бинарному семафору на каждый из клиентов:
	xSema1 = xSemaphoreCreateBinary();
	xSema2 = xSemaphoreCreateBinary();


2) создаём очередь из структур такого вида:
typedef struct {
	SemaphoreHandle_t semaphore; /* какой семафор надо будет дёрнуть после обработки данных */
	uint8_t		Request;
	uint8_t		Response;
} req_t;
....
	xQueue = xQueueCreate(10, sizeof(req_t**));

Семафор внутри такого объекта нужен для того, чтобы сервер мог разблокировать именно тот процесс, который прислал этот конкретный запрос. Я не могу пока придумать более изящный способ (почти наверняка есть что-то готовое, а я просто тупоголовый нуб).

3) внутри каждого из клиентских процессов заводим своё собственное казино, в первом клиенте так:
	req_t req;
	req_t *req_p = &req;
	req.semaphore = xSema1; //  используем семафор 1
	req.Request = '1';

и во втором, аналогично:
	req_t req;
	req_t *req_p = &req;
	req.semaphore = xSema2; //  используем семафор 2
	req.Request = '2';

Указатель на указатель нужен для того, чтобы выстрелить себе в ногу обойти особенность работы функций записи/чтения из очереди: они КОПИРУЮТ данные. Т.е. когда мы из клиента пишем в очередь, то в неё заносится копия подсунутых ей данных. Если мы запишем в очередь копию структуры, то, когда на стороне сервера мы извлечём их из очереди (и там они тоже копируются, что характерно) и начнём как-то менять, клиент об этом не узнает - ведь изменяется копия его данных (даже копия копии). Поэтому в очередь надо заносить указатель на указатель. Таким образом, клиент извлечён указатель на указатель, разыменует его и получит уже указатель на ту самую структуру клиента, с полями которой можно работать.

С точки зрения всяких там секьюрностей и безопасностей это ужасно - глобальные переменные и обнажённые указатели на приватные данные, но если существует какой-то другой способ, то я о нём пока что не знаю.

Каждый из клиентов делает нечто такое:
		xQueueSend(xQueue, &req_p, portMAX_DELAY); //  записываем наш запрос в очередь
		xSemaphoreTake(xSema2, portMAX_DELAY); // берём семафор. Блокируемся здесь, пока идёт обработка!
		vTaskDelay(100); /* задержка в тиках ОС = 100мс*/


Итак, сервер заводит себе переменную типа "указатель на структуру" и сидит ждёт, пока в очереди не появится очередной указатель на указатель на структуру, всё просто:
req_t *req_p;
...
xQueueReceive (xQueue, &req_p, portMAX_DELAY); 
switch (req_p->Request) {
...
}

Так же сервер мигает лампочкой на специфичный для каждого типа запроса период времени. Оттуда же он вытаскивает семафоры и отдаёт их:
xSemaphoreGive (req_p->semaphore); /* отдаём семафор */

..позволяя клиентам продолжать свою важную работу.

Скомпилировал, запустил. Вижу, что лампочка включается на 1 секунду, затем моргает 5 раз (вкл на 100 мс/выкл на 100 мс), затем снова включается на 1 секунду. Как и ожидалось -- клиенты ничего не знают о существовании друг друга, но эдак вот синхронизируются через общий сервер.

А зачем такая кутерьма? А затем, что модуль, способный обслуживать только один запрос за раз, это типичное периферийное устройство типичного микроконтроллера, исключения из этого правила очень редкие, их почти что и нет вообще. Т.е. я щас накидал хоть и корявенький, но вполне рабочий скелет-драйвер для 99% всех железяк в моём любимом STM32F746.
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 11 comments