IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> FreeRTOS操作系统理论知识笔记(全网最详细的亲笔手写的理论知识!我敢保证,你一定会懂!)(持续更新) -> 正文阅读

[嵌入式]FreeRTOS操作系统理论知识笔记(全网最详细的亲笔手写的理论知识!我敢保证,你一定会懂!)(持续更新)

目录

零、ARM架构与C语言汇编指令

1)ARM架构

2)汇编指令

一、什么是FreeRTOS,为什么要学它?

1)第一个问题的回答

2)第二个问题的回答

二、RTOS的任务是什么?RTOS怎么实现多任务的管理?

1)第一个问题的回答

-------->分支问题1:运行位置很好理解,就是被打断的时候函数执行的位置(当前指令PC的值),那什么是运行环境呢?

-------->分支问题2:栈在FreeRTOS里怎么表示呢?

-------->分支问题3:任务在RTOS怎么去创建一个任务?

-------->分支问题4:在FreeRTOS怎么去表示一个任务?

2)第二个问题的回答

-------->分支问题1:具体是怎么实现任务调度?

-------->分支问题2:如果所有任务优先级都是0会发生什么?

(下面是如果设置不抢占,会导致无法开启时间片轮转)

-------->分支问题3:什么情况下一个任务还没完成一个Tick时间就放弃运行了?

三、FreeRTOS中的调度链表

四、FreeRTOS中的队列(关中断,环形缓冲区,链表)

1)为什么引入队列?--->互斥机制、休眠与唤醒、提高CPU利用率

-------->分支问题1:什么是互斥机制?

-------->分支问题2:为什么队列的引入可以提高CPU的利用率?

-------->分支问题3:怎么实现互斥?环形缓冲区和链表有什么作用?怎么休眠一个任务,又怎么知道唤醒哪个任务?

-------->分支问题4:在RTOS中队列怎么表示?

-------->分支问题5:如果一直没有任务写/读队列,那岂不是就会有任务一直等待?

零、ARM架构与C语言汇编指令

1)ARM架构

????????STM32的CPU负责计算和执行指令,RAM负责数据的存储,Flash负责代码的存储。CPU有很多的寄存器,其中有典型的R0到R15寄存器,R13为栈SP,R14为返回地址LR即下一条指令的位置,R15为当前指令地址PC

2)汇编指令

? ? ? ? 1.汇编读(Load,即LDR):CPU读到Flash当中的“读a变量的指令操作”,在RAM中找到a变量的地址,读取a变量的值保存在CPU的一个寄存器当中比如R0(具体读几个字节跟汇编读指令有关,比如LDR就是读4个字节,LDRH就是读2个字节)

? ? ? ? 2.汇编写(Store,即STR):CPU读到Flash当中的“写a变量的指令操作”,在RAM中找到a变量的地址,先读a变量的值保存在CPU的一个寄存器当中,CPU通过计算得到a变量新的值,然后再把这个值写到RAM中的a变量

? ? ? ? 3.汇编计算(常见的加法减法,高级的还有乘除法)

? ? ? ? 4.汇编Push(本质是写内存):比如CPU读到Flash当中的Push{R3,LR},指的是CPU把自己的R3和LR寄存器的值写到CPU的R13寄存器(栈)当前所记录的RAM的位置(注意到这里要写两个寄存器的值到RAM中,意味着写完一个值,R13寄存器所记录的RAM的位置会发生偏移确保写下一个值)

? ? ? ? 5.汇编Pop(本质是读内存):比如CPU读到Flash当中的Pop{R3,PC},指的是把CPU的R13寄存器(栈)当前所记录的RAM的那个位置的值”依次“(说明有偏移)赋值给R3和LR

一、什么是FreeRTOS,为什么要学它?

1)第一个问题的回答

FreeRTOS是基于单片机的一种多线程多任务管理的操作系统,即(Real Time Operating System)实时操作系统

????????比如:一个人不仅要给孩子喂饭还要回同事信息

????????裸机做法是:喂完一口饭然后再去回同事一条信息

????????RTOS做法是:记喂一口饭为A任务并拆分成(假设是4步)4个步骤,记回一个信息为B任务并拆分成(假设是3步)3个步骤,即做A任务的步骤1然后做B任务的步骤1,再做A任务的步骤2然后做B任务的步骤2如此循环

2)第二个问题的回答

