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):
typedef struct tskTaskControlBlock
{
volatile StackType_t* pxTopOfStack;
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t* pxStack;
char pcTaskName[16];
UBaseType_t uxCriticalNesting;
UBaseType_t uxTCBNumber;
UBaseType_t uxTaskNumber;
UBaseType_t uxBasePriority;
UBaseType_t uxMutexesHeld;
uint8_t ucStaticallyAllocated;
uint8_t ucDelayAborted;
int iTaskErrno;
} tskTCB;
typedef tskTCB TCB_t;
typedef struct tskTaskControlBlock* TaskHandle_t;
通过这篇理解TCB数据结构栈顶指针为什么放第一位。和理解C语言结构体内存布局。从编译器的角度结构体就是固定长度的内存占用块。编译器把属性操作翻译为相对结构体地址偏移的地址操作。地址的偏移就按结构体的属性从上到下按每个类型的占用空间计算。就算是C的结构体,起始也可以直接按地址偏移操作内存地址的值。整个C各种类型和指针、取址都建立在CPU位数的地址上。指针即地址,变量只是一个内存地址的别名。如FreeRTOS的当前运行任务块pxCurrentTCB变量就是一个内存地址别名。这个地址指向的内存存TCB结构体的首地址。换了当前运行任务就把该内存值改新要执行TCB结构体首地址。
OS原来如此美妙,这就是通过TCB悟出来的点,分享给大家
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)