RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)
1 rt_list_entry函数
rt_list_entry函数的作用是根据已知成员的地址,算出其结构体的首地址。函数定义如下(在rtservice.h
中):
1.1 源码分析
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
/**
* @brief 获取结构体入口地址
* @param node 入口节点地址
* @param type 节点所在结构体的类型
* @param member 节点在该结构体中的成员名称
*/
#define rt_list_entry(node, type, member) \
rt_container_of(node, type, member)
(unsigned long)(&((type *)0)->member))
:其中(type *)0
表示将数值0强制转换为type 类型(一般为结构体类型)的指针变量,然后让该指针访问结构体中名称为member
的成员。于是&((type *)0)->member)
就表示该成员在内存中的地址,由于结构体是按照定义顺序将每个成员放入到内存中的,因此该成员地址等价于结构体的基地址+该成员所在结构体基地址的偏移字节数,结构体基地址在这里就是指针变量(type *)0
本身,即为0,所以&((type *)0)->member)
就是名为member
的成员相对于type类型的结构体首地址偏移的字节数。最后将其转换为无符号长整型。(char *)(ptr)
:ptr
就是实际定义的结构体变量的某成员地址,然后将其转换为char *
类型的指针变量。
问:为什么一定要将ptr
转换成char*
类型呢?
答:因为指针变量加减某值后偏移的字节数取决于它指向的变量类型,如int*
类型的指针加1后,是相对于指针变量当前地址向后偏移4个字节。在这里需要将ptr
指向的成员,减去该成员相对结构体首地址的偏移字节数(unsigned long)(&((type *)0)->member))
,以得到该成员所在结构体变量的地址。所以只能转换成char*
的指针去减偏移的字节数,如果用int*
类型的指针,那么就会偏移四倍于它的字节,得到一个错误的地址。
1.2 应用示例
如释放互斥量函数rt_mutex_release
中有这样一段代码:
/* 获取互斥量等待队列第一个线程的结构体地址 */
thread = rt_list_entry(mutex->parent.suspend_thread.next,
struct rt_thread,
tlist);
其中 rt_thread
线程结构体定义为(部分省略):
struct rt_thread
{
/* rt object */
char name[RT_NAME_MAX]; /* 线程名称 */
rt_uint8_t type; /* 对象类型 */
rt_uint8_t flags; /* 线程标志位 */
...
rt_list_t list; /* 对象列表 */
rt_list_t tlist; /* 线程列表 */
...
};
mutex->parent.suspend_thread.next
表示该互斥量线程挂起链表中第一个节点,为rt_list_t
类型,为rt_thread
线程结构体中的tlist
成员,通过rt_list_entry函数获取该线程的地址,然后让该线程持有互斥量。
2 字节对齐宏
字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
2.1 源码分析
- RT_ALIGN
直接分析源码,该宏在rtdef.h
中定义:
#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
作用为将字节数size
向上提升为align
的整数倍,需要注意的是align
要为2的幂次方。
(size) + (align) - 1
:align-1
为size
除以align
的最大余数,用size
去加它是为了便于向上取align
的整数倍数值。~((align) - 1)
:为余数的掩码,将其&上(size) + (align) - 1
即可清除其余数,得到align
的整数倍数值。
- RT_ALIGN_DOWN
将字节数size
向下提升到align
的整数倍的:
#define RT_ALIGN_DOWN(size, align) ((size) & ~((align) - 1))
- ALIGN
#define ALIGN(n) __attribute__((aligned(n)))
该宏作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 依然只能取 2 的幂次方。
关键字__attribute__
允许你在定义struct、union、变量等类型时指定特殊属性。此关键字后面是跟着双括号括起来的属性说明。__attribute__
不属于标准C语言,它是GCC对C语言的一个扩展用法。
__attribute__((aligned(n)))
:此属性指定了指定类型的变量的最小对齐(以字节为单位)。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
注意:对齐属性的有效性会受到链接器(linker)固有限制的限制,即如果你的链接器仅仅支持8字节对齐,即使你指定16字节对齐,那么它也仅仅提供8字节对齐。
2.2 应用示例
- RT_ALIGN(17,4)= 20
(size) + (align) - 1 = 17 + 3 = 0001 0100
~((align) - 1) = ~(3) = ~(0000 0011)
0001 0100 & ~(0000 0011) = 0001 0100 = 20 // 将后两位清0,后两位即为最大余数3
上面说过align
必须为2的n次幂,下面测试其不为2的n次幂时的错误用法:
- RT_ALIGN(17,3)
正确结果:18
实际运算结果:17
(size) + (align) - 1 = 17 + 2 = 0001 0011
~((align) - 1) = ~(2) = ~(0000 0010)
0001 0011 & ~(0000 0010) = 0001 0001 = 17
参考文章:Linux下__attribute__((aligned(n)))的使用
END