Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Сохранение контекста в прерываниях
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
gladov
Да простят меня модераторы, если вопрос избитый, но всезнающий поиск вразумительного ответа мне не дал. А проблема, имхо, достаточно часто встречающаяся:

Имеем некоторое прерывание, которое должно автономно обработать некоторый блок данных и по окончании работы сообщить об этом основному софту. Например, рассмотрим отправку пакета по УАРТ.
Основную массу действий (а именно, выборку очередного байта из памяти и его отправку) прерывание делает своими силами, но, вот беда: когда оно закончит, оно должно вызвать некоторую функцию, которая должна просигнализировать основной программе о выполнении задания. Вариант с глобальной переменной отпадает сразу, т.к. ситуация гипотетическая и сигналом в данном случае может служить семафор или еще какие-либо средство РТОС. Естественно компилятор, видя в прерывании вызов внешней, неизвестной ему функции, далает сохранение кучи регистров в стек. Однако, если отправляемый пакет состоит из 1000 байтов, то 999 раз контекст сохранится зря и только последний раз, когда работа завершена, действительно необходимо слить регистры в стек и вызвать функцию - сигнал. Я хочу, чтобы контекст сохранялся только 1 раз из 1000, когда уже ясно, что необходим вызов внешней функции.

Пока единственное решение, приходящее мне в голову, это писать код прерывания на асме и сохранять регистры вручную не на входе в прерывание, а в ветке проверки условия окончания задания. Но что-то мне подсказывает, что этот вопрос можно решить директивами/ключевыми словами/intrinsic функциями/другими расширениями языка, не прибегая к полному написанию кода прерывания на асме. Только вот пока такой вариант в голову не приходит sad.gif

Подскажите, плз, кто и как избавляется от подобного оверхеда?

PS: Интересует решение для компилятора ИАР.
prottoss
Цитата(gladov @ May 14 2008, 19:51) *
Не понятно, для какой платформы вопрос.
Цитата(gladov @ May 14 2008, 19:51) *
Основную массу действий (а именно, выборку очередного байта из памяти и его отправку) прерывание делает своими силами, но, вот беда: когда оно закончит, оно должно вызвать некоторую функцию, которая должна просигнализировать основной программе о выполнении задания.
Для AVR я делаю так:
1. Извлекаем из стека программ (CSTACK) адрес возврата.
2. Загружаем в CSTACK адрес вызываемой после прерывания функции.
3. Загружаем в CSTACK адрес возврата.
Код
CPU_IRQ_Epilog:
/* Restore return address */
pop  r16
pop  r17

ldi  r18, high(post_call>> 1)
ldi  r19, low(post_call >> 1)

push r19
push r18
push r17
push r16

ret;

Данная функция вызывается в конце обработчика прерывания.
SasaVitebsk
Цитата(gladov @ May 14 2008, 15:51) *
Подскажите, плз, кто и как избавляется от подобного оверхеда?


Для начала - я не вызываю никакой функции для приведенного примера. А действительно заканчиваю флагом/семафором. а в другой процедуре - анализирую флаг.

В одном случае (не РТОС) необходимо было после N одних прерываний выполнить другое прерывание. Для этого задействовал неиспользуемый таймер. Он постоянно считает так что переполнение уже обязательно возникает к моменту вызова. Я просто разрешаю прерывание, а в этом прерывании запрещаю его.

Для уменьшения оверхеда при вызове процедуры из прерывания (знающие люди пишут) необходимо чтобы вызываемая процедура находилась в том же файле где и обработчик прерывания. Хотя всётаки это и не рекомендуется делать.

