野火飞鸟嵌入式内核实践_第1页
野火飞鸟嵌入式内核实践_第2页
野火飞鸟嵌入式内核实践_第3页
野火飞鸟嵌入式内核实践_第4页
野火飞鸟嵌入式内核实践_第5页
免费预览已结束,剩余42页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、前言3第一章内核基础.系统结构3内核简介4内核结构5内核的定位5内核基础5多任务机制5时钟节拍6任务上下文6任务切换7任务调度7任务状态7任务的时间片和优先级7任务调度算法8系统资源8临界代码9可重入函数9任务同步、互斥和通讯9优先级反转9优先级天花板和优先级继承111.3.15 死锁12同步和异步12等待和唤醒机制12任务调度方式131.3.20 可抢占式内核13第二章 任务管理与调度14多任务机制概述14多任务机制实现...62.2.7线程的结构15线程的状态15线程优先级17线管理17线程函数1

2、8线程数据18线程队列18线程调度机制19线程调度算法20第 1 页 共 47 页线程调度时机20线程调度步骤20线程管理...62.4.7线程初始化21线程激活21线程休眠21用户级线程切换22线程优先级设定22线程延时22线程挂起与解挂22第三章任务同步、互斥和通讯22IPC 机制介绍23信号量23二值信号量23计数信号量24互斥信号量.5 邮箱253.1.6 消息队列253.17 事件集27IPC 通用机制设计28信号量设计实现2.23.3.3信号量结构定义29信号量功能设计29信号量代码分析3

3、03.4 互斥量设计实现30.23.4.3互斥量结构定义31互斥量功能设计31互斥量代码分析31邮箱设计实现32邮箱结构定义32邮箱功能设计32邮箱代码分析33消息队列设计实现33消息队列结构定义34消息队列功能设计34消息队列代码分析35事件集设计实现35事件集结构定义35事件集功能设计35事件集代码分析36第 章 时间管理364.1 内核定时机制概述364.2定时器实现39第 2 页 共 47 页四定时器定义40定时器功能设计41定时器代码分析4.24.2.3第五章 内存管 理43内存管理机制概述43动态内存管理实现445.3.1 内存管理结构定义455.

4、3.1 内存管理功能设计465.3.1 内存管理代码分析46第六章设备管理46第七章内核移植46附录飞鸟内核.47附录飞鸟内核代码导读47附录 C飞鸟内核编程手册47附录 D飞鸟内核代码注释47前言第一章内核基础本章主要介绍操作系统和内核的概念,阐明操作系统的整体结构和概念,介绍章的内核在系统的功能和在系统中所处的位置,为在以下各内核分析和学习做好准备。本章假设读者已经对系统有初步了解,不再介绍例如发展历史、机制特点这些基本知识。1.1系统结构目前常见的结构可以分为轮询系统、前系统和多任务系统。轮询系统这是最简单的一种结构,主程序是一段无限循环的代码,在循环中,逐个查询各个条件,一旦满足就执行

5、相应的操作,然后再次查询其他条件并处理相应的功能。第 3 页 共 47 页这种方案的好处是实现简单,逻辑清晰,便于开发掌握。但每种操作的时机是不确定的。因为前面的操作如果时间较长,那么后面的操作必然会被延迟。如果需要查询处理的IO 操作很多,因为 IO 的操作是很不确定的。并且由于数据的接收和处理都是在一个操作过处理的,在处理数据的时候,必然不能接收数据。前系统出于对轮询系统的改进,前系统对 IO 的处理做了优化。前系统是由中断驱动的。主程序依然是一段无限循环的代码,称为程序,而其他的 IO 处理则由中断来完成。在程序执行的时候,如果有 IO 操作,则前台的中断程序打断程序,完成必要的数据操作

6、之后,通知并且切换会来继续操作。程序完成数据的最终处理。这种方案最显而易见的好处是由于采用中断方案,比轮询系统响应速度快。缺点则是需要考虑中断的处理,并且中断与主程序之间资源共享需要应用来解决。虽然中断程序可以尽快地响应 IO 操作,但是数据的处理依然有来完成,同样需要循环的处理接收到的数据。从逻辑上讲,数据的收集和处理初步分为两个部分了。和前多任务系统系统相比,多任务系统在接收数据的时候,同样由多个中断处理程序完成的。但是对于接收到的数据,是由多个任务来处理的。也就是说每个任务处理它所负责的数据,如果有就处理,没有就让出处理器,让别的任务来处理它所需要处理的数据。在多任务系统中,为了支撑多个

7、任务的运行,相应配套的功能得到提出,包括多任务、任务切换 共享资源 任务同步和互斥,任务通讯等。内核的概念被提出了。不过这带来了和处理器等资源的额外开销。从数据处理的角度考虑,可以把整个流为数据接收和数据处理两个阶段。正是对着两个阶段的采用的不同技术,才得以发展出这三种结构方案。从这个发展路线可以明显感觉到的就是解决问题的思路越来越清晰,结构和层次越来越合理。以下是对三种结构的比较通过比较,可以看出结构的发展,但这并不能说明后来的技术就比先前的技术更普遍的适用于应用的需求。每种方案都有它产生的年代、硬件资源的发展阶段和所适合的应用领域,这是技术的发展,也是针对于应用方案的细分。1.2内核简介操

8、作系统是一种用于系统的操作系统。它是系统的重要的组成部分,通常包括与硬件相关的底层驱动、系统内核、设备驱动以及图形用户接口,文件系统,网络模块等。操作系统具有通用操作系统的基本特点。但与通用操作系统相比第 4 页 共 47 页数据接收数据处理轮询系统主程序主程序前系统前台多个中断程序单个主程序多任务系统多个中断程序多个任务较,操作系统在系统实时性、硬件的依赖性、固态化以及应用的性等方面具有较为突出的特点。操作系统处于硬件和应用之间,负责管理硬件资源和向应用提供服务。操作系统使的程序的设计和开发变得容易,不需要大的改动就可以增加新的功能。通过将应用程序分割成若干独立的任务模块,使应用程序的设计过

