You are here

About FreeRTOS: Coding Standard, Testing and Style Guide

Перевод может содержать ошибки. Читайте первоисточник: Coding Standard, Testing and Style Guide

Назад: [Как работает FreeRTOS] Вверх: [Как работает FreeRTOS] Вперёд: [Как работает FreeRTOS]

 

На этой странице:

Стандарты кодирования / Соответствие MISRA

Основные исходные файлы FreeRTOS (которые являются общими для всех портов, но не уровня порта) соответствуют рекомендациям стандарта кодирования MISRA. Соответствие проверяется с использованием PC-lint с соответствующими файлами конфигурации (сохранённая копия от 20190319). Поскольку стандарт содержит много страниц, и доступен для покупки у MISRA за небольшую плату, мы не приводим здесь все правила.

Отклонения от стандарта MISRA перечислены ниже:

  • Две функции API имеют более одной точки выхода. Отклонение было допущено в этих двух случаях по причине критического влияния на эффективность.
  • Когда создаётся задача, исходный код манипулирует адресами памяти, чтобы найти начальный и конечный адрес стека, выделенного для созданной задачи. Код должен работать на всех архитектурах, для которых FreeRTOS была портирована, включая архитектуры с 8, 16, 20, 24  32-битными шинами. Это неизбежно требует некоторой арифметики с указателями. Когда используется арифметика указателей, арифметический результат программно проверяется на корректность.
  • Макросы трассировки по умолчанию пусты, поэтому не генерируют никакого кода. Поэтому проверка соответствие MISRA выполняется с помощью фиктивных (dummy) определений макросов.
  • Правила MISRA выключаются построчно, когда это необходимо. То есть, соответствие правилам, приводящее к менее подходящему коду для глубоко встроиваемой системы считается хуже, чем осторожное несоблюдение правил. Каждый такой случай сопровождается обоснованием с ипользованием специального языка разметки комментариев PC-lint MISRA.

FreeRTOS собирается со многими различными компиляторами, некоторые из которых более совершенны, чем другие. По этой причине FreeRTOS не использует какие-либо особенности или синтаксис, введённые в язык C стандартом C99 или после него. Единственным исключением является использование заголовочного файла stdint.h. Каталог FreeRTOS/Source/include содержит файл, называемый stdint.readme, который может быть переименован в stdint.h для предоставления определений типов stdint, минимально необходимых для сборки FreeRTOS, если ваш компилятор не предоставит свой собственный.

 


 

Тестирование

В этом разделе описываются тесты, выполненные для общего кода (код, расположенный в каталоге FreeRTOS/Source, который создаётся всеми портами ядра FreeRTOS), и тесты, выполненные для кода уровня портирования (код, расположенный в подкаталогах FreeRTOS/Source/portable).

  • Общий код

    Стандартные демонстрационные/тестовые файлы пытаются обеспечить покрытие кода тестовыми "ветвлениями" (в большинстве случаев это фактически обеспечивает "условное" покрытие, поскольку стиль кодирования ядра намеренно сохраняет условия простыми специально для этого), в результате чего тесты гарантируют, что пути и 'true', и 'false' через каждое решение осуществлены. Покрытие "ветвлений" измеряется с использованием GCOV путём определения макроса mtCOVERAGE_TEST_MARKER() в виде инструкции NOP (нет операции) в пути ветки 'else' каждого оператора 'if', если путь 'else' в противном случае был бы пустым. mtCOVERAGE_TEST_MARKER() определяется только при измерении тестового покрытия - а в обычно это пустой макрос, который не вырабатывает никакого кода.

  • Уровень порта

    Код уровня порта тестируется с использованием задачи 'reg test' и, для портов, поддерживающих вложенные прерывания, задачей 'interrupt queue'.

    Задачи 'reg test' создают несколько (обычно две) задач, которые сначала заполняют все регистры ЦП известным значением, а затем постоянно проверяют, поддерживает ли каждый регистр ожидаемое известное значение, в то время, как другие тесты непрерывно выполняются (тест выдержки). Каждая задача 'reg test' использует уникальные значения.

    Задачи 'interrupt queue' выполняют тесты прерываний с различными приоритетами как минимум с тремя уровнями вложения. Макросы используются для вставки искусственных задержек в соответствующие места в коде, чтобы обеспечить желаемое покрытие кода тестами.

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


 