Ещё один момент использую в IAR для уменьшения оверхеда.
Как обычно в прерывании используются volatile переменные. В связи с этим компилятор их обрабатывает без должной оптимизации. Поэтому иногда переприсваивание даст более грамотный код.
Например:
Код
#pragma    vector=USART_UDRE_vect                            // Прерывание на передачу по rs485
__interrupt    static void    Exeption(void)
{
  uint8_t    hBuf;
  
hBuf=HeadOutBuf;
  if(hBuf!=EndOutBuf)
  {
    UDR0 = OutBuf[hBuf];                                // Передаём    символ ответа
    hBuf++;
    if(hBuf==LENGTH_OUT_BUF) hBuf=0;
    HeadOutBuf=hBuf;
  }
  else
    UCSR0B = 0x98;                                        // Разрешить RXEN,TXEN,RXCIE чтобы закончить передачу
}
Палыч
Цитата(gladov @ May 14 2008, 14:51) *
Естественно компилятор, видя в прерывании вызов внешней, неизвестной ему функции, далает сохранение кучи регистров в стек.
PS: Интересует решение для компилятора ИАР.
Я использую компилятор ICC. Но, возможно, это справедливо и для ИАР...
Если вызов функции из процедуры обработки прерывания делать ассемблерной вставкой, то сохранения кучи регистров - нет. Естественно, что сохранять регистры используемые вызываемой функцией (ту самую "кучу") и восстанавливать их нужно также ассемблерными вставками, соответственно до и после вызова функции.
gladov
Цитата
Не понятно, для какой платформы вопрос.

Думал это очевидно, если задаю вопрос в ветке AVR.

Цитата
Для начала - я не вызываю никакой функции для приведенного примера. А действительно заканчиваю флагом/семафором. а в другой процедуре - анализирую флаг.

В одном случае (не РТОС) необходимо было после N одних прерываний выполнить другое прерывание. Для этого задействовал неиспользуемый таймер. Он постоянно считает так что переполнение уже обязательно возникает к моменту вызова. Я просто разрешаю прерывание, а в этом прерывании запрещаю его.

По-моему, это больший геморрой, нежели сделать асмовую вставку. И не потребуется использование доп. периферии в виде таймера или еще чего-то.

Цитата
Для уменьшения оверхеда при вызове процедуры из прерывания (знающие люди пишут) необходимо чтобы вызываемая процедура находилась в том же файле где и обработчик прерывания. Хотя всётаки это и не рекомендуется делать.

Это понятно, но в случае с использованием ОС разместить функцию возврата семафора в одном файле с собственным прерыванием практически нереально.

Цитата
Если вызов функции из процедуры обработки прерывания делать ассемблерной вставкой, то сохранения кучи регистров - нет. Естественно, что сохранять регистры используемые вызываемой функцией (ту самую "кучу") и восстанавливать их нужно также ассемблерными вставками, соответственно до и после вызова функции.

Да, такой вариант пока кажется мне наиболее приемлемым...
prottoss
Цитата(gladov @ May 14 2008, 20:56) *
Думал это очевидно, если задаю вопрос в ветке AVR.
Сорри, почему то показалось, что в ветке про IAR smile.gif

Добавлю, что post_call тоже написан на ассемблере, дабы избежать сохранения всего контекста. В данный момент использую все это в собственном вытесняющем планировщике задач
zltigo
Цитата(gladov @ May 14 2008, 13:51) *
Я хочу, чтобы контекст сохранялся только 1 раз из 1000, когда уже ясно, что необходим вызов внешней функции.


Цитата(SasaVitebsk @ May 14 2008, 14:15) *
Для уменьшения оверхеда при вызове процедуры из прерывания (знающие люди пишут) необходимо чтобы вызываемая процедура находилась в том же файле где и обработчик прерывания. Хотя всётаки это и не рекомендуется делать.

рекомендуется, рекомендуется и
#pragma inline=forced

Цитата(gladov @ May 14 2008, 14:56) *
Это понятно, но в случае с использованием ОС разместить функцию возврата семафора в одном файле с собственным прерыванием практически нереально.

Не морочьте себе голову - лишние регистры НЕ сохраняются и многократно "семафор" не вызывается. Проблемы?
defunct
У AVR есть возможность отделить прерывание непрерываной передачи байт от завершения передачи. Используйте прерывание Data Register Empty для отправки очередного байта и TX Complete для сигнализации о завершении передачи пакета.
prottoss
Цитата(gladov @ May 14 2008, 19:51) *
Например, рассмотрим отправку пакета по УАРТ. Основную массу действий (а именно, выборку очередного байта из памяти и его отправку) прерывание делает своими силами, но, вот беда: когда оно закончит, оно должно вызвать некоторую функцию, которая должна просигнализировать основной программе о выполнении задания.
Чет сразу не обратил внимания smile.gif , что вопрос про USART. А что Вам мешает устроить FIFO и вызывать функцию сигнализации только по переполнению буфера? Как раз и получится, если FIFO = 1000 байт - 1 раз из 1000. И только в том случае, если буфер действительно переполнится.