9、程大为简化;而且对实时性要求苛刻的事件都得到了快速、可靠的处理。通过有效的系统服务,更好的利用。实时操作系统使得系统资源得到1.2.1内核结构1.2.2内核的定位操作系统在整个应用中的位置如下图所示:1.3内核基础内核是操作系统的基础部分,它主要包括任务管理、任务切换、任务同步、任务互斥、任务通讯、设备管理、中断管理、时间管理、内存管理等功能。像图形用户接口、文件系统、TCP/IP 协议、数据库引擎等,则是属于内核层之上的功能模块。因为多任务内核允许将具体的应用系统分成若干个任务来管理,所以内核可以简化应用系统的设计。不过内核将消耗部分处理器、代价。器等硬件资源。这是引入内核机制的必要的关于内

10、核,有很多常见的技术概念,熟悉这些知识是学习内核的基础。下面对其中一些重要的概念做详细介绍。注意主要是以单处理器多任务来讲解的,并且任务只是1.3.1 多任务机制在单处理器的计算机系统中,在某一具体时刻处理器只能运行一个任务,但是可以通过将处理器运行时间分成小的时间段,多个任务按照一定的原则这些时间段的方流加载执行各个任务的方法,从而使得在宏观上看,有多个任务在处理器上同时执行,这就是单处理器系统上的多任务机制的原理。如下图所示:第 5 页 共 47 页图 多任务机制演示另外,因为不同任务的运行路径不同,在某一时刻有些任务可能需要等待一些资源,这时可以通过一定方式和策略,使得当前任务让出处理器

11、,从而避免因为任务等待资源而长期占有处理器,使得其他任务得以运行。这样多任务机制可以使处理器的利用率得到提高并提高了系统的响应时间。在多任务操作系统内核中,必须提供解决并发任务的机制。通用内核一般以“进程”、“线程”等为但在很多来管理用户任务。在相关资料中,也会明确“进程”与“线程”的区别。内核中,并没有区分进程和线程,只是把整个内核当作一个大的运行实体,其中运行着很多任务。面曾说明过多任务系统是如何演化过来的。和前系统相比较,多任务可以理解为有多个程序的前系统。每个任务都专注自己处理,并通过一些技术方案来维持多个任务间的联系。在系统中,任务通常被以进程或者线程的方式来实现,并且作为调度的基本

12、。关于多任务机制在这里就不多做解释,很多中都详细介绍过。1.3.2 时钟节拍时钟节拍是多任务系统的基础,它指明了以多大的频率把处理器时间分割成固定长度的时间段。作为多任务系统运行的时间尺度,时钟节拍是通过特定的硬件定时器产生的,硬件定时器产生周期的中断,在相应的中断处理函数中,内核代码得以运行,从而进行任务调度和定时器时间处理等内核工作。中断之间的时间间隔取决于不同的内核设计,一般是毫秒级的。时钟节拍越快,内核函数介入系统运行的几率就大,中断响应次数越多,内核消耗的处理器时间越长。 相反,如果时钟节拍太慢,则任务的切换间隔时间越长,进而影响到系统的响应效果。1.3.3 任务上下文任务可以看做是

13、用户设定的应用逻辑代码在处理器等硬件上的运行,是一个动态的概念。任务在处理器上运行的某一时刻,有它自己的状态,即在处理器所有的寄存器的数据,第 6 页 共 47 页这个叫做任务的上下文,可以理解为是处理器的“寄存器数据快照”。通过这些数据,内核可以随时打断任务的运行或者加载新的任务,从而实现不同任务的切换运行。1.3.4 任务切换任务切换又叫做任务上下文切换。当内核需要运行其他的任务时,内核会保存处理器和当前任务相关的寄存器的内容到该任务的栈中,然后从将要运行的任务的栈中取出全部寄存器内容并加载到相关的寄存器中,从而开始下一个任务的运行。这个过程叫做任务切换。简单的说,就是内核保存当前任务的上

14、下文,然后读加载一个任务的上下文。任务切换过消耗部分处理器时间,但从系统整体上考虑,这样的消耗是值得的。 任务切换过程如下图所示:1.3.5 任务调度任务调度是内核的主要功能之一,它完成在任务调度时机选择合适的任务,替换当前任务占有处理器等硬件资源。根据调度原理的不同,任务调度主要分为抢占式多任务和分时式多任务模式。了解内核会在什么时候发生调度是非常重要的。这些调度时机姑且称之为调度点。一般来说,在时钟节拍发生的时候、中断响应结束和系统调用结束之后,通常会发生任务调度;有些内核也允许任务自身发起任务调度。这是和内核的设计密切相关的。既然任务需要切换,那么就需要一个调度算法来决定在需要调度的时候

15、处理器应该切换到哪一个任务上。一个任务调度算法有很多需要考虑的度、调度机制的公平性等。,比如任务的优先级、时间片长1.3.6 任务状态在多任务的系统中,任务一般具有多种状态,反映任务不同的执行阶段。常见任务状态主要有 3 种:就绪状态运行状态等待状态任务已经获得除处理器之外的一切需要的资源,等待任务调度任务正在运行中,它得到了所有需要的资源任务缺少某些必须的运行条件或资源而不能参与任务调度在不同的运行条件下,任务的状态可能会改变,任务状态的变迁如下图所示:1.3.7 任务的时间片和优先级TODO 介绍时间片调度和优先级调度时间片长度是时钟节拍的整数倍,指的是任务一次投入运行,在不被抢占或者中断

