1、多任务系统中互斥的引入
为什么裸机程序就不用考虑互斥问题呢?
假设有一个变量,因为裸机程序都是一个函数一个函数轮流执行的,因此变量不会在某个时刻被多个函数同时访问。而多任务系统可以看作是同时运行的,一个变量可能会被多个任务同时访问。
没有引入freertos时,a的值经过两个函数后变为2。
引入FREERTOS后,
假设任务a和任务b都需要在自己的任务里面访问a变量,将a变量加1,那么会遇到什么问题呢?
执行a++的操作分为3步,第一步:从存储器中读出a的值赋给寄存器R0;第二步:寄存器R0自加1;第三步:将寄存器的值放入存储器当中。假设任务a在取出a的值给寄存器R0,还没来得及自加1的操作,TICK中断就触发,触发瞬间R0的值会被保存起来;任务2就开始执行,假设任务b比较幸运,能够执行完全部程序,a的值此时为1,切换任务a时,任务a恢复上次的现场,即R0=0,进行自加1,然后把寄存器的值写入到存储器中的a变量,则a的值此时为1。
可见,实现各任务对变量的互斥访问尤为重要。
2、队列怎么实现互斥访问?
任务A只要把变量a成功写入到存储器里边,任务b就可以读取a变量的值。任务a写数据,任务b读数据,就不会产生互斥问题。那么,如何能保证不会产生互斥问题呢?
假设任务a和任务c都要访问变量a,那么就需要把两个任务的程序进入到队列里面,队列里面有一个QueueSend函数,这个函数可以保证一个任务对变量进行访问(也就是写数据到内存的某个变量)之前,可以关掉tick中断,就可以防止进行任务的切换了,写完后立马打开中断就可以了。接着下一个任务要对这个变量进行访问(也就是写数据到内存的这个变量)之前,也是关掉中断,访问完再打开中断即可。
3、使用队列的第二个好处——休眠唤醒——提高CPU的利用率
在裸机系统中
A_fun()函数中执行达到某个条件时flag就为1,B_fun()函数每次进来都需要一直判断flag是不是为1,浪费了CPU资源。在Freertos操作系统中是怎么样的呢?
当A_fun()函数满足某个条件时就可以写队列,B_fun()函数就读队列,如果队列里面没有数据就进入休眠态,直到A_fun()函数有写队列才唤醒。当读取到队列有数据就放回数据。
任务A在执行过程发现不满足条件于是不写队列,接着切换到任务B时读队列发现没有数据,于是就进入休眠状态,切换到任务A时开始全速运行,即使tick中断触发时也不切换任务,直到自身条件满足了,也就是可以写入队列了,那么就会唤醒任务B,就可以切换到任务B上次读取队列的位置,任务B就能读取队列的值并返回。
休眠其实就是把当前任务从就绪链表移到阻塞链表当中。
- 队列的核心--关中断--环形缓冲区--链表
写队列就是执行两个操作步骤:1)写Data;2)唤醒。
唤醒时怎么就能够在队列里找到B的任务呢?
读队列发现无Data后执行两个操作步骤:1)将当前任务从就绪态转到阻塞态;2)把自己的任务通过链表记录到等待队列中。这样当任务A唤醒时,就知道需要唤醒的是B任务了。
环形缓冲区
环形缓冲区其实就是一个数组,为什么是环形的呢?因为当要访问到第len个数组元素时就需要从头开始。数组内容一开始都是空的,长度假设为4,一开始R和W指针都是指向第0个数组元素。