FreeRTOS 任务调度 List 组织

@(嵌入式)

Freertos

FreeRtos

简述

前面了解了 FreeRTOS 的内存管理,接下来看看任务调度,这也是一个操作系统中最重要的一部分,而其任务调度大量使用了链表(list.c 实现),调度器使用链表跟踪不同状态下的任务(就绪、挂起、延时的任务,都会被挂接到各自的链表中),所以这里用一定篇幅介绍下主要供调度使用的链表文件是如何组织的。

我目前使用的源码版本是 v9.0.0

数据结构

FreeRTOS 链表中主要的数据结构是链表(xLIST)和链表项(xLIST_ITEM),以及一个低配的链表项(xMINI_LIST_ITEM)包含于链表中,具体定义和作用下面介绍。定义内容在 Source/include/list.h

xLIST_ITEM

链表项就是链表每个节点的数据结构,其每个成员的具体作用如注释所示。

其中开头和结尾两个宏的作用是检查数据是否完整,当设置 Source/include/projdefs.h configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 为1的时候,以下结构体会在开始和结尾添加两个变量存储 Magic number(0x5a5a...)供校验。
(后面说明假设没有开启校验)

struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
    // 保存如任务优先级(任务切换)、阻塞时间(延时挂起时)等信息
    // 供列表排序用,递减
    configLIST_VOLATILE TickType_t xItemValue;
    // 指向下一个节点
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
    // 指向上一个节点
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
    // 指向对象,主要是 TCB
    void * pvOwner; 
    // 指向所属链表 xLIST
    void * configLIST_VOLATILE pvContainer;         
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE      
};

xLIST

链表包含多个链表项(节点),链表数据结构中包含一个简单的列表项用于表示其尾部(前面提到的低配版列表项)用于标记链表的结束(包含指向第一个节点)

struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
    // =0xFFF... 判断链表结束时用
    configLIST_VOLATILE TickType_t xItemValue;
    // 指向链表第一个节点
    // 在 xLIST 中指向自身说明链表空
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
    // 指向链表的最后一个节点
    // 在 xLIST 中初始化指向自身
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

/*
 * Definition of the type of queue used by the scheduler.
 */
typedef struct xLIST
{
    listFIRST_LIST_INTEGRITY_CHECK_VALUE
    // 节点数统计
    configLIST_VOLATILE UBaseType_t uxNumberOfItems;
    // 用于遍历链表,默认指向 xListEnd
    ListItem_t * configLIST_VOLATILE pxIndex;
    MiniListItem_t xListEnd;                        
    listSECOND_LIST_INTEGRITY_CHECK_VALUE           
} List_t;

每个链表的结构如下所示,通过其成员 xListEnd 的指针指向其他链表项,组织成一个完整的链表。

xList.png

链表实现

结合 list.c, 看看 FreeRTOS 的链表是如何实现的。

初始化

在使用链表前,需要调用 FreeRTOS 提供的初始化函数进行初始化

void vListInitialise( List_t * const pxList )
{
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );           
    // xListEnd 标记链表
    // 设置最大值 作为判断链表结束
    pxList->xListEnd.xItemValue = portMAX_DELAY;
    // 链表入口,指向第一个节点;没有节点 则指向自己
    pxList->xListEnd.pxNext = 
        ( ListItem_t * ) &( pxList->xListEnd );
    // 链表结束,指向最后一个节点;没有节点,则指向自己 
    pxList->xListEnd.pxPrevious = 
        ( ListItem_t * ) &( pxList->xListEnd );
    
    // 初始化链表节点计数器 = 0
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
    
    // 前面提到的检测 如果置 1
    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

初始化结束后,链表组织如下图所示。

init.png

相应地准备插入新节点前,需要对新节点进行初始化,可以调用函数 :
void vListInitialiseItem( ListItem_t * const pxItem )
主要功能是检测新节点是否可用(如果开启检测)

插入新节点

完成链表初始化后,在需要插入新节点的时候,可以调用插入函数完成,新节点按照其 xItemValue 的值逆序插入链表中。

void vListInsert( List_t * const pxList, 
        ListItem_t * const pxNewListItem )
{
    ListItem_t *pxIterator;
    // 取插入排序的依据值
    const TickType_t xValueOfInsertion = 
            pxNewListItem->xItemValue;
    
    // 检测, 如果开启了
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    if( xValueOfInsertion == portMAX_DELAY )
    {
        // 对于value 等于最大值的直接插入链表尾
        // 避免导致下面 for 的死循环
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        // 查找合适的插入位置 从小到大排序
        // 注意等号,如果存在相同值,后插入的在最后,
        // 保证每个 task 都能被运行 
        for(pxIterator = (ListItem_t *) &(pxList->xListEnd);
            pxIterator->pxNext->xItemValue <= xValueOfInsertion;
                pxIterator = pxIterator->pxNext )
        {
        /*
        在调试过程中如果发现程序挂死在此处,可能的情况:
        1.stack overflow
        2.中断优先级错误 (尤其在cotex-m系列 MCU)
        3.进入边界(关闭所有中断)后调用可能导致挂起的API,
            或者中断中使用没有"FrimISR"的API
        4.在队列,信号量没有初始化或者调度器没有起来前使用它们
        */
        }
    }
    // 插入节点 如下图 : 1步2步3步4步..(牵着手)
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;
    
    // 更新插入节点所属链表
    pxNewListItem->pvContainer = ( void * ) pxList;
    // 借点统计
    ( pxList->uxNumberOfItems )++;
}

插入一个xItemValue == 1的节点 xLIST_ITEM_1 :

insert1.png

再插入一个xItemValue == 2 的节点 xLIST_ITEM_2, 按照链表的排序规则(递减),得到如下 链表 :

insert2.png

从链表尾插入新节点

FreeRTOS 提供另外一个插入节点的函数,可以直接把新节点插入到链表的尾部。
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
相比一般插入需要按值查找插入位置,从尾部插入更为简单。

移除节点

由于节点中包含指向所属链表的指针,所以移除节点时只需传递该节点即可。

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
    // 移除节点
    // 并没有释放内存之类的操作
    // 需要注意是否需要释放内存,自行调用。
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

    // !!! 细节 : 避免指向已经移除的节点
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    // 清除所属关系
    pxItemToRemove->pvContainer = NULL;
    // 更新统计
    ( pxList->uxNumberOfItems )--;
    return pxList->uxNumberOfItems;
}

到此,结束,还有一些简单的宏定义这里不做详细介绍,基本都是链表的操作,这里介绍下,避免后续 task 调度分析带来一些不必要的困惑。


参考

源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容

  • 一.线性表 定义:零个或者多个元素的有限序列。也就是说它得满足以下几个条件:  ①该序列的数据元素是有限的。  ②...
    Geeks_Liu阅读 2,701评论 1 12
  • 第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的“同行”Apache吧!Ngi...
    JokerW阅读 32,694评论 24 1,002
  • 。。
    沈溪2019阅读 142评论 0 0
  • 鹧鸪天 山远微醺黛若眉,溪桥柳细嗅蔷薇。 芳尘似被前缘误,香冷扬花尘起飞。 执旧佩,泪空垂,十年相忆...
    嘟嘟嘟杜若蘅阅读 364评论 0 0
  • 浣溪沙《画卿容》(新韵) 梅子又黄人未逢,栏前朝暮数征鸿。 云楼别后忆无穷。 寂月今宵犹暗淡,离愁更甚思更浓。 挑...
    云中吉星阅读 168评论 6 5