Научная статья на тему 'FreeRTOS - операционная система для микроконтроллеров'

FreeRTOS - операционная система для микроконтроллеров Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
503
137
i Надоели баннеры? Вы всегда можете отключить рекламу.

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Курниц Андрей

Мы продолжаем изучение FreeRTOS операционной системы, создан ной специально для микроконтроллеров. В этой статье речь пойдет о поддержке модуля защиты памяти (MPU), который представлен в некоторых микроконтроллерах с ядром ARM Cortex-M3. Показано, как аппаратно предотвратить несанкционированный доступ к памяти, которая принадлежит ядру FreeRTOS, со стороны прикладных задач, а также как предотвратить влияние одной задачи на память, принадлежащую другой задаче. Описаны специфические API-функции и типы данных, предназначенные для работы с модулем MPU.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

Текст научной работы на тему «FreeRTOS - операционная система для микроконтроллеров»

Андрей КУРНИЦ

kurnits@stim.by

FreeRTOS —

операционная система для микроконтроллеров

Мы продолжаем изучение FreeRTOS — операционной системы, созданной специально для микроконтроллеров. В этой статье речь пойдет о поддержке модуля защиты памяти (MPU), который представлен в некоторых микроконтроллерах с ядром ARM Cortex-M3. Показано, как аппаратно предотвратить несанкционированный доступ к памяти, которая принадлежит ядру FreeRTOS, со стороны прикладных задач, а также как предотвратить влияние одной задачи на память, принадлежащую другой задаче. Описаны специфические API-функции и типы данных, предназначенные для работы с модулем MPU.

Особенности ядра ARM Cortex-M3

Семейство ARM Cortex-M3 — новое поколение 32-битных процессоров, основная черта которых — жесткий промышленный стандарт архитектуры ядра. Кроме того, для семейства ARM Cortex-M3 характерны такие черты, как высокое быстродействие, сравнительно малый объем кода и эффективное энергопотребление.

Несмотря на то что Cortex-M3 разрабатывалось как недорогое ядро, в нем присутствуют возможности, предназначенные для поддержки операционных систем реального времени (ОСРВ):

1. В зависимости от возможности доступа к системным ресурсам ядро Cortex-M3 может работать в одном из двух режимов:

• привилегированный режим;

• режим пользователя.

В привилегированном режиме функциональность микроконтроллера не ограничена, этот режим предназначен для выполнения ядра (планировщика) ОСРВ.

В режиме пользователя запрещено (невозможно) выполнение некоторых ассемблерных инструкций, таким образом, ограничен доступ к некоторым системным регистрам ядра Cortex-M3. Этот режим предназначен для выполнения прикладных задач.

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

2. Регистр указателя стека выполнен в виде банка из двух физических регистров:

• Главный указатель стека (Main Stack Pointer) — используется ядром ОСРВ и обработчиками прерываний.

• Указатель стека процесса (Process Stack Pointer) — используется прикладной задачей.

Такая организация упрощает передачу управления от прикладной задачи к ядру ОСРВ и обратно.

3. 24-битный автоматически перезагружаемый таймер, предназначенный для использования ядром ОСРВ для генерации квантов системного времени.

4. Модуль аппаратной защиты памяти (Memory Protection Unit, MPU) позволяет разделить все адресное пространство микроконтроллера (то есть и код, и данные, и регистры специальных функций) на отдельные регионы и назначить определенные права доступа к каждому из них. Использование модуля MPU позволяет повысить надежность системы, а именно:

• Предотвращает воздействие прикладных задач на код и данные ОСРВ.

• Разделяет данные одной задачи от другой, предотвращая воздействие одной задачи на данные другой задачи.

• Позволяет присвоить региону памяти атрибут «только для чтения», таким образом, жизненно важные данные могут быть защищены от случайного изменения.

• Позволяет выявить непредвиденное обращение к памяти за пределами допустимых адресов, например зафиксировать факт переполнения стека или обращение за границы массива.

Спецификация архитектуры ARM Cortex-M лишь допускает наличие в микроконтроллере модуля аппаратной защиты памяти. Решение включить или исключить модуль MPU из конкретного микроконтроллера принимает производитель. Наличие модуля

MPU и его особенности можно выяснить в предоставляемой производителем технической документации на конкретный микроконтроллер. На сегодня микроконтроллеры с архитектурой ARM Cortex-M выпускают более 40 производителей [2].

Например, микроконтроллеры ARM Cortex-M3 фирмы ST Microelectronics семейства STM32x10x не содержат модуль защиты памяти. Появляется он начиная с семейства STM32x20x [3]. Компания Toshiba также не устанавливает модуль защиты памяти в свои микроконтроллеры (семейство TX03) [4]. Наоборот, компания Texas Instruments встраивает модуль защиты памяти во все микроконтроллеры семейства Stellaris [5]. В микроконтроллерах ARM Cortex-M3 производства NXP Semiconductors модуль защиты памяти отсутствует в семействе NXP1300, но появляется в семействах NXP1700 и NXP1800 [6].

Карта памяти микроконтроллеров ARM Cortex-M3

Ядро Cortex-M3 является стандартизованным микроконтроллерным ядром, и поэтому его карта памяти четко расписана. Несмотря на использование нескольких внутренних шин, с точки зрения программиста архитектура ARM Cortex-M3 является фон-неймановской: все адресное пространство линейно и имеет размер 4 Гбайт (рис. 1).

Благодаря именно такой организации адресного пространства модуль MPU может с равным успехом предотвратить случайный доступ:

• к памяти программ;

• к памяти данных;

• к регистрам периферии.

Ox FFFF FFFF

ОхЕОЮОООО Ox EOOF FFFF Ох ЕООО 0000 Ox DFFF FFFF

ОхАООООООО Ox 9FFF FFFF

0x6000 0000 Ox 5FFF FFFF

0x4000 0000 Ox 3FFF FFFF

0x2000 0000 0x1 FFFF FFFF

Ox 0000 0000

рис. 1. Карта памяти микроконтроллеров ARM Cortex-M3

Поддержка модуля MPU во FreeRTOS

Архитектура ARM Cortex-M3 входит в список архитектур, на которые была портирова-на FreeRTOS [8].

Однако, как было сказано выше, некоторые микроконтроллеры ARM Cortex-M3 не содержат модуль MPU, поэтому для микроконтроллеров ARM Cortex-M3 существует два порта FreeRTOS: с поддержкой модуля MPU и без нее.

Порт FreeRTOS с поддержкой модуля MPU (в дальнейшем FreeRTOS-MPU) позволяет создавать задачи, работающие как в режиме пользователя, так и в привилегированном режиме. Таким образом, доступ к оперативной и Flash-памяти, а также к периферии микроконтроллера можно ограничивать от задачи к задаче. Кроме того, код и данные самой FreeRTOS защищены от случайного изменения прикладными задачами.

