ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Суббота
23 ноября
541685
dimonomid (24.08.2014 15:18 - 15:34, просмотров: 6756)
Редкая проблема (и решение) в tnkernel PIC32 port от Alex.B. Вообще, проблема реально редкая и большинство из пользователей tnkernel pic32 port наверняка никогда с ней не сталкивались (например, я столкнулся впервые за пару лет работы с этим портом tnkernel); но тем не менее потенциальная проблема существует, так что напишу. Столкнулся с тем, что при определенных условиях выбранный с большим запасом стек задачи (обычно одной и той же задачи, но не всегда) переполнялся, и если посмотреть на переполненный стек задачи, то видно, что это - следствие многократного сохранения контекста (периодично повторяющиеся 34 слова с характерными значениями типа регистра статуса 0x00100003 и адресов в прог.памяти 0x9d......). Причем, эта "проблема" обратимая: как я уже сказал, размер стека был выбран с большим запасом: выделил 600 слов, а используется около 300, т.е. обычно указатель стека находится где-то посередине. Чтобы переполнить стек задачи, контекст должен быть сохранен около 10 раз. В процессе дебага выяснил: бывает, что контекст сохранен 5-7 раз, т.е. стек еще не переполнен; и после этого указатель стека возвращается в свое "рабочее состояние", т.е. где-то в середину стека. Следовательно, реальных косяков нет, логика не нарушена и все продолжает работать. Но почему-то он сохраняется несколько раз. Беру EPC (exception program counter), сохраненный в каждом таком фрейме, и вижу, что он указывает внуть функции tn_switch_context, туда, где контекст начинает восстанавливаться: обычно сразу за инструкцией ei или несколькими инструкциями позже. Итак, как это получается. АЦП в проекте настроен так, чтобы выдавать сэмплы с частотой около 40 КГц, после каждого готового сэмпла вызывается прерывание. Таким образом, оно вызывается каждые ~25 мкс. ADC ISR - т.н. "системное прерывание", - берет измеренное значение и отправляет сообщение в очередь. Отдельная задача (пусть task_adc) ждет сообщения из этой очереди и обрабатывает их. Имеем: задачу task_x с низким приоритетом, задачу task_adc с высоким приоритетом, системное прерывание isr_adc.
  • Выполняется задача task_x, происходит прерывание по АЦП. Т.к. это системное прерывание, в стеке task_x выделяется stack frame на 34 слова, и весь контекст сохраняется туда.
  • В ходе выполнения ISR, отправляется сообщение в task_adc, так что нужно передать ей управление, т.к. ее приоритет выше. Так что, когда ISR завершается, контекст task_x остается сохраненным в стеке, и task_adc получает управление (ее контекст восстанавливается)
  • task_adc обрабатывает сообщение и уходит в ожидание следующего сообщения. Нужно передать управление обратно task_x. Вызывается tn_context_switch, который сохраняет контекст task_adc в ее стек, запрещает прерывания, переключает текущую задачу на task_x (в т.ч. правит указатель стека), разрешает прерывания и начинает восстанавливать контекст task_x. И тут происходит следующее прерывание по АЦП (впрочем, оно могло произойти и чуть раньше - пока прерывания были запрещены)
  • Тут кстати нужно понимать, что указатель стека опускается только когда весь контекст был уже восстановлен, а не по ходу восстановления. Так что если даже прерывание произойдет в середине процедуры восстановления контекста, указатель стека указывает все еще на самую верхушку сохраненного контекста.
  • Так что, раз произошло прерывание, в стеке task_x выделяется новый stack frame на 34 слова, и все повторяется.
  • После нескольких итераций, стек задачи task_x переполнен. Таким образом, проблема появляется если есть периодически вызывающееся системное прерывание, которое может переключить контекст, и если так получилось, что задачи в системе переключаются туда-сюда с совпадающей периодичностью. Как я в самом начале говорил, штука редкая; но со мной она случилась. Как решить: самое простое - запретить прерывания на все время выполнения tn_switch_context, что я с удовольствием и сделал. Да, время реакции на события страдает, но пока устраивает, и стабильность важнее. Решение посимпатичнее: можно использовать core software interrupt для переключения контекста с приоритетом TN_INTERRUPT_LEVEL. Тогда более приоритетные (но и несистемные) прерывания смогут вытеснить переключение контекста, но т.к. они несистемные, то сами переключить контекст уже не смогут, и изложенная проблема будет устранена. (напомню: системные прерывания все должны иметь одинаковый приоритет) Еще существует другой порт TNKernel под pic32 от товарища andersm: https://github.com/andersm/TNKernel-PIC32 В нем вообще применено, на мой взгляд, интересное решение: для прерываний используется отдельный стек. Таким образом, в стеке каждой задачи не нужно резервировать место для работы прерываний: прерывания используют свой собственный стек. Если задач много и/или у вас есть прожорливые ISR, то память реально можно сэкономить. Более того, этот порт позволяет прерываниям быть вложенными. Недостаток использования отдельного стека для прерываний очевиден: если после выполнения ISR нужно передать управление другой задаче, то сначала все равно будет восстановлен контекст вытесненной задачи (из стека прерываний), и уже потом контекст будет сохранен в стек задачи и новая задача получит управление. Но зато этой проблемы с многократным сохранением контекста тоже не будет; так что такой ли уж это недостаток - х3. Но пока что мне не нравится как устроен этот порт: вместо сишного макроса tn_sys_interrupt(), который придумал Alex.B, там используются ассемблерные макросы; следовательно, нужно в отдельном файле держать список всех используемых прерываний. Очень не люблю, когда вещи нужно править в нескольких местах, по-любому когда-нибудь да забуду. Может быть, найду время и допилю его, чтобы было удобно пользоваться. Кстати, порт основан на более новой версии TNKernel 2.7, там вроде как поправлен косяк с ceiling мютексами ( http://www.microchip.su/showthread.php?t=15629 ). Еще там, кстати, отличается API касательно старта системы, создания задач и еще некоторых вещей, но на самом деле это Alex.B его изменил, а andersm оставил как в оригинальном tnkernel. Я считаю что менять API не есть очень хорошая идея, т.к. усложняется перенос проекта на другие платформы/порты, но что есть, то есть.