ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Пятница
29 марта
543133
dimonomid (29.08.2014 16:14 - 30.08.2014 21:41, просмотров: 1351)
TNKernel 2.7 PIC32 port без timer task, с отдельным стеком для прерываний, поддержкой вложенных прерываний, поддержкой shadow register set и рекурсивных мютексов. http://goo.gl/CrbMBV
Вообще, насколько я вижу, порт от Alex.B всех устраивает, так что этот мой опус должен мало кого заинтересовать, но на всякий случай напишу: допилил существующий TNKernel-PIC32 от Anders Montonen, реализовал в нем некоторые вещи, присутствующие в порте Алекса, также добавил (и продолжаю потихоньку добавлять) свои хотелки; пользуюсь - не нарадуюсь. Почти весь платформозависимый код (переключение контекста и т.д.) написал Anders Montonen, я только подробно изучил его и изменил некоторые вещи (например, tn_start_exe() почему-то переключал управление на tn_curr_run_task, а не на tn_next_task_to_run, это создавало определенные неудобства) и добавил Сишные макросы для объявления прерываний, вместо ассемблерных макросов. Теперь добавляю туда свои хотелки и потихоньку частично рефакторю (just for fun, т.к. мне не нравится как оформлен исходник TNKernel). Anders хочет оставить свой порт настолько близким к оригинальной TNKernel, насколько возможно; поэтому он не согласен вносить в него изменения. Так что я его форкнул, благодаря BSD лицензии. Большое спасибо товарищу Anders Montonen за его работу. Также, конечно, большое спасибо Алексу за его порт, которым я пользовался столько времени и который, несомненно, оказал влияние на этот мой форк. :) ------------------------------- Проект хостится на bitbucket: My TNKernel-PIC32 Отличия от порта Алекса: 1. Поддержка вложенных прерываний: не нужно делать все "системные" прерывания одного приоритета; 2. Поддержка shadow register set для высокоприоритетных прерываний; 3. Отдельный стек для прерываний. Из этого вытекает следующее:
  • Тратится меньше RAM (которой и так вечно мало). Например, если ISR требует, скажем, 64 слова стека, и в системе есть 5 задач, то в порте Алекса нужно добавить 64 слова к стеку каждой задачи: 320 слов. RAM wasting. В этом порте нужно выделить 64 слова только один раз (плюс размер контекста 23 слова);
  • В нормальном режиме работы невозможно попасть в ситуацию, когда контекст сохранен в стеке задачи дважды. В порте Алекса такое возможно , если так повезет, что системное прерывание произойдет в тот момент, когда tn_switch_context переключает контекст. Значит, нужно рассчитывать размер стека задачи таким образом, чтобы туда поместилось как минимум два сохраненных контекста, ведь прерывание по внешнему событию может произойти вообще когда угодно. Еще больше RAM wasting;
  • Прерывания сохраняют не весь контекст (32 или даже 34 слова, как в порте Алекса), а только 23 слова, т.к. регистры $s0-$s9 are "callee-saved", так что компилятор заботится о них сам. В порте Алекса мы не могли себе такого позволить, т.к. и прерывания, и tn_switch_context должны сохранять контекст совершенно одинаково, чтобы после выполнения ISR можно было сразу переключиться на другую задачу;
  • Если прерывание вытесняет низкоприоритетную задачу task_low и ISR отправляет сообщение в высокоприоритетную задачу task_high, то переключение контекста на task_high работает где-то в 1.8 раз медленнее чем в порте Алекса (если прерывание использует SRS, то в 1.5 раз медленнее), потому что сначала нужно восстановить контекст из стека прерываний обратно в task_low, потом сохранить ее контекст в ее стек, и только тогда восстановить контекст task_high из ее стека;
  • 4. Нет такого понятия как "системное" и "пользовательское" прерывания. Каждый ISR должен быть объявлен с использованием макроса tn_soft_isr (сохранение контекста в стек прерываний) или tn_srs_isr (переключение на shadow register set); 5. Core software interrupt 0 используется для переключения контекста, так что в своем проекте это прерывание использовать нельзя; 6. Добавлена опция чтобы собрать TNKernel с поддержкой рекурсивных мютексов, я давно хотел их там видеть. Хотя и спорная фича, но иногда очень удобная - как минимум, когда хочешь использовать какую-то стороннюю либу, которая как раз требует, чтобы мютексы допускали вложенность; 7. Нет сервисов tn_sys_enter_critical() / tn_sys_exit_critical(). Добавлять не планирую, т.к. они нужны нечасто и вместо них вполне можно использовать tn_cpu_int_disable() / tn_cpu_int_enable(). Но надо иметь в виду, что это не полный эквивалент Алексовским критическим секциям: например, из прерывания вызывать tn_sys_enter_critical() нельзя, а запретить/разрешить прерывания можно. 8. Последняя (на данный момент) TNKernel 2.7; как минимум, в ней нет проблем с ceiling мютексами, которые есть в порте Алекса . ---------------------- Итого, кратко: Недостатки
  • Если ISR пробуждает новую задачу, то переключение контекста в новую задачу работает медленнее;
  • Используется Core software interrupt 0, следовательно, его нельзя использовать в самом проекте.
  • Преимущества
  • Если после выполнения ISR нужно вернуться в предыдущую задачу, это работает быстрее, т.к. сохраняется не весь контекст, а только 23 слова. Если используется SRS, то еще быстрее;
  • Используется меньше RAM
  • Стек задач тратится более предсказуемо - контекст может быть сохранен в стек задачи только один раз, безо всяких "но";
  • Можно использовать вложенные прерывания
  • Можно использовать рекурсивные мютексы
  • TNKernel 2.7 с работающими ceiling мютексами
  • На bitbucket все расписано подробно, переписывать все сюда не буду. Но если вдруг кто-то реально захочет перенести свой проект на этот порт, то могу написать типа cheatsheet - список конкретных действий, необходимых для переноса. -------------------------------------------------------------- UPD: А что, на Сахаре нельзя "ответить" на свое сообщение, а можно только отредактировать старое? Хмм... Ну ладно. Редактирую: Избавился от timer task, в результате время обработки каждого системного тика сократилось в 2.7 раза, и уменьшилось использование RAM :) Timer task рулит очередью ожидающих задач tn_wait_timeout_list, т.е. пробуждает задачи, таймаут ожидания которых истек, но с отдельным стеком для прерываний гораздо лучше делать эту работу прямо в tn_tick_int_processing(), который вызывается из ISR. Наверное, в timer task может быть смысл если не использовать отдельный стек для прерываний (хотя тоже под вопросом, зависит от конкретного приложения). Но с отдельным стеком наличие лишней задачи вызывает значительный оверхед. Вот что происходит каждый системный тик, если используется timer task:
  • Текущий неполный контекст (23 слова) сохраняется в стек прерываний;
  • ISR вызывается: как минимум, tn_tick_int_processing();
  • tn_tick_int_processing() запрещает прерывания, управляет round-robin если надо, потом пробуждает timer task, устанавливает tn_next_task_to_run и разрешает прерывания;
  • Контекст восстанавливается из стека прерываний обратно в задачу, которая была прервана
  • Контекст переключается на новую задачу timer task
  • timer task запрещает прерывания, делает свою не такую уж большую работу, уводит себя в ожидание, разрешает прерывания и опять вызывает переключение контекста;
  • Только теперь задача, которая была прервана по системному таймеру, получает управление обратно.
  • Померил с помощью stopwatch в тестовом проекте с тремя задачами (idle, timer, моя задача) - на все это уходит 682 cycles. В порте Алекса работы поменьше (там будет два переключения контекста, а не два с половиной), но тоже немало. Если же обрабатывать tn_wait_timeout_list прямо в tn_tick_int_processing(), эта процедура значительно ускоряется: будет только одно сохранение-восстановление контекста, и то неполного. При тех же условиях на это тратится 251 cycles. Таким образом, память под стек timer task выделять не нужно, а пользовательский callback при старте системы вызывается из idle. На всякий случай, пока оставил себе опцию TN_USE_TIMER_TASK, но не думаю что она когда-нибудь будет нужна..