Для микроконтроллеров без модуля MPU следует использовать порт FreeRTOS без поддержки MPU. При этом все задачи (и код ядра FreeRTOS) будут выполняться в привилегированном режиме и иметь доступ ко всему адресному пространству микроконтроллера. Таким образом, любая задача теоретически может изменить память, принадлежащую другой задаче или ядру FreeRTOS. В этом случае обязанность предотвратить это целиком и полностью ложится на программиста.

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

Особенности использования порта FreeRTOS-MPU

Непосредственное программирование модуля MPU даже с использованием библиотеки CMSIS — это низкоуровневое программирование, связанное с заданием «вручную» значений 11 регистров модуля MPU.

FreeRTOS-MPU предоставляет удобный интерфейс обращения к модулю защиты памяти, скрывая от программиста непосредственные обращения к регистрам. Однако таким образом приложение теряет право на полный доступ ко всем регистрам микроконтроллера.

Использование FreeRTOS-MPU гарантирует:

• защиту кода ядра от ошибочного воздействия задач;

• защиту данных ядра от ошибочного воздействия задач;

• защиту конфигурации системной периферии ARM Cortex-M3, например конфигурации таймера SysTick;

• отслеживание всех ситуаций переполнения стека во время выполнения задачи;

• изоляцию задач друг от друга — каждая задача имеет свою область памяти.

В случае если модуль MPU зафиксировал нарушение прав доступа к памяти (произошла ошибка защиты памяти), то выполнение текущей программы прерывается, защищенная память остается нетронутой и автоматически происходит вызов прерывания MemManage.

Следует отметить, что как порт FreeRTOS-MPU, так и порт FreeRTOS для ARM Cortex-M3 без поддержки модуля MPU используют некоторые прерывания (exceptions) ядра ARM Cortex-M3, которые программист не может использовать в прикладной задаче:

1. Прерывание от 24-разрядного системного таймера SysTick.

2. Программное прерывание, вызванное инструкцией SVC.

3. Отложенный запрос системного сервиса РendSV.

режим пользователя и привилегированный режим

Микроконтроллер ARM Cortex-M3 может выполнять инструкции или в привилегированном режиме, или в режиме пользователя (непривилегированном режиме).

Микроконтроллер автоматически переходит в привилегированный режим, когда выполняет обработчик прерывания. Также ядро FreeRTOS принудительно переводит микроконтроллер в привилегированный режим всякий раз, когда выполняется код ядра или API-функция, и возвращает в прежний режим по завершении выполнения API-функции. Задачи же могут выполняться как в режиме пользователя, так и в привилегированном режиме.

Задачи, которые выполняются в привилегированном режиме, не ограничены в доступе к периферии микроконтроллера и могут вызывать любые инструкции ARM Cortex-M3. По умолчанию им доступно все адресное пространство микроконтроллера. Однако права доступа к связанным с задачей регионам памяти могут быть установлены так, чтобы задача в привилегированном режиме не могла выполнять определенные операции с данными регионами памяти, например запись в регион памяти, который имеет атрибут «только для чтения».

Задачи, которые выполняются в режиме пользователя, ограничены в доступе к некоторой части периферии и в вызове некоторых инструкций процессора. Например, задача в режиме пользователя не может изменять регистры контроллера прерываний NVIC (системная периферия) и вызывать инструкцию CPS, меняющую состояние процессора. По умолчанию задача в режиме пользователя не имеет доступа к оперативной памяти за пределами своего стека (благодаря чему достигается изоляция задач друг от друга и от ядра FreeRTOS). Однако права доступа к связанным с задачей регионам памяти могут быть установлены так, чтобы разрешить доступ задаче в режиме пользователя к тем или иным областям памяти.

Режим работы задачи задается при ее создании. В ходе своего выполнения задача может перейти из привилегированного режима в режим пользователя, однако обратный переход невозможен.

Рекомендации по выбору режима работы задачи

Так как задачи в привилегированном режиме потенциально могут изменить память, принадлежащую ядру FreeRTOS или другим задачам, то рекомендуется по возможности не создавать задачи в привилегированном режиме или как можно быстрее после их создания переводить их в режим пользователя (обратный переход невозможен).

Для эффективного использования преимуществ защиты памяти советуем по возможности все задачи в программе создавать в режиме пользователя.

регионы памяти

Модуль MPU позволяет разделить все адресное пространство микроконтроллера на несколько регионов.

Регион памяти — это область, заданная своим адресом начала и размером, допустимые операции с которой ограничены правами доступа. Допустимые операции для региона памяти зависят от режима, в котором в данный момент находится ядро микроконтроллера, и от атрибутов прав доступа, назначенных региону в момент его конфигурирования.

Спецификация семейства ARM Cortex-M (куда входят M0, M1, M3, M4) допускает под-

Специфическая область производителя 511 Мбайт

Локальная шина периферии 1 Мбайт

Регистры внешней периферии 1 Гбайт

Внешняя память данных (ОЗУ) 1 Гбайт

Регистры встроенной периферии 0,5 Гбайт

Память данных (ОЗУ) на кристалле 0,5 Гбайт

Исполняемый код (Р1азИ-память) 0,5 Гбайт

держку модулем MPU до 256 отдельных регионов памяти [1]. Для микроконтроллеров ARM Cortex-M3 количество регионов сужается до значения 8 плюс один фоновый регион [8]. Это означает, что в любой момент времени максимум восемь регионов памяти может быть определено, но при переключении на другую задачу регионы могут быть легко переконфигурированы для защиты данных уже другой задачи. Нумерация регионов производится от 0 до 7.

Конфигурируя модуль MPU, можно задать до восьми регионов, указав для каждого из них следующие параметры:

• адрес начала региона;

• длину или размер региона;

• права доступа к региону.

Права доступа к региону памяти могут разрешать:

1. Все действия с памятью.

2. Только чтение из региона из любого режима микроконтроллера.

3. Только чтение и только из привилегированного режима.

4. Любые операции только из привилегированного режима.

5. Все действия с памятью, кроме исполнения кода, расположенного в этой памяти. Допускается задать региону любую комбинацию привилегий из этого списка.

Взаимное наложение регионов памяти

Особенностью модуля MPU является то, что в случае, если регионы памяти сконфигурированы так, что диапазоны адресов двух и более регионов накладываются друг на дру-

Регион 2 Только чтение

Область наложения регионов. Права доступа — как у региона с большим номером: только чтение.

Регион 1 Чтение/запись

Рис. 2. Эффект наложения регионов памяти

га (рис. 2), то доступ к области пересечения определяется правами доступа, заданными для пересекающегося региона с наибольшим номером.

Например, если одна и та же область памяти относится к региону 1 с правами доступа «чтение-запись» и к региону 2 с правами доступа «только чтение», то в итоге доступ к этой области памяти будет определяться правами «только чтение», потому что регион 2 имеет больший номер (а значит, и приоритет), чем регион 1.

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

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

Фоновый регион памяти

Кроме восьми «обычных» регионов существует еще один регион с номером -1, называемый фоновым (background), для которого правила использования сильно отличаются. Во-первых, фоновый регион занимает все адресное пространство микроконтроллера, и изменить его расположение невозможно. Во-вторых, фоновый регион допускает только два варианта настройки:

