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 RT_ALIGN宏
2.1 源码分析
直接分析源码,该宏在rtdef.h
中定义:
#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
作用为将字节数size
向上提升为align
的整数倍,需要注意的是align
要为2的n次幂。
(size) + (align) - 1
:align-1
为size
除以align
的最大余数,用size
去加它是为了便于向上取align
的整数倍数值。~((align) - 1)
:为余数的掩码,将其与上(size) + (align) - 1
即可清除其余数,得到align
的整数倍数值。
此外,还有将字节数size
向下提升到align
的整数倍的宏RT_ALIGN_DOWN:
#define RT_ALIGN_DOWN(size, align) ((size) & ~((align) - 1))
2.2 应用示例
- RT_ALIGN(17,4)= 20
(size) + (align) - 1 = 17 + 3 = 0001 0101
~((align) - 1) = ~(3) = ~(0000 0011)
0001 0101 & ~(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
END