Соглашения об именах

Исходный код ядра ОСРВ и демонстрационных приложений использует следующие соглашения:

  • Переменные
    • Переменные типа uint32_t начинаются с префикса ul, где 'u' обозначает 'unsigned', а  'l' обозначает 'long'.
    • Переменные типа uint16_t начинаются с префикса us, где 'u' обозначает 'unsigned', а 's' обозначает 'short'.
    • Переменные типа uint8_t начинаются с префикса uc, где 'u' обозначает 'unsigned', а 'c' обозначает 'char'.
    • Переменные, не стандартных типов stdint начинаются с префикса x. Примеры включают BaseType_t и TickType_t, которые определяются директивой typedefs на уровне порта: BaseType_t для естественного или наиболее эффективного типа для архитектуры, а TickType_t как тип, используемый для хранения количества тиков ОСРВ.
    • Беззнаковые переменные не стандартных типов stdint начинаются с префикса u. Например, переменная типаUBaseType_t (unsigned BaseType_t) имеют префикс ux.
    • Переменные типа size_t также начинаются с префикса x.
    • Переменные, имеющие тип перечисления, начинаются с префикса e.
    • Указатели имеют дополнительный префикс p, например, указатель на uint16_t будет иметь префикс pus.
    • В соответствие с руководствами MISRA, неквалифицированные стандартные типы char могут содержать только символы ASCII и должны начинаться с префикса c.
    • В соответствие с руководствами MISRA, переменные типа char * могут содержать только указатели на строки ASCII и должны начинаться с префикса pc.
  • Функции
    • Статические функции с областью видимости в пределах файла должны начинаться с префикса prv.
    • API-функции имеют префикс своего возвращаемого типа в соответствие с соглашением, определённым для переменных, с дополнительным префиксом v для void.
    • Имена функций API начинаются с имени файла, в котором функция определена. Например, vTaskDelete определна в файле tasks.c, и имеет возвращаемый тип void.
  • Макросы

    • Макросы имеют префикс файла, в котором они определены. Префикс записывается строчными буквами. Например, configUSE_PREEMPTION определён в файле FreeRTOSConfig.h.
    • За исключением префикса, макросы записываются прописными буквами, и используют нижнее подчёркивание для разделения слов.

 

Типы данных

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

  • char

    В соответствие с руководствами MISRA разрешены неквалифицированные типы char, но только если они используются для хранения символов ASCII.

  • char *

    В соответствие с руководствами MISRA, разрешены указатели на неквалифицированные типы char, но только когда они используются для указания на строки ASCII. Это избавляет от необходимости подавления мягких предупреждений компилятора, когда используются стандартные библиотечные функции, которые ожидают использования параметров char *, особенно учитывая, что некоторые компиляторы по-умолчанию используют неквалифицированные типы char со знаком, в то время, как другие компиляторы по-умолчанию определяют неквалифицированные типы char как беззнаковые.

Есть четыре типа данных, которые определены для каждого порта. Вот они:

  • TickType_t

    Если configUSE_16_BIT_TICKS установлен в ненулевое значение (т.е. true), то TickType_t определяется как беззнаковый 16-битный целый тип. Если configUSE_16_BIT_TICKS установлен в ноль (т.е. false), то TickType_t is определяется как беззнаковый 32-битный целый тип. Полная информация находится здесь.

    Для 32-битных архитектур значение configUSE_16_BIT_TICKS всегда должно быть нулевым (т.е. false).

  • BaseType_t

    Этот тип определяется как наиболее эффективный, естественный тип для архитектуры. Например, для 32-битной архитектуры BaseType_t будет определён как 32-битный тип. Для 16-битной архитектуры BaseType_t будет определён как 16-битный тип. Если BaseType_t определён как char, то следует позаботиться о том, чтобы в качестве возвращаемого функцией значения использовались char со знаком, т.к. отрицательные возвращаемые значения могут использоваться для указания на ошибку при выполнении.

  • UBaseType_t

    Это беззнаковый тип, аналогичный BaseType_t.

  • StackType_t

    Определяется на тип, используемый архитектурой для элементов, сохранямых в стеке. Обычно это 16-битный тип для 16-битных архитектур и 32-битный тип для 32-битных архитектур, хотя есть некоторые исключения. Используется внутри FreeRTOS.


 