1. Любое обращение к памяти фонового региона из любого режима микроконтроллера вызывает ошибку защиты памяти.

2. Любое обращение к памяти фонового региона из режима пользователя вызывает ошибку защиты памяти.

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

Предопределенные регионы FreeRTOS

Хотя модуль MPU в микроконтроллерах ARM Cortex-M3 поддерживает восемь регионов памяти плюс один фоновый регион, но в среде FreeRTOS не все регионы доступны для использования в прикладной задаче. Регионы с номерами от 0 до 4 включительно и фоновый регион являются предопределенными и используются ядром FreeRTOS.

Список предопределенных регионов: 1. Фоновый регион покрывает всю карту памяти микроконтроллера и конфигурируется ядром так, чтобы все адресное пространство было доступно только из привилегированного режима. Однако номер фонового региона принимается равным -1, поэтому в соответствии с правилом наложения регионов любой определенный регион с номером 0-7 переопределяет права доступа. Поэтому память, которая не подпадает ни под один предопределенный ре-

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

2. Регион 0 охватывает всю Flash-память и определяет к ней доступ «только для чтения» как из привилегированного режима, так и из режима пользователя.

3. Регион 1 охватывает верхние 16 кбайт Flash-памяти и задает к ней доступ «только для чтения» и только из привилегированного режима. В этом регионе размещается код ядра FreeRTOS.

4. Регион 2 защищает верхние 256 байт оперативной памяти на кристалле. Здесь ядро FreeRTOS размещает свои данные, поэтому этот регион памяти доступен для чтения и записи только из привилегированного режима.

5. Регион 3 настроен на адреса несистемной периферии и запрещает выполнение кода по адресам расположения регистров периферии. Таким образом, несистемная периферия становится доступной в режиме пользователя. Системная же периферия автоматически становится недоступной при переходе микроконтроллера в режим пользователя.

6. Каждый раз при переключении на следующую задачу ядро FreeRTOS конфигурирует регион 4 для защиты области памяти, где размещен стек данной задачи.

Таким образом, задача, выполняющаяся в режиме пользователя, изначально имеет доступ только к своему стеку, однако вся остальная память микроконтроллера доступна задаче в привилегированном режиме.

Область Flash-памяти программ, в которой располагается код ядра, и системная периферия доступны только в привилегированном режиме.

Остальная часть Flash-памяти за пределами кода ядра FreeRTOS и несистемная периферия (например, USART) доступны как в привилегированном режиме, так и в режиме пользователя.

Регионы памяти, связанные с задачей

Оставшиеся три региона с номерами 5-7 могут быть использованы в задаче. В момент создания к задаче могут быть «привязаны» от 0 до трех регионов памяти. Ядро FreeRTOS запоминает настройки трех связанных с задачей регионов памяти для каждой задачи в программе, поэтому при переключении с задачи на задачу связанные с ней регионы памяти автоматически перенастраиваются.

После создания задача может переназначить связанные с ней регионы памяти с помощью явного вызова API-функции.

Связанные с задачей регионы памяти имеют номера больше, чем регионы, используемые ядром, поэтому они могут «перекрыть» предопределенные регионы, используемые ядром (эффект наложения регионов памяти).

Ограничения на размер и адрес начала региона памяти

Особенностью модуля MPU являются два правила относительно адреса начала региона памяти и его размера:

1. Размер каждого региона задается пятью битами регистра MPU Region Attribute and Size Register, причем несколько комбинаций этих бит зарезервированы. Таким образом, размер региона может быть выбран из ряда 28 предопределенных значений. Ряд начинается со значения 32 байт, каждое следующее значение размера региона равно предыдущему, умноженному на 2: 32, 64, 128, 256 байт, ..., 1 кбайт, ..., 1 Мбайт, ..., 4 Гбайт.

2. Адрес начала региона должен быть кратен размеру региона. Например, если размер региона задан равным 256 байт, или 0x100 в шестнадцатеричной системе счисления, то адрес начала региона может принимать значения 0, 256, 512, 768 и т. д. (в шестнадцатеричной системе 0x0, 0x100, 0x200, 0x300 и т. д.). В этом случае говорят, что начало региона выровнено по границе 256 байт. Правильные и ошибочные примеры создания регионов памяти

показаны на рис. 3.

Поддержка субрегионов памяти

Анализируя правила конфигурирования регионов памяти, можно прийти к выводу, что они слишком жесткие и не позволяют эффективно разбивать на регионы ограниченный объем внутреннего ОЗУ микроконтроллеров. Однако модуль MPU содержит средство, призванное сделать разбиение памяти на регионы более гибким, — субрегионы памяти.

Модуль MPU позволяет разделить каждый регион размером 256 байт и больше на восемь одинаковых субрегионов. Каждый субрегион в отдельности может быть отключен. Это значит, что на память, входящую в отключенный субрегион, не будут распространяться права доступа, определенные данным регионом. Таким образом, субрегионы позволяют более гибко настраивать адрес начала и адрес конца региона памяти. Пример использования субрегионов приведен на рис. 4, где реализованы два региона памяти, причем регион с большим номером размером 256 байт не переопределил права доступа региона с меньшим номером, а вместо этого благодаря отключению двух младших субрегионов региона 2 его размер стал равен 0xC0 (192) байт, адрес начала 0x40 (64).

Однако в текущей версии FreeRTOS v7.1.0 возможность конфигурации субрегионов через API-функции отсутствует. Программист может использовать субрегионы только путем записи в регистры модуля MPU напрямую. Таким образом, на данный момент FreeRTOS-MPU не поддерживает все возможности модуля MPU, поддержка субрегионов памяти, возможно, появится в следующих версиях FreeRTOS.

Неправильно определенный регион. Размер не равен степени числа 2.

0х 0000 7А12

Неправильно определенный регион. Адрес начала не кратен размеру региона.

0х 0000 0А35

Ох 0000 0280

0x0000 0100

Ох 0000 0000

рис. 3. Правила конфигурирования регионов памяти

Расширения языка Си

Стандарт языка программирования Си [12] не поддерживает возможности задавать какие-либо правила размещения переменных в памяти. Это происходит автоматически, без участия программиста. Поэтому для размещения переменной так, чтобы ее представление в памяти соответствовало правилам определения региона памяти, необходимо применять расширения языка Си.

Большинство компиляторов языка Си поддерживают расширения языка, позволяющие задать значение выравнивания при размещении переменной или массива. Выравнивание в данном случае означает, что переменная или массив будут размещены в памяти по адресу, кратному заданному значению выравнивания. Например, при значении выравнивания 128 переменная (или первый элемент массива) будет размещена по адресу 0, 128, 256, 384 и т. д.

Регион памяти размером 250 байт

Регион памяти размером 256 байт (0x100 байт)

Регион памяти размером 128 байт (0x80 байт)

Регион памяти размером 256 байт (0x100 байт)