????????一是项目需要,随着产品 要实现的功能越来越多,单纯的裸机系统已经不能够完美地解决问题,反而会使编程变得 更加复杂,如果想降低编程的难度,我们可以考虑引入 RTOS 实现多任务管理,这是使用 RTOS 的最大优势。

? ? ? 二是学习的需要,必须学习更高级的东西,实现更好的职业规划,为 将来走向人生巅峰迎娶白富美做准备,而不是一味的在裸机编程上面死磕。作为一个合格 的嵌入式软件工程师,学习是永远不能停歇的事,时刻都得为将来准备。书到用时方恨少, 我希望机会来临时你不要有这种感觉。
?

二、RTOS的任务是什么?RTOS怎么实现多任务的管理?

1)第一个问题的回答

????????任务是一个个能够运行的函数和它们的栈(栈保存运行位置和运行环境),这个栈是CPU的R13寄存器

-------->分支问题1:运行位置很好理解,就是被打断的时候函数执行的位置(当前指令PC的值),那什么是运行环境呢?

------->在“零部分,ARM架构和C语言汇编指令”中,已经知道了C语言底层实现的过程了。任务其实不仅仅是一个函数,因为随时会发生任务的切换,所以在这里要保存各个任务他们的局部变量,否则当执行完别的函数回到刚才被打断的函数时,局部变量就会丢失,而局部变量的保存又是通过汇编指令保存到CPU的典型寄存器(R0到R15)当中,所以运行环境相当于这16个寄存器的值(当然还有别的寄存器),并且要”依次“保存在CPU的R13寄存器(栈)所指向的RAM的位置

故:任务的切换需要保存现场,保存现场又是保存在栈当中的,所以任务=运行的函数+各自的栈

-------->分支问题2:栈在FreeRTOS里怎么表示呢?

--------->FreeRTOS会事先定义一个巨大的数组,大概有17K的大小,然后以后需要栈就在这个数组里进行划分即可,或者是用malloc进行分配。所以有静态创建(xTaskCreateStatic函数)和动态创建(xTaskCreate函数)

(下面是这个巨大数组的定义)

/* Allocate the memory for the heap. */
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )

/* The application writer has already defined the array used for the RTOS
* heap - probably so it can be placed in a special segment or address. */
    extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];//在这里<--------------
#endif /* configAPPLICATION_ALLOCATED_HEAP */

//这里说明了这个巨大的数组的大小<-----------------
#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 17 * 1024 ) )

-------->分支问题3:任务在RTOS怎么去创建一个任务?

1)--------->●首先明确创建一个任务这件事是一个函数,需要传参

? ? ? ? ? ? ????●第一个参数:传入一个函数(可能顺带还会传入这个函数的参数),所以要有一个指向函数的指针,即函数指针

? ? ? ? ? ? ? ? ●第二个参数:其次是任务的名称

? ? ? ? ? ? ? ? ●第三个参数:栈的深度(字节为单位),用于malloc动态分配一段内存或者是在巨大的数组中进行划分,用来保存这个任务的运行环境,取决于局部变量所占的空间,取决于函数调用的深度(有没有LR的值,如果一个函数它里面不会再去调用别的函数,那么LR的值不会改变,否则就要再额外的把LR的值保存在栈中)

? ? ? ? ? ? ? ? ●第四个参数:第一个参数传入的函数它自己需要的参数,会保存在CPU的R0寄存器

? ? ? ? ? ? ? ? ●第五个参数:这个任务的优先级

? ? ? ? ? ? ? ? ●第六个参数:传出一个句柄,即指向TCB结构体(任务控制块)的指针

(下面是创建函数传入的参数说明)

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                        const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                        const configSTACK_DEPTH_TYPE usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask )

-------->分支问题4:在FreeRTOS怎么去表示一个任务?

2)---------->●首先明确创建一个任务这件事需要传参,传入的参数一个一个赋值给TCB这个结构体

? ? ? ? ? ? ? ? ?●创建任务的第一个参数:函数指针也就是函数的地址,在完成了别的任务后,要想回到刚才被打断的任务,只要知道被打断的任务的地址就可以回来了,所以把这个函数的地址保存在PC寄存器,也保存在栈里面了,这样要返回到这个被打断的任务,就可以去它自己的栈里找PC寄存器的值找到它被打断的位置了

? ? ? ? ? ? ? ? ?●创建任务的第二个参数:任务的名称保存在TCB的第六个成员