16、的情况下,能够连续执行的最长时间(以时钟节拍计数)。第 7 页 共 47 页TODO 更详细介绍时间片时间片的长度由具体内核规定,有些内核中不同任务可以有不同的时间片长度,或者是在运行过可以动态改变时间片长度。任务的优先级说明个任务的重要性,任务越重要,它的优先级高,越应该获得处理器资源。任务优先级的安排有两种方式:静态优先级和动态优先级。如果任务优先级在运行的过不能改变,则称为静态优先级,这是在任务初始化时候决定的;反之如果任务优先级是可以改变的,则称为动态优先级。时间片和优先级是任务的两个重要参数,分别描述了任务获得处理器资源的能力高低和保持处理器时间长短的能力。同时这两者也是任务抢占的重

17、要参数。因任务时间片运行完毕而引起的任务调度可以从另外的角度理解为是时间片抢占,而因为内核中最高就绪优先级的变化而引起的调度则成为优先级抢占。1.3.8 任务调度算法常见的任务调度机制主要有时间片轮转调度算法(时分式)、优先级调度算法(抢占式)和基于优先级的时间片调度算法。 时间片调度算法指的是内核先运行某个任务一个时间片(多个时钟节拍),然后再切换给另一个任务。这种算法保证每个任务都能够轮流占有处理器一段时间。并且不是在每个时钟节拍都做任务调度,对处理器资源的消耗又做到了最少。缺点是在任务占有处理器的时间段内,即是有更紧急任务就绪,也不能立刻执行它。 优先级调度算法指的是当发生任务切换的时候

18、,内核总是让别的可运行任务先运行。即当有任务的优先级高于当前任务优先级并且就绪后,在下一个调度点就一定会发生任务调度,这种内核最大的了系统的实时性。不好的地方是,当多个就绪任务拥有相同的最高优先级的时候,第一个就绪的任务将持续占有处理器资源直到任务结束或者阻塞,其他拥有最高优先级的任务无法获得运行机会而导致任务饥饿。 基于优先级的时间片调度算法,吸收了以上两种算法的优点,同时又解决了它们的。这种算法为每个任务都安排了优先级和时间片,不同优先级的任务采用优先级调度算法,相同优先级使用时间片轮转调度算法。任务调度策略首先考虑优先级,不同优先级的任务中,不考虑时间片的,优先级高的任务必定会抢占低优先

19、级的任务。这样既保证了尽快响应紧急任务,又保证相优先级的任务都有机会轮流占有处理器。1.3.9 系统资源内核和任务所使用的任何对象都可称为资源,包括各种设备,甚至是内存中的一个变量,只不过某些资源需要特殊对待,而有些则不需要过多考虑。比如,两个任务都可以使用它,但使用它的时候,则需要单独,否则两个任务各自输出各自的数据,结果打印结果就会是的;而任务所必需的内存也是一种资源,但每个任务的变量则是私有的,不需考虑其它任务会对它有操作,也就不用进行保护了。原则上,在使用资源的时候,只要有跨任务或者 ISR 的情况,必须特殊处理该资源。第 8 页 共 47 页多个任务共同使用的资源叫做共享资源。为了防

20、止,每个任务(包括内核)在使用共享资源时,必须通过内核来申请而不是自己直接该资源。具体方法就是采用互斥等手段防止使用资源的时候发生(或者 LockFree 方式)。1.3.10 临界代码代码的临界段也称为临界区,它意味着这段代码是关键的操作,一旦开始运行之后就不允许任何理由来打断(主要指的是各种中断会打断当前任务的执行路径)。为确保临界段代码的执行,在单处理器系统中,在进入临界段之前要关中断,在临界段代码执行完以后要立即开中断。 注意关闭系统中断,会暂停对外设的响应,影响外部事件的处理,关中断时间越长,对系统吞吐量影响越大。关闭中断的时间越短越好,这也是内核好坏的重要指标之一。1.3.11 可

21、重入函数在多任务环境中,可重入的函数可以理解为是可以被多个任务同时调用、可以随时被打断并且当恢复运行后仍然能保证它所使用数据的正确性的函数。从具体代码实现上来看,这是因为可重入型函数只使用局部变量(保存在处理器寄存器中或任务栈中)或者已经被互斥保护过的全局变量。从执行结果上看,只要输入数据相同,可重入函数就会产生相同的结果。下图是一个不可重入函数在运行时的内存使用情况:从中可以看出,假如任务 A 执行了函数 count(),那么在任务 A 的栈中将存在函数局部变量 x 和 y,而对于全局变量 R 的地址则是在全局数据区。对于任务 B 来说它的内存使用情况同样如此。如果任务 A 在执行完第 4

22、行代码的时候因为一些原因被抢占,而任务 B 随后又调用函数 count(),那么此时对于全局变量 R 的数值是,对全局数据区的变量 R 的并发将产生错误的结果。1.3.12 任务同步、互斥和通讯任务和任务之间、中断处理函数和任务之间是有数据传递的,这些数据或者是结构化的数据或者是无结构的数据。前者一般是通过消息队列/邮箱机制实现的,而后者是通过管道机制实现的。1.3.13 优先级反转在系统中,有些资源必须是独占使用的,否则多个任务对这样的资源的并发将导致第 9 页 共 47 页错误的发生。一般来说,对需要独占使用的资源必须使用互斥来将对其的并发串行化。而互斥方案的引入,又会导致任务优先级反转:

23、低优先级的任务占有资源,这时有高优先级的任务申请资源,因为不能满足而被挂起了,即低优先级任务阻塞了高优先级任务的运行。假如这时又有一个中优先级任务,那么它会把低优先级任务抢占。最终高优先级任务会间接的被优先级低于它的中优先级任务抢占了。这种高优先级任务需要等待低优先级任务明:资源,而低优先级任务又正在等待中等优先级任务的现象叫做优先级反转。举例说假如 ABC 三个线程优先级从高到底排列,线程 A 和C 共享互斥信号量 M,如果某一时刻线程 C 已经获得互斥信号量 M,而线程 A 此时尝试锁定 M,那么线程 A 会因为得不到 M 而阻塞在 M 的线程等待队列中,再假设此时线程 B 因为优先级高于

24、线程 C 从而抢占了 C,进而长期占有处理器资源,那么就相当于低优先级的线程 B 间接阻塞了高优先级的线程 A 的运行。这个过程可以用下图来说明:T0 时刻,只有任务 C 处于可运行状态,运行过,任务 C 获得了独占资源 R;T1 时刻,任务 A 就绪进入可运行状态,由于优先级高于正在运行的 Thread C,所以它抢占了 Thread C, Thread A 被调度执行;T2 时刻,任务 A 需要独占资源 R,但 R 被更低优先级的任务 C 所拥有,所以任务 A被挂起等待该资源。任务 C 继续运行。T3 时刻,此时任务 B 就绪,因为任务 B 的优先级高于任务 C,所以任务 B 抢占任务C,

25、任务B 得到处理器资源。现在看看这些任务的情况:任务 A 优先级最高,但是因为得不到资源 R 而阻塞,任务 C 占有资源 R 但是优先级低于任务 B,所以也无法运行。任务 B 不需要任何资源并且是就绪任务的最高优先级,所以只有它在运行。从整体上看,高优先级任务 A 被低优先级任务 B间接的抢占了。此时优先级最高的任务 A 不仅要等优先级低的任务 B 运行完,还要等优先级更低的任务 C 运行完之后才能被调度,如果任务 B 和任务C 需要执行很长时间,那么任务 A的响应就不能保证,整个系统的实时性能就很差了。优先级翻转现象对于分时调度系统的影响不大,但是对于基于优先级调度的实时系统则有很大的影响。

26、在基于优先级调度的系统中,处理器资源是按照优先级分配给任务的,就绪的高优先级任务必须实时的得到处理器。另外系统中的其他各种资源,如果采用按照任务优第 10 页 共 47 页先级分配的原则,那么在某资源的任务等待队列中,高优先级的任务应该是首先被关注的。优先级反转则将打乱这些原则。优先级翻转是由来已久型的一个优先级翻转引起,自从出现多任务系统后,就一直困扰着开发,很典是在 1997 年 7 月探路者火星车(Pathfinder)发生的在火星表面不停重启的故障,读者如果有的话可以自己去找些资料看看。1.3.14 优先级天花板和优先级继承优先级反转问题的原因在于独占资源的规则,即只能被一个资源占用,

27、被占用后其它任务不能强迫占有该资源的任务放弃这个资源。在优先级反转问题上,高优先级任务被低优先级任务阻塞是必定的,但被中优先级阻塞则是很无奈的。为了避免因为中优先级任务挟持低优先级任务从而要挟高优先级任务的现象,可以采用一些必要的算法。常见的算法是优先级继承协议和优先级天花板协议。优先级继承协议指的是当一个任务占有了资源并且随后阻塞了其他申请该资源的任务的时候,该任务将临时改变它的优先级为所有申请该资源的任务的最高的优先级。并以这个临时优先级在临界区执行。当任务资源后,则恢复它原有的优先级。从行为上看,占有资源的任务的优先级将是“水涨船高”式的多次改变,因为他的优先级最高,所以它将不被曾经比它

28、优先级高的那些任务的抢占。内核从优先级角度安排它尽快执行,尽快资源,为了尽快调度被低优先级任务阻塞的高优先级任务。但是这样做内核却牺牲了中等优先级任务的调度机会。两种经典的防止反转的方法:优先级继承策略(Priority inheritance):继承现有被阻塞任务的最高优先级作为其优先级,任务退出临界区,恢复初始优先级。优先级天花板策略(Priority ceilings): 优先级天花板是指将申请(占有)某资源的任务的优先级到可能该资源的所有任务中最高优先级任务的优先级.(这个优先级称为该资源的优先级天花板)第 11 页 共 47 页优先级继承策略对任务执行流程的影响相对较小,因为只有当高

29、优先级任务申请已被低优先级任务占有的临界资源这一事实发生时,才抬升低优先级任务的优先级。而天花板策略是谁占有就直接升到最高。形象一些说,优先级继承协议是“水涨船高”,而优先级天花板协议则是“一次到位”.1.3.15 死锁指多个任务无限期地互相等待彼此占有的资源,从而形成了闭合的等待链。设任务 T1正独享资源 R1,任务 T2 在独享资源 T2,而此时 T1 又要独享 R2,T2 也要独享 R1,于是哪个任务都没法继续执行了,发生了死锁。最简单的防止发生死锁的方法是让每个任务都:先得到全部需要的资源再做下一步的工作用同样的顺序去申请多个资源资源时使用相反的顺序内核大多允许用户在申请资源时规定一个