Регион памяти размером 256 байт (0x100 байт)

Регион 1

0x40

0x00

Регион 2 с субрегионами

32 байт

Отключенный субрегион

Отключенный субрегион

Регион 2 с отключенными субрегионами

Регион 1

OxFF

0x40 0x3F

0x00

рис. 4. Использование субрегионов для более гибкой конфигурации регионов

FirstArray[1050]

[255]

[26]

SecondArray

[1]

[0]

[1023]

[1]

FirstArray

Регион 2

Регион 1

FirstArray[1050]

[256] [255]

SecondArray

[1]

[???]

[1024]

[1023]

FirstArray

1 I

Регион 2 I

Регион 1

н

и

Рис. 5. Влияние взаимного расположения массивов на работу модуля

Рассмотрим определение массива размером 1 кбайт, адрес начала которого выровнен по границе 256 байт. Примеры приведены для распространенных компиляторов GCC, IAR и Keil:

SecondArray кратен 256 байт. Так как 1024 без остатка делится на 256, то наиболее вероятно, что компоновщик разместит в памяти массив SecondArray сразу после массива К^Аггау.

Далее допустим, что первый регион памяти, в котором размещается Би^Аи-ау, позволяет осуществить операцию записи. Запись вне его пределов недопустима. Второй регион, в котором размещен SecondArray, также допускает запись. В этом случае модуль МРи не будет предотвращать операцию записи за пределами массива Б^Атау, например такую:

КыАпау[Ш50] = 'А';

гать друг к другу. Размеры регионов памяти следует оставить прежними, однако в этом случае доступ к последнему, 1025-му байту в массиве FirstArray и к 257-му байту в массиве SecondArray не будет контролироваться модулем защиты памяти. Теперь запись за пределами массивов:

Кге1Ап-ау[Ш50] = 'А';

или

SecondArray[260] = 'А';

/* Выравнивание массива по границе 1024 байт.

Синтаксис компилятора GCC. */ char Array[1024]_attribute_((alligned(1024)));

/* Выравнивание массива по границе 1024 байт.

Синтаксис компилятора IAR. */ #pragma data_alignment = 1024 char Array[1024];

/* Выравнивание массива по границе 1024 байт. Синтаксис компилятора Keil.

* Только для глобальных переменных!

* Компилятор Keil поддерживает также синтаксис компилятора GCC. */

_align(1024) char Array[1024];

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

Взаимное расположение переменных в памяти

При определении нескольких переменных (например, массивов), доступ к которым контролируется отдельными регионами памяти, необходимо учитывать эффект, связанный с взаимным расположением массивов в памяти относительно друг друга.

Рассмотрим следующий пример:

Адрес начала и конца массива Б^Атау кратен 1024 байт. Адрес начала и конца массива

Запись за пределами первого массива (первого региона) будет приводить к ошибочной записи во второй массив (второй регион) (рис. 5а).

Эта ситуация может быть разрешена, если определить массивы следующим образом:

/* Определить два массива, доступ к которым будет контролироваться двумя разными регионами модуля защиты памяти. */

char FirstArray[1025]_attribute_((alligned(1024)));

char SecondArray[257]_attribute_((alligned(256)));

В этом случае адрес конца массива FirstArray и адрес конца массива SecondArray не будут находиться ни на границе 256 байт (1025 и 257 не делится на 256), следовательно, компоновщик разместит массивы FirstArray и SecondArray так, что они не будут приле-

будет приводить к срабатыванию механизма защиты памяти и вызову прерывания MemManage (рис. 5б).

Следует отметить, что приведенный выше прием не гарантирует на 100%, что ошибочный доступ к одному массиву не приведет к доступу ко второму, имеющему такой же набор прав доступа. Этот прием работает только в случае, если выход за пределы массива имеет небольшую величину (меньше, чем размер меньшего массива).

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

/* Определить два массива, доступ к которым будет контролироваться двумя разными регионами модуля защиты памяти. */

char FirstArray[1024]_attribute_((alligned(1024)));

char SecondArray[256]__attribute_((alligned(256)));

Таблица 1. Макроопределения, задающие права доступа к региону памяти

Конфигурационное макроопределение Права доступа для задач, работающих в привилегированном режиме Права доступа для задач, работающих в режиме пользователя

portMPU_REGION_READ_WRITE Полный доступ Полный доступ

portMPU_REGION_PRIVILEGED_READ_ONLY Только чтение Нет доступа

portMPU_REGION_READ_ONLY Только чтение Только чтение

portMPU_REGION_PRIVILEGED_READ_WRITE Полный доступ Нет доступа

portMPU_REGION_EXECUTE_NEVER Регион не может содержать выполняемого кода

в какой секции следует размещать те или иные переменные. Далее будет рассмотрен пример конфигурации компоновщика, поставляемого вместе с компилятором GCC.

Создание задач, защищенных модулем MPU

Все API-функции, доступные для порта FreeRTOS ARM Cortex-M3 без поддержки модуля MPU, доступны и для порта FreeRTOS-MPU. Подчеркнем некоторые незначительные отличия в создании задач, защищенных модулем защиты памяти.

Для создания задачи, ограниченной в правах доступа к памяти, предназначена API-функция xTaskCreateResticted() — расширенная версия xTaskCreate(). API-функция xTaskCreateResticted() применяется для создания задач с ограниченными правами по выполнению кода и ограниченными правами доступа к регионам оперативной памяти и периферии микроконтроллера.

Аргументы API-функции xTaskCreateResticted() включают все аргументы xTaskCreate() плюс четыре дополнительных параметра, которые определяют три региона памяти и стек создаваемой задачи. Прототип API-функции xTaskCreateResticted():

portBASE_TYPE xTaskCreateRestricted(xTaskParameters

*pxTaskDefinion, xTaskHandle *pxCreatedTask);

В [9] говорится, что размещение всех аргументов непосредственно в списке аргументов API-функции было бы слишком громоздким и привело бы к потреблению большого объема памяти стека (аргументы функции передаются обычно через стек). Вместо этого в порте FreeRTOS-MPU определена структура xTaskParameters, которая содержит параметры, используемые API-функцией xTaskCreateResticted():

/*

* Parameters required to create an MPU protected task.

*/

typedef struct xTASK_PARAMTERS

{

pdTASK_CODE pvTaskCode;

const signed char * const pcName;

unsigned short usStackDepth;

void *pvParameters;

unsigned portBASE_TYPE uxPriority;

portSTACK_TYPE *puxStackBuffer;

xMemoryRegion xRegions[ portNUM_CONFIGURABLE_REGIONS ];

} xTaskParameters;

Структура xTaskParameters может быть объявлена как константа (квалификатор const) и поэтому может остаться во Flash-памяти.

Однако анализ заголовочного файла task.h, входящего в дистрибутив FreeRTOS, показал, что в действительности вызов xTaskCreateResticted() заменяется вызовом недоступной напрямую системной функции xTaskGenericCreate(), в которой все члены структуры xTaskParameters передаются в качестве аргументов по отдельности:

#define xTaskCreateRestricted( x, pxCreatedTask ) xTaskGenericCreate ( ((x)->pvTaskCode), ((x)->pcName), ((x)->usStackDepth), ((x)->pvParameters), ((x)->uxPriority), (pxCreatedTask), ((x)->puxStackBuffer), ((x)->xRegions) )

Следовательно, говорить об экономии памяти стека при использовании структуры xTaskParameters вместо списка отдельных параметров неправильно.

API-функция xTaskCreateResticted() получает указатель на структуру xTaskParameters в качестве первого аргумента. Второй аргумент используется для передачи дескриптора создаваемой задачи из API-функции в вызывающую программу: точно так, как это происходит с помощью одноименного аргумента API-функции xTaskCreate(). Указатель pxCreatedTask перед вызовом xTaskCreateResticted() может быть установлен в NULL, когда в получении дескриптора создаваемой задачи нет необходимости.

Прежде чем говорить о специфических для FreeRTOS-MPU параметрах (в составе структуры xTaskParameters) API-функции xTaskCreateResticted(), перечислим члены структуры xTaskParameters, которые полностью аналогичны соответствующим аргументам API-функции xTaskCreate(), которая подробно рассмотрена в [10, № 3]:

1. pvTaskCode — указатель на функцию, реализующую задачу.

2. pcName — строка (имя функции).

3. usStackDepth — глубина собственного стека создаваемой задачи, задается в словах, а не в байтах.

4. pvParameters — произвольный параметр, передаваемый задаче при ее создании. Остановимся подробнее на члене xRegions

структуры xTaskParameters. Он представляет собой массив из portNUM_ CONFIGURABLE_REGIONS структур xMemoryRegion. Макроопределение portNUM_CONFIGURABLE_REGIONS задано в заголовочном файле portmacro.h равным 3. Структура xMemoryRegion определена как:

/*

* Defines the memory ranges allocated to the task when an MPU is used.

*/

typedef struct xMEMORY_REGION

{

void *pvBaseAddress; unsigned long ulLengthInBytes; unsigned long ulParameters; } xMemoryRegion;

Члены структуры xMemoryRegion: 1. pvBaseAddress — адрес начала региона памяти, должен быть кратен размеру региона.

2. ulLengthInBytes — размер региона в байтах, должен представлять собой степень числа 2 в диапазоне от 32 байт до 4 Гбайт включительно.

3. ulParameters — права доступа к региону, определяются как «побитовое ИЛИ» конфигурационных макроопределений, приведенных в таблице 1. То есть имеется возможность назначить региону произвольную комбинацию атрибутов прав доступа, перечисленных в таблице 1. Таким образом, программисту предоставляется возможность сконфигурировать максимум три региона памяти для каждой задачи. Каждый раз, когда создаваемая задача будет переходить в режим выполнения, ядро автоматически будет конфигурировать модуль защиты памяти в соответствии с содержимым массива xRegions. В теле задачи регионы памяти могут быть переопределены с помощью АР1-функции vTaskAllocateMPURegions(), которая описана ниже. Все три региона памяти должны быть определены в массиве xRegions, даже если используются только два или один регион памяти. Для того чтобы не использовать регион памяти, необходимо задать значения всех членов соответствующей структуры xMemoryRegion равными 0.

Внимания также заслуживает член uxPriority структуры xTaskParameters. В АР1-функции xTaskCreate() аргумент uxPriority используется только для задания приоритета задачи при ее создании [10, № 3]. В АР1-функции xTaskCreateRestricted() этот аргумент используется также для выбора режима, в котором будет выполняться создаваемая задача: привилегированный или режим пользователя. Для создания задачи в режиме пользователя достаточно задать аргументу uxPriority желаемое значение приоритета. Для создания задачи в привилегированном режиме аргументу uxPriority необходимо присвоить значение, полученное операцией побитового ИЛИ значения требуемого приоритета задачи и макроопределения portPRIVILEGE_BIT. Например, для создания задачи в режиме пользователя с приоритетом 3 необходимо задать значение аргумента uxPriority равным 3. Для создания задачи в привилегированном режиме с таким же приоритетом необходимо задать значение uxPriority равным (3 I portPRIVILEGE_BIT). Пример исходного кода создания задачи в привилегированном режиме приведен ниже.

Каждая задача в среде FreeRTOS имеет свой собственный стек. В случае исполь-

зования FreeRTOS-MPU при создании задачи необходимо задать указатель на начало стека задачи — член puxStackBuffer структуры xTaskParameters.

Порт FreeRTOS-MPU использует регион памяти, чтобы гарантировать, что текущая выполняющаяся задача имеет доступ только к своему стеку, но попытка записать что-либо за пределами стека вызовет ошибку защиты памяти. Это означает, что адрес начала стека задачи и его размер должны удовлетворять требования к региону памяти, оговоренные выше: размер стека — степень двойки (от 32 байт до 4 Гбайт), адрес начала должен быть кратен размеру.

Существует два пути, чтобы гарантировать совместимость с этими требованиями:

1. Предоставить реализацию API-функции pvPortMallocAligned(), которая будет выделять блок памяти из кучи с заданным значением выравнивания в байтах. Реализация этой функции, скорее всего, будет достаточно сложной и потенциально трудоемкой, поэтому эта возможность не будет больше упоминаться. По умолчанию API-функция pvPortMallocAligned() не определена, и вместо нее вызывается стандартная API-функция pvPortMalloc(). Если программист реализовал свою API-функцию pvPortMallocAligned(), то аргумент puxStackBuffer необходимо установить в NULL.

2. Статически выделенный буфер (массив) для использования в качестве стека создаваемой задачи. В этом случае необходимо использовать рассмотренные выше расширения компилятора, чтобы гарантировать, что этот массив удовлетворяет требования к региону памяти. В этом случае puxStackBuffer должен указывать на начало этого массива. Этот метод продемонстрирован далее. Рассмотрим упрощенный пример программы, в которой задача

создается API-функцией xTaskCreateResticted(). Задача конфигурируется для доступа к трем массивам, которые располагаются в трех регионах памяти с различными правами доступа к каждому из них. Эти массивы и массив, выполняющий роль стека задачи, определены в программе в соответствии с требованиями к регионам памяти модуля MPU. Для задания значения выравнивания используется синтаксис компилятора GCC.

{

/* Создать задачу vDemoTask в среде FreeRTOS-MPU.

Дескриптор задачи не нужен, поэтому второй аргумент равен NULL. */

xTaskCreateRestricted( &xTskParms, NULL );

vTaskStartScheduler();

}

void vDemoTask( void *pvParameters )

{

char cTemp; short sTemp;

/* Тело задачи vDemoTask. */

for (;;) {

/* Задача может писать/читать из массива, сконфигурированного для доступа из этой задачи. */ cReadWriteArray[15] = 'S'; cTemp = cReadWriteArray[15]; /* ОШИБКА!

Следующая инструкция вызовет ошибку защиты памяти. Доступ за пределы массива cReadWriteArray не разрешен! */ cReadWriteArray[300] = 'S'; cTemp = cReadWriteArray[300];

/* Чтение из массива cReadOnlyArray разрешено. */ cTemp = cReadOnlyArray[5]; /* ОШИБКА!

Следующая инструкция вызовет ошибку защиты памяти. Массив cArray защищен регионом, не допускающим запись! */ cReadOnlyArray[5] = 'A';

/* Задача была создана и сейчас выполняется в привилегированном режиме. Доступ к массиву sPrivilegedOnlyAccessArray разрешен. */ sPrivilegedOnlyAccessArray[10] = 15; sTemp = sPrivilegedOnlyAccessArray[10];

/* Следующий макрос переведет задачу в режим пользователя.*/

portSWITCH_TO_USER_MODE(); /* ОШИБКА!

Теперь задача работает в режиме пользователя

Доступ к массиву sPrivilegedOnlyAccessArray, защищенному атрибутом "доступ только из привилегированного режима", запрещен!

В результате выполнения любой из двух следующих инструкций возникнет ошибка защиты памяти. */ sPrivilegedOnlyAccessArray[10] = 15; sTemp = sPrivilegedOnlyAccessArray[10];

/* Запись в первую и последнюю ячейку стека задачи не приведет к ошибке памяти. Однако если макроопределение configCHECK_FOR_STACK_OVERFLOW задано равным 1, то будет зафиксировано переполнение стека задачи. */ xTaskStack[ 0 ] = 0; xTaskStack[ 511 ] = 0; /* ОШИБКА!

Попытка записи за пределами стека задачи вызовет ошибку защиты памяти. */ xTaskStack[ 512 ] = 0;

}

}

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

Отметим, что в данном примере не применяется никаких приемов предотвращения эффекта прилегания массивов друг к другу, который был описан выше. В реальной программе необходимо гарантировать, что массивы располагаются в памяти раздельно. Иначе инструкция вида:

cTemp = cReadWriteArray[300];

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

может не вызвать срабатывание защиты памяти, если компоновщик расположил массив cReadOnlyArray сразу за массивом cReadWriteArray. Вместо срабатывания прерывания защиты памяти MemManage произойдет чтение из массива cReadOnlyArray.

Сразу после своего создания задача будет работать в привилегированном режиме (к значению приоритета применено значение «побитное ИЛИ» с макроопределением portPRIVILEGE_BIT). Однако в теле задачи использован вызов API-функции portSWITCH_TO_USER_MODE(), которая принудительно переводит задачу в режим пользователя. Более подробно эта API-функция будет рассмотрена далее.

/* Определить массив, в котором будет располагаться стек создаваемой задачи. Ядро FreeRTOS автоматически создаст регион памяти MPU для этого массива. Поэтому параметры массива должны удовлетворять требования к региону MPU. Размер стека — 512 слов. Значение выравнивания задано в байтах.*/

static portSTACK_TYPE xTaskStack[ 512 ] _attribute_((aligned(512 * sizeof( portSTACK_TYPE ))));

/* Определить массив, к которому задача будет иметь полный доступ из любого режима. Но попытка обратиться по адресу за пределами этого массива вызовет ошибку защиты памяти. Определение массива должно удовлетворять требования к размеру массива и выравниванию. */ char cReadWriteArray[256]__attribute_((alligned(256)));

/* Определить массив, к которому задача в любом режиме будет иметь доступ "только для чтения". */ char cReadOnlyArray[128] _attribute__((alligned(128)));

/* Определить массив, к которому задача будет иметь доступ, только находясь в привилегированном режиме. Значение выравнивания указано в байтах. */

short sPrivilegedOnlyAccessArray[32]_attribute__((alligned(32 * sizeof( short ))));

/* Определить структуру xTaskParameters, определяющую помимо остального права доступа к массивам cReadWriteArray, cReadOnlyArray, sPrivilegedOnlyAccessArray. */

static const xTaskParameters xTskParms =

{

vDemoTask, /* pvTaskCode — идентификатор функции, реализующей задачу. */ "Demo", /* pcName — имя задачи. */ 512, /* usStackDepth — размер стека. */

NULL, /* pvParameters — параметр, передаваемый в задачу при ее создании — не используется. */ (1 I portPRIVILEGE_BIT), /* uxPriority — приоритет = 1, привилегированный режим. */ xTaskStack, /* puxStackBuffer — массив, используемый для размещения стека задачи. */

/* xRegions — массив из трех структур xMemoryRegion. */

{

/* Адрес начала региона Длина Тип доступа */

{ cReadWriteArray, 256, portMPU_REGION_READ_WRITE}

{ cReadOnlyArray, 128, portMPU_REGION_READ_ONLY },

{ sPrivilegedOnlyAccessArray, 32 * sizeof(short), portMPU_REGION_PRMLEGED_READ_WRITE},

}

};

int main( void )

Этот пример демонстрирует простой случай, где модуль MPU использован для контроля доступа к трем переменным (в данном случае к массивам), но эта техника может быть применена для контроля доступа к группе переменных с помощью объединения переменных в одну структуру. Если это покажется непрактичным, то существует возможность использовать расширения компилятора для «ручного» размещения переменных в областях памяти, удовлетворяющих требования к размеру и выравниванию, или для размещения в секциях памяти, определенных в скрипте компоновки.

Использование API-функции xTaskCreate() во FreeRTOS-MPU

API-функция xTaskCreate() также может использоваться для создания задач в среде FreeRTOS-MPU. Функция xTaskCreate() позволяет создавать задачи как в режиме пользователя, так и в привилегированном режиме, но не позволяет связать задачу с набором регионов памяти в момент ее создания. Вместо этого задачи, созданные в привилегированном режиме, будут иметь доступ ко всей карте памяти, тогда как задачи в режиме пользователя будут иметь доступ только к своему стеку и несистемной периферии.

Как и в случае с API-функцией xTaskCreateRestricted(), установка аргумента uxPriority непосредственно в значение необходимого приоритета приведет к созданию задачи в режиме пользователя, а «логическое ИЛИ» значения приоритета с макроопределением portPRIVILEGE_BIT приведет к созданию задачи в привилегированном режиме.

API-функция vTaskAllocateMPURegions()

Как было сказано выше, максимум три региона памяти могут быть связаны с задачей при ее создании. Регионы памяти затем могут быть переопределены с помощью API-функции vTaskAllocateMPURegions(). Ее прототип:

void vTaskAllocateMPURegions(xTaskHandle xTask, const xMemoryRegion * const pxRegions);

Ее аргументы:

1. xTask — дескриптор задачи, чье определение регионов памяти будет изменено: может быть получен как аргумент pxCreatedTask API-функций xTaskCreate() и xTaskCreateRestricted(). Задача может модифицировать регионы памяти, которые связаны с ней самой, путем задания аргументу xTask значения NULL вместо дескриптора задачи.