? ? ? ? ? ? ? ? ?●创建任务的第三个参数:由于需要保存现场要有一个栈,要么是动态分配内存要么是在巨大的数组中划分一块内存,最终每个任务有自己的栈,这个栈的地址保存在TCB的第五个成员,这样如果要恢复一个任务的现场,就可以找到任务它自己的栈在哪里了

?????????????????●创建任务的第四个参数:传入的函数它自己的参数已经事先保存在R0寄存器,保存在栈里面了,这样返回被打断的任务就可以找到R0的值传递给创建任务的第一个参数--函数指针所指向的函数它自己的参数

?????????????????●创建任务的第五个参数:任务优先级保存在TCB的第四个成员

? ? ? ? ? ? ? ? ?●TCB的第一个成员是栈的另一个端头的位置

? ? ? ? ? ? ? ? ?●TCB的第二个成员?????

? ? ? ? ? ? ? ? ?●TCB的第三个成员?????

故:在TCB结构体里,没有体现到创建任务传入的第一个参数和第四个参数

(下面是这个TCB结构体的说明)

typedef struct tskTaskControlBlock{
    volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
    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. */
    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[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
}

2)第二个问题的回答

? ? ? ? 1.出现任务的切换一定会保存现场(已在上面详细说明)

? ? ? ? 2.任务的调度机制----即暂停当前的任务,找到下一个要完成的任务并且去执行它

-------->分支问题1:具体是怎么实现任务调度?

--------->●任务有各自优先级(数字越大优先级越高),对于高优先级的任务可以抢占低优先级的任务,并且如果高优先级的任务不停止,低优先级的任务就无法进行;对于同等的优先级的多个任务,轮流执行,也就是时间片轮转

--------->●任务有各自的运行状态,有正在运行状态(running),有准备好了的状态(ready),有在等待别人被阻塞的状态(blocked),有暂停状态(stop)

--------->●处理方法:找到最高优先级的running态或者ready态的任务,如果大家都是同等优先级,就“依次排队”,轮流执行,排在前面的先运行一个Tick,然后自动的排到队伍的尾部,一直循环

--------->●链表的引入(超级重要!)

1)RTOS中有如下3种链表
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; ?<-----就绪链表数组
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; ? <-----------延迟链表 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
PRIVILEGED_DATA static List_t xPendingReadyList; ? <-----------等待链表

2)分析一下任务调度的过程
--------●首先是把任务按照状态和优先级进行分类。就绪态就放在pxReadyTasksLists中,延迟态就放在pxDelayedTaskList中。对于pxReadyTasksLists它其实本质是一个数组,它的每个元素又是一条链表。假设它是a[5],那a[0]表示优先级为0的任务,并且把这些任务依次排序组成一条链表,那a[1]表示优先级为1的任务,并且把这些任务依次排序组成一条链表,后面依次类推,而对于其他的链表它就是一条单链表vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( (pxTCB)->xStateListItem ) );<---------------------这个函数其实就是把就绪态的任务,根据它的优先级放到它所对应的就绪态链表中,比如任务A为就绪态,优先级是1,那就放在pxReadyTasksLists[1]的尾部
--------●其次是怎么实现同等优先级的任务轮流执行。比如现在依次创建A,B,C三个任务,优先级都是1,并且都是就绪态,那就先按照第一步进行任务分类,之后系统先去这个就绪链表数组从高优先级开始找有没有高优先级的任务,然后发现都在pxReadyTasksLists[1]中,并且顺序是A--B--C,此时当前TCB指向的是C任务所以C任务先运行(也就是右边的先运行,可以看下面的代码),C先执行一个Tick,然后执行完排到A后面轮到B执行,B执行完一个Tick后排在C后面(此时顺序是B--C--A)轮到A执行,A执行完一个Tick后排在B后面轮到C执行(此时是A--B--C)

if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    pxCurrentTCB = pxNewTCB;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
//一开始创建A任务,pxCurrentTCB 指向A,然后创建B任务,pxNewTCB就是B任务,
满足if条件所以pxCurrentTCB 指向B任务(此时是A--B顺序,但是在右边的先运行),
最后创建C任务,pxNewTCB就是C任务,满足if条件所以pxCurrentTCB 指向C任务,
最终顺序就是A--B--C(右边的先运行)


