FreeRTOS之TCB

9 篇文章 1 订阅

FreeRTOSMini实现了最小任务调度。现在分开介绍进程调度重要部分。进程调度的基础首先是定义任务调度的数据结构,来保存任务堆栈结构和任务状态所在状态列表,然后就是任务的优先级唯一号等。

最小Mini内核参照“FreeRTOSMini”篇,包含源码下载地址。

TCB重要信息有:
1.栈顶指针(pxTopOfStack并且告诉编译器不要优化,随时会变化),和芯片位数一致的整形指针指向任务栈顶。任务让出CPU时候要把寄存器R0-R15、程序计数器、程序状态寄存器等CPU执行上下文压到任务栈。任务被执行时候要通过TCB的栈顶指针从任务栈出栈恢复之前保存的上下文信息再接着执行。

2.状态列表项(xStateListItem),通过该列表项所属列表的指针快速调整状态列表项所在状态,即TCB所在状态。调度时候该列表项在就绪列表,当前运行项,等待列表不停切换。

3.任务优先级(uxPriority),任务调度按优先级实行抢占事调度,优先级也决定任务状态列表项所进的相应优先级列表。

4.其他的就不是那么重要了,像任务唯一号等等都是些辅助方面的。

为什么栈顶指针变量要放TCB结构体第一位(第二位就不行吗?)。这里就涉及到C语言结构体的实现了。
1.什么是结构体?
2.结构体内存本质是什么?

下面是我对结构体的理解:
结构体就是按结构把一些属性打包。这些属性占用的内存空间要求固定,如果是字符数组,长度是固定的。或者char*指针,指针大小是固定的,指针指向的东西长度可以不固定。结构体更像是面向简化开发写代码的东西。代码只要某个结构体对象->属性来操作指定属性。对编译器来说都是把属性缓存地址的偏移量。结构在内存的表现就是一个固定长度的内存占用。从第一个属性下来依次按类型从结构体开始位置指针偏移。

比如下面结构体成员内存就如下图:

//测试
struct Test
{
	char * A;
	int B;
	char C[10];
	int D;
} 

在这里插入图片描述

所以为什么要把存栈顶地址的变量放到结构体第一个属性。因为第一个数组的起始地址等于结构体地址。这样可以方便汇编代码通过当前运行TCB结构体快速得到栈顶指针方便操作。类似下图代码,因为是汇编代码,不好操作结构体的->属性。如果不放在第一个那么这些代码都得按位置偏移找到栈顶指针内存位置了。

在这里插入图片描述

TCB结构体如下(task control block):

//任务控制块结构体TCB,创建任务成功后返回该结构体
typedef struct tskTaskControlBlock
{
	//告诉编译器该变量值可能随时发生变化,且这种变化并不是代码引起的。
	//指向放置在任务堆栈上的最后一项的位置。 这必须是 TCB 结构的第一个成员
	volatile StackType_t* pxTopOfStack;

	//表示任务状态,不同的状态会挂接在不同的状态链表下
	//一共有四种状态分别是运行状态(Running State),就绪状态(Ready State),
	//阻塞状态(Blocked State),挂起状态(Suspended State)
	//运行状态:正在执行的任务。
	//就绪状态:等待获得执行权的任务。
	//阻塞状态:直到某些条件达成才会重新进入就绪态等待获得执行权,否则不会执行的任务。
	//挂起状态:除非被主动恢复,否则永远不会执行。
  //这四种链表分别对应着pxCurrentTCB,pxReadyTasksLists,pxDelayedTaskList,xSuspendedTaskList这四个变量。
	//除运行状态外,任务处于其它状态时,都是通过将任务TCB中的xStateListItem挂到相应的链表下来表示的
	ListItem_t xStateListItem;                  /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
	//事件链表项,会挂接到不同事件链表下
	ListItem_t xEventListItem;                  /*< Used to reference a task from an event list. */
//任务优先级,0是最低的,越大优先级越高
	UBaseType_t uxPriority;                     /*< The priority of the task.  0 is the lowest priority. */
  //指向堆栈起始位置(低地址),这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
	StackType_t* pxStack;                      /*< Points to the start of the stack. */
		//任务名称
	char pcTaskName[16];
	//记录临界段的嵌套层数
	UBaseType_t uxCriticalNesting;
	//跟踪调试用的变量
	UBaseType_t uxTCBNumber;  /*< Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */
	UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
	//任务优先级被临时提高时,保存任务原本的优先级
	UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
	UBaseType_t uxMutexesHeld;

	//用来标记这个任务的栈是不是静态分配的
	uint8_t ucStaticallyAllocated;
	//延时是否被打断
	uint8_t ucDelayAborted;
	//错误标识
	int iTaskErrno;
} tskTCB;

//老别名
typedef tskTCB TCB_t;
//定义tcb别名
typedef struct tskTaskControlBlock* TaskHandle_t;

通过这篇理解TCB数据结构栈顶指针为什么放第一位。和理解C语言结构体内存布局。从编译器的角度结构体就是固定长度的内存占用块。编译器把属性操作翻译为相对结构体地址偏移的地址操作。地址的偏移就按结构体的属性从上到下按每个类型的占用空间计算。就算是C的结构体,起始也可以直接按地址偏移操作内存地址的值。整个C各种类型和指针、取址都建立在CPU位数的地址上。指针即地址,变量只是一个内存地址的别名。如FreeRTOS的当前运行任务块pxCurrentTCB变量就是一个内存地址别名。这个地址指向的内存存TCB结构体的首地址。换了当前运行任务就把该内存值改新要执行TCB结构体首地址。
在这里插入图片描述

OS原来如此美妙,这就是通过TCB悟出来的点,分享给大家

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小乌鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值