CMSIS-RTOS2 文档翻译 之 RTX v5 实现(MISRA C: 规范)

时间:2024-04-08 15:26:38
MISRA C:2012 规范

RTX5 C 源文件使用 MISRA C:2012 指南作为基础编码标准。

对于 MISRA 验证,PC-lint V9.00L 与 Arm Compiler V6.9 的配置一起使用。PC-Lint 验证设置是项目文件的一部分。\CMSIS\RTOS2\RTX\Library\ARM\MDK\RTX_CM.uvprojx 如下所示。有关更多信息,请参阅设置 PC-Lint 。

CMSIS-RTOS2 文档翻译 之 RTX v5 实现(MISRA C:2012 规范)
在 MDK - uVision 中运行 PC-Lint 

PC-Lint 配置使用 Tools - PC-Lint Setup ... 下的以下选项:

  • 配置文件:co-ARMCC-6.lnt(2017年3月20日)以及其他选项:
    +rw(__restrict)
    -esym(526,__builtin_*) -esym(628,__builtin_*)
    -sem(__builtin_clz, pure)
    +doffsetof(t,m)=((size_t)&((t*)0)->m) -emacro((413,923,9078),offsetof)
    -ecall(534,__disable_irq)
  • 包含项目信息:
    • 启用:添加 “头文件” 路径
    • 启用:添加 “软件包” 路径
    • 启用:验证 “软件包” 包含
    • 启用:添加 “预处理器” 符号
    • 启用:添加 “定义” 符号
  • MISRA 规则设置和配置:
    • MISRQ_C_2012_Config.lnt; 所有规则已启用
    • 包括定义文件:au-misra3.lnt(12-Jun-2014)
  • 额外的 Lint 命令(对于单个和多个文件):

C 源代码注释了 PC-Lint 控制注释以允许 MISRA 偏差。下面描述这些与下层设计决定的偏差。

偏差

RTX 源代码与 MISRA 存在以下偏差:

所有源代码偏差都清楚地标出,总之这些偏差会影响下列 MISRA 规则:

  • [MISRA 2012 指令 4.9,咨询]:应优先使用函数,而不是类似功能的宏,而宏可以互换
  • [MISRA 2012 规则 1.3,要求]:不应发生未定义或严重未指定的行为
  • [MISRA 2012 规则 10.3,必填]:分配给较窄或不同基本类型的表达式
  • [MISRA 2012 规则 10.5,咨询]:不合格的演员; 不能从 '基本无符号' 转换为 '基本枚举'
  • [MISRA 2012 规则 11.1,要求]:不得在指向函数的指针和任何其他类型之间进行转换
  • [MISRA 2012 规则 11.3,必填]:在指向对象类型的指针和指向不同对象类型的指针之间不应执行强制转换
  • [MISRA 2012 规则 11.4,咨询]:不应在指向对象的指针和整数类型之间执行转换
  • [MISRA 2012 规则 11.5,建议]:不应该将指针转换为无效指针指向对象
  • [MISRA 2012 规则 11.6,要求]:在无效指针和算术类型之间不应执行强制转换
  • [MISRA 2012 规则 15.5,咨询]:函数最后应该只有一个单一的退出点
  • [MISRA 2012 规则 20.10,咨询]:不应使用 # 和 ## 预处理器操作符

以下详细描述所有偏差。

[MISRA 注释 1]:返回参数检查语句

返回语句用于几个函数的开始,以验证参数值和对象状态。该函数立即返回,没有任何副作用,并且通常会设置错误状态。这种结构可以使源代码结构更好,更易于理解。

这种设计决定意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 15.5,咨询]:函数最后应该只有一个单一的退出点

源代码中的所有位置都标有:

//lint -e{904} "Return statement before end of function" [MISRA Note 1]

[MISRA 注释 2]:对象标识符是无效指针

CMSIS-RTOS 独立于底层的 RTOS 实现。因此对象标识符被定义为 void 指针:

  • 允许基础 RTOS 实现不可知的应用程序。
  • 避免意外地从应用程序访问 RTOS 控制块。

这个设计决定意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.3,必填]:在指向对象类型的指针和指向不同对象类型的指针之间不应执行强制转换
  • [MISRA 2012 规则 11.5,建议]:不应该将指针转换为无效指针指向对象

源代码中的所有位置都标有:

//lint -e{9079} -e{9087} "cast from pointer to void to pointer to object type" [MISRA Note 2]

在 RTX5 实现中,需要的指针转换在头文件 rtx_lib.h 中实现,具有以下内联函数:

osRtxThread_t *osRtxThreadId (osThread_t thread_id);
osRtxTimer_t *osRtxTimerId (osTimer_t timer_id);
osRtxEventFlags_t *osRtxEventFlagsId (osEventFlags_t ef_id);
osRtxMutex_t *osRtxMutexId (osMutex_t mutex_id);
osRtxSemaphore_t *osRtxSemaphoreId (osSemaphore_t semaphore_id);
osRtxMemoryPool_t *osRtxMemoryPoolId (osMemoryPoolId_t mp_id);
osRtxMessageQueue_t *osRtxMessageQueueId(osMessageQueueId_t mq_id);

[MISRA 注释 3]:转换为统一对象控制块

RTX 使用包含通用对象成员的统一对象控制块结构。统一控制块在结构开始处使用固定布局,并始终以对象标识符启动。这允许通用对象函数接收指向统一对象控制块的指针,并仅引用指针或固定布局中的成员。使用通用的对象函数和数据(例如 ISR 队列)可以降低代码复杂度并保持源代码结构更好。另请参阅[MISRA 注释 4]:从统一对象控制块转换

这个设计决定意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.3,必填]:在指向对象类型的指针和指向不同对象类型的指针之间不应执行强制转换
  • [MISRA 2012 规则 11.5,建议]:不应该将指针转换为无效指针指向对象

源代码中的所有位置都标有:

//lint -e{9079} -e{9087} "cast from pointer to void to pointer to object type" [MISRA Note 3]

在 RTX5 实现中,要求指针转换在头文件 rtx_lib.h 中通过以下内联函数实现:

osRtxObject_t *osRtxObject (void *object);

[MISRA 注释 4]:从统一对象控制块转换而来

RTX 使用包含通用对象成员的统一对象控制块结构。请参阅[MISRA 注释 3]:转换为统一对象控制块以获取更多信息。为了处理特定的控制块数据,需要指针转换。

这个设计决定意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 1.3,要求]:不应发生未定义或严重未指定的行为
  • [MISRA 2012 规则 11.3,必需]:在指向对象类型的指针和指向不同对象类型的指针之间不应执行强制转换。另外 PC-Lint 问题:
  • 信息 826:可疑的指针到指针转换(区域太小)

源代码中的所有位置都标有:

//lint -e{740} -e{826} -e{9087} "cast from pointer to generic object to specific object" [MISRA Note 4]

在 RTX5 源代码中,需要的指针转换在头文件 rtx_lib.h 中通过以下内联函数实现:

osRtxThread_t *osRtxThreadObject (osRtxObject_t *object);
osRtxTimer_t *osRtxTimerObject (osRtxObject_t *object);
osRtxEventFlags_t *osRtxEventFlagsObject (osRtxObject_t *object);
osRtxMutex_t *osRtxMutexObject (osRtxObject_t *object);
osRtxSemaphore_t *osRtxSemaphoreObject (osRtxObject_t *object);
osRtxMemoryPool_t *osRtxMemoryPoolObject (osRtxObject_t *object);
osRtxMessageQueue_t *osRtxMessageQueueObject (osRtxObject_t *object);
osRtxMessage_t *osRtxMessageObject (osRtxObject_t *object);

[MISRA 注释 5]:转换为对象类型

RTX5 内核具有使用空指针的公共内存管理功能。这些内存分配函数返回一个 void 对象,该对象类型正确对齐。

此设计决策意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.5,建议]:不应该将指针转换为无效指针指向对象

源代码中的所有位置都标有:

//lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 5]

代码示例:

os_thread_t *thread;
:
//lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 5]
thread = osRtxMemoryPoolAlloc(osRtxInfo.mpi.thread);

[MISRA 注释 6]:从用户提供的存储转换而来

CMSIS-RTOS2 和 RTX5 支持用户为对象控制块,堆栈和数据存储提供的存储。该 API 使用空指针来定义此用户提供的存储的位置。因此需要将 void 指针转换为底层存储类型。在访问内存之前检查用户提供的存储的对齐限制。另请参阅[MISRA 注释 7]:检查指针对齐是否正确。

这个设计决定意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.3,必填]:在指向对象类型的指针和指向不同对象类型的指针之间不应执行强制转换
  • [MISRA 2012 规则 11.5,建议]:不应该将指针转换为无效指针指向对象

源代码中的所有位置都标有:

//lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 6]

代码示例:

static osTimerId_t svcRtxTimerNew (osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr_t *attr) {
os_timer_t *timer;
:
if (attr != NULL) {
:
//lint -e{9079} "conversion from pointer to void to pointer to other type" [MISRA Note 6]
timer = attr->cb_mem;
:

[MISRA 注释 7]:检查指针对齐是否正确

RTX5 验证用户提供的对象控制块,堆栈和数据存储的对齐情况。另请参阅[MISRA 注释 6]:从用户提供的存储转换以获取更多信息。

此设计决策意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.4,咨询]:不应在指向对象的指针和整数类型之间执行转换
  • [MISRA 2012 规则 11.6,要求]:在无效指针和算术类型之间不应执行强制转换

源代码中的所有位置都标有:

//lint -e(923) -e(9078) "cast from pointer to unsigned int" [MISRA Note 7]

代码示例:

static osThreadId_t svcRtxThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) {
:
void *stack_mem;
:
if (stack_mem != NULL) {
//lint -e(923) -e(9078) "cast from pointer to unsigned int" [MISRA Note 7]
if ((((uint32_t)stack_mem & 7U) != 0U) || (stack_size == 0U)) {
:

[MISRA 注释 8]:内存分配管理

RTX5 实现了需要指针算术来管理内存的内存分配函数。在 rtx_memory.c 中定义了用于存储内存分配块的类型为 mem_block_t 的结构

此设计决策意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.4,咨询]:不应在指向对象的指针和整数类型之间执行转换
  • [MISRA 2012 规则 11.6,要求]:在无效指针和算术类型之间不应执行强制转换

源代码中的所有位置都标有:

//lint -e(923) -e(9078) "cast from pointer to unsigned int" [MISRA Note 8]

所需的指针计算在 rtx_memory.c 中用以下函数实现:

__STATIC_INLINE mem_block_t *MemBlockPtr (void *mem, uint32_t offset) {
uint32_t addr;
mem_block_t *ptr;
//lint --e{923} --e{9078} "cast between pointer and unsigned int" [MISRA Note 8]
addr = (uint32_t)mem + offset;
ptr = (mem_block_t *)addr;
return ptr;
}

[MISRA 注释 9]:寄存器访问的指针转换

CMSIS-Core 外设寄存器模块使用结构进行访问。该结构的存储器地址被指定为无符号整数。指针转换需要访问特定的寄存器。

此设计决策意味着以下 MISRA 偏差:

  • [MISRA 2012 规则11.4,咨询]:不应在指向对象的指针和整数类型之间执行转换
  • [MISRA 2012 规则 11.6,要求]:在无效指针和算术类型之间不应执行强制转换

源代码中的所有位置都标有:

//lint -emacro((923,9078),SCB) "cast from unsigned long to pointer" [MISRA Note 9]

代码示例:

#define SCS_BASE (0xE000E000UL)
#define SCB ((SCB_Type *)SCB_BASE)
typedef struct {...} SCB_Type;
SCB->... = ...;

[MISRA 注释 10]:SVC 调用使用函数式的宏

RTX5 使用 SVC(服务调用)在线程模式(用于用户代码执行)和处理程序模式(用于 RTOS 内核执行)之间切换。SVC 函数调用机制通过汇编指令来实现,以构建 SVC 的代码。源代码使用 C 宏,并被设计为类似 C 函数的宏,以根据宏参数为变量生成参数传递。另一种替代代码会很复杂。C 宏使用多个 '##' 运算符,但已经验证了评估顺序无关紧要,并且宏扩展的结果总是可预测的。

此设计决策意味着以下 MISRA 偏差:

  • [MISRA 2012 指令 4.9,咨询]:应优先使用函数,而不是类似功能的宏,而宏可以互换
  • [MISRA 2012 规则 1.3,要求]:不应发生未定义或严重未指定的行为
  • [MISRA 2012 规则 20.10,咨询]:不应使用 # 和 ## 预处理器操作符

相关源代码位于文件 rtx_core_cm.h 中,并标记为:

//lint -save -e9023 -e9024 -e9026 "Function-like macros using '#/##'" [MISRA Note 10]

[MISRA 注释 11]:SVC 调用使用汇编代码

SVC(服务调用)函数构造为 C 和内联汇编的混合,因为它需要访问 CPU 寄存器以进行参数传递。功能参数映射到 CPU 寄存器 R0..R3 和 SVC 功能编号到 CPU 寄存器 R12(或 R7)。对于装配互通,函数参数被转换为无符号整型值。

SVC 调用后的函数返回值映射到 CPU 寄存器 R0。返回值从无符号整型转换为目标值。

已经证实,这种方法没有副作用并且定义明确。

此设计决策意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 10.3,必填]:分配给较窄或不同基本类型的表达式
  • [MISRA 2012 规则 10.5,咨询]:不合格的演员; 不能从 '基本无符号' 转换为 '基本枚举'
  • [MISRA 2012 规则 11.1,要求]:不得在指向函数的指针和任何其他类型之间进行转换
  • [MISRA 2012 规则 11.4,咨询]:不应在指向对象的指针和整数类型之间执行转换
  • [MISRA 2012 规则 11.6,要求]:在无效指针和算术类型之间不应执行强制转换

SVC 功能被标记为库模块,不被 PC-lint 处理。

//lint ++flb "Library Begin" [MISRA Note 11]
:
//lint --flb "Library End"

代码示例:

// Service Calls definitions
//lint ++flb "Library Begin" [MISRA Note 11]
SVC0_1(Delay, osStatus_t, uint32_t)
SVC0_1(DelayUntil, osStatus_t, uint32_t)
//lint --flb "Library End"

PC-lint 不处理 ASM 输入/输出操作数列表,因此错误地识别问题:

  • 最后一个值分配给变量未使用
  • 符号未被引用

[MISRA 注释 12]:独占访问指令的使用

RTX5 实现使用 CPU 指令 LDREX 和 STREX(当处理器支持时)实现原子操作。这种原子操作消除了中断锁定的要求。原子操作是使用内联汇编实现的。

PC-lint 无法处理包括输入/输出操作数列表在内的汇编程序指令,因此会错误地识别问题:

  • 符号未初始化
  • 符号未被引用
  • 符号未被引用
  • 指针参数可以被声明为指向 const

已经证实,原子操作没有副作用,并且已经定义好。

实现原子指令的函数被标记为库模块,并且不被 PC-lint 处理。相关的源代码标有:

//lint ++flb "Library Begin" [MISRA Note 12]
:
//lint --flb "Library End"

[MISRA 注释 13]:事件记录器的使用

事件记录器是一个通用事件记录器,并调用相关函数来记录事件。 函数参数为 32 位 ID,32 位值,指向 void(数据)的指针,并记录为 32 位数字。 事件记录器的参数可能需要将操作转换为无符号整型,但无副作用且定义良好。

返回值表示成功或失败。没有必要检查返回值,因为当事件记录器功能失败时不采取任何操作。EventID 宏(外部事件记录器的一部分)基于移动的输入参数构建 ID,用 '&' 掩盖并与 '|' 组合。零值输入参数是有效的,并使用 '&' 和 '|' 作为零。

事件记录器的使用意味着以下 MISRA 偏差:

  • [MISRA 2012 规则 11.1,要求]:不得在指向函数的指针和任何其他类型之间进行转换
  • [MISRA 2012 规则 11.4,咨询]:不应在指向对象的指针和整数类型之间执行转换
  • [MISRA 2012 规则 11.6,要求]:在无效指针和算术类型之间不应执行强制转换。另外 PC-Lint 问题:
  • 信息 835:零作为操作符 '&' 的左参数给出,
  • 信息 845:运算符 '|' 的正确参数肯定是 0

调用事件记录器的函数位于模块 rtx_evr.c 中,并且相关的 PC-Lint 消息被禁用:

//lint -e923 -e9074 -e9078 -emacro((835,845),EventID) [MISRA Note 13]