- •Встроенные микропроцессорные системы
- •Оглавление
- •2. Программное обеспечение встроенных систем ……….
- •Введение
- •Модуль 1
- •Аппаратные средства встроенных систем
- •1.2. Элементы архитектуры процессоров встроенных систем
- •1.2.1. Множество команд
- •1.2.3.1. Адресное пространство
- •1.2.3.2. Порядок байт
- •1. 2.3.3. Когерентность памяти
- •1. 2.3.4. Защита памяти
- •1. 2. 4. Модель прерываний
- •1.2. 5.Модель управления памятью
- •1.2.5.1. Страничная организация памяти
- •1.2.5.2. Сегментация памяти
- •1.3. Типы процессоров
- •1.4. Формы параллелизма в процессорах
- •1.4.1. Конвейеризация
- •1.4.2. Параллелизм уровня команд
- •1.5.Технологии памяти
- •1.5.1. Оперативная память
- •1.5.1. 1. Статическое озу
- •1.5.2. Постоянное запоминающее устройство (rom)
- •1.6. Иерархия памяти
- •1.6.1. Распределение или карта памяти
- •1.6.2. Блокнотная и кэш память
- •1.6.2.1. Кэш-память прямого отображения
- •1.6.2.2. Ассоциативная по множеству кэш-память
- •1.6.2.3. Обновление кэш-памяти.
- •1.6.2.4. Протокол когерентности кэширования с обратной записью
- •1.7. Магистраль микропроцессорной системы
- •1.8. Базовые устройства ввода-вывода встроенных систем
- •1.8.1. Порты ввода-вывода общего назначения
- •1.7.2. Таймер-счетчик
- •1.8.3. Импульсно-кодовая модуляция.
- •1.8.4. Многоканальный аналого-цифровой преобразователь
- •1.9. Базовые последовательные интерфейсы ввода-вывода
- •1.9.2. Последовательный интерфейс spi
- •1.9.4.1. Введение в usb
- •1.9.4.2. Интерфейс Open Host Controller для usb
- •Вопросы для самоконтроля
- •Модуль 2
- •1.10. Язык проектирования аппаратуры vhdl
- •1.10.2. Введение в vhdl
- •1.10.2.1. Программирование на vhdl для моделирования и синтеза [19]
- •1.10.2.2. Entity и architecture
- •1.10.2.3. Операторы присваивание и process [19]
- •1.10.2.4. Цикл моделирования vhdl
- •1.10.2.5. Многозначная логика и стандарт ieee 1164
- •1.11. Проектирование устройств ввода-вывода и контроллеров
- •1.12. Интегрированная среда разработки аппаратных средств
- •Вопросы для самоконтроля
- •Модуль 3
- •2. Программное обеспечение встроенных систем
- •2.1 Модель вычислений
- •2.2 Автомат с конечным числом состояний
- •2.3. Асинхронный язык проектирования sdl
- •2.4. Синхронный язык проектирования Lustre
- •2.5. Многозадачность.
- •2.5.1. Язык программирования Си
- •2.5.2. Потоки
- •2.5.2.1. Реализация потоков
- •2.5.2.2. Взаимное исключение
- •2.5.2.3. Взаимная блокировка
- •2.5.2.4. Модели непротиворечивости памяти
- •2.5.2.5. Проблемы с потоками
- •2.5.3. Процессы и передача сообщений
- •2.6. Интегрированная среда разработки прикладного программного
- •2.6.2. Комплект программ Telelogic Tau sdl Suite
- •2.6.3. Средства разработки программного обеспечения
- •2.7.1. Моделирование, эмуляция и макетирование
- •2.7.2. Формальная верификация
- •2.7.3. Оценка производительности
- •2.7.3.1. Оценка wcet
- •2.7.3.2. Исчисление реального времени
- •1 2 3 E
- •2.7.4. Модели энергии и мощности
- •2.7.5. Тепловая модель
- •Вопросы для самоконтроля
- •Заключение
- •Задания
- •1. Конвейеризация
- •2. Иерархия памяти
- •3. Базовые устройства ввода-вывода встроенных систем
- •5. Многозадачность
- •6. Валидация и оценка проекта
- •Библиографический список
- •Встроенные микропроцессорные системы
2.5.2.1. Реализация потоков
Ядром реализации потоков является программа планировщик потоков, решающий какой поток выполнить следующим, когда процессор доступен для выполнения потока. Решение может быть основано на равноправии, когда даются одинаковые возможности всем активным потокам или временных ограничениях или измерении важных характеристик или приоритетах.
Первый ключевой вопрос как и когда вызывается планировщик.
Кооперативная многозадачность. Это простая техника не прерывает поток пока сам поток не вызовет определенную процедуру. Например, планировщик может вмешиваться всякий раз, как только работающим потоком вызывается из библиотеки некоторый сервис ОС. Каждый поток имеет собственный стек и когда процедура вызывается, адрес возврата проталкивается в стек потока. Если планировщик определяет, что текущий поток должен продолжить работу, запрошенный сервис завершается, и процедура нормально возвращается. Если вместо этого планировщик определяет, что поток должен быть приостановлен и следующий поток должен быть выбран для выполнения, тогда вместо возврата планировщик сохраняет указатель стека текущего потока и изменяет указатель стека на значение, соответствующее новому выбранному потоку. Затем планировщик из стека адрес возврата и запускает новый поток.
Главный недостаток кооперативной многозадачности состоит в том, что программа может выполняться очень долго без вызовов сервисов, когда бы могли стартовать другие потоки. Для коррекции этого большинство ОС включает подпрограмму обработки прерываний, которая запускается через фиксированный интервал времени. Это подпрограмма обслуживает системные часы, которые обеспечивают прикладные программы механизмом получения текущего времени дня и разрешают периодический вызов планировщика через прерывание таймера. Для ОС с системными часами период вызова обработчика прерываний системных часов является «мигом» (тик). Для версий Linux этот период варьируется от 1 до 10 ms.
Величина тика определяется балансировкой производительности в соотношении с требуемой временной точностью. Маленький тик означает, что функции планировщика выполняются чаще, что уменьшает общую производительность. Большой тик означает, что точность системных часов
грубая и что переключение задач происходит реже, а это может вызвать нарушение ограничений реального времени. Иногда тик диктуется приложением.
В дополнении к периодическим прерываниям и вызовам сервисов ОС планировщик может быть вызван, когда блокируется поток.
2.5.2.2. Взаимное исключение
Поток может быть приостановлен между двумя атомарными операциями для выполнения другого потока и/или обработки прерывания. Это делает максимально трудным рассуждение о взаимодействии между потоками.
Рассмотрим функцию addListene из примера 78. Предположим, что она вызывается больше чем в одном потоке. Что может произойти неправильно. Первое, два потока могут одновременно модифицировать список связей структуры данных, что может привести к искажению данных. На рис. 81 приведены результаты моделирования этого асинхронного взаимодействия потоков. Предположим, например, что поток 1 приостановлен перед выполнением оператора tail->listener = listener. Предположим, что пока поток 1 приостановлен другой поток 2 вызывает addListene.
Когда поток 1 снова получает управление, он начинает выполняться с оператора tail->listener = listener, но значение указателя tail уже изменено потоком 2. Оно больше не является величиной, вычисленной предыдущим оператором tail = tail->next, до приостановки потока 1. Анализ показывает, что это может закончиться случайным указателем на listener (случайное значение после выделения памяти функцией malloc) в элементе списка i+1. Второй слушатель, добавленный в список потоком 2, будет перезаписан потоком 1 и таким образом будет утрачен. Когда вызывается функция update, она пытается выполнить действия со случайным адресом listener?, что может окончиться ошибкой сегментации или еще хуже, выполнением случайного кода.
Рис. 81. Результат одновременного изменения связанного списка двумя потоками
Подобные проблемы известны под названием состояние гонок (состязания). Две одновременных части кода в предыдущем примере состязались за доступ к одному и тому же ресурсу и порядок, в котором они получали доступ, влиял на результат. Не все состязания являются такими плохими с катастрофическими последствиями как в примере. Один из путей предотвращения этого несчастья состоит в использовании замка для взаимного исключения или мутекса (mutex). В Pthreads мутексы реализуются созданием структуры, называемой pthread mutex. Например можно модифицировать функцию addListener следующим образом:
pthread_mutex_t
lock = PTHREAD_MUTEX_INITIALIZER; void
addListener(notifyProcedure*
listener) { pthread_mutex_lock(&lock); if
(head
== 0) { ... }
else
{ ... } pthread_mutex_unlock(&lock); }
В первой строке создается и инициализируется глобальная переменная lock.
В первой строке функции addListener берет замок. Принцип такой, что только один поток может владеть замком в каждый момент времени. Функция mutex_lock блокирует поток пока вызывающий поток не получит замок. Итак, когда addListener вызывается потоком и начинает выполняться, pthread mutex не возвращает замок, пока им владеет другой поток. Получив замок, вызывающий поток сохраняет его за собой. Функция pthread_mutex_unlock вызывается в конце для освобождения замка. В многопоточном программировании серьезной ошибкой является не освободить замок.
Замок взаимного исключения предотвращает одновременный доступ потоков или модификацию потоками разделяемого ресурса. Код между закрыванием и открыванием замка является критической секцией. В каждый момент времени только один поток может выполнять код критической секции. Программист может с необходимостью обеспечить, чтобы всякий доступ к разделяемому ресурсу был защищен аналогичным образом.
Функция update из примера на рис.78 не модифицирует список слушателей, она его только читает. Предположим, что поток A вызывает addListener и откладывается после выполнения оператора tail->next = malloc(sizeof(element_t)). Предположим, что пока A отложен другой поток B вызывает update с кодом:
element_t*
element = head; while
(element
!= 0) {
(*(element->listener))(newx); element
= element->next; }
Что случится при выполнении оператора element = tail->next? В этой точке поток B будет работать со случайным содержимым, полученным от malloc при работе потока A, вызывая функцию отсылаемую указателем element->listener. И снова это приведет к ошибке сегментации или того хуже.
Мутех, добавленный в предыдущем примере, не устраняет этих ошибок. Он не защищает поток A от перевода в состояние отложен. Таким образом, необходимо защитить все возможные доступы к структуре данных с помощью мутексов. Модифицируем update следующим образом:
void
update(int
newx)
{
x =
newx; //
оповещение слушателей.
pthread_mutex_lock(&lock); element_t*
element = head;
while
(element
!= 0) {
(*(element->listener))(newx); element
= element->next; }
pthread_mutex_unlock(&lock); }
Это защитит функцию update от чтения списка, пока не закончится его модификация другим потоком.