--------●还有一种情况是优先级不同的任务怎么实现调度。比如现在有A,B,C三个任务,都是就绪态,优先级依次是3,2,1。那第一步还是进行任务分类,A在pxReadyTasksLists[3],B在pxReadyTasksLists[2],C在pxReadyTasksLists[1]。首先系统先去这个就绪链表数组从高优先级开始,如果pxReadyTasksLists[5](假设最高是5)有任务那就去执行,发现没有找级别为4,发现还是没有往下找,发现到级别为3有任务A,那就执行A任务一个Tick,然后要进行任务切换了,下一次找任务还是从就绪链表数组从高优先级开始往下找,找到了就执行,这时会发现系统会一直运行A任务,无法执行B和C任务。这时候就需要对高优先级任务进行休眠处理,也就是A任务第一次运行了一个Tick之后,让A任务从pxReadyTasksLists[3]移到延迟链表pxDelayedTaskList中等待一个指定的休眠时间(不要理解成这个时间会让整个CPU停止工作!),这样就能保证第二次任务是B任务来执行,然后执行完B任务放在延迟链表pxDelayedTaskList中A任务的后面,并等待一个指定的休眠时间,这样第三次任务就是C任务来执行,这样就实现了级别不同也能也可以被运行。当休眠时间达到的时候,A任务被唤醒回到自己原本的位置,也就是插入到pxReadyTasksLists[3]的末尾,然后再是B任务被唤醒回到自己原本的位置,也就是插入到pxReadyTasksLists[2]的末尾,依次循环这个过程,这样就实现了休眠后还可以继续运行

(下面是休眠函数操作)

判断这个被休眠的任务(假设是A任务)能否回到就绪链表,
主要看它被休眠那一时刻定时器的值(记为a),比如那个时刻定时器计数到1,
那每次计数一次都会去这个延时链表看看排在第一个的任务能不能回去,(因为第一个任务最先被放进来)
能回去表示a+5==现在的定时器计数值

vTaskDelay( xDelay5ms );<------让高优先级的任务进行休眠,但不是让CPU停止运行!

--------●Tick中断来解决“谁去调度任务”这个问题,在中断函数中进行相应的任务调度工作,它做的事情:取出下一个要完成的任务,保存当前任务,恢复上一次任务

-------->分支问题2:如果所有任务优先级都是0会发生什么?

--------●空闲任务(IdleTack)指优先级为0并且是就绪态的任务,保存在pxReadyTasksLists[0]中,这个任务是在启动调度器后创建的(可以看下面的代码),作用是:如果有一些任务“自杀了”(差不多那个意思哈哈)空闲任务就会去释放这个“自杀”任务的栈

//主函数调用---启动调度器
	vTaskStartScheduler();


//在启动调度器函数中有这么一句话,静态创建任务,表示栈是事先分配好的
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
                                     configIDLE_TASK_NAME,
                                     ulIdleTaskStackSize,
                                     ( void * ) NULL,       
                                     portPRIVILEGE_BIT,  
                                     pxIdleTaskStackBuffer,
                                     pxIdleTaskTCBBuffer);

--------●如果依次创建A,B,C三个任务,优先级为0并且他们都是就绪态任务,那此时他们在pxReadyTasksLists[0]中,且顺序是A--B--C(右边的先运行),由于调用了“启动调度器”这个函数,它还会创建一个空闲任务Idle,这个时候在pxReadyTasksLists[0]中,且顺序是A--B--C--Idle,但是右边的先运行,所以先运行完Idle后(并且在这种特例中空闲函数一直最先被运行),Idle移到A后面,轮到C运行,依次类推

---------●值得注意的是:在上述特例中,且在一定的条件下,空闲任务会有礼让操作!也就是主动放弃一次CPU执行的机会,让别的任务运行,然后终于轮到自己执行的时候,再从被打断的位置进行

---------●根据代码框架有下面这种情况(在上面的特例中):

1)设置了不抢占(有没有设置可礼让结果都会礼让),那就不会有轮流执行(下面代码有),那就是空闲任务礼让后轮到A任务并且一直都是A任务因为不会开启时间片轮转 ,不会因为Tick中断而切换任务,但如果要让别的任务运行,就需要A任务主动放弃,然后轮到B任务并且一直是B任务一直运行? ? ??

2)设置了可抢占可礼让,就是上面说的过程

(下面是空闲函数的代码,已经把核心部分找出来了,注释和一些其他的先删除了)