2. pxRegions — массив из трех xMemoryRegion-структур, которые определяют параметры для трех регионов памяти (смотрите описание структуры xMemoryRegion выше). Для предотвращения использования региона памяти следует установить все члены определяющей этот регион структуры xMemoryRegion в 0.

После того как регионы сконфигурированы, выполнение задачи может быть прервано выполнением другой задачи. Однако ядро будет автоматически конфигурировать модуль MPU в соответствии с параметрами последнего вызова vTaskAllocateMPURegions() каждый раз, когда задача снова переходит в состояние выполнения.

Рассмотрим пример функции, которая получает дескриптор задачи в качестве своего аргумента, конфигурирует регионы памяти, связанные с этой задачей, а также регионы памяти той задачи, которая вызвала эту функцию:

/* Сконфигурировать регионы памяти для задачи, заданной дескриптором,

* который был передан в эту функцию через аргумент xTask. */ vTaskAllocateMPURegions(xTask, xRegions);

/* Сконфигурировать регионы памяти для задачи, которая вызвала эту функцию.

* Для этого в качестве дескриптора задается значение NULL. */ vTaskAllocateMPURegions(NULL, xRegions);

}

API-функция portSWITCH_TO_USER_MODE()

Задача, выполняющаяся в привилегированном режиме, может вызвать API-функцию portSWITCH_TO_USER_MODE(), в результате чего режим ее работы будет понижен до режима пользователя. В действительности API-функция portSWITCH_TO_USER_MODE() реализована как макрос.

API-функция portSWITCH_TO_USER_MODE() не требует никаких параметров и ничего не возвращает.

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

Конфигурация компоновщика

Для корректной работы порт FreeRTOS-MPU требует наличия скрипта компоновщика (линкера), в котором должны определяться две секции, перечисленные в таблице 2, и восемь переменных компоновщика, которые описаны в таблице 3.

Таблица 2. Секции памяти, используемые компоновщиком для размещения ядра и данных FгeeRTOS

имя секции Описание

privileged_functions Секция, в которой размещается исполняемый образ ядра. Секция pгivileged_functions должна объединять таблицу векторов прерываний, начинающуюся с адреса 0, и следующий сразу за таблицей исполняемый образ ядра. Регион памяти используется для защиты доступа к секции pгivileged_functions, поэтому ее размер должен быть равен степени двойки. (На размер и начальный адрес (выравнивание) секции pгivileged_functions накладываются требования к регионам памяти)

privileged_data Секция, в которой размещаются данные ядра. Защищена модулем MPU, поэтому также должна соответствовать требованиям к региону памяти

Таблица 3. Переменные компоновщика, используемые ядром FreeRTOS

имя переменной Значение переменной

FLASH_segment_start Адрес начала Flash-памяти микроконтроллера

FLASH_segment_end Адрес конца Flash-памяти микроконтроллера

privileged_functions_start Адрес начала секции privileged_functions

privileged_functions_end Адрес конца секции privileged_functions

_SRAM_segment_start_ Адрес начала оперативной памяти (SRAM) микроконтроллера

_SRAM_segment_end_ Адрес конца оперативной памяти (SRAM) микроконтроллера

privileged_data_start Адрес начала секции privileged_ data

privileged_data_end Адрес конца секции privileged_ data

Синтаксис, используемый для определения требуемых секций и переменных, зависит от того, какая среда разработки используется. Примеры ниже демонстрируют работу с компоновщиком LD, который поставляется вместе с компилятором GCC [11]. Наиболее простой способ создать соответствующий скрипт компоновки — это взять за отправную точку работоспособный скрипт компоновки, который входит в демонстрационный проект из дистрибутива FreeRTOS.

Пример скрипта компоновки для микроконтроллера LPC1768:

void vSomeFunction(xTaskHandle xTask) {

/* Определить массив структур xMemoryRegion, который задает два региона памяти:

* 1. Размер 8 кбайт, адрес начала 0x0 с доступом только для чтения.

* 2. Размер 2 кбайт, адрес начала 0x10004000 с доступом только из привилегированного режима.

* Массив должен содержать определения трех регионов памяти.

* Члены структуры xMemoryRegion для неиспользуемого региона следует задать равными 0.

*/

static const xMemoryRegion xRegions[3] = { /* Адрес начала Длина Тип доступа */ { 0x0, 8096, portMPU_REGION_READ_ONLY},

{ 0x10004000, 2048, portMPU_REGION_PRIVILEGED_READ_WRITE}, { 0, 0, 0}

GROUP(libgcc.a libc.a)

FLASH (rx) : ORIGIN = 0x0 LENGTH = 0x80000 SRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 0x8000 AHBRAM0 : ORIGIN = 0x2007c000, LENGTH = 0x4000 AHBRAM1 : ORIGIN = 0x20080000, LENGTH = 0x4000

MEMORY

_vRamTop = ORIGIN( SRAM ) + LENGTH( SRAM );

/* Variables used by FreeRTOS-MPU. */ _Privileged_Functions_Region_Size = 16K; _Privileged_Data_Region_Size = 256;

_FLASH_segment_start_= ORIGIN( FLASH );

__FLASH_segment_end__=__FLASH_segment_start__

+ LENGTH( FLASH );

_privileged_functions_start_= ORIGIN( FLASH );

__privileged_functions_end__ = __privileged_functions_start__ + _Privileged_Functions_Region_Size;

_SRAM_segment_start_= ORIGIN( SRAM );

__SRAM_segment_end__ = __SRAM_segment_start__ + LENGTH( SRAM );

_privileged_data_start__= ORIGIN( SRAM );

__privileged_data_end__ = ORIGIN( SRAM ) + _Privileged_Data_ Region_Size;

ENTRY(ResetISR)

SECTIONS

I

/* Privileged section at the start of the flash - vectors must be first whatever. */ privileged_functions :

I

KEEP(*(.isr_vector)) *(privileged_functions) i > FLASH

.text :

I

/* Non privileged code kept out of the first 16K or flash. */ . = __privileged_functions_start__ + _Privileged_Functions_ Region_Size;

»(.text*) *(.rodata*)

i > FLASH

/* for exception handling/unwind - some Newlib functions * (in common with C++ and STDC++) use this. */

.ARM.extab :

{

*(.ARM.extab* .gnu.linkonce.armextab.*) } > FLASH

_exidx_start = .;

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

.ARM.exidx :

{

*(.ARM.exidx* .gnu.linkonce.armexidx.*) } > FLASH _exidx_end = .;

_etext = .;

/* zero initialized data */

privileged_data :

{

_bss = .;

*(privileged_data)

/* Non kernel data is kept out of the first 256 bytes of SRAM. */ } > SRAM

. = ORIGIN( SRAM ) + _Privileged_Data_Region_Size;

.bss :

{

*(.bss*) »(COMMON) _ebss = .; } > SRAM

.data : AT (_exidx_end)

{

_data = .; »(vtable) »(.data*) _edata = .; } > SRAM

/* Where we put the heap with cr_clib */

.cr_heap :

{

end = .;

_pvHeapStart = .; } > SRAM

/*

Note: (ref: M0000066)

Moving the stack down by 16 is to work around a GDB bug. This space can be reclaimed for Production Builds.

*/

_vStackTop = _vRamTop - 16;

.ETHRAM :

{

} > AHBRAM0

.USBRAM :

{

} > AHBRAM1

}

/* Эта переменная является локальной для функции vTask(),

* следовательно, компоновщик разместит ее в стеке задачи. */ xQueueHandle xQueuelnStack;

/* Эта задача находится в привилегированном режиме

* после своего создания.

* Поэтому она имеет доступ к глобальной переменной.

* Скопировать дескриптор очереди из глобальной переменной в локальную переменную в стеке. */

xQueuelnStack = xGlobalQueue;

/* Перевести задачу в режим пользователя.

* При этом она потеряет доступ к глобальной переменной,

* но сможет выполнять операции с очередью с помощью

* локальной копии дескриптора очереди,

* которая размещается в стеке задачи. */ portSWITCH_TO_USER_MODE();

/* Бесконечный цикл. */

for (;;) {

/* Здесь задача выполняется в режиме пользователя.

* Работа с очередью осуществляется через ее локальный

* дескриптор в стеке. */

xQueueSend(xQueueInStack, &iVar, portMAX_DELAY);

/* ... */

}

}