30、等待时限,一旦时限到达则立刻返回申请操作超时失败的错误码,以此解除死锁。1.3.16 同步和异步1.3.17 等待和唤醒机制当任务在试图资源的时候,有时候资源条件而被迫返回失败值或者进入该资源的等待队列。而当有任务资源从而使得资源条件可以满足时,内核将会唤醒该资源的等待任务,使得被唤醒任务继续运行。不同的等待机制和唤醒机制是不同内核的重要区别。用于任务资源的等待机制主要包括直接返回、阻塞等待、时限等待三种: 在直接返回模式下,任务直接返回结果,或者成功或者失败,没有其他操作或第 12 页 共 47 页者结果; 在阻塞等待模式下,任务如果得不到资源,则进入该资源的等待队列,直到得到该资源为止;

31、在时限等待模式下,任务如果得不到资源,则进入等待状态并开始计时。如果在等待期间得到了资源则返回操作成功;如果当计时结束的时候任务仍然没有得到资源,那么它并不会继续等下去,而是返回失败的结果。当任务不能获得资源而进入资源的等待队列之后,如果某个时刻资源可用,那么内核就该决定怎么处理这些等待任务。这就涉及到了内核任务唤醒机制。内核唤醒机制主要有的三种模型: 当资源可以可使用时,唤醒该资源的全部等待任务。让这些任务与系统中的其他任务竞争资源。这种策略会使系统瞬间繁忙,在参与竞争资源的所有任务中,最终只有一个任务获取到资源,没有得到资源的任务将再次进入资源的等待队列。 将该资源等待队列中的一个合适的任

32、务唤醒。这个任务将和系统中可能该资源的其他任务一起竞争这个资源。如果这个任务最终没有竞争到资源,它会再次进入该资源的等待队列。 内核从等待队列中直接找到一个最佳的任务并立刻把资源交给它,这样该任务直接从资源的那个任务那里获得资源。目前主流实时内核都采用第三种方案。1.3.18 任务调度方式任务调度方式可分为可抢占调度和不可抢占式调度两类。对于基于优先级的系统而言,可抢占型调度是指内核可以抢占正在运行任务的 CPU 使用权并将使用权交给进入就绪态的优先级更高的任务,是内核抢了 CPU 让别的任务运行。不可抢占型调度使用某种算法并决定让某个任务运行后,就把 CPU 的控制权完全交给了该任务,直到它

33、主动将 CPU 控制权还回来。某些内核操作或者中断处理程序可以激活一个休眠态的任务,使之进入就绪态;但任务还不能运行,一直要等到当前运行的任务主动把处理器的控制权交给它。可抢占型调度的实时性好,优先级高的任务只要具备了运行的条件,也就是说进入了就绪态,就可以立即运行。除了优先级最高的任务,其他任务在运行过都可能随时被比它优先级高的任务抢占,让后者运行。通过这种方式的任务调度保证了系统的实时性。1.3.20 可抢占式内核内核也是分为可抢占内核和不可抢占式内核两种,这和任务的可抢占式调度是不一样的。抢占式内核指的是在任务执行内核操作的时候,仍然是可抢占的调度。那些不可抢占的内核,在内核进行操作的时

34、候是不能被抢占调度的。第 13 页 共 47 页第二章 任务管理与调度2.1 多任务机制概述面曾说明过多任务系统是如何演化过来的。和前系统相比较,多任务可以理解为有多个程序的前系统。每个任务都专注自己处理,并通过一些技术方案来维持多个任务间的联系。在系统中,任务通常被以进程或者线程的方式来实现,并且作为调度的基本。关于多任务机制在这里就不多做解释,很多中都详细介绍过。2.2 多任务机制实现在飞鸟内核目前的版本中,没有明确提供进程机制,而是以线程来代表用户任务,线程同时作为资源分配和任务调度的基本。从整体上考虑,可以把内核和用户任务看作是一个大的进程,而整个系统内则有多个用户线程。飞鸟内核是基于

35、轻量级多线程的多任务系统。也就是上面所讲的单进程多线程的任务模型。在内核中,某一时刻,可能会有多个线程同时处于就绪与运行状态,内核必须决定某个时刻应该执行那个线程,调度算法是内核的关键特性。程调度部分,需要注意有几个可能发生线程调度的时机和可能引起内核调度的函数,这是了解内核线程调度的关键问题。一般来说,线程调度分为用户级线程调度和中断级线程调度。可以这样对飞鸟内核的线程模型做个简要的归纳:线程指得是任务逻辑代码在处理器上执行的过程。它是内核调度的基本,也是资源分配的基本。线程对象由线程结构、线程堆栈、线程函数和线程数据等部分组成。线程具有多种运行状态,由内核中的各种线程队列组织管理。飞鸟内核

36、的线程管理功能,主要包括:线程创建和删除(CreateDelete)线程激活和休眠(ActivateDormant)线程挂起和唤醒挂(SuspendResume)线程调度(YieldSchedule)线程延时(Delay)线程优先级管理(Set Priority)第 14 页 共 47 页2.2.1 线程的结构飞鸟内核是通过线程结构管理所有线程的,每个线程都存在一个线程结构。线程结构定义在 includekernelthread.h 中,其中保存着用于管理线程的全部数据信息。线程结构是内核中的一种 数据结构,决定了内核的很多特点。飞鸟内核当前版本(v0.1.0)的线程结构结构定义如下:type

37、def struct ThreadDef/* 线程 ID/* 线程堆栈栈底指针/* 线程堆栈栈顶指针/* 线程状态/* 线程当前优先级TThreadID ThreadID; TStackAddr StackBase; TStackAddr StackTop;TThreadS us S us;*/*/*/*/*/*/*/*/*/TThreTThreriority Priority;riority BasePriority; /* 线程基本优先级TWord TimeTicks; TWord TimeSlice;TThreadQueue* Queue;/* 时间片中下的 ticks 数目/* 时间片

38、长度(ticks 数目)/* 线程所属线程队列指针#if (TCL_IPC_ENABLE)TIPCData IPCData; TIPCThreadQueue* IPCQueue;#endif/* 线程互斥、同步、通讯的控制结构 */* 线程所属 IPC 线程队列指针*/#if (TCL_TIMER_ENABLE)TTimer Timer; #endif/* 线程自带定时器*/TLinkN TThread;inkNode;/* 进程所在队列的兄弟进程的指针*/2.2.2 线程的状态线程由于不同的代码执行路径,可能会处于不同的状态,这些状态之间并不都是可以互相转化的。飞鸟内核中的线程主要包括以下这