static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
    portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );

    for( ; ; )
    {
        prvCheckTasksWaitingTermination();
        
        #if ( configUSE_PREEMPTION == 0 )

<-----------这里表示如果设置了任务之间不抢占,空闲函数永远都会去礼让

<-----------但是如果设置了不抢占,那就不会有任务轮流执行的操作,因为在第二个函数代码有写到

            {
              
                taskYIELD();
            }
        #endif 

        #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )

<----------这里表示:设置了任务之间可抢占并且空闲函数会去礼让

            {
               
                if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )             > ( UBaseType_t ) 1 )<-----------------首先看这里,说的是就绪链表中级别为0的除了空闲任务以

外,还有别的级别为0的任务,执行空闲函数退让,也就是空闲任务到这里停止了,并且发起一次任务的调度

                {
                    taskYIELD();<----------空闲函数退让!!!先不运行下面的代码了
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
 
}

(下面是如果设置不抢占,会导致无法开启时间片轮转)

        /* Tasks of equal priority to the currently running task will share
         * processing time (time slice) if preemption is on, and the application
         * writer has not explicitly turned time slicing off. */
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
            {
                if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
                {
                    xSwitchRequired = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }

-------->分支问题3:什么情况下一个任务还没完成一个Tick时间就放弃运行了?

----------●主动放弃:一个是休眠函数;一个是读队列但是目前没数据(后面会介绍)

----------●被动放弃:外设(比如GPIO)唤醒了处于延时链表中的一个高优先级的任务,此时高优先级的任务会立刻抢占

三、FreeRTOS中的调度链表

在”二、RTOS的任务是什么?RTOS怎么实现多任务的管理?“中的”2)第二个问题的回答”中的“分支问题1:具体是怎么实现任务调度?”中的“2)分析一下任务调度的过程”已经非常详细的介绍了调度链表,如果想看视频的话,可以看韦东山老师的讲解:09_回顾调度链表_哔哩哔哩_bilibili

四、FreeRTOS中的队列(关中断,环形缓冲区,链表)

1)为什么引入队列?--->互斥机制、休眠与唤醒、提高CPU利用率

---------●如果有一种情景:A任务和B任务都要对同一个全局变量(假设为a)进行操作,A任务是a++,B任务是a--。对于裸机STM32来说没有任何问题,因为STM32在主函数中假设先完成了A任务,那么a++,然后轮到B任务,a--,一直循环a的值也会按照我们想要的方式变化;对于RTOS来说,由于a++和a--是被拆分成3条汇编指令(1--从内存中读a的值保存在CPU的R0(假设是R0),2--计算a的新值,3--把a的新值写在内存中变量a的位置),假设A任务执行了第1条后,发生任务调度需要保存A任务现场,然后被切换到B任务,然后B任务完成了a--的操作,被切换到A任务,这个时候A任务去执行第2条和第3条。但是我们想要实现的效果是A任务执行完了a++,才轮到B任务去执行a--的操作,所以这就出现了多系统任务中的互斥机制引入

-------->分支问题1:什么是互斥机制?

----------●出现上面情景的问题是因为有多个任务会同时访问同一个变量(假设是a),所以需要有这种机制:各个任务访问a变量时,如果此时是A任务去访问,那就等A任务完成有关对a变量的访问操作执行完之后再进行任务的切换,如果此时是B任务去访问,就等B任务完成对a变量的访问操作再进行任务的切换,以此类推---------->这就是互斥机制(互相排斥互不干扰的访问同个变量)

-------->分支问题2:为什么队列的引入可以提高CPU的利用率?

首先引入一个情景:假设只有两个任务A,B,有一个全局变量Flag一开始为0,A任务在一定条件下会让Flag=1,B任务有一个while循环时刻判断Flag是否为1,如果是逻辑程序跑了完整的100w次,相当于B任务中这个判断会进行100w次,但是对于RTOS程序,可以这么设计:A任务在一定的条件下去写队列,B任务去读队列。

读队列的过程(写过程也类似):

1)如果B任务第一次读队列就有数据(相当于一开始A任务满足条件了让Flag=1),那B任务就不会休眠,继续执行