Похоже, дело просто в не ясном понимании задачи...
zltigo
Цитата(prottoss @ May 14 2008, 15:48) *
Похоже, дело просто в не ясном понимании задачи...

Естественно и в этом тоже - сначала неправильно глобально поставить задачу (1000 вызовов некой функции из одного прерывания!) а потом доблестно "c помощью ASM" и трюков ее решать sad.gif - к сожалению встречается очень часто sad.gif.
prottoss
может вот это поможет - комменты на русском. Драйвер выдран из другого проекта, но полностью функционален
AHTOXA
Цитата(zltigo @ May 14 2008, 19:52) *
Естественно и в этом тоже - сначала неправильно глобально поставить задачу (1000 вызовов некой функции из одного прерывания!) а потом доблестно "c помощью ASM" и трюков ее решать sad.gif - к сожалению встречается очень часто sad.gif.


имхо автор всё очень внятно объяснил:

Цитата(gladov @ May 14 2008, 17:51) *
компилятор, видя в прерывании вызов внешней, неизвестной ему функции, далает сохранение кучи регистров в стек. Однако, если отправляемый пакет состоит из 1000 байтов, то 999 раз контекст сохранится зря и только последний раз, когда работа завершена, действительно необходимо слить регистры в стек и вызвать функцию - сигнал.


Вызов функции происходит не каждое прерывание, а сохранение контекста - каждое.
zltigo
Цитата(AHTOXA @ May 14 2008, 20:10) *
Вызов функции происходит не каждое прерывание, а сохранение контекста - каждое.

Это уже проблемы конкретного компилятора/платформы/оптимизации. Радикальное решение уже приводил - inline.

P.S.
Посмотрел, что творится на тестике c вызовом подпрограммы из обработчика пркрывания - честно говоря ужас sad.gif. При этом для того-же ARM генерится много более разумный код. Явные проблемы у AVR с соглашениями о вызове функций - чрезмерное количество регистров отдается в безраздельное использование вызываемой функции. В результате реальные функции, в том числе и обработчик прерывания, их все и близко не использует и при вызове действительно жуткая картина.

z.
Дон Амброзио
Цитата(zltigo @ May 14 2008, 23:56) *
Это уже проблемы конкретного компилятора/платформы/оптимизации. Радикальное решение уже приводил - inline.

Проблема ещё в том, что нити бывают разные: с лёгким и тяжёлым контектстом... И сохранять весь контекст для облегчённой часто запусаемой нити крайне неэффективно. Тут надо подходить комплексно... И учитывать все аспекты проектируемой Вами RTOS
IgorKossak
Цитата(defunct @ May 14 2008, 16:35) *
У AVR есть возможность отделить прерывание непрерываной передачи байт от завершения передачи. Используйте прерывание Data Register Empty для отправки очередного байта и TX Complete для сигнализации о завершении передачи пакета.

В примере речь идёт о приёме пакета, а в этом случае прерывание как раз одно.
Если же подходить абстрактно, то можно поступить так, как это было реализовано в scmRTOS предыдущей версии, т. е. сделать прерывание без сохранения\восстановления контекста
Код
__raw __interrupt void my_interrupt_function()
, а в том или ином случае применять разные обёртки. Но на мой взгляд это достаточно трудно автоматизируемо и потребует тщательного изучения листингов, а иначе неприятных глюков не избежать.
MrYuran
моё глубоко закоренелое ИМХО - не надо в прерываниях вызывать никакие функции. Либо уж накрайняк inline и описывать в одном модуле.
А лучше всего всё-таки по приёму кинуть байт в буфер, выставить флаг и функцию запускать снаружи, тайм-аут можно засекать по переполнению таймера.
А создавать себе трудности и потом героически их преодолевать - это неотъемлемая черта русского менталитета...
zltigo
Цитата(MrYuran @ May 15 2008, 07:42) *
моё глубоко закоренелое ИМХО - не надо в прерываниях вызывать никакие функции.