39、些状态:/* 线程状态定义 */ typedef enumExThreadDormant = 0, ExThreadReady, ExThreadBlocked,ExThreadDelayed,/* 休眠*/* 就绪 & 运行 */* 阻塞/* 延时*/*/第 15 页 共 47 页ExThreadSuspended TThreadS us;/* 就绪挂起*/飞鸟内核的线程状态迁移图,如下所示。Blocked阻塞DORMANT休眠Ready/Run ning 就绪或运行Suspend挂起Delayed延时线程创建之后就处于休眠状态,此时它置于线程辅助队列中,等待内核激活它。激活后的线程转为就绪

40、状态,置于线绪队列中。等待内核调度它。一旦它被内核选中并调度运行,就处于运行状态(飞鸟内核没有真正设置一个“运行状态”,而是继续以就绪态代表正在运行中的线程的状态)。处于运行状态的线程保持绪队列中。内核中某一时刻可以有多个线程出于就绪状态,但只有一个线程处于运行状态。程运行过,经常被内核时钟中断打断,此时内核根据线程优先级和当前线程的时间片来决定内核中哪个线程应该得到运行的机会:假更高优先级的线绪,则内核会调度高优先级线程;如果当前线程的时间片用完,内核则选择其它的线程来运行。如果是优先级抢占,那么当前线被抢占,仍然保持在就绪队列;如果是时间片抢占,则当前线程的时间片还会被恢复初始的数值。综上

41、所述,不同优先级的就绪线通过优先级来实现抢占,相同优先级线则是通过时间片来实现抢占。另外,如果线程尝试某些资源但是由于某些原因而没能够得到,根据规则,如果是不阻塞的方式,那么线程返回后继续运行;如果是阻塞方式的,那么线会第 16 页 共 47 页被内核阻塞在该资源的线程阻塞队列上(每个拥有线程阻塞特性的资源都存在一个线程阻塞队列,用来保存那些希望得到资源但又不能得到资源的线程)。随后,内核发起线程调度,使得当前线程放弃处理器,并将处理器交给其它线程。程资源的时候,假如该资源的线程阻塞队列上有线程等待该资源,那么当前线会通过内核唤醒这些等待线的一个线程,并使它得到该资源,随后内核将该线程加入内核

42、中的线绪队列。此时因为有新的线程可以运行,所以此时当前线程通过内核尝试发起线程调度。但是这样也只是给了新线程一个参与竞争处理器资源的机会,但不一定保证新线程能够得到处理器。线程调用延时函数的时候,线程首先将自置于延时状态并且加入内核延时线程队列,然后开启自己的线程定时器。因为在等待定时结束的这段时间里,当前线程不再参与内核线程调度,所以此时也需要进行一次线程调度。处于就绪或者运行的线程被加以悬挂操作之后,就直接转为悬挂状态,此时被悬挂的线从线绪队列转到线程辅助队列。2.2.3 线程优先级线程的优先级线程任务的重要性,它决定线获得调度机会上的能力,原则上越是紧急的任务,它获得处理的机会就越高。飞

43、鸟内核采用的是动态优先级机制。每个线程都拥有自己的优先级,优先级的范围是 031,数值越小说明优先级越高。不同线程可以拥有不同的优先级,也可以拥有相同的优先级。用户可以通过系统调用直接改变某个线程的优先级,也可能是通过其他操作间接的修改某个线程的优先级,具体情况相关的章节会有相应的讲解。程调度的时候,优先级高的线先后顺序轮流得到运行机会。首先得到调度机会,相同优先级的线程之间按照在飞鸟内核中,默认的线程优先级分配原则是:优先级 02 和 31 共 4 个优先级保留给内核线程使用,其余优先级分配给用户线程使用。内核初始线程使用最低优先级 31。2.2.4 线管理在飞鸟内核线程管理过,应该非常注意

44、线的使用。飞鸟内核没有采用任何保护机制来安全的使用内存,也就是说,线程和内核同时处于一个地址空间,各个线程以及内核的数据和代码是编址的,稍有错误,就可能引起系统。内核中的每个线程都有自己的栈空间。用户线是在用户线程初始化的时候指定的,并且在指定用户线之前,必须以全局数组的方式定义用户堆栈,栈最好为整数类型(字长),并且必须由连续的内存空间组成。具体实现方式请参考线程创建函数的实现。 在系统运行的时候,内核代码和用户线程一样,同样需要一个运行栈。在飞鸟内核中并没有为内核代码特别设定运行栈,而是使用当前用户线程的运行栈。当用户线程调用内核函数的时候,被调用的内核函数将使用当前用户线程的栈。飞鸟内核

45、中所有中断服务程序共个中断栈,这个中断栈与用户线程和内核代码所使用的运行栈没有任何联系。但是要保障任何运行类型的栈都不会溢出,否则内核会发生未知的。第 17 页 共 47 页2.2.5 线程函数线程函数必须为一个大的循环体,应用功能代码在循环体内,这样保证线程代码不会执行完毕而直接返回,否则内核无法捕获线程退出消息,从而导致内核错误。如果确实需要退出,则应该通过系统调用,显式通知内核该线程需要退出(当前版本的内核没有真正删除该线程)。举例说明:void thread_x_func () while (1) / application code, do sth. if (exit_conditi

46、on)TclThreadDormantt(&self);2.2.6 线程数据线程数据主要包括栈数据、全局数据和动态数据。栈数据编译器会确保完整和正确。但是全局数据是各个线程和内核都能的,所以对全局数据的必须是互斥的。另外,线程通过内存管理函数申请的内存,必须及时,防止内存泄漏。2.2.7 线程队列在内核中,线程队列基本上是按照线程状态的不同而专门设置的,这些线程队列是分类管理线程的有效方法,分为两种类型的线程队列结构。内核中的线程,某个时刻必定属于某个线程队列。在当前版本的内核中,主要包括以下线程队列:线绪队列用于保存处于就绪状态(包括运行中的)的线程结构用于保存处于挂起、休眠、延时状态的线程

47、结构线程辅助列列线程同步/阻塞队列 用于保存阻塞在 IPC 对象上的线程结构需要注意的是处于运行状态的线程其实是属于就绪队列的。前两个线程队列属于第一种线程队列结构,这种线程队列结构的特点是能够实现固定时间的调度算法。内核中只有一个线绪队列和线程辅助队列。另外在 IPC 机制中的各种内核设施中,大多具有等待队列,就是上面提到的线程同步/阻塞队列,这种队列和线绪队列和阻塞队列的结构是不一样的。并且这种类型的队列的个数是不确定的,是根据实际使用的 IPC 对象的多少决定的。线程队列结构定义在文件 thread.h 中,具体内容如下:/* 线程队列结构定义,该结构大小随内核支持的优先级范围而变化,可

48、以实现固定时间的线程调度算法 */typedef struct ThreadQueueDef第 18 页 共 47 页TPriorityMask PriorityMask; /* 队列中就绪优先级掩码 */TPolicy Policy;/* 队列中线程的调度策略 */TLinkNode* HandleMX_THREAD_LOWEST_PRIORITY + 1; /* 队列中线 TThreadQueue;队列 */从上面的线程队列定义可以看出,进程队列内设置了就绪线程队列的指针数组,这些队列头指针指向了同优先级线程组成的双向循环链表。成员 PriorityMask 表明了哪些优先级的线程处于队列

49、中。通过快速的硬件算法,内核可以以固定时间差找到线程队列中最高的就绪优先级,然后找到处于该优先级队列的第一个线程。和线程队列相关的操作主要包括线程加入队列、线程移出队列、选择队列中线程的最高优先级和选择队列中的最高优先级线程。这其中主要涉及到链表的操作和查找优先级算法。线程队列内,按照优先级的不同有多个双向循环链表。线程出入某个线程队列的时候,就是对这些的链表进行的操作,同时完成线程队列其它数据的更新。线程加入队列的时候,根据优先级查找和它匹配的链表头,然后将它追加到链表尾部,然后更新所属优先级线程存在标记。线程从队列离开的时候,同样根据优先级查找和它匹配的链表头、将它从链表中删除,最后更新所

50、属优先级线程存在标记。用与 IPC 阻塞的线程队列结构和上面的线程结构有所不同,如下所示/* IPC 线程等待队列结构定义 */ struct IPCSuspendQueueDefTPolicy PrimaryPolicy; TPolicy AuxiliaryPolicy; TLinkNode* PrimaryQueue;TLinkNode* AuxiliaryQueue;/* 队列中线程的调度策略/* 队列中线程的调度策略*/*/* 队列中线/* 队列中线*/*/队列队列;之所以这样设计,有几点考虑:1.如果像就绪线程结构那样设置每个优先级的头指针,那内核中每个 IPC 对象(信号量、邮箱等

51、)都要占用 MX_THREAD_LOWEST_PRIORITY 个指针大小的空间,那在系统中,会显得占用了太多的空间2.对于类似邮箱这种 IPC 对象,因为可能发送不同类型的邮件(紧急、普通),所以需要设置两个队列,一个用于阻塞发送紧急消息发送的线程、另一个用于阻塞发送普通消息的线程。因为上面的原因,两个用于不同目地的线程阻塞队列,也需要考虑是按照先到先出的原则来处理唤醒时的顺序还是按照优先级的原则来处理。所以需要设置两个队列的调度策略的字段。3.2.3 线程调度机制飞鸟内核的线程调度机制包括优先级抢占机制和时间片轮转机制。在不同优先级的线程第 19 页 共 47 页之间,采用优先级抢占方式,

52、在相同优先级线程之间,采用时间片轮转机制。特点是不同的线程可以采用相同的优先级,每个线程可以拥有任意长度的时间片。这种调度算法,兼顾线优先级上的不同和同优先级线程之间在占有 CPU 机会上的公平性。支持这种调度机制的数据结构是线绪队列结构。另外,内核进行任务调度操作所花费的时间是常数(IPC 队列不是这样的),与应用程序中建立的任务数无关,这归功于内核队列结构的定义和队列操作函数的实现。线程的优先级和时间片在建立的时候分配的,并且可以在运行期间修改,内核的优先级和时间片管理具有相当的灵活性。2.3.1 线程调度算法目前版本的内核只支持 Cortex M3 处理器,采用了该型号处理器的特殊指令,

53、可以以最快最简洁的方式得到内核中所有就绪线程的最高就绪优先级。2.3.2 线程调度时机面线程状态的部分,下发生调度是很重要的,这里面几个:已经介绍了部分线程调度的情况,清楚内核会在什么情况总结一下线程调度的时机。飞鸟内核的调度时机主要有下线程主动直接放弃处理器,通过函数实现线程主动间接放弃处理器(无法获得资源、优先级变化、线程挂起等操作)时钟嘀嗒中断处理过,线程时间片用尽,被迫放弃处理器中断处理过,线程被更高就绪优先级线程抢占内核调度分为主动和调度,同时也有直接和间接的区别。而调度的原因主要是优先级变化、时间片变化、资源阻塞或者阻塞时限到达等。程调用一些内核功能的时候,可能运行环境发生了变化,