2)如果B任务第一次读队列但是里面没有数据(当于A任务没有执行Flag=1的操作),要么B任务不选择等待然后返回错误,要么就让B任务休眠,进入休眠有两个步骤:第1个是从就绪态链表移到延时链表,第2个是放在队列当中的一个等待读链表(后面会介绍的)。如果一直没数据,B任务就一直休眠,CPU就会一直去处理A任务。如果某个时刻A写了数据了,也就是队列有数据(相当于Flag=1了),A任务会去唤醒B任务,唤醒有两个步骤:第1个是从延时链表移到它对应的就绪态链表中,第2个是从等待读链表当中进行移除,然后B任务就可以读数据继续运行这样就可以提高CPU利用率,让其他任务去执行!

3)还有一种情况是:如果有别的任务(假设为C任务)等待A任务写完队列,也就是C任务会被放到等待写链表中,并且进行休眠,如果A任务写完数据了,就会去唤醒C任务

-------->分支问题3:怎么实现互斥?环形缓冲区和链表有什么作用?怎么休眠一个任务,又怎么知道唤醒哪个任务?

----------●队列的引入:对于同个变量有写变量和读变量两种操作,队列的一头就是一些任务对变量a的(沿用上面的情景)写操作,队列的另一头就是一些任务对变量a的读操作

----------●写/读队列函数:

(1)关中断-----比如此刻是A任务写数据,关了中断后(包括定时器中断),A任务就可以不用担心在写完数据之前会被打断-------->实现了互斥

(2)写数据到队列中:环形缓冲区

? ? ? ? 这个环形缓冲区其实是一个数组,操作这个数组是让它有点像是环形的。在这个缓冲区中会有W(这次要写的位置的数组下标)和R(这次要读的位置的数组下标),每次写数据,就会把那个要写的值写在数组中,并且W偏移,到了末尾时,在末尾写完了数据,W就回到起点(也就是有取余操作,读数据也是一样的)--------->实现了对数据的保存

(3)链表的操作:在队列里的链表会单独的保存互斥机制中需要休眠的任务,也就是休眠一个任务不仅仅是把它从就绪链表转移到延时链表,还要把它放在队列里面的一个链表当中!这样就可以知道怎么休眠一个任务;并且当A任务实现了Flag=1,那就可以去链表里面找到B任务并且唤醒它,也就可以实现要去唤醒谁了!--------->实现了“唤醒谁,怎么休眠“

????????队列里面的链表有两个:一个是专门保存写队列失败的任务的链表;一个是专门保存读队列失败的任务的链表

(4)开中断-----因为A任务已经写完数据了,所以完成了互斥机制,不用担心数据紊乱,以后别的任务也要参与写数据,那就总是按照这样的操作依次进行

-------->分支问题4:在RTOS中队列怎么表示?

----------●应该明确队列里面有一个队列结构体,后面紧跟着就是环形缓冲区

----------●对于环形缓冲区的读和写操作类似于队列的操作

----------●以下代码会涉及到环形缓冲区的内存大小,读写指针;两个链表

(下面是队列结构体和环形缓冲区读指针)

typedef struct QueueDefinition kernel aware debuggers. */
{
    int8_t * pcHead;           
    int8_t * pcWriteTo;<-----------------环形缓冲区写指针     
    union
    {
        QueuePointers_t xQueue;     
        SemaphoreData_t xSemaphore; 
    } u;

    List_t xTasksWaitingToSend;<-------------保存有等待写数据的任务的链表           
    List_t xTasksWaitingToReceive;<-------------保存有等待读数据的任务的链表                  

    volatile UBaseType_t uxMessagesWaiting;
    UBaseType_t uxLength;                  
    UBaseType_t uxItemSize;                

    volatile int8_t cRxLock;              
    volatile int8_t cTxLock;               

} xQUEUE;



typedef struct QueuePointers
{
    int8_t * pcTail;   
    int8_t * pcReadFrom;<-----------------环形缓冲区读指针
} QueuePointers_t;

(下面是创建队列的函数)

表示在队列结构体的后面,也就是环形缓冲区,要有uxQueueLength这么多个元素,
并且每个元素占有uxItemSize 字节的大小
xQueueCreate( uxQueueLength, uxItemSize )


表示指向队列结构体的指针
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

-------->分支问题5:如果一直没有任务写/读队列,那岂不是就会有任务一直等待?

---------●可以在等待的任务中限定它等待的时间,也就是超时唤醒

---------●也就是:一个因为没有读到或者写队列成功过的任务被休眠了,它被唤醒的原因有两个,一个是被别的任务唤醒了,一个是过了等待的时间了被唤醒

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 02:09:57  更:2022-09-15 02:12:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:43:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码