Видно, что под код ядра FreeRTOS отводится регион памяти размером 16 кбайт в самом начале адресного пространства Flash-памяти. Под данные ядра FreeRTOS отводится регион размером 256 байт, который также располагается в самом начале внутреннего ОЗУ.

Ознакомиться с правилами написания скриптов компоновки в среде GCC можно в [11].

Практические советы

Доступ к глобальным переменным из задачи в режиме пользователя

Задача, выполняющаяся в режиме пользователя, не может получить доступ к памяти за пределами своего стека, за исключением адресов, попадающих в один из связанных с задачей регионов памяти. Если, например, задача в режиме пользователя «хочет» провести операцию c очередью, которая определена в глобальной области видимости, то напрямую это сделать нельзя:

/* Дескриптор очереди представляет собой

* глобальную переменную. */ xQueueHandle xGlobalQueue;

void vTask(void *pvParameters) {

/* Перевести задачу в режим пользователя. */ portSWITCH_TO_USER_MODE();

/* Бесконечный цикл. */

for (;;) {

/* Здесь задача выполняется в режиме пользователя. ОШИБКА!

Обращение к глобальной переменной вызовет срабатывание защиты памяти!*/ xQueueSend(xGlobalQueue, &iVar, portMAX_DELAY);

/* ... */

}

}

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

Первый способ — изначально создать задачу в привилегированном режиме, затем скопировать глобальную переменную в переменную, размещенную в стеке, после чего перевести задачу в требуемый для нее режим пользователя. Этот способ приведен в следующем примере:

/* Дескриптор очереди представляет собой

* глобальную переменную. */ xQueueHandle xGlobalQueue;

void vTask(void *pvParameters) {

Второй способ получить доступ к глобальной переменной — это передать значение глобальной переменной в задачу как параметр при ее создании:

/* Дескриптор очереди представляет собой * глобальную переменную. */ xQueueHandle xGlobalQueue;

/* ... */

/* Создать задачу vTask. В качестве аргумента при создании передать дескриптор глобальной очереди, приведенный к типу (void *). */ xTaskCreate( vTask,

"DemoTask", 400,

(void *) xGlobalQueue, 1,

NULL);

/* ... */

void vTask(void *pvParameters) {

/* Эта переменная является локальной для функции vTask(),

* следовательно, компоновщик разместит ее в стеке задачи. */ xQueueHandle xQueuelnStack;

/* Эта задача создана в режиме пользователя.

* Она имеет доступ только к своему стеку, следовательно,

* и к передаваемому в нее аргументу.

* Значение дескриптора задачи xGlobalQueue было задано

* как параметр задачи при ее создании.

* Поэтому оно может быть скопировано в локальную переменную

* с применением операции приведения типа. */ xQueuelnStack = (xQueueHandle)pvParameters;

/* Бесконечный цикл. */

for (;;) {

/* Работа с очередью осуществляется через ее локальный * дескриптор в стеке. */

xQueueSend(xQueueInStack, &iVar, portMAX_DELAY);

/* ... */

}

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

}

Межзадачное взаимодействие в режиме пользователя

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

Оперативная память, где размещаются элементы очереди, контролируется ядром,

и доступ к ней возможен только из привилегированного режима (то же самое относится к семафорам и мьютексам). Вызов АР1-функции, такой как xQueueSendQ, приводит к тому, что процессор временно переходит в привилегированный режим. Задача, которая временно перешла в привилегированный режим, получает доступ к памяти, в которой хранятся элементы очереди. После завершения выполнения АР1-функции процессор возвращается в режим пользователя.

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

Демонстрационные проекты

Поддержка модуля МРи изначально включена в файлы с исход-ным кодом ядра FreeRTOS. Используя значение макроопределения portUSING_MPU_WRAPPERS в заголовочном файле роНтжтоЛ, препроцессор языка Си определяет, будет ли включен код поддержки модуля МРи при компиляции проекта.

Как и при создании проекта на FreeRTOS для любого микроконтроллера, при использовании порта FreeRTOS-MPU рекомендуется начать свой проект с редактирования демонстрационного проекта, входящего в дистрибутив FreeRTOS. Демонстрационные проекты для этого порта размещаются в общей для всех демонстрационных проектов дирек-

тории FreeRTOS\Demo в поддиректориях, название которых начинается с "Cortex-MPU".

В последнюю на момент написания статьи версию FreeRTOS v7.1.0 включены два демонстрационных проекта с поддержкой модуля MPU: для микроконтроллеров Stellaris фирмы Texas Instruments (LM3Sxxxx) и для микроконтроллера LPC1768 фирмы NXP. ■

Литература

1. http://infocenter.arm.com/help/topic/com.arm. doc.ddi0337g/DDI0337G_cortex_m3_r2p0_trm. pdf

2. http://www.arm.com/products/processors/ cortex-m/index.php

3. http://www.st.com/internet/mcu/class/1734.jsp

4. http://www.semicon.toshiba.co.jp/eng/product/ micro/arm/tx03 series/index.html

5. http://www.ti.com/stellaris

6. http://www.nxp.com/products/microcontrollers/ cortex_m3/

7. http://www.freertos.org/a00090.html

8. http://infocenter.arm.com/help/index.jsp? topic=/ com.arm.doc.dai0179b/ar01s01s02.html

9. Barry R. Using the FreeRTOS Real Time Kernel. ARM Cortex-M3 Edition. 2010.

10. Курниц А. FreeRTOS — операционная система для микроконтроллеров // Компоненты и технологии. 2011. № 2-11.

11. http://www.opennet.ru/docs/RUS/gnu_ld/ gnuld-3.html

12. http://ru.wikipedia.org/wiki/ANSI_C

i Надоели баннеры? Вы всегда можете отключить рекламу.