Гид по стилю

  • Отступы

    Для отступа используются символы табуляции. Один символ табуляции эквивалентен четырём пробелам.

  • Комментарии

    Комментарии никогда не выходят за столбец 80, если они не следуют за параметром для его описания.

    Стиль комментирования C++ двойной косой чертой (//) не используется.

  • Структура кода

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


/* Сначала подключаются библиотеки... */
#include <stdlib.h>

/* ...Далее подключаются заголовочные файлы FreeRTOS... */
#include "FreeRTOS.h"

/* ...и затем все прочие заголовочные файлы. */
#include "HardwareSpecifics.h"

/* Следом идут #defines. Где возможно, определения заключаются в скобки. */
#define A_DEFINITION	( 1 )

/*
 * Далее идут прототипы статических функций с областью видимости внутри
 * этого файла, с комментариями в этом стиле, с символом '*' в
 * начале каждой строки.
 */
static void prvAFunction( uint32_t ulParameter );

/* Переменные области видимости файла это последнее, что находится перед определениями функций. Комментарии для переменных выполнены в этом стиле (без символа '*' в начале каждой строки). */
static BaseType_t xMyVariable.

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

/*-----------------------------------------------------------*/

void vAFunction( void )
{
    /* Определение функции здесь - обратите внимание на разделитель
    после закрывающей фигурной скобки. */
}
/*-----------------------------------------------------------*/

static UBaseType_t prvNextFunction( void )
{
    /* Здесь располагается определение функции. */
}
/*-----------------------------------------------------------*/

Структура файла

 


/* Имена функций всегда записываются в одной строке, включая тип
возвращаемого значения. Как всегда, пробела перед открывающей скобкой нет.
После открывающей скобки пробел есть. Перед закрывающей скобкой пробел есть.
После каждой запятой тоже есть пробел. Параметры имеют подробные описательные
имена (в отличие от этого примера!). Открывающие и закрывающие фигурные скобки
располагаются в отдельных строках и выстраиваются друг под другом. */
void vAnExampleFunction( long lParameter1, unsigned short usParameter2 )
{
/* Объявления переменных не имеют отступов. */
uint8_t ucByte;

    /* Код располагается с отступом. Фигурные скобки находятся на отдельных
    строках и выстраиваются друг под другом. */
    for( ucByte = 0U; ucByte < fileBUFFER_LENGTH; ucByte++ )
    {
        /* Снова отступ. */
    }
}

/* Конструкции 'for', 'do', 'while' и 'if' форматируются аналогичным
способом. Перед открывающей скобкой пробела нет. После открывающей скобки
пробел есть. Перед закрывающей скобкой пробел есть. Есть пробел после
каждой запятой (если они есть). Есть пробелы перед и после каждого оператора.
Нельзя полагаться на приоритет оператора - круглые скобки всегда используются,
чтобы сделать приоритет оператора явным. "Магические" числа, отличные от нуля,
всегда заменяются константой или макроопределением константы (#define).
Открывающие и закрывающие фигурные скобки располагаются в отдельных строках. */
for( ucByte = 0U; ucByte < fileBUFFER_LENGTH; ucByte++ )
{
}

while( ucByte < fileBUFFER_LENGTH )
{
}

/* Не должно быть никакой зависимости от приоритета оператора - каждое
условие в решении с несколькими условиями должно быть однозначно заключено
в скобки, как и все подвыражения. */
if( ( ucByte < fileBUFFER_LENGTH ) && ( ucByte != 0U ) )
{
    /* Пример отсутствия влияния приоритета операторов! */
    ulResult = ( ( ulValue1 + ulValue2 ) - ulValue3 ) * ulValue4;
}

/* Условная компиляция выкладывается и отступается как и любой другой код. */
#if( configUSE_TRACE_FACILITY == 1 )
{
    /* Счётчик добавляется в TCB только для трассировки. */
    pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif

/* Пробел ставится после открывающей квадратной скобки и перед закрывающей
квадратной скобкой. */
ucBuffer[ 0 ] = 0U;
ucBuffer[ fileBUFFER_LENGTH - 1U ] = 0U;

Форматирование кода на языке C

 

Hobby's category: