FreeRTOS 列表 List 源码解析


链表是 FreeRTOS 的核心数据结构,有关任务调度、延时、阻塞、事件等操作都是通过对链表进行操作进而实现的。本节将详细分析源码文件 list.clist.h 的内容,为后续的任务队列等的实现奠定基础。

一、链表及链表项的定义

FreeRTOS 使用的链表结构是环形的双向链表,而关于链表节点的数据结构都在 list.h 中定义。

1、链表节点数据结构 xList_ITEM

首先来看链表节点数据结构定义:

struct xLIST_ITEM
{
   
   
	// 第一个和最后一个成员值
	// 当 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 被使能的时候会被设定为一个固定值,用来检验一个列表项数据是否完整
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE             
    configLIST_VOLATILE TickType_t xItemValue;           // 辅助值,用于帮助节点做顺序排列
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;      // 指向前一个链表项
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;  // 指向后一个链表项
    void * pvOwner;                                      // 类似侵入式链表,指向包含该链表项的对象的地址,通常是TCB 
    struct xLIST * configLIST_VOLATILE pxContainer;      // 指向该节点所在的链表
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE             
};
typedef struct xLIST_ITEM ListItem_t;

其结构如下:

这里如果使用 configLIST_VOLATILE,其会被替换为 volatile 关键字。

#define configLIST_VOLATILE volatile

volatile 关键字是让编译器不对该变量进行优化,所谓的优化可以理解为其在生成汇编时,若多次读取该变量时其可能会将变量值放入寄存器中以加快读取速度,而不是真正的读取,这使得当某个变量会快速变化时,优化后“读取”的值并不是变量真实的值。当使用 volatile 关键字时,其会强迫编译器每次使用该变量时都真正的对它进行一次读取。

在链表项的结构体构造中值得注意的是 pvOwnerpxContainer 这两个成员变量。

  • pvOwner 指向该节点的拥有者,即该节点内嵌在哪个数据结构中,属于哪个数据结构的一个成员。它提供了一种可以快速访问由此链表项代表的对象的方法。
  • pxContainer 用于指向该节点所在的链表,通常指向链表的根节点。它则提供了一种快速访问其所属链表的方法。

这种处理方式大大提高了链表在任务调度等应用中的处理速度,提高系统效率。

侵入式链表


在 Linux 内核中有很多侵入式的链表的设计,比如在 Linux 中提供的链表项的定义为:

struct list_head
{
        struct list_head *next, *prev;
}

使用链表时只需要将其包含进定义的对象中即可:

struct list_head
{
         // 一些其它成员定义....
         struct list_head *next, *prev;
         // 一些其它成员定义....
}

在此它没有定义类似 ListItem_tpxContainer 这样的成员变量,其获得包含该链表项的对象地址是通过下面一段著名的宏定义实现的:

#define offsetof(s,m) (size_t)&(((s *)0)->m
#define container_of(ptr, type, member) \
({ \
         const typeof( ((type *)0)->member ) *__mptr = (ptr); \
         (type *)( (char *)__mptr - offsetof(type,member) ); \
})

使用实例:

struct node test;
struct list_head *list_item_add = &test.list_item;
struct node *test_add = container_of(list_item_add, struct node, list_item);

container_of() 的实现思路简单概括就是:将成员变量地址减去成员变量在结构体类型中的变量便是实例对象的存储地址。以 struct node 结构体为例,其实例 test 在内存中的存储方式如下图左侧所示。下图右侧给出了如何获得成员在结构体存储中的偏移量,当 &test=0x00 时,其成员的地址便是所需要的偏移量。

因此,offsetof() 宏,其所作的事就是获得偏移量。而 container_of() 宏中的:

(type *)( (char *)__mptr - offsetof(type,member) );

便是用成员地址减去偏移量来获得实例的地址。至于 container_of() 宏中的前一句:

const typeof( ((type *)0)->member ) *__mptr = (ptr);

实时上是起到一个类型检验的作用,拓展关键字 typeof 可以获得变量的类型,如果传入的ptr的类型与成员变量类型不符,那么编译器便会抛出警告,便于检查是否出错。注意 typeof 并不是标准 C 中的关键字,如果所用的编译器不支持,可以将第一句删除,将第二句中的__mptr 替换为 ptr,宏 container_of() 仍然是正确的。

2、链表精简节点结构 xMINI_LIST_ITEM

这个结构是专门用来在下面要讲的 xLIST 表示尾节点,相比于刚才讲到的 xList_ITEM 要精简不少。

struct xMINI_LIST_ITEM
{
   
   
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE;           // 校验值
    configLIST_VOLATILE TickType_t xItemValue;			 // 辅助值,用于帮助节点做升序排列
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;      // 指向下一个链表项
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;  // 指向前一个链表项
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

3、链表根节点结构 xLIST

typedef struct xLIST
{
   
   
    listFIRST_LIST_INTEGRITY_CHECK_VALUE       // 校验值
    volatile UBaseType_t uxNumberOfItems;      // 记录该链表里有多少成员(根节点除外)
    ListItem_t * configLIST_VOLATILE pxIndex;  // 链表节点索引指针
    MiniListItem_t xListEnd;                   // 链表尾部(实际也是链表的第一个节点),为节省内存使用Mini 链表项
    listSECOND_LIST_INTEGRITY_CHECK_VALUE      // 校验值
} List_t;

结构图如下:

评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值