一篇讲透嵌入式操做系统任务调度

进互联网公司操做系统和网络库是基础技能,面试过不去的看,这里基于嵌入式操做系统分几章来总结一下任务调度、内存分配和网络协议栈的基础原理和代码实现。linux

处理器上电时会产生一个复位中断,接下来会执行复位中断服务函数,这才是软件执行的起始点。复位函数前后调用SystemInit和__main函数,SystemInit是处理器自带的库函数,通常执行各类时钟和外设的初始化;__main函数执行C语言运行环境的初始化,包括将目标程序从flash搬运到RAM、初始化全局变量等内存段初值,初始化C语言库函数等操做,最后跳转到main函数,执行用户程序。不一样型号的处理器启动流程不太一致,若是不是须要设计bootloader,能够不必关心。面试

mian的主要工做大体为:内存初始化、硬件中断初始化,此外还会分配基础的资源如锁、信号量等,最后建立idletask。idletask任务优先级最低,里面通常循环执行WFI指令使芯片保持低功耗状态。算法

任务相关结构初始化很简单,主要是分配任务块空间,并挂接在freelist链表上。后续分配任务时,从freelist链表上取就行。编程

                           


接下来看看任务建立的步骤:数组

首先将须要回收的任务资源挂在freetasklist上去。这里是尽可能延迟了任务的回收时间,不是在任务该回收的时候而是在新任务建立的时候回收旧任务。这样能够尽可能减小CPU占用。因此每次新建任务,都是从freetasklist链表上取一个TCB下来而后根据用户需求分配栈大小,设置任务优先级和入口函数等。这里要注意操做freetasklist的过程当中要关闭中断,防止操做过程当中来了中断引发任务调度,致使crash。这里写代码须要注意由于参数检查、分配内存等流程都有可能出错,在设计程序的时候最好统一收口,全部错误有一个统一的出口处理,这样能够防止遗忘开中断等重要操做。大体流程以下:微信



从freelist头部摘取一个任务块,并分配任务栈空间、设置优先级等,新建立的任务须要挂接到priqueue链表数组中等待任务调度:网络



这里有个小知识点,通常的链表指向的都是结构体的首部,这样能够将链表指针直接强制转换成对应的数据结构(这里是任务块)。而在任务队列中链表位于任务块结构体中间,须要用宏来获取到链表指针对应的任务块首地址,这个宏的实现大同小异,各个操做系统都是借鉴Linux来实现的,详情百度:list_entry。数据结构

接下来就要说说操做系统是怎么作到常数级的任务切换时间的。初学者写的操做系统任务调度功能时可能会出现一种场景,那就是任务量少的时候任务切换时间快,任务量多的时候任务切换时间变慢,这种任务切换耗时不肯定对用户任务影响时很是大的。咱们来看看ucos系统是如何设计来保证常数级任务切换时间的。函数

一个全局的priqueue链表数组,上面挂接ready状态的任务队列,一个全局int32数,用于标记某优先级是否有须要调度的队列。要扩展到128位优先级也很是方便,设置4个int32数组便可。每次取优先级最高的任务,直接用CLZ汇编命令从bitmap中读出须要调度的最高优先级任务。职业规划



插入ready任务的时候须要将bitmap对应位置1,任务从ready状态移除的时候须要检查该优先级是否还有须要调度的任务,若是没有了,bitmap对应位须要置0。讲完嵌入式操做系统的进程调度,再来看看Linux的CFS的基础原理,就好理解多了,嵌入式系统低优先级队列可能会存在饿死现象,Linux的CFS调度算法给每一个优先级分配了不一样权重,根据就绪队列里全部任务的权重之和来分配任务的时间,来保证彻底公平调度。

内存分配

前面分配任务块、分配任务栈等都用到了内存分配动做,具体的内存分配算法有:best-fit算法、TLSF算法、LWIP中的最快匹配算法、伙伴算法等,基础原理相似,下期再分析。

时钟中断

适配操做系统首要任务就是把时钟tick给起起来,这是芯片的心跳,全部的任务切换和延时操做都是基于时钟tick中断驱动的。

以ARM芯片的Cortex-M3核为例,启动时钟中断主要是调用osSetVector将tick回调函数设置进中断向量表里面的15号中断:



中断向量表长这样子:



前面15个中断号属于系统中断,后面预留中断号可供用户配置,M3是240个,M0是32个。

结合PendSV中断,能够在tick中断中完成别的事物(如定时器处理等),经过低优先级的PendSV中断来执行任务切换动做,从而减小中断响应时间。具体的分析在以前文章中:嵌入式操做系统的任务调度



在设置tick中断的时候还须要配置systick定时器的中断间隔。systick定时器是个简单的向下计数的24位计数器,寄存器位置位于系统控制空间基址SysTick_BASE偏移16字节,将须要的频率数值写入便可。systick寄存器的内部细节为:



详情可参考《ARM Cortex-M3 Cortex-M4权威指南》9.5节systick相关部分,其他相关linux中断知识可参考:Linux中断编程

顺带说一下,若是职业规划是往互联网方向转,那么寄存器硬件知识了解便可。若是想在物联网嵌入式领域深耕,不一样ARM芯片之间的区别是必定要掌握的。

咱们能够给tick中断配置为每10ms中断一次,防止过多的任务上下文切换占用CPU资源。中断到来后系统都要处理哪些事务呢?一般状况下会维护一个全局计数器,该中断到来时变量自增,而后会处理定时器任务和超时任务。

超时任务是什么呢?好比用户任务等待某个资源(锁、信号量等),若是获取不到就会设置超时时间阻塞等待,直到资源可用或者任务超时。所以每次时钟中断到来都须要判断是否有任务超时。

定时器任务就比较简单了,可使用全局链表有序挂接定时任务,每次只须要判断链表头的任务是否到时,到时了摘取下来执行对应的回调函数便可。



不一样的操做系统可能会在tick中断里面作一些别的事情,好比定时器对齐等。

貌似还有不少没写完,下周末再来总结。最后推荐嵌入式操做系统入门的三本书:《嵌入式操做系统内核调度--底层开发者手册》、《嵌入式网络那些事--STM32物联实战》、《ARM Cortex-M3 Cortex-M4权威指南》

本文分享自微信公众号 - 机械猿(on_ourway)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索