Уточнение - для AVR - обязательно, если это только не очень "тяжелый обработчик" и без того использущий массу регистров. Рассуждения на эту тему добавил в свой предыдущий пост.
Палыч
Цитата(zltigo @ May 15 2008, 08:53) *
Уточнение - для AVR - обязательно, если это только не очень "тяжелый обработчик" и без того использущий массу регистров.
"Обязательно" - это слишком категорично. Бывают проекты в которых необходимо обрабатывать очень частые прерывания, большая часть которых не требует сложных действий (а, значит и ресурсов процессора - тех же регистров). Возникает желание сэканомить на сохранении-востановлении контекста... Если "тяжелую" часть обработчика выделить в отдельную функцию и при её вызове самому позаботиться о сохранении-восстановлении регистров, то можно получить неплохой результат. Вот так это можно сделать в ICC
Код
void func(void) // Функция, вызываемая из процедуры обработки прерывания
{
...............
}

#pragma interrupt_handler proc_interrupt:x
void proc_interrupt(void) // Процедура обработки прерывания
{
  ............
  if(...)
  {
    asm("xcall push_lset"); // Вызов стандартной функции сохранения регистров
    asm("xcall _func"); // Вызов функции void func(void)
    asm("xcall pop_lset"); // Вызов стандартной функции восстановления регистров
  }
  ............
}

при этом, при входе(выходе) в(из) процедуры обработки прерывания proc_interrupt транслятор генерит код, сохраняющий(востанавливающий) необходимый минимум регистров. Если, ассемблерные вставки заменить на с-ишный вызов функции func(); , то транслятор генерит код сохраняющий(востанавливающий) все регистры.

zltigo
Цитата(Палыч @ May 15 2008, 08:36) *
Если...

В данном случае идет речь о чисто сишных + расширения компилятора возможностях. О ASM и его смесях, естественно, разговор был-бы другой.
Rst7
Цитата(zltigo @ May 15 2008, 09:41) *
В данном случае идет речь о чисто сишных + расширения компилятора возможностях.


Вообщем, есть достаточно простой метод. Без асма, но надо проследить один момент (об этом чуть позже). Я обычно поступаю следующим образом

Код
//Флаг необходимости запуска тяжелой части
#define NeedRunPart2 GPIOR0_Bit0
//Флаг блокировки, если не нужно разрешать прерывания, можно убрать
#define Part2Lock GPIOR0_Bit1

#pragma diag_suppress=Ta006
__interrupt void Int_part1(void)
{
  //Легкая часть прерывания
  ....

  if (....) NeedRunPart2=1;
  ....
}

__interrupt void Int_part2(void)
{
  //Тут вызываем функции, извращаемся как хотим                                                                
....
....
....
}
#pragma diag_default=Ta006


#pragma vector=какой надо
__interrupt __raw void INT_dispatch(void)
{
  ((void(*)(void))Int_part1)();
  __disable_interrupt();
  if (!NeedRunPart2) return;
  if (Part2Lock) return;
  Part2Lock=1;
  NeedRunPart2=0;
  __enable_interrupt();
  ((void(*)(void))Int_part1)();
  __disable_interrupt();
  Part2Lock=0;
}


Главное тут - проверить, чтобы проверяемые и устанавливаемые флаги были в младших адресах пространства ввода-вывода, чтобы компилятор мог пользоваться SBIC/SBIS для проверки и SBI/CBI для манипуляций. Тогда можно не сохранять контекст в диспечере прерываний - эти комманды не портят SREG и другие регистры.

Вот такой код получается у диспечера:
Код
//   34 __interrupt __raw void INT_dispatch(void)
INT_dispatch:
//   35 {
//   36   ((void(*)(void))Int_part1)();
        RCALL   Int_part1
//   37   __disable_interrupt();
        CLI
//   38   if (!NeedRunPart2) return;
        SBIS    0x1E, 0x00
        RJMP    ??INT_dispatch_0
//   39   if (Part2Lock) return;
        SBIC    0x1E, 0x01
        RJMP    ??INT_dispatch_0
//   40   Part2Lock=1;
        SBI     0x1E, 0x01
//   41   NeedRunPart2=0;
        CBI     0x1E, 0x00
//   42   __enable_interrupt();
        SEI
//   43   ((void(*)(void))Int_part1)();
        RCALL   Int_part1
//   44   __disable_interrupt();
        CLI
//   45   Part2Lock=0;
        CBI     0x1E, 0x01
//   46 }
??INT_dispatch_0:
        RETI


Кстати, приведенный код разрешает вложенные прерывания, для выполнения быстрой части пока выполняется медленная часть. Если этого не нужно, можно упростить.

И на посошок, не забыть запретить варнинг линкеру, что производится вызов обработчика прерывания как фунцкции.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.