54、比如在更改其他就绪线程的优先级的时候,就不能保证当前线程的优先级是最高的,所以需要尝试调度一次。同理,如果程获得资源的时候,如果因为得不到而阻塞在该资源的等待队列上,那么必须进行线程调度。也就是说,当有其他线程加入内核就绪线程队列或者当前线程由于一些原因不能继续运行的时候,必须尝试或必须进行线程调度。另外,飞鸟内核提供了 API 用来完成线程主动放弃处理器的功能,这就是所谓的协作式任务调度模型。此时任务是否进行调度都是由任务自身决定的,没有抢占调度的概念。2.3.3 线程调度步骤内核的线程调度主要包含三个步骤:根据需求和调度算法查找新的就绪线程。处理当前线程的时间片和当前线程的状态,如果需要则

55、修改线程所处的线程队列。在两个线程之间进行线程切换。第 20 页 共 47 页其中线程切换是一个缜密的过程,功能需要由汇编代码来完成,因为 C/C+等高级语言是无法处理此时的处理器上下文的。内核首先把当前线程的寄存器上下文保存到它的线中,然后再把需要切换运行的线程寄存器上下文从它的线中,完成线程切换。线程切换的过程如下图所示:中读并且加载道寄存器组2.4 线程管理在多任务内核中,任务是参与调度与资源竞争的基本,内核的最基本功能就是任务的管理与调度。任务管理主要包括任务创建、任务删除、任务挂起与唤醒、任务调度、优先级处理等。飞鸟内核提供基本的线程管理与调度 API 函数。在本章的最后的篇幅中,主

56、要分析了与线程管理与调度相关的各个 API 函数的功能与实现,对相关代码也进行了讲解,但是对于 API 函数调用的函数,则只是根据需要进行了合理的取舍后,对部分代码进行了分析讲解。在本书后半部分的源代码中有非常详细的注解,在本章中就不再具体讲解。在以后各章中,也是如此。2.4.1 线程初始化线程初始化函数为线配和初始化相关的数据。线程可以在多线程调度开始前建立,也可以在其它线程的执行过线程机构。在整个初始化的过被建立,但是在开始多线程调度前,用户必须定义所需要的中断是被的。如果应用程序开放了中断系统,有可能产生意想不到的初始化后的线。被放在内核线程辅助队列 uThreadAuxiliaryQu

57、eue 中。2.4.2 线程激活线程激活时,会将处于休眠的线程从 uThreadAuxiliaryQueue 中移出,随后放入uThreadReadyQueue 队列中,如果需要还会进行线程调度。激活成功的线程的状态转为ExThreadReady。2.4.3 线程休在飞鸟内核中,线程的删除并不是真正的删除,只是简单的设置该线程的状态为ExThreadDormant 状态,并且将该线程放入内核 uThreadAuxiliaryQueue 队列来管理,此时内核调度等操作不会再考虑这个线程。从功能原理上讲,通过特殊以被重新激活的,但是飞鸟内核并没有给出具体实现。,这个线程仍然可第 21 页 共 47

58、 页眠2.4.4 用户级线程切换用户级线程切换是通过 TclYieldThread ()函数来实现的。2.4.5 线程优先级设定用户线程可以在运行过主动地修改自己或者其它线程的优先级.2.4.6 线程延时程运行的过,有可能需要延时一段时间,这时候需要使用线程延时函数,它启动当前线程的软定时器,将其加入内核软定时器队列,随后再把线程加入内核延时队列,相当于阻塞在该队列上,直到定时器到时,内核自动唤醒该线程,该线程继续运行。需要注意的是,每个线程都拥有一个软定时器,线程延时和资源时限等待功能都是通过这个软定时器实现的。另外,因为软定时器是通过系统时钟嘀嗒中断实现的,它的精度是有限定的,在 100m

59、s 的时钟嘀嗒的环境下,软定时器的触发是上下 100ms,而相应时间则需要根据线程的优先级来决定。如果线程优先级最高,则它的软定时器的精度在上下 100 毫秒,否则不好估计,要看优先级高于等于该线程的其他线程的运行情况来分析。如果需要更高精度的定时效果,需要另外处理。2.4.7 线程挂起与解挂在飞鸟内核里,一个线程被挂起时需要把任务的状态修改并从就绪队列中删除,然后加入 uThreadAuxiliaryQueue 中。第三章 任务同步、互斥和通讯系统中运行的各种实体主要包括线程和 ISR,在这些可运行实体的运行过程在中,有时需要同步它们的运行步骤,有时需要互斥的资源,有时也要彼此交换数据。因此

60、内核必须提供相应的机制来完成这些功能。在这里把这些机制统称为进(线)通讯(IPC ,ernal Pros Comunication),常见的机制主要包括信号量、消息队列、邮箱、事件集、管道、信号和条件变量等。本章主要分析实时内核提供的各种 IPC 机制的功能特征和设计实现。第 22 页 共 47 页3.1 IPC 机制介绍内核中的任务之间,大多需要互相交换数据,这就涉及到任务间通讯绍的任务同步和互斥相比,任务间的通讯机制更强调数据的传输。和前面介任务间的数据传输,可以是直接的,也可以是间接的。在直接数据传输方式下,一个任务可以把数据直接发给指定的任务,发送过程很明确的说明了谁从谁那里得到了数据

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论