做同性恋网站犯法吗,横沥做网站的电话,伪静态一个虚拟空间做两个网站,网站建设流程一般可分为哪几个阶段嵌入式操作系统内核原理和开发#xff08;开篇#xff09;
操作系统是很多人每天必须打交道的东西#xff0c;因为在你打开电脑的一刹那#xff0c;随着bios自检结束#xff0c;你的windows系统已经开始运行了。如果问大家操作系统是什么#xff1f;可能有的人会说操作…嵌入式操作系统内核原理和开发开篇
操作系统是很多人每天必须打交道的东西因为在你打开电脑的一刹那随着bios自检结束你的windows系统已经开始运行了。如果问大家操作系统是什么可能有的人会说操作系统就是windows就是那些可以放大、缩小、移动的窗口。对曾经是计算机专业的朋友来说这个答案还要稍微复杂一些操作系统可能还有linux、unix、ios、sun solaris、aix等。如果再细化一点对嵌入式工具比较解的朋友还会有新的补充因为在他们看来vxworks、eCos、ucos也都是操作系统虽然它们好多系统连界面都没有。 既然操作系统称之为一个系统那么它必然是由好多的部件组成的。有过linux嵌入式开发经验的朋友都知道要想使一个linux在arm芯片上真正跑起来它必须有三个部分组成即boot 内核 文件系统。而真正内核的东西其实很少也就是cpu初始化、线程调度、内存分配、文件系统、网络协议栈、驱动这些部分组成。那么是不是所有的芯片都需要跑操作系统呢我们可以举个例子。 现在有一个简单的温度测量电路它由三部分组成1、单片机2、温度传感器模块3、无线发射模块。我们设计这么一个温度测量电路其实就是一个目的那就是为了实时获取当前的温度信息。那么这么一个简单的电路应该怎么设计程序呢其实很简单。 [cpp] view plaincopy void sleep(int value) { int outer; int inner; for(; outer value; outer) { for(inner 0; inner 1000; inner) ; } } void main() { while(1) { /* read temperature from port*/ sleep(1000); /* send temperature to wireless module */ sleep(1000); } } 如果我们需要cpu干的事情很少甚至极端一点说只有一件事情那么根本没有设计操作系统的必要。我们设计出操作系统主要是想在单位时间内完成几件事情。打个比方来说你完全可以在工作的时候一遍写文档、一遍收发电子邮件偶尔还能开个小差休息一会。 所以操作系统就是为了共享资源而存在的。认识操作系统的用途不难关键是如何把操作系统用代码写出来。也许有人会跟你说免费的代码一大堆Linux就不错你下载下来直接读就好了。但是我告诉你最新的Linux内核版本已经轻松的越过了3.0整个代码的长度远在千万行之上你可能从哪看起都不知道。可能此时又有人不同意了看不懂高版本的linux可以看看linux低版本的代码0.11版本的代码就不错因为赵炯就是怎么推荐的。我要说的是0.11的代码固然好但是怎么编译版本、怎么修改代码、怎么构造文件系统、怎么跑起来是我们绕不过的一道难题。对于很多朋友来说阅读linux代码尚且困难更不要说后面还需要完成的一大摊子烂事了。说了这么多我们需要的的内核代码是什么样的其实在我看来很简单。它只要满足下面两个条件就可以了1像用户软件一样可以运行2像用户软件一样可以单步调试。 要解决这些问题对linux系统来说上不难解决。要解决os的运行和调试问题关键就在于如何仿真中断和实现os的任务切换。至于任务的开启、运行和挂起内存分配互斥量信号量文件系统tcp/ip协议栈GUI操作这些其实都是可以在linux上进行仿真和操作的朋友们可以尽请放心。这部分的内容我们会在以后的博客中陆续展开。为了能够更好地阅读后面发表的博文我建议你巩固一下下面这些知识这样会对你的理解有很大的裨益。1cpu 结构了解中断流程就行2linux 汇编语言3函数堆栈格式和内容4互斥量、信号量的使用方法5调度的基本策略6内存分配的基本方法7tcp/ip socket编程8gui编程方法可以参考windows的方法9系统中的内存布局、编译原理等等。 嵌入式操作系统内核原理和开发cpu的那些事 cpu是数字处理系统中的一个重要环节。在我看来单片机、微处理器、dsp都可以称作是cpu只是它们的侧重点有所不同罢了。具体来说传统意义上的单片机更偏重于嵌入式的计算比如说我们经常使用的51、avr、arm芯片中不仅仅含有了运算和控制功能它还涵盖了定时器、串口、并口、usb、i2c总线等外部资源。dsp呢cpu一般只是作为dsp的一个核存在它通常还会包含另外一个核专门用于数字信号的处理工作。而微处理器也就是我们经常说的pc上的处理器它的工作比较单一专注于计算和控制功能的处理因此一般来说在这方面的性能上面单片机和dsp都是不能和它相比的有了南桥芯片和北桥芯片的帮助pc的微处理器就可以专注于自己的本职工作了效率上面也会有一个很大的提高。 对于朋友们来说生活中遇到的最多的cpu其实是x86的cpu。当然如果有哪位朋友喜欢apple之类的玩具也会知道一些arm的相关事情。剩下的就是一些专用领域的cpu了比如说在通信行业用到的比较多的powerpc芯片在高性能服务器用的到的sun sparc芯片在科学计算领域使用到的mips芯片。所以无论遇到什么芯片对于应用层开发的朋友都是一样的只是在一些小地方需要还有一些注意的地方。比如说
1数据的对齐方式
2数据的字节序问题
3函数参数的压栈问题
4cpu的乱序执行问题
5cpu中cache和内存一致性的问题 当然如果我们所要思考只是简单的应用层设计考虑到这些内容本身已经实属不易了。然而我们考虑的是如何设计嵌入式操作系统的问题所以接下来还要看看一般cpu下面都包含了那些内容。这样只要熟练掌握了一款cpu的设计和实现对其他cpu的知识也会触类旁通了。 任何一款cpu不管是完成的功能是什么样的通常都会有这样一些基本设计
1寄存器 堆栈寄存器 pc寄存器 状态寄存器 运算寄存器 寄存器是cpu内部的基本资源。不管cpu的代码执行到什么时候这些资源都是共享的所以在cpu发生中断、函数调用、线程切换的时候都需要对这些寄存器进行保护常用的基本措施就是把把它们保存到临时堆栈当中去。堆栈寄存器记录了当前堆栈使用到了什么地方pc寄存器则记录当前代码跑到了什么地方下一条指令在什么地方等。状态寄存器则保存了当前cpu的执行情况包括计算有没有溢出、中断是关还是开、有没有o除数异常等等。至于运算寄存器就因cpu而异了有的cpu寄存器比较少比如说x86的寄存器有的cpu寄存器就比较多比如说powerpc。运算寄存器的用途很多比如说数据访问、计算、移位、返回计算结果等等。 2指令集 寻址指令 数学运算指令 逻辑运算指令 软中断指令 跳转指令 远程调用指令 io访问指令 栈操作指令 指令集在某种程度上直接决定了某一种cpu的类型。就像intel和amd生产的cpu虽然有差别但是它们的cpu使用的都是x86的指令集而marwell、samsung和高通生产的cpu当然也不同但是它们的指令集都是arm指令集。所以如果软件在marwell上跑一般来说也可以在Samsung上跑起来。指令集很复杂内容很多。但是通常来说上面这些内容都是cpu所必须要完成的几种指令。当然重中之重的还是中断和栈处理指令。 3中断、异常处理机制 不管是什么cpu中断部分的内容都是少不了的。试想一想如果一颗cpu只知道不停地运行那么它的执行效率实际上是很低的。拥有了中断的cpu不仅使得cpu可以和更多的外设io打交道还能极大地提高自身运行的效率。不同的cpu处理中断的方法其实都差不多在整个cpu的地址空间里面通常在低地址区域会有一张中断向量表表中每一项记录了每一个中断对应的处理函数。所以只要中断发生时cpu就会首先将下一条pc地址压入堆栈然后跳转到中断向量表中对应的中断地址处执行的相应的处理函数。这个过程是cpu自动完成的不需要我们关心。这样对我们来说它和cpu中的函数调用其实没有什么区别。等待中断处理结束后我们使用ret指令返回到之前压入的那个ip地址处继续下面的代码。整个过程就好像中断根本没有发生过一样。 所以对于cpu的了解其实主要就是对寄存器、指令集和中断的了解。有了对中断和堆栈的深入理解其实也就没有什么困难的了。在这里我们大家可以考虑一个问题如何在Windows或者linux下仿真中断完成我们的操作系统开发呢大家可以自己先思考一下我们会在随后的博客中继续介绍。整篇文章我们没有介绍编码的相关内容其实只要把这里的基本概念弄清楚了剩下来其实就是一些流程性的工作了。在软件开发中设计其实是最难的剩下的才是开发和调试。
嵌入式操作系统内核原理和开发中断 在我个人看来中断是cpu最重要的特色。从某种意义上来说没有中断就没有嵌入式操作系统。一旦你明白了中断的真正含义你对操作系统的了解就算真正入门了。什么是中断呢我们可以看看单片机下面是怎么做的。 [cpp] view plaincopy #include REG51.h sbit LED P1 ^ 6; unsigned int led_enable 0; void Delay(unsigned int a) { unsigned int i; while(a) { a --; for(i 0; i 1200; i); } } void led_switch(void) interrupt 0 using 1 { if(0 led_enable) { led_enable 1; } else { led_enable 0; } EX0 1; } void main(void) { EA 1; EX0 1; while(1){ if(led_enable) { LED 0; Delay(100); LED 1; Delay(100); } } } 上面的代码是一段真实的51单片机代码。它完成的功能很简单就是对led灯进行点亮处理。怎么解释呢?在单片机上电后我们发现一开始led二极管没有发生闪烁。在我们单击按键之后led开始出现间隙性闪烁的现象之后再一次单击按键又可以发现led的闪烁现象消失了。为什么会出现这种现象主要是因为我们单击按键的时候在单片机的引脚处产生了中断。查看到中断的单片机此时就会跳转到中断向量表里面查找中断处理函数。这里的按键中断处理函数就是led_switch。处理完led_switch之后单片机又会回到原来的main函数继续执行所以整个中断的过程就像没有发生过一样。因为在led_switch中我们对led_enable进行了处理所以就出现了我们在前面说过的各种现象。 说到这也许有的朋友会说cpu的这种中断属性怎么才能在pc上面仿真出来呢其实很简单。linux系统本身就有一个优秀的特性那就是信号。只要我们设定相应的信号和处理函数那么linux系统就会在系统调度返回之前调用相应的信号函数来处理。整个信号处理的过程和中断是一模一样的。因为在处理中断的时候我们需要对cpu的现场进行保存和恢复处理而信号的处理也是一样。在信号处理前系统肯定是处于内核态那么linux系统肯定已经为我们做好了现场的保护工作处理完信号之后系统本身又会恢复到原来的用户态继续执行下面的代码。所以linux自身也会默认对原来的场景进行恢复处理就好象中断返回一样。 [cpp] view plaincopy #include stdio.h #include time.h #include sys/time.h #include stdlib.h #include signal.h static int count 0; static struct itimerval oldtv; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void signal_handler(int m) { count ; printf(%d\n, count); } int main() { signal(SIGALRM, signal_handler); set_timer(); while(count 10000); exit(0); return 1; } 大家可以把这样一段代码在Linux编译一下然后使用gdb调试即可。查看整个signal_handler在被断点断住的时候本身线程是不是只有一个函数堆栈是不是在一个线程里面。如果不出意外整个信号的处理过程应该是这样的 [cpp] view plaincopy (gdb) bt #0 signal_handler(m14) at code.c: 23 #1 signal handler caller #2 main() at code.c:32 到了这里相应大家应该还有一个疑问既然可以利用linux的signal对cpu的中断进行仿真那么能不能利用windows的signal对中断进行仿真呢。因为Windows下面的没有SIGALRM这个信号所以我们可以重新编写一段代码然后利用visual studio 6.0进行编译看看对应的情况。 [cpp] view plaincopy #include stdio.h #include stdlib.h #include signal.h #include tchar.h #include Windows.h void SignalHandler(int signal) { printf(Application over..\n); } int main() { typedef void (*SignalHandlerPointer)(int); SignalHandlerPointer previousHandler; previousHandler signal(SIGINT, SignalHandler); while(1) { Sleep(0); } exit(1); } 下面我们首先编译这一段代码。接着在程序run之后我们可以在SignalHandler之处设置一个断点。一切就绪完毕再按下ctrlc之后系统就会在SignalHandler之处断住。此时单击【Debug】- 【Threads】就可以看到这个情况。 相信看到这里大家应该看明白了。其实在Windows下面信号是专门有一个线程来完成的和原来的main函数不是同一个线程。既然线程都不是一样的而中断本身是必须在一个thread中完成的。我们怎么能利用windows来仿真cpu的中断处理流程呢。
嵌入式操作系统内核原理和开发地址空间 不管是什么样的嵌入式cpu它必然有自己的访问地址空间。至于这个具体的访问空间是什么那cpu就不知道了。它可以是ram当然也可以是flash、uart、ide、i2c等。当然cpu不仅需要地址总线它还需要数据总线和控制总线。这三个总线的目的都非常明确控制总线主要是为了实现cpu对外设接口的控制地址总线是为了实现地址的输出数据总线则是为了实现数据内容的获取或者设置。所以对于一般的嵌入式cpu来说它的基本架构应该是这样的 在x86的cpu上很多对外设的操作是需要通过北桥或者通过南桥芯片完成的。而在嵌入式硬件中我们就把经常使用到的接口芯片集成到了cpu里面。所以在嵌入式cpu功能上面你除了看到cpu的字长、时钟、指令集、运算速率这些通常的数据之外你还会看到很多的接口控制寄存器比如说定时器寄存器lcd寄存器uart寄存器i2c寄存器。这些都表明了此时的cpu完成的不仅仅是简单的计算功能它还需要完成对外设接口的设置。通过对应的寄存器设置我们就可以对外设的接口状态进行控制。 说了这么多我们接下来要看看嵌入式系统在地址空间里面是怎么设计的啊其实一个完整的嵌入式软件系统并不复杂。一般来说一个完整的系统需要有boot、kernel、文件系统三部分。其中boot主要放在norflash里面而kernel和文件系统是存放在nandflash里面。在系统上电之后cpu会有一个初始地址这个地址要么是0x00000001或者是0xFFFF0000。通常这个地址会指向Norflash下面开始执行的代码当然就是boot代码。因为Norflash的访问速度要比Ram速度慢很多所以boot代码很快会把自己拷贝到Ram中然后跳到Ram中继续运行。boot的功能比较简单主要就是为了获取芯片参数设置芯片属性设置堆栈空间加载操作系统内核等。在boot完成自己的功能之后它会把系统内核加载到Ram中然后jump到系统的运行首地址处运行。系统内核主要完成整个系统的初始化工作比如说内存分配信号量初始化net初始化驱动结构初始化等工作。在内核即将完成初始化的时候它会进行最后一步操作mount一个文件系统加载文件系统的脚本数据开启相关的系统进程最后一步就是开启shell进程接受用户的命令输入。至此一个系统算是真正跑起来了。 前面我们说过cpu需要数据总线、地址总线和控制总线才能与外设接口打交道。既然cpu是通过状态寄存器设置外设接口的状态的那么cpu是如何通过地址总线与外界联系的呢这里面就涉及到片选信号的问题。我们知道一个32位的cpu有32条地址线和外界相连那么也就是说如果没有其他的方法它只能外设32个接口。那么有没有什么办法可以扩大外设接口呢说到这里你应该知道了它就是解码器和片选信号了。比如说现在有4个外设接口我们可以怎么从地址总线中挑出两条线00表示外设101表示外设210表示外设311表示外设4。这样有了解码器的帮助我们就可以用两个地址线实现对4个外设接口的控制了。有了cpu状态寄存器我们可以设置当前外设接口的执行状态。如果是读命令首先设置外设接口的状态模式为读状态然后发送地址此时片选信号选中的芯片就会处于使能状态一会cpu就可以从数据总线上获得数据存储在寄存器或者是内存当中如果是写命令那么cpu首先设置外设接口为写模式然后在地址总线上输出地址在收到芯片ready信号后cpu再将数据从寄存器上传输到数据总线上在等到外设芯片的ack信号后整个数据的传输过程才算完成。我们看到一个汇编指令的操作竟然涉及到这么多信号的操作可见cpu的处理过程还是很复杂的。有的时候中间还会涉及到信号完整性或者是时序的问题那么这时候逻辑分析仪就可以派上用场了。 嵌入式操作系统内核原理和开发基础 在编写我们的操作系统之前我们需要明确一些事情。比如说这个系统的运行环境是什么怎么编译基本中断环境是什么上下文怎么切换准备实现那些内容基本数据类型是什么等等。 1你的嵌入式操作系统准备叫什么名称运行环境是什么可以在实际环境上面运行吗 我们准备把这个嵌入式操作系统称之为MiniOS。虽然这个操作系统实现的功能不多但是麻雀虽小五脏俱全。一般操作系统该有的功能MiniOS都会有这个功能。起初我们会在linux上运行MiniOS之后我们会把MiniOS移植到51单片机上去。 2操作系统怎么编译 MiniOS系统采用基本的C语言进行编写没有汇编语言但是移植到51上面需要一些汇编语言。编写完C语言文件后我们需要一个简单的makefile文件这样就可以实现自动化编译进一步提高我们开发的效率。 3基本中断环境是什么 因为MiniOS起初是在linux实现运行的所以它是依靠SIGALRM实现中断调度的。当然在OS中还会有I/O操作这里我们用信号进行仿真。SIGINT就是一种方法每当我们使用键盘输入ctrlC的时候就会调用相应的函数。这和外设的中断是一模一样的。 4上下文怎么切换 上下文的切换就是堆栈的切换。要弄清楚堆栈的切换首先你要明白函数函数里面的数据是怎么安排的压栈是怎么回事退栈是怎么回事。这些知识点我们在后面都会一一介绍。 5MiniOS要实现哪些内容 MiniOS虽然比较小巧但是操作系统该有的功能它都会有。因此我们准备实现的下面这些内容比如说中断开关、互斥量、定时器、线程调度、内存分配都会拥有。 6基本数据类型是什么 为了移植的方便我们需要对基本数据进行重新定义一下基本数据类型。 [cpp] view plaincopy #define UINT32 unsigned int #define INT32 signed int #define UINT16 unsigned short #define INT16 signed short #define UINT8 unsigned char #define INT8 signed char #define TRUE 1L #define FALSE 0L #define OK 0L #define ERROR ~0L #define BOOLEAN UINT32 #define STATUS UINT32 7把MiniOS移植到51单片机需要做些什么 其实把MiniOS移植到51上面其实不困难只要做到这几个方面就可以了。a基本数据类型重新定义b中断开关重新进行设置c任务切换的压栈、出栈处理。要是做到这里我们就可以在51单片机上面把自己的OS跑起来了那是多么开心的一件事情啊。 嵌入式操作系统内核原理和开发系统中断仿真 在嵌入式操作系统中最难模仿的是系统中断的问题。在windows下面这是没有办法的事情。但是在linux下面却有一个非常便利的条件那就是linux的信号处理。因为用户程序处理的时候signal的处理和用户线程的处理堆栈是一致的这和中断是完全吻合的。所以如果你需要关闭中断可以这么写 [cpp] view plaincopy sigprocmask(SIG_BLOCK, new_set, old_set); 当然如果需要再次打开中断了还可以这么写 [cpp] view plaincopy sigprocmask(SIG_UNBLOCK, new_set, NULL); 所以这里我们可以编写一个完整的程序说明问题。 [cpp] view plaincopy #include stdio.h #include time.h #include sys/time.h #include stdlib.h #include signal.h static int count 0; static struct itimerval oldtv; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void signal_handler(int m) { count ; printf(%d\n, count); } void signal_int() { printf(hello!\n); } int main() { sigset_t new_set; sigset_t old_set; signal(SIGALRM, signal_handler); signal(SIGINT, signal_int); set_timer(); sigemptyset(new_set); sigaddset(new_set, SIGALRM); sigaddset(new_set, SIGINT); sigprocmask(SIG_BLOCK, new_set, old_set); sleep(10); sigprocmask(SIG_UNBLOCK, new_set, NULL); while(count 10 ) sleep(1); return 0; } 基本说明如下 1我们的系统暂时只负责SIGINT和SIGALRM前者负责I/O的仿真后则负责线程的调度 2 sigprocmask函数用来实现中断的打开和关闭 3程序首先关闭中断然后打开中断打印数据完即结束仅作为示范使用。
嵌入式操作系统内核原理和开发线程切换 在操作系统中线程切换是很重要的一个环节。如果没有线程的切换我们如何才能实现多线程的并发运行呢既然要实现切换那么一方面我们需要对原来的寄存器进行保存另外一方面我们还要压入新堆栈的寄存器这样才能实现线程切换的效果。在x86下面因为切换线程的ip地址是固定的所以切换所需要的寄存器也是固定的一般来说保存eax、ebx、ecx、edx、esi、edi、ebp和esp即可。比如说像这样 [cpp] view plaincopy void swap(UINT32* prev, UINT32* next) { __asm(push %%eax\n\t push %%ebx\n\t push %%ecx\n\t push %%edx\n\t push %%esi\n\t push %%edi\n\t push %%ebp\n\t push %%esp\n\t lea 0x8(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov %%esp, (%%eax)\n\t lea 0xc(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov (%%eax), %%esp\n\t pop %%esp\n\t pop %%ebp\n\t pop %%edi\n\t pop %%esi\n\t pop %%edx\n\t pop %%ecx\n\t pop %%ebx\n\t pop %%eax\n\t ::); } 上面说的都是对已经运行的线程进行切换。那么刚刚创建的线程怎么进行切换呢一个不错的方法就是仿真出栈的处理流程。把初始状态的寄存器放在堆栈里面模仿线程的出栈过程设置好线程的初始寄存器数值即可。比如说像这样 [cpp] view plaincopy void signal_handler(int m) { UINT32* data; UINT32 unit; if(count ! 0) { printf(count %d\n, count); return; } printf(count %d\n, count); data (UINT32*)malloc(STACK_LENGTH); unit STACK_LENGTH 2; if(NULL data) return; memset(data, 0, STACK_LENGTH); data[unit -1] (UINT32) hello; data[unit -2] 0; data[unit -3] 0; data[unit -4] 0; data[unit -5] 0; data[unit -6] 0; data[unit -7] 0; data[unit -8] 0; data[unit -9] 0; data[unit -10] (UINT32) data[unit - 9]; new (UINT32) data[unit -10]; swap(old, new); free(data); } 最后我们给出一份完整的代码。在程序收到第一个signal的时候我们发现代码不仅申请了内存还初始化成了堆栈的格式完美地解决了堆栈切换的问题。当然在hello处理结束后代码又恢复成了原来的格式而且内存正常释放一切就像没有发生过一样。试想如果每一次处理的都是一个function和stack那基本上就可以模仿线程的运行过程了。[cpp] view plaincopy #include stdio.h #include time.h #include sys/time.h #include stdlib.h #include signal.h #define UINT32 unsigned int #define STACK_LENGTH 1024 static struct itimerval oldtv; UINT32 old 0; UINT32 new 0; UINT32 count 0; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void swap(UINT32* prev, UINT32* next) { __asm(push %%eax\n\t push %%ebx\n\t push %%ecx\n\t push %%edx\n\t push %%esi\n\t push %%edi\n\t push %%ebp\n\t push %%esp\n\t lea 0x8(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov %%esp, (%%eax)\n\t lea 0xc(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov (%%eax), %%esp\n\t pop %%esp\n\t pop %%ebp\n\t pop %%edi\n\t pop %%esi\n\t pop %%edx\n\t pop %%ecx\n\t pop %%ebx\n\t pop %%eax\n\t ::); } void hello() { printf(hello!\n); swap(new, old); } void signal_handler(int m) { UINT32* data; UINT32 unit; if(count ! 0) { printf(count %d\n, count); return; } printf(count %d\n, count); data (UINT32*)malloc(STACK_LENGTH); unit STACK_LENGTH 2; if(NULL data) return; memset(data, 0, STACK_LENGTH); data[unit -1] (UINT32) hello; data[unit -2] 0; data[unit -3] 0; data[unit -4] 0; data[unit -5] 0; data[unit -6] 0; data[unit -7] 0; data[unit -8] 0; data[unit -9] 0; data[unit -10] (UINT32) data[unit - 9]; new (UINT32) data[unit -10]; swap(old, new); free(data); } int main() { set_timer(); signal(SIGALRM, signal_handler); while(count 10); exit(0); return 1; } 嵌入式操作系统内核原理和开发任务创建和堆栈溢出检查 虽然写操作系统的博客要比写普通的技术点要麻烦一些但是心中还是挺开心的。一方面通过几行代码就可以说明一些问题把理论实践化这本身就很具有挑战性另外一方面还锻炼自己的沟通能力让更多的人明白你的想法认可你的想法。 其实通过上面一篇博客我们就已经清楚任务的创建是怎么一回事但是我们还是愿意就这个问题讲得更细一点说得更多一点。系统本身是多线程的那说明所有线程的地址空间都是共享的。由于资源都是操作系统本身提供的所以线程本身的要求就很低函数名、堆栈、入口点、堆栈大小、优先级大体上也就是这么多。至于这个堆栈是哪里的内存其实已经不太重要了。为了简单起见我们对原来的初始化函数 稍微修改了一下 [cpp] view plaincopy void task_init() { UINT32 unit STACK_LENGTH; memset((void*)data, 0, STACK_LENGTH * sizeof(UINT32)); data[unit -1] (UINT32) hello; data[unit -2] 0; data[unit -3] 0; data[unit -4] 0; data[unit -5] 0; data[unit -6] 0; data[unit -7] 0; data[unit -8] 0; data[unit -9] 0; data[unit -10] (UINT32) data[unit - 9]; new (UINT32) data[unit -10]; } 上面的操作比较简陋只是对堆栈进行了设置。这是线程初始化的时候必须要做的一步。当然这里的hello就是我们的函数入口点。因为这里用SIGALRM代替的时钟中断是没有办法做到抢占的所以我们可以人为多设置一些调度点比如象这样 [cpp] view plaincopy void hello() { printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); quit 1; swap(new, old); } 在编写程序的时候最恐怖的事情就是堆栈溢出了。但是在操作系统中我们完全可以自己判断当前的堆栈是否已经溢出。因为我们知道在线程调度的时候保存的堆栈esp永远指向最低的那个地址。 [cpp] view plaincopy int check_stack_overflow(unsigned int base, unsigned int current) { assert(0 ! base 0 ! current); return (current base) ? 1 :0; } 当然这些说的都是线程调度的事你也可以编写输入输出命令实现对嵌入式操作系统的某种控制。要打印什么设置什么保存什么都可以通过你的输入命令来解析执行这些都是和signal处理是分开来的。后面这部分还要详细讨论这里可以稍微添加一下 [cpp] view plaincopy int main() { char val; task_init(); set_timer(); signal(SIGALRM, signal_handler); while(1) { scanf(%c, val); } exit(0); return 1; } 最后还是老规矩附上详细的代码。虽然这一过程有点繁琐和冗余但是至少看上去更完整一些。 [cpp] view plaincopy #include stdio.h #include time.h #include stdlib.h #include signal.h #include assert.h #include sys/time.h #define UINT32 unsigned int #define STACK_LENGTH 512 static struct itimerval oldtv; UINT32 old 0; UINT32 new 0; UINT32 count 0; UINT32 data[STACK_LENGTH] {0}; UINT32 quit 0; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void swap(UINT32* prev, UINT32* next) { __asm(push %%eax\n\t push %%ebx\n\t push %%ecx\n\t push %%edx\n\t push %%esi\n\t push %%edi\n\t push %%ebp\n\t push %%esp\n\t lea 0x8(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov %%esp, (%%eax)\n\t lea 0xc(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov (%%eax), %%esp\n\t pop %%esp\n\t pop %%ebp\n\t pop %%edi\n\t pop %%esi\n\t pop %%edx\n\t pop %%ecx\n\t pop %%ebx\n\t pop %%eax\n\t ::); } void hello() { printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); swap(new, old); printf(count %d in sub!\n, count ); quit 1; swap(new, old); } void task_init() { UINT32 unit STACK_LENGTH; memset((void*)data, 0, STACK_LENGTH * sizeof(UINT32)); data[unit -1] (UINT32) hello; data[unit -2] 0; data[unit -3] 0; data[unit -4] 0; data[unit -5] 0; data[unit -6] 0; data[unit -7] 0; data[unit -8] 0; data[unit -9] 0; data[unit -10] (UINT32) data[unit - 9]; new (UINT32) data[unit -10]; } int check_stack_overflow(unsigned int base, unsigned int current) { assert(0 ! base 0 ! current); return (current base) ? 1 :0; } void signal_handler(int m) { if(0 quit) { swap(old, new); assert(0 check_stack_overflow(data,new)); return; } printf(count %d in main!\n, count ); } int main() { char val; task_init(); set_timer(); signal(SIGALRM, signal_handler); while(1) { scanf(%c, val); } exit(0); return 1; } 嵌入式操作系统内核原理和开发多线程轮转 之前我们也谈到了线程创建基本上简单的系统就可以跑起来了但是还没有到多线程运行的地步。所以我们下面试图所要做的工作就是创建更多的线程让更多的线程运行起来。为了做好这一点首先我们需要对task_init重新修整一下 [cpp] view plaincopy void task_init(int index, UINT32 data[], int size, void (*func)()) { UINT32 unit size; memset((void*)data, 0, size * sizeof(UINT32)); data[unit -1] (UINT32) func; data[unit -2] 0; data[unit -3] 0; data[unit -4] 0; data[unit -5] 0; data[unit -6] 0; data[unit -7] 0; data[unit -8] 0; data[unit -9] 0; data[unit -10] (UINT32) data[unit - 9]; new[index] (UINT32) data[unit -10]; } 这是一个创建线程的函数有堆栈、大小、函数入口。那么我们的函数什么时候创建呢其实就是在系统的开始位置就可以 [cpp] view plaincopy void set_all_task() { int index; for(index 0; index THREAD_MAX_NUMBER; index ) task_init(index, task_stack[index], STACK_LENGTH, hello); } 既然任务创建没有问题那么下面就会涉及到简单轮转的问题。其实我们的方法特别简单就是根据current_thread_id叠加每一个thread都有自己的运转机会。代码如下所示 [cpp] view plaincopy void signal_handler(int m) { current_thread_id current_thread_id % THREAD_MAX_NUMBER; if(0 quit[current_thread_id]) { swap(old, new[current_thread_id]); } printf(count %d in main!\n\n, count ); current_thread_id ; } 当然为了要实现真正的多线程运行我们还要保证线程始终在运行。要达到这一点也不是很复杂只需要把子函数设计为while1即可 [cpp] view plaincopy void hello() { while(1) { printf(id %i, count %d in thread!\n,current_thread_id, count ); swap(new[current_thread_id], old); printf(id %i, count %d in thread!\n,current_thread_id, count ); swap(new[current_thread_id], old); } } 基本上要做到以上几点就可以实现了最后给出完整的代码大家可以在linux系统好好试试这个代码。 [cpp] view plaincopy #include stdio.h #include time.h #include stdlib.h #include signal.h #include assert.h #include sys/time.h #define UINT32 unsigned int #define STACK_LENGTH 512 #define THREAD_MAX_NUMBER 10 struct itimerval oldtv; UINT32 old 0; UINT32 count 0; UINT32 task_stack[THREAD_MAX_NUMBER][STACK_LENGTH] {0}; UINT32 new[THREAD_MAX_NUMBER] {0}; UINT32 quit[THREAD_MAX_NUMBER] {0}; UINT32 current_thread_id 0; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void swap(UINT32* prev, UINT32* next) { __asm(push %%eax\n\t push %%ebx\n\t push %%ecx\n\t push %%edx\n\t push %%esi\n\t push %%edi\n\t push %%ebp\n\t push %%esp\n\t lea 0x8(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov %%esp, (%%eax)\n\t lea 0xc(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov (%%eax), %%esp\n\t pop %%esp\n\t pop %%ebp\n\t pop %%edi\n\t pop %%esi\n\t pop %%edx\n\t pop %%ecx\n\t pop %%ebx\n\t pop %%eax\n\t ::); } void hello() { while(1) { printf(id %i, count %d in thread!\n,current_thread_id, count ); swap(new[current_thread_id], old); printf(id %i, count %d in thread!\n,current_thread_id, count ); swap(new[current_thread_id], old); } } void task_init(int index, UINT32 data[], int size, void (*func)()) { UINT32 unit size; memset((void*)data, 0, size * sizeof(UINT32)); data[unit -1] (UINT32) func; data[unit -2] 0; data[unit -3] 0; data[unit -4] 0; data[unit -5] 0; data[unit -6] 0; data[unit -7] 0; data[unit -8] 0; data[unit -9] 0; data[unit -10] (UINT32) data[unit - 9]; new[index] (UINT32) data[unit -10]; } void signal_handler(int m) { current_thread_id current_thread_id % THREAD_MAX_NUMBER; if(0 quit[current_thread_id]) { swap(old, new[current_thread_id]); } printf(count %d in main!\n\n, count ); current_thread_id ; } void set_all_task() { int index; for(index 0; index THREAD_MAX_NUMBER; index ) task_init(index, task_stack[index], STACK_LENGTH, hello); } int main() { char val; set_all_task(); set_timer(); signal(SIGALRM, signal_handler); while(1) { scanf(%c, val); } exit(0); return 1; } 嵌入式操作系统内核原理和开发通用优先级调度 相比较其他调度算法而言时间片的轮转更多的注重公平性。但是任务与任务之间也是有先后之分的有的任务我们希望多安排一些时间片而有的任务我们则希望少安排一些时间片。比较说如果我们在上网的话我们就希望上网的操作响应的更快一些如果我们在进行GUI操作我们当然就希望图形响应更快一些。这些都是可以理解的下面我们就绪要对数据结构进行一些修改。[cpp] view plaincopy typedef struct _TASK_INFO { UINT32 id; UINT32* stack; UINT32 size; UINT32 context; UINT32 priority; UINT32 time_slice; void (*func)(); }TASK_INFO; 这里的priority就是当前线程的优先级所以最简单的方法就是根据priority直接分配对应的time_slice。也就是这个函数[cpp] view plaincopy void reset_time_slice () { int index; for(index 0; index THREAD_MAX_NUMBER; index) gAllTask[index].time_slice gAllTask[index].priority 1; } 所以以后每次调度的时候我们就首先寻找当前最高优先级的任务看看当前任务安排的时间片是否用完了没有用完就继续运行。如果当前优先级的任务已经没有时间片了那么此时就可以安排低优先级的任务进行调度了。[cpp] view plaincopy void signal_handler(int m) { int index; start: index find_next_thread(); if(-1 index) { reset_time_slice(); goto start; } gAllTask[index].time_slice --; current_thread_id index; swap(old, gAllTask[current_thread_id].context); } 下面我们就根据任务优先级挑选下一个需要运行的thread了[cpp] view plaincopy int find_next_thread() { int index; for(index THREAD_MAX_NUMBER -1; index 0; index --) { if(0 ! gAllTask[index].time_slice) break; } return index; } 整个代码的流程也不复杂大家可以运行、单步调试一把试试看。[cpp] view plaincopy #include stdio.h #include time.h #include stdlib.h #include signal.h #include assert.h #include string.h #include sys/time.h #define UINT32 unsigned int #define STACK_LENGTH 512 #define THREAD_MAX_NUMBER 10 typedef struct _TASK_INFO { UINT32 id; UINT32* stack; UINT32 size; UINT32 context; UINT32 priority; UINT32 time_slice; void (*func)(); }TASK_INFO; static struct itimerval oldtv; UINT32 old 0; UINT32 count 0; UINT32 task_stack[THREAD_MAX_NUMBER][STACK_LENGTH] {0}; TASK_INFO gAllTask[THREAD_MAX_NUMBER] {0}; UINT32 current_thread_id 0; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void swap(UINT32* prev, UINT32* next) { __asm(push %%eax\n\t push %%ebx\n\t push %%ecx\n\t push %%edx\n\t push %%esi\n\t push %%edi\n\t push %%ebp\n\t push %%esp\n\t lea 0x8(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov %%esp, (%%eax)\n\t lea 0xc(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov (%%eax), %%esp\n\t pop %%esp\n\t pop %%ebp\n\t pop %%edi\n\t pop %%esi\n\t pop %%edx\n\t pop %%ecx\n\t pop %%ebx\n\t pop %%eax\n\t ::); } void hello() { int temp 0; while(1) { printf(id %d, temp %d, count %d in thread!\n,current_thread_id, temp , count ); swap(gAllTask[current_thread_id].context, old); printf(id %d, temp %d, count %d in thread!\n,current_thread_id, temp , count ); swap(gAllTask[current_thread_id].context, old); } } int find_next_thread() { int index; for(index THREAD_MAX_NUMBER -1; index 0; index --) { if(0 ! gAllTask[index].time_slice) break; } return index; } void reset_time_slice () { int index; for(index 0; index THREAD_MAX_NUMBER; index) gAllTask[index].time_slice gAllTask[index].priority 1; } void task_init(int index) { UINT32 unit gAllTask[index].size; UINT32* pData gAllTask[index].stack; memset((void*)pData,(int) 0, unit * sizeof(UINT32)); pData[unit -1] (UINT32) gAllTask[index].func; pData[unit -2] 0; pData[unit -3] 0; pData[unit -4] 0; pData[unit -5] 0; pData[unit -6] 0; pData[unit -7] 0; pData[unit -8] 0; pData[unit -9] 0; pData[unit -10] (UINT32) pData[unit - 9]; gAllTask[index].context (UINT32) pData[unit -10]; } void signal_handler(int m) { int index; start: index find_next_thread(); if(-1 index) { reset_time_slice(); goto start; } gAllTask[index].time_slice --; current_thread_id index; swap(old, gAllTask[current_thread_id].context); } void set_all_task() { int index; memset(gAllTask, 0, sizeof(gAllTask)); for(index 0; index THREAD_MAX_NUMBER; index ) { gAllTask[index].id index; gAllTask[index].stack task_stack[index]; gAllTask[index].size STACK_LENGTH; gAllTask[index].context 0; gAllTask[index].func hello; gAllTask[index].priority index; gAllTask[index].time_slice index 1; task_init(index); } } int main() { char val; set_all_task(); set_timer(); signal(SIGALRM, signal_handler); while(1) { scanf(%c, val); } exit(0); return 1; } 嵌入式操作系统内核原理和开发改进型优先级调度 上面的一篇博客说到了优先级调度但是那个优先级调度算法比较极端。打个比方说现在王先生有三个小孩分别是老大、老二、老三。假设现在到了饭点王先生需要给三个小孩喂饭。此时如果是时间片轮转的话那么就是绝对公平王先生每人一口不停地进行喂饭。如果是优先级调度那么王先生首先自己有一个优先级考量比如说三个小孩按照年龄顺序优先级是逐渐提高的毕竟小孩需要更多的照顾嘛。这个时候如果需要进行喂饭的话那么王先生需要首先伺候好最小的那个小孩老三才会有时间照顾老二至于老大什么时候才能得到照顾那就看造化了。 现在我们打算重新换一种方法。假设三个小孩的优先级分别是1、2、3其中年龄越小优先级越高3代表高优先级。接着我们按照优先级给三个小孩安排时间片分别是1、2、3。同时这个时间片不光代表了当前可用的剩余时间还代表了小孩此时的临时优先级。 1首先王先生给老三喂饭时间片降低1即临时优先级为2 2接着王先生判断当前优先级最高的仍为老三毕竟老二的优先级也没有超过老三所以老三的时间片降1临时优先级为1 3王先生获知当前优先级最高的为老二老二获得时间片 4此时王先生发现三个孩子的临时优先级都一样那么就会按照固定优先级的大小依次对老三、老二、老大进行喂饭。 我们发现这中间受益最大的就是老二。当然我们可以做进一步推论如果老王的孩子越多那么优先级处于中间的孩子在时间片的分配上将更加均匀响应也会更加及时交互性也会变得很好。 根据以上的想法我们重新改写了优先级调度算法修改为改进型优先级调度算法 [cpp] view plaincopy int find_next_thread() { int index; int choice THREAD_MAX_NUMBER -1; int value gAllTask[choice].time_slice; for(index choice -1; index 0; index --) { if(value gAllTask[index].time_slice) { choice index; value gAllTask[index].time_slice; } } if(0 value) choice -1; return choice; } 当然加上原来的时间片轮转调度、通用优先级调度方法此时就存在三种调度方法了。我们可以自己设置宏通过宏的设置灵活选用调度算法 [cpp] view plaincopy #define TIME_ROUND_SCHEDULE 0 #define HARD_PRIORITY_SCHEDULE 0 #define SOFT_PRIORITY_SCHEDULE 1 这些代码都是可以在系统中共存的。选用什么算法取决于实际情况是什么样的情形。[cpp] view plaincopy #include stdio.h #include time.h #include stdlib.h #include signal.h #include assert.h #include string.h #include sys/time.h #define UINT32 unsigned int #define STACK_LENGTH 512 #define THREAD_MAX_NUMBER 10 #define TIME_ROUND_SCHEDULE 0 #define HARD_PRIORITY_SCHEDULE 0 #define SOFT_PRIORITY_SCHEDULE 1 typedef struct _TASK_INFO { UINT32 id; UINT32* stack; UINT32 size; UINT32 context; UINT32 priority; UINT32 time_slice; void (*func)(); }TASK_INFO; static struct itimerval oldtv; UINT32 old 0; UINT32 count 0; UINT32 task_stack[THREAD_MAX_NUMBER][STACK_LENGTH] {0}; TASK_INFO gAllTask[THREAD_MAX_NUMBER] {0}; UINT32 current_thread_id 0; void set_timer() { struct itimerval itv; itv.it_interval.tv_sec 1; itv.it_interval.tv_usec 0; itv.it_value.tv_sec 1; itv.it_value.tv_usec 0; setitimer(ITIMER_REAL, itv, oldtv); } void swap(UINT32* prev, UINT32* next) { __asm(push %%eax\n\t push %%ebx\n\t push %%ecx\n\t push %%edx\n\t push %%esi\n\t push %%edi\n\t push %%ebp\n\t push %%esp\n\t lea 0x8(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov %%esp, (%%eax)\n\t lea 0xc(%%ebp), %%eax\n\t mov (%%eax), %%eax\n\t mov (%%eax), %%esp\n\t pop %%esp\n\t pop %%ebp\n\t pop %%edi\n\t pop %%esi\n\t pop %%edx\n\t pop %%ecx\n\t pop %%ebx\n\t pop %%eax\n\t ::); } void hello() { int temp 0; while(1) { printf(id %d, temp %d, count %d in thread!\n,current_thread_id, temp , count ); swap(gAllTask[current_thread_id].context, old); printf(id %d, temp %d, count %d in thread!\n,current_thread_id, temp , count ); swap(gAllTask[current_thread_id].context, old); } } #if HARD_PRIORITY_SCHEDULE int find_next_thread() { int index; for(index THREAD_MAX_NUMBER -1; index 0; index --) { if(0 ! gAllTask[index].time_slice) break; } return index; } #endif #if SOFT_PRIORITY_SCHEDULE int find_next_thread() { int index; int choice THREAD_MAX_NUMBER -1; int value gAllTask[choice].time_slice; for(index choice -1; index 0; index --) { if(value gAllTask[index].time_slice) { choice index; value gAllTask[index].time_slice; } } if(0 value) choice -1; return choice; } #endif void reset_time_slice () { int index; for(index 0; index THREAD_MAX_NUMBER; index) gAllTask[index].time_slice gAllTask[index].priority 1; } void task_init(int index) { UINT32 unit gAllTask[index].size; UINT32* pData gAllTask[index].stack; memset((void*)pData,(int) 0, unit * sizeof(UINT32)); pData[unit -1] (UINT32) gAllTask[index].func; pData[unit -2] 0; pData[unit -3] 0; pData[unit -4] 0; pData[unit -5] 0; pData[unit -6] 0; pData[unit -7] 0; pData[unit -8] 0; pData[unit -9] 0; pData[unit -10] (UINT32) pData[unit - 9]; gAllTask[index].context (UINT32) pData[unit -10]; } #if TIME_ROUND_SCHEDULE void signal_handler(int m) { current_thread_id current_thread_id % THREAD_MAX_NUMBER; swap(old, gAllTask[current_thread_id].context); current_thread_id ; } #else void signal_handler(int m) { int index; start: index find_next_thread(); if(-1 index) { reset_time_slice(); goto start; } gAllTask[index].time_slice --; current_thread_id index; swap(old, gAllTask[current_thread_id].context); } #endif void set_all_task() { int index; memset(gAllTask, 0, sizeof(gAllTask)); for(index 0; index THREAD_MAX_NUMBER; index ) { gAllTask[index].id index; gAllTask[index].stack task_stack[index]; gAllTask[index].size STACK_LENGTH; gAllTask[index].context 0; gAllTask[index].func hello; gAllTask[index].priority index; gAllTask[index].time_slice index 1; task_init(index); } } int main() { char val; set_all_task(); set_timer(); signal(SIGALRM, signal_handler); while(1) { scanf(%c, val); } exit(0); return 1; } 嵌入式操作系统内核原理和开发头文件调整 很长一段时间我个人对头文件的功能了解得不是很明白。虽然在平时的开发中对于头文件也没有犯过什么大的错误但是总觉得对头文件这块理解得不是很透彻。所以趁着这次嵌入式开发的机会好好对头文件这部分的内容进行了分析和总结。下面我们主要从两个方面对头文件进行分析即头文件是做什么的头文件编写的过程中要注意些什么1头文件的作用 其实很多的编程语言是没有头文件的比如说C#、java语言。为什么呢因为这些语言数据结构和函数操作是捆绑在一起的。而C语言则不一样它是把头文件和实现文件分开来的。头文件的内容主要有哪些呢也就是嵌套头文件、宏定义、数据类型、函数原型定义、static函数等等。 2头文件的编写 a头文件要简洁 很多源文件在编写的时候常常喜欢添加很多的头文件不管是需要的还是不需要的。可是我们要知道头文件的作用主要是定义数据类型和函数类型的。本质上来说头文件很少会创建实质性的代码不管是数据段的内容还是代码段的内容。简洁的头文件不仅有利于快速排除编译故障还能提高编译的速度。有经验的朋友都知道源文件的编译错误比较容易解决而头文件的编译错误常常十分复杂。所以我们必须在一切可能的条件下保证头文件的简洁。 b头文件注意互斥性 注意头文件的互斥性需要我们在开发中养成良好的编程习惯。不管是创建头文件首先要做的事情就是添加编译宏。看上去这是一个十分不起眼的举动但是常常可以帮助你减少许多不必要的麻烦。 [cpp] view plaincopy #ifndef _DATA_H #define _DATA_H #endif c全局变量不要在头文件里面定义如果是外部引用必须添加上extern [cpp] view plaincopy extern int g_Data; d不要在头文件里面实现函数如果要实现也必须要添加static [cpp] view plaincopy static int add(int a, int b) { return a b; } e头文件当中如果需要嵌入别的头文件那么只是为了引用另外一个头文件的数据结构 f头文件中引用的数据类型如果没有说明那么在被源文件引用的时候只要保证其他的头文件存在这个数据类型定义即可 g源文件引用头文件的时候需要注意头文件的顺序有的时候顺序变了可能编译就失败了。原因就是之前后面头文件中定义的数据类型找不到出处了 h某些工程没有把头文件和源文件绑定在一起修改头文件必须删除工程重新编译 i头文件的存在只是为了源文件才存在的如果没有必要不要写头文件。要写影响范围也要控制在最小的范围内 j如果头文件定义了数据结构那么需要嵌入引用头文件反之如果只是指针声明一下即可比如说 [cpp] view plaincopy struct _Data; typedef struct _Data Data; k如果有可能经常整理自己的头文件全部删除再一个一个添加这样就知道哪些是我们需要的哪些不是 l对于某些宏如果不确定文件本身是在哪里定义的可以在源文件中再定义一次这样编译器就会精确提示我们原来这个宏是在那里定义的 好了差不多就这么多了。 嵌入式操作系统内核原理和开发内存分配算法 内存分配是操作系统必须面对的一个环节除非这个系统本身不需要内存安排所有业务可以通过全局数据和堆栈搞定。内存分配其实不困难但是由内存引申出来的东西就比较复杂了。早前没有MMU系统本身的空间和用户空间没有优先级之分所以不同的程序之间的内存都是共享的相互影响也是不可避免的。所以一般来说除了内存分配之外还需要一些日志信息、检测信息帮助我们进行调试和分析。当然这些都不是我们关心的内容我们关注的就是内存有哪些通用的分配算法。 1固定内存分配 固定内存分配算法是最简单的算法也是最好理解的算法。比如说有16M内存现在我们假设分配的基本内存是4K那么总共有16M/4K 4K个单元。所以如果用户想申请内存最多就是4K次。如果用户想要多一点内存那么系统把相邻的内存分给用户使用即可。 2链表内存分配 固定内存分配虽然好但是还有一个缺点那就是如果存在很多的浪费机会。试想一下如果用户只要几十个byte那么也要分配给它4K个字节浪费的空间超过了99%。所以在此基础之上我们提出了链表内存算法。链表算法中保存有空闲结点内存释放的时候那么内存查到空闲结点该合并合并该释放的释放当然如果要申请内存的话那方法就多了去了可以最差申请、最优申请、最好申请这些都是可以的。 3伙伴算法 链表算法相比较固定内存算法可以节省不少内存。但是链表算法本身有一个特点那就是容易形成内存碎片。所以我们可以结合固定分配和链表算法的特点把内存分配成8、16、32、64、128、256、512大小的几种链表。链表内部的大小都是相同的链表之间是倍数的关系。分配内存的时候我们首先寻找最合适的链表然后分配内存如果内存空间不够可以向高一级的内存链表申请这样拆解下来的内存可以分配到低一级别的链表释放内存的时候我们也要注意内存的合并和组合。 4基于内存池的伙伴算法 伙伴算法固然好但是如果某一种内存申请特别频繁那么在伙伴算法中就需要进行反复的拆分和合并处理。一方面这会影响了内存的分配效率另外一方面也比较容易造成内存的分配碎片。所以我们可以在伙伴算法的基础之上构建一个内存池在内存释放的时候只是标注当前内存不再使用但是并没有真正释放等到内存池中所有的内存都不再使用的时候再进行释放这在一定的程度上会提高内存的分配效率。特别是系统运行一段时间后这种效果是特别明显的。 5工作集算法 工作集的算法本质上说不是一种算法它只是一种基本思想。我们知道在系统稳定之后内存中分配的大小、配置的比例关系都是相对固定的变化不是特别大。如果我们可以把这些数据给记录下来在系统启动的时候预先分配好这些内存那么不就可以提升系统的启动速度了吗当然工作集中的参数设定更多的是一种经验值它需要我们综合各种因素进行分析反复比较才会得出比较好的结果。 这五种算法只是给出了基本思想只有付出于实践多加操练才能从中有所收获。
嵌入式操作系统内核原理和开发基于链表节点的内存分配算法 链接节点的内存分配方法其实就是一种按需分配的内存分配方法。简单一点说就是你需要多少内存我就给你多少内存。当然为了把分配的内存都连起来我们还需要对分配节点进行管理记录。就比如下面这个数据结构 [cpp] view plaincopy typedef struct _MNG_NODE { struct _MNG_NODE* next; unsigned int size; }MNG_NODE; 其中next节点记录了下面一个节点的位置size表示了当前节点下方的内存大小。在内存初始化的时候我们默认起始内存块就是一个大节点其中前面8个字节就是上面的内容。此时如果需要进行内存拆分我们就可以把一个节点拆分成两个节点就比如这样 [cpp] view plaincopy pNew (MNG_NODE*)((char*)pOld sizeof(MNG_NODE) pOld-size - (sizeof(MNG_NODE) size)); pNew代表了新生成的结点pOld代表了原来的节点。为了不影响原来的节点每次分配新节点的时候必须在内存的高端位置进行分配。这样也保证了原来节点结构和数据的连贯性。当然分配结束之后只需要对节点的的size重新进行一下赋值就可以了。[cpp] view plaincopy pNew-size size; pOld-size - sizeof(MNG_NODE) size; 此时pOld节点会归还到pFreeList当中而生成的pNew节点则插入到pAllocList当中。其中pFreeList表示当前的空闲节点pAllocList表示已分配节点。相比较而言内存的释放就比较简单只需要把节点找出来插入到pAllocList中即可。当然此时如果能做一下前后节点的合并工作就更好了。 不过上面描述的步骤只是就比较重要知识点讲解了一下。在真正设计代码的过程中还需要考虑到节点的查找、内存大小的判断、数据初始化、链表插入、测试用例编写等工作步骤会稍微繁琐一下。下面我们就给出完整的代码示例。 [cpp] view plaincopy /************************************************* * malloc free in link node algorithm **************************************************/ #include string.h #include malloc.h /************************************************* * struct definition **************************************************/ typedef struct _MNG_NODE { struct _MNG_NODE* next; unsigned int size; }MNG_NODE; /************************************************* * global variable declaration **************************************************/ #define MEM_BUFFER_LENGTH (0x1 24) static void* pGlbData; static MNG_NODE* pFreeList; static MNG_NODE* pAllocList; /************************************************* * function: add node into headlist **************************************************/ static void add_node_into_list_head(MNG_NODE* pNode, MNG_NODE** ppList) { pNode-next *ppList; *ppList pNode; } /************************************************* * function: find best fit node **************************************************/ static MNG_NODE* find_best_fit_node(unsigned int size) { MNG_NODE* pCur pFreeList; MNG_NODE* pPre pCur; while(pCur pCur-size (size sizeof(MNG_NODE))) { pPre pCur; pCur pCur-next; } if(NULL pCur) return NULL; if(pFreeList pCur) pFreeList pFreeList-next; else pPre-next pCur-next; return pCur; } /************************************************* * function: implement memory allocation **************************************************/ static void* _mem_malloc(unsigned int size) { MNG_NODE* pOld; MNG_NODE* pNew; pOld find_best_fit_node(size); if(NULL pOld) return NULL; pNew (MNG_NODE*)((char*)pOld sizeof(MNG_NODE) pOld-size - (sizeof(MNG_NODE) size)); pNew-size size; pOld-size - sizeof(MNG_NODE) size; add_node_into_list_head(pOld, pFreeList); add_node_into_list_head(pNew, pAllocList); return (void*)((char*)pNew sizeof(MNG_NODE)); } /************************************************* * function: memory allocation **************************************************/ void* mem_malloc(unsigned int size) { if(0 size) return NULL; if(size (MEM_BUFFER_LENGTH - sizeof(MNG_NODE))) return NULL; return _mem_malloc(size); } /************************************************* * function: find previous node **************************************************/ static MNG_NODE* find_previous_node(MNG_NODE* pNode) { MNG_NODE* pFind pAllocList; MNG_NODE* pPre NULL; while(pFind pFind ! pNode) { pPre pFind; pFind pFind-next; } if(NULL pFind) return NULL; return pPre; } /************************************************* * function: implement memory free **************************************************/ static void _mem_free(MNG_NODE* pNode) { MNG_NODE* pPreNode; if(pNode pAllocList) { pAllocList pAllocList-next; add_node_into_list_head(pNode, pFreeList); return; } pPreNode find_previous_node(pNode); if(NULL pPreNode) return; pPreNode-next pNode-next; add_node_into_list_head(pNode, pFreeList); return; } /************************************************* * function: free memory function **************************************************/ void mem_free(void* pData) { if(NULL pData) return; if(pData pGlbData || pData (void*)((char*)pGlbData MEM_BUFFER_LENGTH)) return; _mem_free(pData - sizeof(MNG_NODE)); } /************************************************* * function: get memory buffer **************************************************/ void mem_init() { pGlbData (void*)malloc(MEM_BUFFER_LENGTH); if(NULL pGlbData) return; memset(pGlbData, 0, MEM_BUFFER_LENGTH); pFreeList (MNG_NODE*)pGlbData; pFreeList-size MEM_BUFFER_LENGTH - sizeof(MNG_NODE); pAllocList NULL; } /************************************************* * function: free memory buffer **************************************************/ void mem_exit() { if(NULL ! pGlbData) free(pGlbData); pFreeList NULL; pAllocList NULL; } /************************************************* * function: file starts here **************************************************/ int main(int argc, char* argv[]) { mem_init(); mem_exit(); return 1; } 嵌入式操作系统内核原理和开发最快、最优、最差内存分配算法 前面我们说到了基于
链表的内存分配算法。但是之前我们也说过其实内存分配一般有三个原则最快、最优和最差。最快比较好理解就是寻找到合适的节点就立即分配内存我们在前面一篇博客采用的就是这个方法。最优呢就是寻找可以满足当前内存分配的最小节点这样不会有很大的浪费但是有可能会产生碎片节点。最后一种就是最差分配算法说是最差效果未必最差。因为在大的内存分配的时候至少不会很快产生内存碎片对整个系统的稳定来说有可能是好事。所以这三种方法很难说哪一种好哪一种不好需要结合具体的应用场景客观进行分析。不过话说回来内存碎片是无论如何都避免不了的。 首先为了灵活对这三种分配算法进行配置我们定义了宏开关需要哪个就把那个开关放开。暂时默认打开的算法的是最快分配算法。 [cpp] view plaincopy #define MAX_SPEED_MALLOC 1 #define MIN_SIZE_MALLOC 0 #define MAX_SIZE_MALLOC 0 因为之前已经讨论过最快分配算法所以这里着重讨论的最优分配算法和最差分配算法。又由于两者的差别极小所以单独分析其中一种算法也行。就拿最优分配算法来说为了寻找到最小的节点我们需要对整个链表进行遍历这个还是比较消耗时间的。 [cpp] view plaincopy while(pCur) { if(pCur-size (size sizeof(MNG_NODE))) { if(NULL pFind || pFind-size pCur-size) { pFind pCur; } } pPre pCur; pCur pCur-next; } 寻找到pFind这个我们需要的节点之后还需要从pFreeList中删除该节点。所以我们需要进一步的判断和分析 [cpp] view plaincopy if(NULL pFind) return NULL; pPre find_previous_node_in_list(pFind, pFreeList); if(NULL pPre) pFreeList pFreeList-next; else pPre-next pFind-next; return pFind; 首先判断pFind前面有没有节点如果没有表示pFreeList就是pFind那么pFreeList需要自行向后退缩当然如果当前的pFind节点是有前节点的那么只需要把前节点的next指针重新更改一下即可。当然这里还对原来的查找节点函数作了一下修改使之更合理更通用。 [cpp] view plaincopy /************************************************* * function: find previous node **************************************************/ MNG_NODE* find_previous_node_in_list(MNG_NODE* pNode, MNG_NODE* pList) { MNG_NODE* pFind pList; MNG_NODE* pPre NULL; while(pFind pFind ! pNode) { pPre pFind; pFind pFind-next; } if(NULL pFind) return NULL; return pPre; } 上面也只是说了个大概具体的内容可以参见下面的源代码。既可以在VC上编译也可以在GCC上面编译都没有问题。当然如果本地os没有编译器可以选择网上在线编译也是个不错的选择。 [cpp] view plaincopy /************************************************* * malloc free in link node algorithm **************************************************/ #include string.h #include malloc.h /************************************************* * struct definition **************************************************/ typedef struct _MNG_NODE { struct _MNG_NODE* next; unsigned int size; }MNG_NODE; /************************************************* * macro declaration **************************************************/ #define MAX_SPEED_MALLOC 1 #define MIN_SIZE_MALLOC 0 #define MAX_SIZE_MALLOC 0 #define MEM_BUFFER_LENGTH (0x1 24) /************************************************* * global variable declaration **************************************************/ static void* pGlbData; static MNG_NODE* pFreeList; static MNG_NODE* pAllocList; /************************************************* * function declaration **************************************************/ MNG_NODE* find_previous_node_in_list(MNG_NODE* pNode, MNG_NODE* pList); /************************************************* * function: add node into headlist **************************************************/ static void add_node_into_list_head(MNG_NODE* pNode, MNG_NODE** ppList) { pNode-next *ppList; *ppList pNode; } #if MAX_SPEED_MALLOC /************************************************* * function: find best fit node in max_speed **************************************************/ static MNG_NODE* find_best_fit_node(unsigned int size) { MNG_NODE* pFind pFreeList; MNG_NODE* pPre pFind; while(pFind pFind-size (size sizeof(MNG_NODE))) { pPre pFind; pFind pFind-next; } if(NULL pFind) return NULL; if(pFreeList pFind) pFreeList pFreeList-next; else pPre-next pFind-next; return pFind; } #endif #if MIN_SIZE_MALLOC /************************************************* * function: find best fit node in min size **************************************************/ MNG_NODE* find_best_fit_node(unsigned int size) { MNG_NODE* pCur pFreeList; MNG_NODE* pPre pCur; MNG_NODE* pFind NULL; while(pCur) { if(pCur-size (size sizeof(MNG_NODE))) { if(NULL pFind || pFind-size pCur-size) { pFind pCur; } } pPre pCur; pCur pCur-next; } if(NULL pFind) return NULL; pPre find_previous_node_in_list(pFind, pFreeList); if(NULL pPre) pFreeList pFreeList-next; else pPre-next pFind-next; return pFind; } #endif #if MAX_SIZE_MALLOC /************************************************* * function: find best fit node in max size **************************************************/ MNG_NODE* find_best_fit_node(unsigned int size) { MNG_NODE* pCur pFreeList; MNG_NODE* pPre pCur; MNG_NODE* pFind NULL; while(pCur) { if(pCur-size (size sizeof(MNG_NODE))) { if(NULL pFind || pFind-size pCur-size) { pFind pCur; } } pPre pCur; pCur pCur-next; } if(NULL pFind) return NULL; pPre find_previous_node_in_list(pFind, pFreeList); if(NULL pPre) pFreeList pFreeList-next; else pPre-next pFind-next; return pFind; } #endif /************************************************* * function: implement memory allocation **************************************************/ static void* _mem_malloc(unsigned int size) { MNG_NODE* pOld; MNG_NODE* pNew; pOld find_best_fit_node(size); if(NULL pOld) return NULL; pNew (MNG_NODE*)((char*)pOld sizeof(MNG_NODE) pOld-size - (sizeof(MNG_NODE) size)); pNew-size size; pOld-size - sizeof(MNG_NODE) size; add_node_into_list_head(pOld, pFreeList); add_node_into_list_head(pNew, pAllocList); return (void*)((char*)pNew sizeof(MNG_NODE)); } /************************************************* * function: memory allocation **************************************************/ void* mem_malloc(unsigned int size) { if(0 size) return NULL; if(size (MEM_BUFFER_LENGTH - sizeof(MNG_NODE))) return NULL; return _mem_malloc(size); } /************************************************* * function: find previous node **************************************************/ MNG_NODE* find_previous_node_in_list(MNG_NODE* pNode, MNG_NODE* pList) { MNG_NODE* pFind pList; MNG_NODE* pPre NULL; while(pFind pFind ! pNode) { pPre pFind; pFind pFind-next; } if(NULL pFind) return NULL; return pPre; } /************************************************* * function: implement memory free **************************************************/ static void _mem_free(MNG_NODE* pNode) { MNG_NODE* pPreNode; if(pNode pAllocList) { pAllocList pAllocList-next; add_node_into_list_head(pNode, pFreeList); return; } pPreNode find_previous_node_in_list(pNode, pAllocList); if(NULL pPreNode) return; pPreNode-next pNode-next; add_node_into_list_head(pNode, pFreeList); return; } /************************************************* * function: free memory function **************************************************/ void mem_free(void* pData) { if(NULL pData) return; if(pData pGlbData || pData (void*)((char*)pGlbData MEM_BUFFER_LENGTH)) return; _mem_free((MNG_NODE*)((char*)pData - sizeof(MNG_NODE))); } /************************************************* * function: get memory buffer **************************************************/ void mem_init() { pGlbData (void*)malloc(MEM_BUFFER_LENGTH); if(NULL pGlbData) return; memset(pGlbData, 0, MEM_BUFFER_LENGTH); pFreeList (MNG_NODE*)pGlbData; pFreeList-size MEM_BUFFER_LENGTH - sizeof(MNG_NODE); pAllocList NULL; } /************************************************* * function: free memory buffer **************************************************/ void mem_exit() { if(NULL ! pGlbData) free(pGlbData); pFreeList NULL; pAllocList NULL; } /************************************************* * function: file starts here **************************************************/ int main(int argc, char* argv[]) { mem_init(); mem_exit(); return 1; } 嵌入式操作系统内核原理和开发信号量 之前因为工作的原因操作系统这块一直没有继续写下去。一方面是自己没有这方面的经历另外一方面就是操作系统比较复杂和琐碎调试起来比较麻烦。目前在实际项目中使用的实时操作系统很多很多国内的朋友也写过操作系统有些项目现在还在维护和修改中这是十分难得的。就我知道和熟悉的就有三个系统比如 1RT-THREAD 2RAW-OS 3ClearRTOS 这里有比较介绍一下这三个系统是国内的三位朋友开发的。其中rt-thread时间比较久一点模块也比较全bsp、cpu、fs、lwip、gui等辅助的代码也比较多有兴趣的朋友可以到网站上面下载代码看一看。raw-os是我今年才发现的一个实时系统从网站的注册时间和软件版本号上来看系统开发的时间不是很长不过整个系统代码的结构非常清晰是我重点推荐阅读的代码。如果朋友们自己download下来好好看一下其中的代码肯定会有不少的收获。最后一个代码是作者李云在编写《专业嵌入式软件开发》这本书的时候为了说明os的基本原理而开发的软件前后设计了线程、互斥、内存、定时器、驱动框架等内容值得一读。 当然有了这么多优秀的代码我觉得现在自己的工作就不是重新造一个车轮了而是和大家分享这些优秀的代码是如何设计的。理解代码本身不是目的关键是理解代码背后的基本思路。就我个人看过来rt-thread和raw-os都可以用来学习不过raw-os更好一些主要是因为作者将raw-os移植到的vc上面学起来也十分方便要是个人在使用过程中有什么疑问可以通过邮件和作者及时交流。不过由于raw-os的版本在一直在update之中所以部分代码在前后稍微有点差别不过这些都不是重点暂时不了解的内容可以通过后面的了解和学习逐步掌握不会成为太大的障碍。 就像今天的题目一样我们重点介绍一下信号量的设计原理。首先看一下信号量的数据结构是怎么样的 [cpp] view plaincopy typedef struct RAW_SEMAPHORE { RAW_COMMON_BLOCK_OBJECT common_block_obj; RAW_U32 count; } RAW_SEMAPHORE; 这些代码都是从raw-os上面摘抄下来的这个版本是0.94版本和最新的0.96c版本有点差别。首先分析一下信号量的基本结构其实非常简单就两个变量其中comm_block_obj是一个通用类型记录了当前结构的名称、类型和阻塞队列而count就是计数判断是否还有释放的资源。 说到了信号量的操作无非就是信号量的创建、获取、释放、删除操作当然这里作者考虑的比较详细在信号量释放的时候还分成了 WAKE_ONE_SEM和WAKE_ALL_SEM两种类型。意思很简单就是当信号量来临的时候是唤醒一个等待线程呢还是唤醒所有的等待线程呢就是这么回事。下面我们就按照顺序介绍这几个函数首先是创建函数 [cpp] view plaincopy RAW_U16 raw_semaphore_create(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 *name_ptr, RAW_U32 initial_count) { #if (RAW_SEMA_FUNCTION_CHECK 0) if (semaphore_ptr 0) { return RAW_NULL_OBJECT; } if (initial_count 0xffffffff) { return RAW_SEMOPHORE_OVERFLOW; } #endif /*Init the list*/ list_init(semaphore_ptr-common_block_obj.block_list); /*Init resource*/ semaphore_ptr-count initial_count; semaphore_ptr-common_block_obj.name name_ptr; semaphore_ptr-common_block_obj.block_way 0; return RAW_SUCCESS; } 看着初始化函数我们发现信号量的初始化其实也非常简单基本工作主要有 1判断参数合法性 2初始化阻塞队列、名称等 3初始化信号量的计数。 说完了这些我们看看信号量的获取是怎么完成的代码可能长度稍微长一些不过也不用太紧张 [cpp] view plaincopy RAW_U16 raw_semaphore_get(RAW_SEMAPHORE *semaphore_ptr, RAW_U32 wait_option) { RAW_U16 error_status; RAW_SR_ALLOC(); #if (RAW_SEMA_FUNCTION_CHECK 0) if (semaphore_ptr 0) { return RAW_NULL_OBJECT; } if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); if (semaphore_ptr-count) { semaphore_ptr-count--; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*Cannt get semphore, and return immediately if wait_option is RAW_NO_WAIT*/ if (wait_option RAW_NO_WAIT) { RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } raw_pend_object(semaphore_ptr-common_block_obj, raw_task_active, wait_option); RAW_CRITICAL_EXIT(); raw_sched(); error_status block_state_post_process(raw_task_active, 0); return error_status; } 信号量的获取情况比较复杂一些这在长度上也体现出来了。不过没关系我们一步一步看函数做了什么 1判断参数合法性 2判断当前函数是否处于中断处理的流程中如果是选择返回 3判断当前count是否为0如果不为 0则减1返回 4如果当前count是0且线程不愿意等待那么选择返回 5如果当前禁止调度那么依然选择返回 6当前线程将自己挂起从ready队列中删除把自己pend到信号量的阻塞队列中 7阻塞的线程再次获得了运行的机会我们从task数据结构获得返回结果此时也不一定是因为获得了资源的缘故哦。 上面的get函数看上去比较复杂但是所有的同步函数基本上都是这样设计的看多了反而有一种八股文的感觉。刚开始看的同学可能觉得不是很习惯。不要紧每天多看两眼时间长了就ok了。好了接着我们继续去看看信号量的释放函数是怎么处理的大家做好心理准备哦 [cpp] view plaincopy static RAW_U16 internal_semaphore_put(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 opt_wake_all) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_SEMA_FUNCTION_CHECK 0) if (semaphore_ptr 0) { return RAW_NULL_OBJECT; } #endif block_list_head semaphore_ptr-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*if no block task on this list just return*/ if (is_list_empty(block_list_head)) { if (semaphore_ptr-count 0xffffffff) { RAW_CRITICAL_EXIT(); return RAW_SEMOPHORE_OVERFLOW; } /*increase resource*/ semaphore_ptr-count; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*wake all the task blocked on this semphore*/ if (opt_wake_all) { while (!is_list_empty(block_list_head)) { raw_wake_object(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list)); } } else { /*Wake up the highest priority task block on the semaphore*/ raw_wake_object(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list)); } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 看上去信号量的释放函数也比较长不过只要有耐心都是可以看明白的我们就来具体分析一下 1判断参数的合法性 2判断当前是否有等待队列如果没有则count自增函数返回当然如果count达到了0xffffffff也要返回不过概率极低 3 当前存在等待队列根据opt_wake_all的要求是唤醒一个线程还是唤醒所有的线程 4调用系统调度函数让高优先级任务及时得到运行的机会 5当前线程再次得到运行的机会函数返回。 有了上面的讲解我们发现os的代码其实也没有那么恐怖。所以请大家一鼓作气看看信号量是怎么删除的吧 [cpp] view plaincopy RAW_U16 raw_semaphore_delete(RAW_SEMAPHORE *semaphore_ptr) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_SEMA_FUNCTION_CHECK 0) if (semaphore_ptr 0) { return RAW_NULL_OBJECT; } #endif block_list_head semaphore_ptr-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*All task blocked on this queue is waken up*/ while (!is_list_empty(block_list_head)) { delete_pend_obj(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list)); } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 信号量删除的工作其实很少也很简单同样我们也来梳理一下 1判断参数合法性 2唤醒阻塞队列中的每一个线程 3调用系统调度函数因为高优先级的任务很有可能刚刚从阻塞队列中释放出来 4当前线程再次运行函数返回。 通过上面几个函数的讲解我们发现关于os互斥部分的代码其实也不复杂。只要对系统本身和中断有一些了解其实代码都是可以看懂的。当然上面的代码我们还是讲的比较粗糙所以有些细节还是要补充一下 1全局变量操作的函数必须在关中断的情况下进行操作 2实时系统的抢占是每时每刻都在进行的比如中断返回时、信号量释放时、调用延时函数、调用调度函数的时候所以大家心中要有抢占的概念 3互斥函数中大量使用了链表的结构建议大家好好掌握链表的相关算法 4关于os的代码一定要多看、多思考、多练习才会有进步和提高纸上得来终觉浅、绝知此事要躬行。
嵌入式操作系统内核原理和开发互斥量 今天下午打开邮箱打开rawos作者给我发的邮件甚是惊喜。感谢他对我的支持因为自己阅读过很多os的代码包括ucos、rtthread、vxWorks、linux等等所以阅读rawos对于我来说不算特别辛苦的事情。除了某些细节之外我对整个系统的设计还算得上是比较了解的所以也打算把这个代码介绍给大家。能在现实的硬件中使用当然最好如果没有这样的机会也可以提高个人的认识水平或者介绍给内部的团队成员大家一起分析和学习也不失为一个很好的方法。闲话不多说话题还是转到我们今天的主题上面即互斥量。学过操作系统课程的朋友对这个词汇肯定不会很陌生。和信号量相比互斥保护的资源一般是唯一的。也就是说资源就一份你占有了我就没有办法占有当然如果你释放了此时我就有机会占有了。一切的一切看上去没有什么问题。但是我们都知道在实时嵌入式系统当中线程之间的调度是严格按照优先级来进行调度。比方说优先级为10的任务必须比优先级为11的任务优先得到调度。那么有同学会问了那优先级为11的任务什么时候才能得到调度呢其实这个要求还是蛮苛刻的。要想优先级为11的任务得到调度此时必须没有优先级10的任务、或者任务pend到资源上了、或者自身delay、或者被人suspend了。否则优先级为10的任务会这么一直运行下去。那这和我们的互斥量有什么关系呢请听我一一讲来。我们假设现在有两个任务都准备运行分别人任务A、B优先级依次是10、11。某一段时间后优先级为10和优先级为11的任务都在尝试获取某个资源。本来按照优先级的先后顺序优先级为10的任务应该率先获取资源这都没问题。但是假设在尝试获取资源前优先级为10的任务开了个小差sleep一会那么这个时候优先级为11的任务就可以开始运行了。等到优先级为10的任务苏醒过来想重新获取资源的时候惊讶地发现资源早就被别人给占了。因为资源目前只有一份所以它只好把自己pend到等待队列里面慢慢等待好心人能快点把资源释放出来。一切的一切看上去没有什么问题但是这却和实时系统设计的初衷是相违背的。前面我们规定高优先级的任务必须优先得到运行的机会而目前这种情况和我们的设计原则是背道而驰的。当然这个问题很早就被大家发现了大家也在尝试不同的方法来解决。目前使用的比较多的就是两种方法一种是给互斥量设定一个优先级另外一种就是对优先级进行继承处理。看上去是两种方法其实目的只有一个就是让那些占有互斥量的thread提高优先级赶快运行结束把资源还给后面真正需要的人。看上去一切解决得都很完美但是大家有没有考虑过这样一个问题如果线程连续占有多个互斥量优先级又该怎么处理如果pend的任务被修改了优先级该怎么处理如果这两种方法一起被使用那又该怎么处理我想这就是作者在后期对互斥量代码进行重构的原因吧。当然了上面讨论的内容已经是比较深的了大家可以看看早期互斥量是怎么设计的慢慢来这样才会对作者的设计意图更加了解一些。老规矩我们首先看看互斥量是怎么设计的[cpp] view plaincopy typedef struct RAW_MUTEX { RAW_COMMON_BLOCK_OBJECT common_block_obj; RAW_U8 count; /*ponit to occupy task*/ RAW_TASK_OBJ *occupy; /*occupy task original priority*/ RAW_U8 occupy_original_priority; } RAW_MUTEX; 看上去互斥量的东西多一点其实也还可以只要大家明白了互斥量处理逻辑再回头来看看这些东西的时候认识就会更加深刻。我们看看数据结构里面都有什么1通用互斥结构这在前面信号量的时候已经介绍过一遍2计数判断资源是否还在3当前所属的任务4该任务原来的优先级。说好了基本结构我们看看互斥量的构造、申请、释放、删除函数是怎么设计的首先当然还是初始化函数[cpp] view plaincopy RAW_U16 raw_mutex_create(RAW_MUTEX *mutex_ptr, RAW_U8 *name_ptr) { #if (RAW_MUTEX_FUNCTION_CHECK 0) if (mutex_ptr 0) return RAW_NULL_OBJECT; #endif /*Init the list*/ list_init(mutex_ptr-common_block_obj.block_list); mutex_ptr-common_block_obj.block_way 0; mutex_ptr-common_block_obj.name name_ptr; /*No one occupy mutex yet*/ mutex_ptr-occupy 0; /*resource is available at init state*/ mutex_ptr-count 1; mutex_ptr-occupy_original_priority 0; return RAW_SUCCESS; } 初始化的函数还是比较简单的主要做了下面的流程1初始化互斥结构的公共属性比如名字、阻塞方式等等2初始化当前资源数量3初始化占有资源的线程指针还有就是线程的优先级。创建了互斥量之后我们就要看看互斥量是怎么申请的代码有点长同学们可以心理调整一下了[cpp] view plaincopy RAW_U16 raw_mutex_get(RAW_MUTEX *mutex_ptr, RAW_U32 wait_option) { RAW_U16 error_status; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK 0) if (mutex_ptr 0) { return RAW_NULL_OBJECT; } if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); /* mutex is available */ if (mutex_ptr-count) { mutex_ptr-occupy raw_task_active; mutex_ptr-occupy_original_priority raw_task_active-priority; mutex_ptr-count 0; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if the same task get the same mutex again, it causes deadlock*/ if (raw_task_active mutex_ptr-occupy) { #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif RAW_CRITICAL_EXIT(); return RAW_MUTEX_DEADLOCK; } /*Cannt get mutex, and return immediately if wait_option is RAW_NO_WAIT*/ if (wait_option RAW_NO_WAIT) { RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } /*if current task is a higher priority task and block on the mutex *priority inverse condition happened, priority inherit method is used here*/ if (raw_task_active-priority mutex_ptr-occupy-priority) { switch (mutex_ptr-occupy-task_state) { case RAW_RDY: /*remove from the ready list*/ remove_ready_list(raw_ready_queue, mutex_ptr-occupy); /*raise the occupy task priority*/ mutex_ptr-occupy-priority raw_task_active-priority; /*readd to the ready list head*/ add_ready_list_head(raw_ready_queue, mutex_ptr-occupy); break; case RAW_DLY: case RAW_DLY_SUSPENDED: case RAW_SUSPENDED: /*occupy task is not on any list, so just change the priority*/ mutex_ptr-occupy-priority raw_task_active-priority; break; case RAW_PEND: /* Change the position of the task in the wait list */ case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: /*occupy task is on the block list so change the priority on the block list*/ mutex_ptr-occupy-priority raw_task_active-priority; change_pend_list_priority(mutex_ptr-occupy); break; default: RAW_CRITICAL_EXIT(); return RAW_INVALID_STATE; } } /*Any way block the current task*/ raw_pend_object(mutex_ptr-common_block_obj, raw_task_active, wait_option); RAW_CRITICAL_EXIT(); /*find the next highest priority task ready to run*/ raw_sched(); /*So the task is waked up, need know which reason cause wake up.*/ error_status block_state_post_process(raw_task_active, 0); return error_status; } 这段代码其实开头都还好关键是末尾要结束的时候有一段代码比较费解。我想这就是我前面说过的优先级反转问题。为了解决这一问题在rawos版本中采取了优先级继承的方法。我们还是详细看一下逻辑本身是怎么样的1判断参数合法性2判断资源是否可取如果可取则在记录当前线程和优先级后返回3如果资源被自己重复申请返回4如果线程不愿等待返回5如果此时禁止调度返回6如果此时优先级大于互斥量占有者的优先级分情况处理a占有者处于ready的状态那么修改它的优先级重新加入调度队列b占有者处于sleep的状态直接修改优先级即可c占有者也被pend到别的资源上面了那么修改那个资源的pend列表可能设计到调度顺序问题。7线程把自己pend到互斥量等待队列上面8线程调用系统调度函数切换到其他线程运行9线程再次得到运行的机会从task获取结果后返回。基本上上面的介绍算得上是很详细了那么互斥量的释放基本上是一个逆操作的过程朋友也可以思考一下应该怎么解决才好[cpp] view plaincopy RAW_U16 raw_mutex_put(RAW_MUTEX *mutex_ptr) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK 0) if (mutex_ptr 0) { return RAW_NULL_OBJECT; } #endif block_list_head mutex_ptr-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*Must release the mutex by self*/ if (raw_task_active ! mutex_ptr-occupy) { RAW_CRITICAL_EXIT(); return RAW_MUTEX_NOT_RELEASE_BY_OCCYPY; } /*if no block task on this list just return*/ if (is_list_empty(block_list_head)) { mutex_ptr-count 1; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if priority was changed, just change it back to original priority*/ if (raw_task_active-priority ! mutex_ptr-occupy_original_priority) { remove_ready_list(raw_ready_queue, raw_task_active); raw_task_active-priority mutex_ptr-occupy_original_priority; add_ready_list_end(raw_ready_queue, raw_task_active); } /* there must have task blocked on this mutex object*/ mutex_ptr-occupy list_entry(block_list_head-next, RAW_TASK_OBJ, task_list); /*the first blocked task became the occupy task*/ mutex_ptr-occupy_original_priority mutex_ptr-occupy-priority; /*mutex resource is occupied*/ mutex_ptr-count 0; /*Wake up the occupy task, which is the highst priority task on the list*/ raw_wake_object(mutex_ptr-occupy); RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 和之前的信号量释放相比互斥量的释放要复杂一切关键就在于修改优先级的问题。我们来梳理一下1判断参数合法性2判断线程是否为互斥量的占有线程不是则返回3判断等待队列是否为空为空的话则返回4判断占有任务的优先级有没有发生变化如果有则需要重新修改优先级重新加入调度队列中5选择下一个可以调度的线程6函数返回。说了这么些就剩下最后一个删除互斥量了大家再接再厉一起去学习。[cpp] view plaincopy RAW_U16 raw_mutex_delete(RAW_MUTEX *mutex_ptr) { LIST *block_list_head; RAW_TASK_OBJ *mutex_occupy; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK 0) if (mutex_ptr 0) { return RAW_NULL_OBJECT; } #endif block_list_head mutex_ptr-common_block_obj.block_list; RAW_CRITICAL_ENTER(); mutex_occupy mutex_ptr-occupy; /*if mutex is occupied and occupy priority is not the original priority*/ if ((mutex_occupy) (mutex_occupy-priority ! mutex_ptr-occupy_original_priority)) { switch (mutex_occupy-task_state) { case RAW_RDY: /*remove from the ready list*/ remove_ready_list(raw_ready_queue, mutex_ptr-occupy); /*raise the occupy task priority*/ mutex_occupy-priority mutex_ptr-occupy_original_priority; /*readd to the ready list head*/ add_ready_list_end(raw_ready_queue, mutex_ptr-occupy); break; case RAW_DLY: case RAW_SUSPENDED: case RAW_DLY_SUSPENDED: /*occupy task is not on any list, so just change the priority*/ mutex_occupy-priority mutex_ptr-occupy_original_priority; break; case RAW_PEND: case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: /*occupy task is on the block list so change the priority on the block list*/ mutex_occupy-priority mutex_ptr-occupy_original_priority; change_pend_list_priority(mutex_occupy); break; default: RAW_CRITICAL_EXIT(); return RAW_STATE_UNKNOWN; } } /*All task blocked on this queue is waken up*/ while (!is_list_empty(block_list_head)) { delete_pend_obj(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list)); } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 互斥量的操作在实际情形下未必是存在的所以作者在设计的时候添加了一个编译宏。不过删除所做的工作也不难理解一个是处理好当前占有者的关系一个是处理好等待队列的关系。我们来细看一下流程1判断当前参数合法性2判断占有者的情况修改任务优先级这里的情形和上面申请互斥量的处理方式是一样的不再赘述3唤醒所有的等待线程如果线程已经suspend掉了那么继续suspend4调度到其他线程防止有优先级高的任务已经被释放出来了5函数返回结束。嵌入式操作系统内核原理和开发事件 在很多操作系统的书上其实互斥和同步是放在一起进行介绍的。互斥比较简单就是对某一份资源或者几份资源进行抢占获取。而同步是什么意思呢就是某一个线程等待另外一个线程的通知只有收到了通知它才会去干某些事情。 通常情况下如果是抢占的话那么两个人使用的必须是同一个锁而同步的话则需要好几个锁因为一般情况下大家等待的东西都是不一样的所以好几个锁是不可避免的。那么有没有什么办法可以用一个锁实现几个事情的并发和同步呢这就是我们今天所要说的事件。可以从一个例子说明一下。 比方说我们现在打算进行八宝饭的烹饪。那么在此之前需要进行各个辅料的准备工作等到这些辅料都准备好了就可以开始煮八宝饭了。因为辅料之间是相互独立的所以完全可以分开独立完成而在所有辅料都没有完成之前我们只能等待。等到材料全部准备好我们就可以开始烹饪的工作了。当然在烹饪的时候我们又可以准备进行下一轮工作了也就是说进行下一次八宝饭的辅料准备。在这个地方辅料的准备是由各个子线程完成的而煮饭这个工作是主线程完成的主线程和子线程之间就是通过事件进行沟通的。主线程需要知道当前各个材料准备好了没而子线程需要知道八宝饭烧好了没是不是该进行下一轮辅料的准备了。这个中间就存在一个同步的问题了。 如果大家对之前的信号量还有印象的话当初我们是用count来表示资源的个数。而今天我们用flags来表示事件状态而其中的bit则表示了一个一个具体的事件。只不过有的线程在等待多个事件而有的线程在等待一个事件有的线程在获取事件后bit位立即清除有的线程在获取事件后继续留存。所以下面我们就看看raw-os上面的事件是怎么设计的。当然我们首先看到的还是关于事件的基本数据结构 [cpp] view plaincopy typedef struct RAW_EVENT { RAW_COMMON_BLOCK_OBJECT common_block_obj; RAW_U32 flags; } RAW_EVENT; 这和我们之前介绍的没什么不一样就是通用结构加上flag标志。关于事件的基本处理函数也不复杂主要就是创建、申请、设置和删除四个基本操作。我们来看看每一步分别是怎么实现的首先介绍的还是事件的创建过程 [cpp] view plaincopy RAW_U16 raw_event_create(RAW_EVENT *event_ptr, RAW_U8 *name_ptr, RAW_U32 flags_init) { #if (RAW_EVENT_FUNCTION_CHECK 0) if (event_ptr 0) { return RAW_NULL_OBJECT; } #endif /*Init the list*/ list_init(event_ptr-common_block_obj.block_list); event_ptr-common_block_obj.block_way 0; event_ptr-common_block_obj.name name_ptr; event_ptr-flags flags_init ; return RAW_SUCCESS; } 看了代码相信要说的部分不是很多关键就是flags的赋值部分其他的都和信号量差不太多。这里的flags代表了某一个起始状态也就是说当前可以干什么事情、满足哪些条件等等。下面我们继续看事件的获取函数稍微复杂一些 [cpp] view plaincopy RAW_U16 raw_event_get(RAW_EVENT *event_ptr, RAW_U32 requested_flags, RAW_U8 get_option, RAW_U32 wait_option) { RAW_U16 error_status; RAW_U8 status; RAW_SR_ALLOC(); #if (RAW_EVENT_FUNCTION_CHECK 0) if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } if ((get_option ! RAW_AND) (get_option ! RAW_OR) (get_option ! RAW_AND_CLEAR) (get_option ! RAW_OR_CLEAR)) { return RAW_NO_THIS_OPTION; } #endif RAW_CRITICAL_ENTER(); /*if option is and flag*/ if (get_option RAW_FLAGS_AND_MASK) { if ((event_ptr-flags requested_flags) requested_flags) { status RAW_TRUE; } else { status RAW_FALSE; } } /*if option is or flag*/ else { if (event_ptr-flags requested_flags) { status RAW_TRUE; } else { status RAW_FALSE; } } if (status) { /*does it need to clear the flags*/ if (get_option RAW_FLAGS_CLEAR_MASK) { event_ptr-flags ~requested_flags; } RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*Cannt get event, and return immediately if wait_option is RAW_NO_WAIT*/ if (wait_option RAW_NO_WAIT) { RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } /*Remember the passed information*/ raw_task_active-raw_suspend_option get_option; raw_task_active-raw_suspend_flags requested_flags; raw_pend_object(event_ptr-common_block_obj, raw_task_active, wait_option); RAW_CRITICAL_EXIT(); raw_sched(); RAW_CRITICAL_ENTER(); /*does it need to clear the flags*/ if (get_option RAW_FLAGS_CLEAR_MASK) { event_ptr-flags ~requested_flags; } RAW_CRITICAL_EXIT(); /*So the task is waked up, need know which reason cause wake up.*/ error_status block_state_post_process(raw_task_active, 0); return error_status; } 注意这里事件和其他get函数的最大差别就是函数多了一个get_option它表示当前是同时申请多个事件还是多个事件中的一个事件申请后是否需要进行clear置位等等我们不妨看看具体细节 1判断函数是否在中断中 2判断get_option是否合法 3判断是否存在可以获取的事件and或者是or 4如果事件可以获取那么再判断是否需要置位操作函数返回 5判断是否愿意等待否则返回 6判断是否禁止调度是则返回 7将自己pend到等待队列中 8调用公共调度函数转到其他线程继续运行 9当前线程重新得到运行的机会根据选项清除标志位函数返回。 看完了事件的申请下面就可以看看事件的设置函数了 [cpp] view plaincopy RAW_U16 raw_event_set(RAW_EVENT *event_ptr, RAW_U32 flags_to_set, RAW_U8 set_option) { LIST *iter; LIST *event_head_ptr; LIST *iter_temp; struct RAW_TASK_OBJ *task_ptr; RAW_U8 status; RAW_U8 need_sche 0; RAW_SR_ALLOC(); #if (RAW_EVENT_FUNCTION_CHECK 0) if (event_ptr 0) { return RAW_NULL_OBJECT; } if ((set_option ! RAW_AND) (set_option ! RAW_OR)) { return RAW_NO_THIS_OPTION; } #endif event_head_ptr event_ptr-common_block_obj.block_list; status RAW_FALSE; RAW_CRITICAL_ENTER(); /*if the set_option is AND_MASK, it just clear the flags and will return immediately!*/ if (set_option RAW_FLAGS_AND_MASK) { event_ptr-flags flags_to_set; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if it is or mask then set the flag and continue.........*/ else { event_ptr-flags | flags_to_set; } iter event_head_ptr-next; /*if list is not empty*/ while (iter !event_head_ptr) { task_ptr list_entry(iter, RAW_TASK_OBJ, task_list); iter_temp iter-next; if (task_ptr-raw_suspend_option RAW_FLAGS_AND_MASK) { if ((event_ptr-flags task_ptr -raw_suspend_flags) task_ptr -raw_suspend_flags) status RAW_TRUE; else status RAW_FALSE; } else { if (event_ptr-flags task_ptr -raw_suspend_flags) status RAW_TRUE; else status RAW_FALSE; } if (status) { /*Ok the task condition is met, just wake this task*/ raw_wake_object(task_ptr); /*if task is waken up*/ need_sche 1; } iter iter_temp; } RAW_CRITICAL_EXIT(); if (need_sche) { raw_sched(); } return RAW_SUCCESS; } 从函数上也看得出来这里有一个set_option的选项主要是为了供调用者选择是进行and设置还是or设置细节如下所示 1判断参数合法性 2判断set_option合法性 3如果选项为and在设置完flags之后函数返回 4设置flags标志位开始遍历每一个等待线程 5如果存在合适的线程不管是等待多个事件还是一个事件都将它们唤醒设置重新调度标志 6如果重新调度标志为1调用系统调度函数切换到其他线程运行 7当前线程再次获取到运行的机会函数返回。 转眼之间我们就到了事件的删除过程了。其实事件的删除非常简单它就是把所有的等待线程唤醒就这么简单不知道我说清楚了没当然了这中间可能会有高优先级的线程被加入到ready队列里面所以重新schedule一下也是很有必要的。 [cpp] view plaincopy RAW_U16 raw_event_delete(RAW_EVENT *event_ptr) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_EVENT_FUNCTION_CHECK 0) if (event_ptr 0) { return RAW_NULL_OBJECT; } #endif block_list_head event_ptr-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*All task blocked on this queue is waken up until list is empty*/ while (!is_list_empty(block_list_head)) { delete_pend_obj(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list)); } event_ptr-flags 0; RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 嵌入式操作系统内核原理和开发消息队列 消息队列是线程交互的一种方法任务可以通过消息队列来实现数据的沟通和交换。在嵌入式系统上这可以说这是用的最多的一种方法。通过消息队列无论是发送者还是接受者都可以循环地处理各种消息。而我们知道存储消息最好的方式就是循环队列如果消息已满那么发送者可以把自己pend到等待队列上而如果此时没有消息那么接受者也可以把自己pend到等待队列上。当然实现消息队列的方法很多甚至用户可以自己利用互斥量和信号量来实现而嵌入式系统常常会默认提供这样的功能函数我想主要的目的还是为了方便用户让他们可以更多地从业务的角度来看问题而不是把重点关注在这些底层的细节上面。首先我们还是看看rawos上面关于消息队列的数据结构是怎么定义的[cpp] view plaincopy typedef struct RAW_MSG_Q { RAW_VOID **queue_start; /* Pointer to start of queue data */ RAW_VOID **queue_end; /* Pointer to end of queue data */ RAW_VOID **write; /* Pointer to where next message will be inserted in the Q */ RAW_VOID **read; /* Pointer to where next message will be extracted from the Q */ RAW_U32 size; /* Size of queue (maximum number of entries) */ RAW_U32 current_numbers; /* Current number of entries in the queue */ RAW_U16 blocked_send_task_numbers; /*number of blocked send task numbers */ RAW_U16 blocked_receive_task_numbers; /*number of blocked send task numbers */ } RAW_MSG_Q; typedef struct RAW_QUEUE { RAW_COMMON_BLOCK_OBJECT common_block_obj; RAW_MSG_Q msg_q; } RAW_QUEUE; 上面的代码中有两段数据结构第一段主要表示循环队列的内容其中包括了队列首地址、队列末尾地址、当前队列读取地址、当前队列插入地址、队列大小、消息个数、阻塞的发送线程数据、阻塞的接受线程数目。而第二段数据结构就比较简单它把通用等待结构和循环队列合在了一起共同构成了消息队列的数据结构。根据我们以前的经验互斥同步数据结构的操作都会分成几个部分当然消息队列也不例外也会分成初始化、发送消息、接受消息、清除消息、删除消息队列等几种操作函数。当然消息队列还是增加了一个新的选项那就是插入消息的时候可以插入在队列的前方还是插入在队列的尾部这在某种程度上决定了消息的优先级。说到这我们还是看看消息队列是怎么初始化的[cpp] view plaincopy RAW_U16 raw_queue_create(RAW_QUEUE *p_q, RAW_U8 *p_name, RAW_VOID **msg_start, RAW_U32 number) { #if (RAW_QUEUE_FUNCTION_CHECK 0) if (p_q 0) { return RAW_NULL_OBJECT; } if ( msg_start 0) { return RAW_NULL_POINTER; } if (number 0) { return RAW_ZERO_NUMBER; } #endif list_init(p_q-common_block_obj.block_list); p_q-common_block_obj.name p_name; p_q-common_block_obj.block_way 0; p_q-msg_q.queue_start msg_start; /* Initialize the queue */ p_q-msg_q.queue_end msg_start[number]; p_q-msg_q.write msg_start; p_q-msg_q.read msg_start; p_q-msg_q.size number; p_q-msg_q.current_numbers 0; p_q-msg_q.blocked_send_task_numbers 0; p_q-msg_q.blocked_receive_task_numbers 0; return RAW_SUCCESS; } 虽然相比较之前的互斥函数消息队列的初始化内容好像多一些。但是大家如果对循环队列的知识比较了解的话其实也不是很复杂的。我们看到函数除了对通用阻塞结构进行初始化之外就是对这些循环队列进行初始化。接着我们就可以看看消息发送函数是怎么样的[cpp] view plaincopy static RAW_U16 internal_msg_post(RAW_QUEUE *p_q, RAW_VOID *p_void, RAW_U8 opt_send_method, RAW_U8 opt_wake_all, RAW_U32 wait_option) { RAW_U16 error_status; LIST *block_list_head; RAW_U8 block_way; RAW_SR_ALLOC(); #if (RAW_QUEUE_FUNCTION_CHECK 0) if (raw_int_nesting) { if (wait_option ! RAW_NO_WAIT) { return RAW_NOT_CALLED_BY_ISR; } } if (p_q 0) { return RAW_NULL_OBJECT; } if (p_void 0) { return RAW_NULL_POINTER; } #endif block_list_head p_q-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*queue is full condition, there should be no received task blocked on queue object!*/ if (p_q-msg_q.current_numbers p_q-msg_q.size) { if (wait_option RAW_NO_WAIT) { RAW_CRITICAL_EXIT(); return RAW_MSG_MAX; } else { /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } /*queue is full and SEND_TO_FRONT method is not allowd*/ if (opt_send_method SEND_TO_FRONT) { RAW_CRITICAL_EXIT(); return RAW_QUEUE_FULL_OPT_ERROR; } p_q-msg_q.blocked_send_task_numbers; raw_task_active-msg p_void; block_way p_q-common_block_obj.block_way; p_q-common_block_obj.block_way RAW_BLOCKED_WAY_FIFO; /*there should be no blocked received task beacuse msg exits*/ raw_pend_object(p_q-common_block_obj, raw_task_active, wait_option); p_q-common_block_obj.block_way block_way; RAW_CRITICAL_EXIT(); raw_sched(); error_status block_state_post_process(raw_task_active, 0); return error_status; } } /*Queue is not full here, there should be no blocked send task*/ /*If there is no blocked receive task*/ if (is_list_empty(block_list_head)) { p_q-msg_q.current_numbers; /* Update the nbr of entries in the queue */ if (opt_send_method SEND_TO_END) { *p_q-msg_q.write p_void; if (p_q-msg_q.write p_q-msg_q.queue_end) { p_q-msg_q.write p_q-msg_q.queue_start; } } else { if (p_q-msg_q.read p_q-msg_q.queue_start) { p_q-msg_q.read p_q-msg_q.queue_end; } p_q-msg_q.read--; *p_q-msg_q.read p_void; /* Insert message into queue */ } RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*wake all the task blocked on this queue*/ if (opt_wake_all) { while (!is_list_empty(block_list_head)) { wake_send_msg(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list), p_void); } p_q-msg_q.blocked_receive_task_numbers 0; } /*wake hignhest priority task blocked on this queue and send msg to it*/ else { wake_send_msg(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list), p_void); p_q-msg_q.blocked_receive_task_numbers--; } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 这里消息发送函数稍显冗长这主要是因为消息发送的情况比较复杂方方面面考虑的情况比较多。但是整个函数处理的逻辑还是比较清晰的只要有耐心慢慢读下去还是没有什么问题。这里不妨和大家一起看一下消息发送函数是怎么实现的1检验参数合法性注意在中断下调用这个函数时必须是RAW_NO_WAIT的选项中断毕竟是不好调度的2 处理消息已满的情况a如果线程不想等待函数返回b如果禁止调度函数返回c消息存储到线程的msg里面线程把自己pend到等待队列中d调用系统调度函数等待再次被调度的机会函数返回。3当前消息未满但是当前没有等待队列那么根据要求把消息压入循环队列函数返回4当前消息未满且存在等待队列说明此时已经没有消息可读a如果需要唤醒所有的等待线程那么唤醒所有的线程等待线程总数置为0b如果只是唤起某一个线程那么唤醒第一个等待线程等待线程总数自减5调用系统调度函数防止有高优先级的线程加入调度队列6线程再次得到运行的机会函数返回。看到上面的代码我们发现只要梳理好了代码的逻辑其实消息发送函数也是比较好理解的。当然有消息的发送就必然会存在消息的接受了。此时肯定也会出现没有消息、有消息两种情况了。[cpp] view plaincopy RAW_U16 raw_queue_receive (RAW_QUEUE *p_q, RAW_U32 wait_option, RAW_VOID **msg) { RAW_VOID *pmsg; RAW_U16 result; LIST *block_list_head; RAW_TASK_OBJ *blocked_send_task; RAW_SR_ALLOC(); #if (RAW_QUEUE_FUNCTION_CHECK 0) if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } if (p_q 0) { return RAW_NULL_OBJECT; } if (msg 0) { return RAW_NULL_POINTER; } #endif block_list_head p_q-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*if queue has msgs, just receive it*/ if (p_q-msg_q.current_numbers) { pmsg *p_q-msg_q.read; if (p_q-msg_q.read p_q-msg_q.queue_end) { p_q-msg_q.read p_q-msg_q.queue_start; } *msg pmsg; /*if there are blocked_send_tasks, just reload the task msg to end*/ if (p_q-msg_q.blocked_send_task_numbers) { blocked_send_task list_entry(block_list_head-next, RAW_TASK_OBJ, task_list); p_q-msg_q.blocked_send_task_numbers--; *p_q-msg_q.write blocked_send_task-msg; if (p_q-msg_q.write p_q-msg_q.queue_end) { p_q-msg_q.write p_q-msg_q.queue_start; } raw_wake_object(blocked_send_task); RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } p_q-msg_q.current_numbers--; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } if (wait_option RAW_NO_WAIT) { /* Caller wants to block if not available? */ *msg (RAW_VOID *)0; RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } raw_pend_object(p_q-common_block_obj, raw_task_active, wait_option); p_q-msg_q.blocked_receive_task_numbers; RAW_CRITICAL_EXIT(); raw_sched(); RAW_CRITICAL_ENTER(); *msg (RAW_VOID *)0; result block_state_post_process(raw_task_active, msg); RAW_CRITICAL_EXIT(); return result; } 和发送消息函数相比接受消息的操作还是要少一些不要紧大家一起来看一下实现逻辑1判断参数合法性2如果当前存在消息a读取循环队列中的消息b判断当前是否存在等待线程因为之前有可能存在没有压入队列的消息那么此时压入消息唤醒该线程即可调用系统调度函数返回c没有等待线程消息总数自减函数返回。3当前没有消息a线程不愿等待函数返回b系统禁止调度函数返回c线程将自己pend到等待队列中d调用系统调度函数切换到其他线程继续运行e线程再次获得运行的机会从thread结构中获取返回结果函数返回。和发送消息、接受消息比较起来清除消息和删除消息的处理就比较简单了。为了说明问题我们不妨放在一起讨论一下[cpp] view plaincopy RAW_U16 raw_queue_flush(RAW_QUEUE *p_q) { LIST *block_list_head; RAW_SR_ALLOC(); RAW_TASK_OBJ *block_task; #if (RAW_QUEUE_FUNCTION_CHECK 0) if (p_q 0) { return RAW_NULL_OBJECT; } #endif block_list_head p_q-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*if queue is full and task is blocked on this queue, then wake all the task*/ if (p_q-msg_q.current_numbers p_q-msg_q.size) { while (!is_list_empty(block_list_head)) { block_task list_entry(block_list_head-next, RAW_TASK_OBJ, task_list); raw_wake_object(block_task); block_task-block_status RAW_B_ABORT; } p_q-msg_q.blocked_send_task_numbers 0; } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } #endif #if (CONFIG_RAW_QUEUE_DELETE 0) RAW_U16 raw_queue_delete(RAW_QUEUE *p_q) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_QUEUE_FUNCTION_CHECK 0) if (p_q 0) { return RAW_NULL_OBJECT; } #endif block_list_head p_q-common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*All task blocked on this queue is waken up*/ while (!is_list_empty(block_list_head)) { delete_pend_obj(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list)); } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } #endif 从代码据结构上也看得出来两个函数的处理逻辑十分相像所以可以放在一起研究一下1判断参数合法性2唤醒等待线程这里消息清除函数唤醒的是发送线程而消息删除函数唤醒的所有线程3调用系统调度函数切换到其他线程继续运行4当前线程再次获得运行的机会函数返回一切ok搞定。嵌入式操作系统内核原理和开发实时调度
和很多通用的操作系统相比 实时操作系统有自己的一个特点那就是实时调度。通用操作系统的线程优先级一般是可以变化的而实时系统的线程优先级却是不变的。之所以这么设计是为了保证高优先级的任务在第一时间获得调度这样才能保证调度的实时性。因为实时系统是严格按照优先级搞定调度的所以不管什么时候我们只要寻找到最高优先级的任务即可。 rawos系统可以支持256个优先级对任务的创建个数也没有限制所以就会出现多个任务共享一个优先级的情况。因此系统本身对同优先级的任务分配了定额的时间片一旦该任务时间片用完就会被放到优先级的末尾直到获得下一次的调度机会下面的代码就说明了这一情况它是在时钟中断的时候被调度的 [cpp] view plaincopy void caculate_time_slice() { RAW_TASK_OBJ *task_ptr; LIST *head; RAW_SR_ALLOC(); task_ptr raw_task_active; head raw_ready_queue.task_ready_list[task_ptr-priority]; RAW_CRITICAL_ENTER(); if (is_list_empty(head)) { RAW_CRITICAL_EXIT(); return; } /*there is only one task on this ready list, so do not need to caculate time slice*/ if (head-next-next head) { RAW_CRITICAL_EXIT(); return; } if (task_ptr-time_slice) { task_ptr-time_slice--; } /*if current active task has time_slice, just return*/ if (task_ptr-time_slice) { RAW_CRITICAL_EXIT(); return; } /*Move current active task to the end of ready list for the same priority*/ move_to_ready_list_end(raw_ready_queue, task_ptr); /*restore the task time slice*/ task_ptr-time_slice task_ptr-time_total; RAW_CRITICAL_EXIT(); } 上面说的是一个优先级下面有多个任务的情况如果优先级本身只有一个任务那么就很抱歉了下面还得继续运行这个任务。另外我们在windows上面编程的时候喜欢暂时释放线程的运行权利调用sleep0即可那么这在rawos上是怎么实现的呢 [cpp] view plaincopy RAW_U16 raw_sleep(RAW_U32 dly) { RAW_U16 error_status; RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); if (dly) { /*system is locked so task can not sleep just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } raw_task_active-task_state RAW_DLY; tick_list_insert(raw_task_active, dly); remove_ready_list(raw_ready_queue, raw_task_active); } else { /*make current task to the end of ready list*/ move_to_ready_list_end(raw_ready_queue, raw_task_active); } RAW_CRITICAL_EXIT(); raw_sched(); if (dly) { /*task is timeout after sleep*/ error_status block_state_post_process(raw_task_active, 0); } else { error_status RAW_SUCCESS; } return error_status; } 通过的上面的代码我们可以看到其实系统啥也没干只是把任务方法放到优先级的链表末尾了。因为我们的系统需要实时调度所以即使把使用权出让出来也不可能让低优先的任务运行只能让同优先级的其他任务运行了。当然同优先级没有其他任务的时候只好它自己继续玩了。说了这么多我们看看系统是怎么调度的 [cpp] view plaincopy void raw_sched() { RAW_SR_ALLOC(); /*if it is in interrupt or system is locked, just return*/ if (raw_int_nesting || raw_sched_lock) { return; } RAW_CRITICAL_ENTER(); get_ready_task(raw_ready_queue); /*if highest task is currently task, then no need to do switch and just return*/ if (high_ready_obj raw_task_active) { RAW_CRITICAL_EXIT(); return; } CONTEXT_SWITCH(); RAW_CRITICAL_EXIT(); } 这个函数看上去很长其实最重要的部分就是get_ready_task这个函数它的目的就是寻找到当前最高优先级下面的任务大家看看代码就明白了 [cpp] view plaincopy void get_ready_task(RAW_RUN_QUEUE *rq) { LIST *node ; RAW_S32 highest_pri rq-highest_priority; /*Highest task must be the first element on the list*/ node rq-task_ready_list[highest_pri].next; high_ready_obj list_entry(node, RAW_TASK_OBJ, task_list); } 所以实时系统的核心就是寻找到那个最高优先级就可以了。在实时系统上面我们一般用bitmap表示优先级如果对应的优先级存在那么该位置1反之置0。那么什么情况下会发生优先级的改变呢其实就两种情况一种是需要把任务加入调度队列的时候还有一种就是把任务清除出调度队列的时候。 [cpp] view plaincopy void add_ready_list(RAW_RUN_QUEUE *rq, RAW_TASK_OBJ *task_ptr) { /*if task priority is equal current task priority then add to the end*/ if (task_ptr-priority raw_task_active-priority) { add_ready_list_end(rq, task_ptr); } /*if not add to the list front*/ else { add_ready_list_head(rq, task_ptr); } } void remove_ready_list(RAW_RUN_QUEUE *rq, RAW_TASK_OBJ *task_ptr) { RAW_S32 i; RAW_S32 priority task_ptr-priority; list_delete(task_ptr-task_list); /*if the ready list is not empty, we do not need to update the highest priority*/ if (!is_list_empty(rq-task_ready_list[priority]) ) { return; } bit_clear(rq-task_bit_map, priority); /*If task priority not equal to the highest priority, then we do not need to update the highest priority*/ if (priority ! rq-highest_priority) { return; } i bit_search_first_one(rq-task_bit_map, priority, CONFIG_RAW_PRIO_MAX - priority); /*Update the next highest priority task*/ if (i 0) { rq-highest_priority priority i; } else { #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif } } 加入调度队列的情况考虑的比较少我们只需要判断当前的最高优先级和加入任务优先级之间的关系即可。如果新加入的任务优先级高那么优先级发生了改变反之什么也不需要做。反之删除任务则比较复杂一些我们需要判断移除的任务是不是最高优先级不是还好处理如果清除出去的任务正好是最高优先级我们就需要从bitmap中寻找下一个最高优先级了这个函数就是bit_search_first_one。函数第一个参数是bitmap数组第二个参数是当前最高优先级第三个参数是剩下的优先级总数返回值为次优先级距离当前最高优先级的偏移值。 [cpp] view plaincopy /*For 32 bit cpu*/ RAW_S32 bit_search_first_one(RAW_U32 *base, RAW_S32 offset, RAW_S32 width) { register RAW_U32 *cp, v; register RAW_S32 position; cp base; cp offset 5; if (offset 31) { #if (CONFIG_RAW_LITTLE_ENDIAN 0) v *cp ~(((RAW_U32)1 (offset 31)) - 1); #else v *cp (((RAW_U32)1 (32 - (offset 31))) - 1); #endif } else { v *cp; } position 0; while (position width) { if (v) { /* includes 1 -- search bit of 1 */ if (!position) position - (offset 31); #if (CONFIG_RAW_LITTLE_ENDIAN 0) if (!(v 0xffff)) { v 16; position 16; } if (!(v 0xff)) { v 8; position 8; } if (!(v 0xf)) { v 4; position 4; } if (!(v 0x3)) { v 2; position 2; } if (!(v 0x1)) { position; } #else if (!(v 0xffff0000)) { v 16; position 16; } if (!(v 0xff000000)) { v 8; position 8; } if (!(v 0xf0000000)) { v 4; position 4; } if (!(v 0xc0000000)) { v 2; position 2; } if (!(v 0x80000000)) { position; } #endif if (position width) { return position; } else { return -1; } } else { /* all bits are 0 -- 1 Word skip */ if (position) { position 32; } else { position 32 - (offset 31); } v *cp; } } return -1; } 这个函数其实有两个其中一个是32位cpu另一个是为8位cpu准备的。当然我们这里看到的是前一种情形这里作者还考虑了大小端的情况小端就是x86之类的cpu大端就是powerpc之类的cpu主要是指字节序不同而已。按照作者的说法这是目前最快的查找方法能比ucos查找表的方法快多少我不太清楚估计只能按照系统设备性能的平均值来估算了别的还真不好说。要是本身用户侧的代码写的比较差那么争取的这点调度性能其实意义也不大。
嵌入式操作系统内核原理和开发延时操作 延时操作是操作系统中经常遇到的一种情形。延时的原因很多有的时候是为了等待外设芯片处理结束有的时候是为了暂时释放cpu的使用权有的就是为了希望在一段时间获取资源如果没法在单位时间内获取放弃等待。但是不管怎么说延时都是操作系统必不可少的一个工作。下面我们就看看延时是怎么实现的[cpp] view plaincopy static void tick_list_priority_insert(LIST *head, RAW_TASK_OBJ *task_ptr) { RAW_U32 val; LIST *q,*start, *end; RAW_TASK_OBJ *task_iter_temp; start end head; val task_ptr-tick_remain; for (q start-next; q ! end; q q-next) { task_iter_temp list_entry(q, RAW_TASK_OBJ, tick_list); /*sorted by remain time*/ if ((task_iter_temp-tick_match - raw_tick_count) val) { break; } } list_insert(q, task_ptr-tick_list); } void tick_list_insert(RAW_TASK_OBJ *task_ptr, RAW_U32 time) { LIST *tick_head_ptr; RAW_U16 spoke; if (time) { task_ptr-tick_match raw_tick_count time; task_ptr-tick_remain time; spoke (RAW_U16)(task_ptr-tick_match (TICK_HEAD_ARRAY - 1) ); tick_head_ptr tick_head[spoke]; tick_list_priority_insert(tick_head_ptr, task_ptr); task_ptr-tick_head tick_head_ptr; } } 延时的代码其实不是很多所以我在这里把最重要的两个函数给粘贴到这里了。因为每个线程都有可能延时那么怎么处理这些线程之间的关系就是我们需要做的事情了。我们看到了我们直接用tick_match表示线程需要等待的那个时间点就可以了。当然tick是不断增加的我们可以把尾数相同的线程按照高低顺序排列在一起这样在对应的tick到来的时候就直接按照尾数查找就可以了tick_list_priority_insert就是干了这么一件事情。 那么tick什么时候到期呢到期又该怎么处理呢我们接着往下看 [cpp] view plaincopy void tick_list_update(void) { LIST *tick_head_ptr; RAW_TASK_OBJ *p_tcb; LIST *iter; LIST *iter_temp; RAW_U16 spoke; RAW_SR_ALLOC(); RAW_CRITICAL_ENTER(); raw_tick_count; spoke (RAW_U16)(raw_tick_count (TICK_HEAD_ARRAY - 1) ); tick_head_ptr tick_head[spoke]; iter tick_head_ptr-next; while (RAW_TRUE) { /*search all the time list if possible*/ if (iter ! tick_head_ptr) { iter_temp iter-next; p_tcb list_entry(iter, RAW_TASK_OBJ, tick_list); /*Since time list is sorted by remain time, so just campare the absolute time*/ if (raw_tick_count p_tcb-tick_match) { switch (p_tcb-task_state) { case RAW_DLY: p_tcb-block_status RAW_B_OK; p_tcb-task_state RAW_RDY; tick_list_remove(p_tcb); add_ready_list(raw_ready_queue, p_tcb); break; case RAW_PEND_TIMEOUT: p_tcb-block_status RAW_B_TIMEOUT; p_tcb-task_state RAW_RDY; p_tcb-block_obj 0; tick_list_remove(p_tcb); /*remove task on the block list because task is timeout*/ list_delete(p_tcb-task_list); add_ready_list(raw_ready_queue, p_tcb); break; case RAW_PEND_TIMEOUT_SUSPENDED: p_tcb-block_status RAW_B_TIMEOUT; p_tcb-task_state RAW_SUSPENDED; p_tcb-block_obj 0; tick_list_remove(p_tcb); /*remove task on the block list because task is timeout*/ list_delete(p_tcb-task_list); break; case RAW_DLY_SUSPENDED: p_tcb-task_state RAW_SUSPENDED; p_tcb-block_status RAW_B_OK; tick_list_remove(p_tcb); break; default: #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif break; } iter iter_temp; } /*if current task time out absolute time is not equal current system time, just break because timer list is sorted*/ else { break; } } /*finish all the time list search */ else { break; } } RAW_CRITICAL_EXIT(); } 这个函数是在时钟中断的时候被调用的根据函数的先后顺序看看函数实现了哪些功能 1自增raw_tick_count 2根据尾数获取tick队列的头指针 3开始循环迭代处理延时线程 a如果没有没有延时线程循环跳出 b如果线程的终点tick和当前tick不匹配跳出循环因为tick都是排序好的所以后面的tick肯定不满足要求 c如果当前tick满足要求根据线程状态进行处理主要分为延时、阻塞超时、延时挂起、阻塞超时挂起四种状态 d获取下一个延时线程观察是否满足要求如果是继续回到c否则退出循环。 4函数返回继续时钟中断的剩余操作。 最后我们补充一下关于有限时间等待的知识。就像以前关于互斥操作的内容一样其实某些情况下我们是有时间限制的。一段时间没有获取资源我们就不希望等待了所以这里的延时操作还包括这部分的内容我们看看阻塞函数的相关代码就明白了。 [cpp] view plaincopy RAW_U16 raw_pend_object(RAW_COMMON_BLOCK_OBJECT *block_common_obj, RAW_TASK_OBJ *task_ptr, RAW_U32 timeout) { #if (CONFIG_RAW_ASSERT 0) if (timeout 0) { RAW_ASSERT(0); } #endif task_ptr-block_obj block_common_obj; if (timeout RAW_WAIT_FOREVER) { task_ptr-task_state RAW_PEND; } /*task is blocked with timeout*/ else { tick_list_insert(task_ptr,timeout); task_ptr-task_state RAW_PEND_TIMEOUT; } /*Remove from the ready list*/ remove_ready_list(raw_ready_queue, task_ptr); if (block_common_obj-block_way RAW_BLOCKED_WAY_FIFO) { list_insert(block_common_obj-block_list, task_ptr-task_list); } else { /*add to the priority sorted block list*/ add_to_priority_list(block_common_obj-block_list, task_ptr); } return RAW_SUCCESS; } 大家留意一下这里timeout参数的处理过程关注一下对应的tick_list_insert函数这样就可以明白我的意思了。 嵌入式操作系统内核原理和开发实时系统中的定时器 关于定时器的内容其实我们之前也讨论过也书写过相应的代码但是表达得比较晦涩效率也比较低。所以我们这里重新再讲一下定时器的相关代码看看嵌入式系统中的定时器是怎么实现的。在我们之前讨论线程延时的时候就使用hash的方法将不同的线程归类到不同的延时队列当中并且按照时间长短先后排列这样在最短的时间内就可以寻找到最合适的线程了。本质上线程延时和定时器的基本原理是一样的。唯一的区别就是线程延时响应的优先级要高一些而定时器一般由独立线程完成rawos也是这么做的。 [cpp] view plaincopy void timer_task(void *pa) { RAW_U16 position; LIST *timer_head_ptr; LIST *iter; LIST *iter_temp; RAW_TIMER *timer_ptr; timer_sem.count 0; while (1) { /*timer task will be blocked after call this function*/ raw_semaphore_get(timer_sem, RAW_WAIT_FOREVER); /*Disable the system schedule we do not need disable interrupt since nothing to do with interrupt*/ raw_disable_sche(); /*calculate which timer_head*/ raw_timer_count; position (RAW_U16)(raw_timer_count (TIMER_HEAD_NUMBERS - 1) ); timer_head_ptr timer_head[position]; iter timer_head_ptr-next; while (RAW_TRUE) { /*if timer exits*/ if (iter !timer_head_ptr) { /*Must use iter_temp because iter may be remove later.*/ iter_temp iter-next; timer_ptr list_entry(iter, RAW_TIMER, timer_list); /*if timeout*/ if (raw_timer_count timer_ptr-match) { /*remove form timer list*/ timer_list_remove(timer_ptr); /*if timer is reschedulable*/ if (timer_ptr-reschedule_ticks) { /*Sort by remain time*/ timer_ptr-remain timer_ptr-reschedule_ticks; timer_ptr-match raw_timer_count timer_ptr-remain; position (RAW_U16)(timer_ptr-match (TIMER_HEAD_NUMBERS - 1)); timer_ptr-to_head timer_head[position]; timer_list_priority_insert(timer_head[position], timer_ptr); } /*Any way both condition need to call registered timer function*/ if (timer_ptr-raw_timeout_function) { timer_ptr-raw_timeout_function(timer_ptr-raw_timeout_param); } iter iter_temp; } else { break; } } /*exit because timer is not exit*/ else { break; } } raw_enable_sche(); } } 由于基本原理和之前的线程延时是一样的所以这里就不重复了。定时器的基本操作其实也不多主要包括定时器创建、启动定时器、修改定时器、关闭定时器、删除定时器共五个基本函数大家可以和我一起慢慢看过来。[cpp] view plaincopy RAW_U16 raw_timer_create(RAW_TIMER *timer_ptr, RAW_U8 *name_ptr, RAW_VOID (*expiration_function)(RAW_U32), RAW_U32 expiration_input, RAW_U32 initial_ticks, RAW_U32 reschedule_ticks, RAW_U8 auto_activate) { #if (RAW_TIMER_FUNCTION_CHECK 0) if (timer_ptr 0) { return RAW_NULL_OBJECT; } if (expiration_function 0) { return RAW_NULL_POINTER; } #endif timer_ptr-name name_ptr; timer_ptr-raw_timeout_function expiration_function; timer_ptr-raw_timeout_param expiration_input; timer_ptr-init_count initial_ticks; timer_ptr-reschedule_ticks reschedule_ticks; timer_ptr-remain 0; timer_ptr-match 0; timer_ptr-timer_state TIMER_DEACTIVE; timer_ptr-to_head 0; list_init(timer_ptr-timer_list); if (auto_activate) { raw_timer_activate(timer_ptr); } return RAW_SUCCESS; } 创建定时器的操作很简单主要是把输入的参数存入到RAW_TIMER结构里面。相关的参数包括名称、函数指针、函数参数、初始tick、循环tick、标志参数。当然由于定时器分为单次定时和循环定时两种所以这里会有两个参数如果不是循环定时器循环tick参数可以不用配置。[cpp] view plaincopy RAW_U16 raw_timer_activate(RAW_TIMER *timer_ptr) { RAW_U16 position; RAW_SR_ALLOC(); #if (RAW_TIMER_FUNCTION_CHECK 0) if (timer_ptr 0) { return RAW_NULL_OBJECT; } #endif /*Timer state TIMER_ACTIVE TIMER_DELETED is not allowed to delete*/ if (timer_ptr-timer_state TIMER_ACTIVE) return RAW_TIMER_STATE_INVALID; if (timer_ptr-timer_state TIMER_DELETED) return RAW_TIMER_STATE_INVALID; RAW_CRITICAL_ENTER(); timer_ptr-match raw_timer_count timer_ptr-init_count; position (RAW_U16)(timer_ptr-match (TIMER_HEAD_NUMBERS - 1) ); /*Sort by remain time*/ timer_ptr-remain timer_ptr-init_count; /*Used by timer delete*/ timer_ptr-to_head timer_head[position]; timer_list_priority_insert(timer_head[position], timer_ptr); timer_ptr-timer_state TIMER_ACTIVE; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } 启动定时器就是把tick加入到定时器队列当中看看timer_list_priority_insert这个函数你就明白了。因为整个函数的处理过程涉及到 全局变量timer_head所以需要在前后面添加中断的处理动作。[cpp] view plaincopy RAW_U16 raw_timer_change(RAW_TIMER *timer_ptr, RAW_U32 initial_ticks, RAW_U32 reschedule_ticks) { RAW_SR_ALLOC(); #if (RAW_TIMER_FUNCTION_CHECK 0) if (timer_ptr 0) { return RAW_NULL_OBJECT; } #endif /*Only timer state TIMER_DEACTIVE is not allowed here*/ if (timer_ptr-timer_state ! TIMER_DEACTIVE) { return RAW_TIMER_STATE_INVALID; } if (timer_ptr-timer_state TIMER_DELETED) { return RAW_TIMER_STATE_INVALID; } RAW_CRITICAL_ENTER(); timer_ptr-init_count initial_ticks; timer_ptr-reschedule_ticks reschedule_ticks; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } 定时器修改函数就是把初始tick和循环tick修改一下当然如果此时定时器还没有运行初始tick还有用。但是一旦定时器起作用了起作用的就只能是循环tick了这一点大家要好好注意。[cpp] view plaincopy RAW_U16 raw_timer_deactivate(RAW_TIMER *timer_ptr) { RAW_SR_ALLOC(); #if (RAW_TIMER_FUNCTION_CHECK 0) if (timer_ptr 0) { return RAW_NULL_OBJECT; } #endif /*Timer state TIMER_DEACTIVE TIMER_DELETED is not allowed to delete*/ if (timer_ptr-timer_state TIMER_DEACTIVE) { return RAW_TIMER_STATE_INVALID; } if (timer_ptr-timer_state TIMER_DELETED) { return RAW_TIMER_STATE_INVALID; } RAW_CRITICAL_ENTER(); timer_list_remove(timer_ptr); timer_ptr-timer_state TIMER_DEACTIVE; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } 停止定时器顾名思义就是将定时器从队列中删除同时设置状态为TIMER_DEACTIVE。当然在进行操作之前需要判断一下当前定时器的属性如果定时器本身已经删除或者停止那什么也不用做了。 [cpp] view plaincopy RAW_U16 raw_timer_delete(RAW_TIMER *timer_ptr) { RAW_SR_ALLOC(); #if (RAW_TIMER_FUNCTION_CHECK 0) if (timer_ptr 0) { return RAW_NULL_OBJECT; } #endif if (timer_ptr-timer_state TIMER_DELETED) { return RAW_TIMER_STATE_INVALID; } RAW_CRITICAL_ENTER(); timer_list_remove(timer_ptr); timer_ptr-timer_state TIMER_DELETED; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } 定时器的删除函数和定时器的停止函数是一样的主要是判断逻辑不一样的。删除定时器只要定时器的状态不是已经删除就可以了其他的操作都是一样的。
嵌入式操作系统内核原理和开发线程状态 从第一篇的os博客以来谈了很多内容有中断、切换、调度、内存、互斥和延时等等但是线程的状态却没有涉及到今天我们要好好说一说。说到线程的状态按照一般的说法主要包括就绪、延时、阻塞、阻塞超时四个状态。如果线程没有死亡的话那么这几个状态也够用了但是我们后来发现可能需要对某些线程进行挂起处理这可能是出现了故障或者是为了调试使用。因此除了上面的四个状态我们还要补充对应的四个挂起状态分别是挂起、延时挂起、阻塞挂起、阻塞延时挂起。 说到了线程状态下面我们就看看常见的线程处理函数有哪些无外乎线程创建、线程延时、线程挂起、线程恢复和线程删除等等。 [cpp] view plaincopy RAW_U16 raw_task_create(RAW_TASK_OBJ *task_obj, RAW_U8 *task_name, RAW_VOID *task_arg, RAW_U8 task_prio, RAW_U16 time_slice, PORT_STACK *task_stack_base, RAW_U32 stack_size, RAW_TASK_ENTRY task_entry, RAW_U8 auto_start) { #if (RAW_TASK_STACK_CHECK 0) PORT_STACK *p_stack; RAW_U32 i; #endif RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (task_obj 0) { return RAW_NULL_OBJECT; } if (task_prio CONFIG_RAW_PRIO_MAX) { return RAW_BYOND_MAX_PRIORITY; } if (task_stack_base 0) { return RAW_NULL_POINTER; } if (task_entry 0) { return RAW_NULL_POINTER; } #endif RAW_CRITICAL_ENTER(); if (task_prio IDLE_PRIORITY) { if (idle_task_exit) { RAW_CRITICAL_EXIT(); return RAW_IDLE_EXIT; } idle_task_exit 1; } RAW_CRITICAL_EXIT(); raw_memset(task_obj, 0, sizeof(RAW_TASK_OBJ)); #if (CONFIG_ROUND_ROBIN 0) if (time_slice) { task_obj-time_total time_slice; } else { task_obj-time_total TIME_SLICE_DEFAULT; } task_obj-time_slice task_obj-time_total; #endif if (auto_start) task_obj-task_state RAW_RDY; else task_obj-task_state RAW_SUSPENDED; #if (RAW_TASK_STACK_CHECK 0) task_obj-task_stack_base task_stack_base; p_stack task_stack_base; for (i 0; i stack_size; i) { *p_stack 0; } #endif task_obj-task_stack port_stack_init(task_stack_base, stack_size, task_arg, task_entry); task_obj-task_name task_name; task_obj-priority task_prio; task_create_hook(task_obj); RAW_CRITICAL_ENTER(); #if (RAW_TASK_STACK_CHECK 0) task_obj-stack_size stack_size; list_insert(task_head, task_obj-stack_check_list); #endif if (auto_start) { add_ready_list_end(raw_ready_queue, task_obj); } if (raw_os_active ! RAW_OS_RUNNING) { /* Return if multitasking has not started */ RAW_CRITICAL_EXIT(); return RAW_OS_STOPPED; } RAW_CRITICAL_EXIT(); if (auto_start) { raw_sched(); } return RAW_SUCCESS; } 创建线程的函数是比较复杂的内容长一些参数也多一些。首先看看有哪些参数虽然很多但是慢慢梳理一下也不难理解有名称、参数、优先级、时间片、堆栈起始指针、堆栈大小、入口函数和标志。整个函数基本上都是赋值的过程最重要的其实就两个部分一个是port_stack_init另一个就是add_ready_list_end。前者可以对堆栈进行默认处理比如压入一些寄存器、压入函数参数、函数指针等等后者就是把线程加入到就绪队列。[cpp] view plaincopy RAW_U16 raw_sleep(RAW_U32 dly) { RAW_U16 error_status; RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); if (dly) { /*system is locked so task can not sleep just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } raw_task_active-task_state RAW_DLY; tick_list_insert(raw_task_active, dly); remove_ready_list(raw_ready_queue, raw_task_active); } else { /*make current task to the end of ready list*/ move_to_ready_list_end(raw_ready_queue, raw_task_active); } RAW_CRITICAL_EXIT(); raw_sched(); if (dly) { /*task is timeout after sleep*/ error_status block_state_post_process(raw_task_active, 0); } else { error_status RAW_SUCCESS; } return error_status; } 我们之前也介绍过系统的延时功能。延时就是把线程暂时从就绪队列清除出来添加到延时队列中。当然如果参数为0那表示作者只是希望暂时释放cpu的使用权如果此时没有同等优先级的任务那么下一个运行的线程还是它自己。 [cpp] view plaincopy RAW_U16 raw_task_suspend(RAW_TASK_OBJ *task_ptr) { RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (task_ptr 0) { return RAW_NULL_OBJECT; } #endif if (task_ptr-priority IDLE_PRIORITY) { return RAW_SUSPEND_TASK_NOT_ALLOWED; } RAW_CRITICAL_ENTER(); if (task_ptr raw_task_active) { if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_LOCKED; } } switch (task_ptr-task_state) { case RAW_RDY: task_ptr-task_state RAW_SUSPENDED; remove_ready_list(raw_ready_queue, task_ptr); break; case RAW_DLY: task_ptr-task_state RAW_DLY_SUSPENDED; break; case RAW_PEND: task_ptr-task_state RAW_PEND_SUSPENDED; break; case RAW_PEND_TIMEOUT: task_ptr-task_state RAW_PEND_TIMEOUT_SUSPENDED; break; case RAW_DLY_SUSPENDED: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: RAW_CRITICAL_EXIT(); return RAW_SUSPENDED_AGAIN; default: #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif RAW_CRITICAL_EXIT(); return RAW_STATE_UNKNOWN; } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 挂起任务的动作其实是比较残暴的因为此时你不知道线程处于什么状态。当然任务如果已经被挂起了那什么也不用做了否则就需要把任务修改为对应的挂起状态就可以了。当然如果任务是就绪态的还得把任务清除处理来。在函数结束的时候我们需要重新进行调度因为很有可能当前最高优先级的线程已经发生了改变。[cpp] view plaincopy RAW_U16 raw_task_resume(RAW_TASK_OBJ *task_ptr) { RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (task_ptr 0) { return RAW_NULL_OBJECT; } #endif RAW_CRITICAL_ENTER(); switch (task_ptr-task_state) { case RAW_RDY: case RAW_DLY: case RAW_PEND: case RAW_PEND_TIMEOUT: RAW_CRITICAL_EXIT(); return HAS_NOT_SUSPENDED; case RAW_SUSPENDED: task_ptr-task_state RAW_RDY; add_ready_list(raw_ready_queue, task_ptr); break; case RAW_DLY_SUSPENDED: task_ptr-task_state RAW_DLY; break; case RAW_PEND_SUSPENDED: task_ptr-task_state RAW_PEND; break; case RAW_PEND_TIMEOUT_SUSPENDED: task_ptr-task_state RAW_PEND_TIMEOUT; break; default: #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif RAW_CRITICAL_EXIT(); return RAW_STATE_UNKNOWN; } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 恢复函数其实就是挂起函数的逆向操作。如果任务没有被挂起那么什么也不用做。否则就需要把任务的状态修改为对应的非挂起状态当然该就绪的线程还得加入到就绪队列当中去。同时在函数结束之前不忘调度一下说不定刚刚释放的这个线程就是优先级最高的那个线程。[cpp] view plaincopy RAW_U16 raw_task_delete(RAW_TASK_OBJ *task_ptr) { RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (task_ptr 0) { return RAW_NULL_OBJECT; } if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif if (task_ptr-priority IDLE_PRIORITY) { return RAW_DELETE_TASK_NOT_ALLOWED; } RAW_CRITICAL_ENTER(); if (task_ptr raw_task_active) { if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_LOCKED; } } switch (task_ptr-task_state) { case RAW_RDY: remove_ready_list(raw_ready_queue, task_ptr); break; case RAW_SUSPENDED: break; case RAW_DLY: /* Task is only delayed, not on any wait list */ case RAW_DLY_SUSPENDED: tick_list_remove(task_ptr); break; case RAW_PEND: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT: case RAW_PEND_TIMEOUT_SUSPENDED: tick_list_remove(task_ptr); list_delete(task_ptr-task_list); break; default: #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif RAW_CRITICAL_EXIT(); return RAW_STATE_UNKNOWN; } task_ptr-task_state RAW_DELETED; #if (RAW_TASK_STACK_CHECK 0) /*make after_delete_list to right position*/ after_delete_list task_ptr-stack_check_list.next; if (after_delete_list task_head) { after_delete_list task_head.next; } list_delete(task_ptr-stack_check_list); #endif RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; } 删除函数的动作其实是比较残忍的因为此时你不清楚线程已经执行到哪一步了拥有了那些资源正在处理哪些资源所以没事不要用这个函数。这里做的只是把任务从就绪队列、等待队列和阻塞队列清除出来但是真正善后的工作要比这多得多如果有兴趣你看看linux的exit函数就明白了。
嵌入式操作系统内核原理和开发等值block内存池设计 内存池设计是嵌入式系统的一个重要环节之前我们也讨论过相关的内容。但是看了rawos的代码之后我觉得rawos的内存池设计更有特点。整个内存池的设计非常健壮不但考虑了字节对齐的问题而且还引入了等待调度机制这是我所没有想到的。所以在此我很愿意和大家分享这份优秀的代码。闲话不多说我们看看rawos的mempool数据结构是什么样的[cpp] view plaincopy typedef struct MEM_POOL { RAW_COMMON_BLOCK_OBJECT common_block_obj; /* Define the number of available memory blocks in the pool. */ RAW_U32 raw_block_pool_available; /* Define the head pointer of the available block pool. */ RAW_U8 *raw_block_pool_available_list; } MEM_POOL; 内存池的结构非常简单主要包括了通用阻塞结构、block数值block起始指针。内存池下面可以包括若干个block每个block的大小都是相等的同时block之间是通过链表串联在一起的这个我们看了后面的代码就明白了。mempool的处理函数不多就三个初始化、申请、释放函数。[cpp] view plaincopy RAW_U16 raw_block_pool_create(MEM_POOL *pool_ptr, RAW_U8 *name_ptr, RAW_U32 block_size, RAW_VOID *pool_start, RAW_U32 pool_size) { //MEM_POOL *tail_ptr; /* Working block pool pointer */ RAW_U32 blocks; /* Number of blocks in pool */ RAW_U8 *block_ptr; /* Working block pointer */ RAW_U8 *next_block_ptr; /* Next block pointer */ RAW_U8 *end_of_pool; /* End of pool area */ RAW_U8 block_align_mask; #if (RAW_BLOCK_FUNCTION_CHECK 0) /* Check for invalid pool size. */ if (pool_size (block_size block_size) ) { return RAW_BLOCK_SIZE_ERROR; } if (pool_ptr 0) { return RAW_NULL_OBJECT; } if (pool_start 0) { return RAW_NULL_POINTER; } #endif block_align_mask sizeof(void *) - 1u; if (((RAW_U32)pool_start block_align_mask)){ return RAW_INVALID_ALIGN; } if ((pool_size block_align_mask)) { return RAW_INVALID_ALIGN; } if ((block_size block_align_mask)) { return RAW_INVALID_ALIGN; } /*Init the list*/ list_init(pool_ptr-common_block_obj.block_list); /* Setup the basic block pool fields. */ pool_ptr -common_block_obj.name name_ptr; pool_ptr -common_block_obj.block_way 0; /* Calculate the end of the pools memory area. */ end_of_pool (RAW_U8 *) pool_start pool_size; /* Walk through the pool area, setting up the available block list. */ blocks 0; block_ptr (RAW_U8 *) pool_start; next_block_ptr block_ptr block_size; while (next_block_ptr end_of_pool) { blocks; if (next_block_ptr end_of_pool) { break; } /* Setup the link to the next block. */ *((RAW_U8 * *) block_ptr) next_block_ptr; /* Advance to the next block. */ block_ptr next_block_ptr; /* Update the next block pointer. */ next_block_ptr block_ptr block_size; } /* Set the last blocks forward pointer to NULL. */ *((RAW_U8 * *) block_ptr) 0; /* Save the remaining information in the pool control block. */ pool_ptr -raw_block_pool_available blocks; pool_ptr -raw_block_pool_available_list (RAW_U8 *) pool_start; return RAW_SUCCESS; } 上面就是内存池的创建函数入参共有五个参数分别是mempool结构、名称、block大小、pool起始地址、pool大小。函数基本内容如下所示1判断内存池、指针参数合法性2检验指针是否n字节对齐n取决于地址的大小3构建block链表前后相连最后一个block指向NULL指针4将pool首地址赋值给raw_block_pool_available_list函数返回。[cpp] view plaincopy RAW_U16 raw_block_allocate(MEM_POOL *pool_ptr, RAW_VOID **block_ptr, RAW_U32 wait_option) { RAW_U16 status; RAW_U8 *work_ptr; RAW_SR_ALLOC(); #if (RAW_BLOCK_FUNCTION_CHECK 0) if (pool_ptr 0) { return RAW_NULL_OBJECT; } if (block_ptr 0) { return RAW_NULL_POINTER; } if (raw_int_nesting) { if (wait_option ! RAW_NO_WAIT) { return RAW_NOT_CALLED_BY_ISR; } } #endif RAW_CRITICAL_ENTER(); /* Determine if there is an available block. */ if (pool_ptr -raw_block_pool_available) { /* Yes, a block is available. Decrement the available count. */ pool_ptr -raw_block_pool_available--; /* Pickup the current block pointer. */ work_ptr pool_ptr -raw_block_pool_available_list; /* Return the first available block to the caller. */ *((RAW_U8 **)block_ptr) work_ptr; /* Modify the available list to point at the next block in the pool. */ pool_ptr -raw_block_pool_available_list *((RAW_U8 **)work_ptr); /* Set status to success. */ status RAW_SUCCESS; } /*if no block memory is available then do it depend wait_option*/ else { if (wait_option RAW_NO_WAIT) { *((RAW_U8 **)block_ptr) 0; RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { *((RAW_U8 **)block_ptr) 0; RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } raw_pend_object(pool_ptr-common_block_obj, raw_task_active, wait_option); RAW_CRITICAL_EXIT(); raw_sched(); RAW_CRITICAL_ENTER(); *((RAW_U8 **)block_ptr) 0; status block_state_post_process(raw_task_active, block_ptr); RAW_CRITICAL_EXIT(); } return status; } 和其他的内存池申请函数不一样这里有一个wait_option选项。也就是说如果当前没有合适的block那么你可以选择等待处理。一旦别的线程释放内存你就可以得到调度继续运行了。当然你也可以不等待一旦寻找不到合适的block立即返回为NULL。[cpp] view plaincopy RAW_U16 raw_block_release(MEM_POOL *pool_ptr, RAW_VOID *block_ptr) { LIST *block_list_head; RAW_U8 *work_ptr; /* Working block pointer */ RAW_U8 need_schedule 0; RAW_SR_ALLOC(); #if (RAW_BLOCK_FUNCTION_CHECK 0) if (block_ptr 0) { return RAW_NULL_OBJECT; } if (pool_ptr 0) { return RAW_NULL_OBJECT; } #endif block_list_head pool_ptr-common_block_obj.block_list; RAW_CRITICAL_ENTER(); work_ptr ((RAW_U8 *) block_ptr); if (is_list_empty(block_list_head)) { /* Put the block back in the available list. */ *((RAW_U8 **) work_ptr) pool_ptr -raw_block_pool_available_list; /* Adjust the head pointer. */ pool_ptr -raw_block_pool_available_list work_ptr; /* Increment the count of available blocks. */ pool_ptr -raw_block_pool_available; } else { need_schedule 1; wake_send_msg(list_entry(block_list_head-next, RAW_TASK_OBJ, task_list), block_ptr); } RAW_CRITICAL_EXIT(); if (need_schedule) { raw_sched(); } /* Return completion status. */ return RAW_SUCCESS; } 和其他的内存free函数不一样这里的free函数多了一个wake_send_msg的功能。这也就是说当然如果存在阻塞等待资源的线程那么把资源送给该线程同时把该线程唤醒还要把need_schedule设置为1才可以。当然如果没有等待的线程那么直接把内存插入到链表前面中即可就是这么简单。
嵌入式操作系统内核原理和开发改进的链表内存分配算法 之前我自己也写过基于链表的内存分配算法但是看了rawos的内存分配算法还是感觉rawos写的要更好些。大家都知道链表内存分配就是从链表中快速找到最合适的内存块分配给用户线程。因为这种分配是随机的所以一般来说内存碎片是不可避免的。但是我们在编写代码的时候还是应该注意内存合并的问题。这里我们为什么要介绍rawos的内存分配呢还包括有这么几个原因 1整个内存链表采用循环链表的方法使用起来简单可靠 2内存分配的时候所有节点都是连在一起的通过标志数据判断当前数据是否已经分配 3如果当前没有合适的内存可以选择等待 4代码充分考虑了合并和切分的问题 5在遍历内存的时候灵活处理了中断问题可以及时响应中断这个比我考虑得要周到很多 6代码注释丰富只要多读几遍是可以慢慢理解代码内容的。 整个代码的结构清晰共四个函数分别是创建函数、内存查找函数、内存分配函数、内存释放函数。其中在内存分配和释放的时候都会调用到内存查找函数。 [cpp] view plaincopy RAW_U16 raw_byte_pool_create(RAW_BYTE_POOL_STRUCT *pool_ptr, RAW_U8 *name_ptr, RAW_VOID *pool_start, RAW_U32 pool_size) { RAW_U8 *block_ptr; /* Working block pointer */ RAW_U8 byte_align_mask; #if (RAW_BYTE_FUNCTION_CHECK 0) if (pool_ptr 0) { return RAW_NULL_POINTER; } if (pool_start 0) { return RAW_NULL_POINTER; } /* Check for invalid pool size. */ if (pool_size RAW_BYTE_POOL_MIN) { return RAW_BYTE_SIZE_ERROR; } #endif byte_align_mask sizeof(void *) - 1u; /*pool_start needs 4 bytes aligned*/ if (((RAW_U32)pool_start byte_align_mask)){ return RAW_INVALID_ALIGN; } /*pool_size needs 4 bytes aligned*/ if ((pool_size byte_align_mask)) { return RAW_INVALID_ALIGN; } /*Init the list*/ list_init(pool_ptr-common_block_obj.block_list); /* Setup the basic byte pool fields. */ pool_ptr -common_block_obj.name name_ptr; pool_ptr -common_block_obj.block_way 0; pool_ptr -raw_byte_pool_search (RAW_U8 *) pool_start; pool_ptr -raw_byte_pool_owner 0; /* Initially, the pool will have two blocks. One large block at the beginning that is available and a small allocated block at the end of the pool that is there just for the algorithm. Be sure to count the available blocks header in the available bytes count. */ pool_ptr -raw_byte_pool_available pool_size - sizeof(void *) - sizeof(RAW_U32); pool_ptr -raw_byte_pool_fragments 2; /* Calculate the end of the pools memory area. */ block_ptr ((RAW_U8 *) pool_start) (RAW_U32) pool_size; /* Backup the end of the pool pointer and build the pre-allocated block. */ block_ptr block_ptr - sizeof(RAW_U32); *((RAW_U32 *) block_ptr) RAW_BYTE_BLOCK_ALLOC; block_ptr block_ptr - sizeof(RAW_U8 *); *((RAW_U8 * *) block_ptr) pool_start; /* Now setup the large available block in the pool. */ *((RAW_U8 * *) pool_start) block_ptr; block_ptr (RAW_U8 *) pool_start; block_ptr block_ptr sizeof(RAW_U8 *); *((RAW_U32 *) block_ptr) RAW_BYTE_BLOCK_FREE; return RAW_SUCCESS; } 内存池的创建还是比较简单的基本的步骤如下所示1验证参数的合法性比如是否是NULL指针、是否对齐 2初始化内存池的基本参数比如说名称、默认阻塞结构、入口地址、剩余大小等等 3构建两个block节点前面是free节点后面是alloc节点两个节点构成循环节点。每个节点有三个部分组成分别是下一跳地址、标志、buffer。 [cpp] view plaincopy static void *raw_byte_pool_search(RAW_BYTE_POOL_STRUCT *pool_ptr, RAW_U32 memory_size) { RAW_U8 * current_ptr; /* Current block pointer */ RAW_U8 * next_ptr; /* Next block pointer */ RAW_U32 available_bytes; /* Calculate bytes available */ RAW_U32 examine_blocks; /* Blocks to be examined */ RAW_SR_ALLOC(); /* Disable interrupts. */ RAW_CRITICAL_ENTER(); /* First, determine if there are enough bytes in the pool. */ if (memory_size pool_ptr -raw_byte_pool_available) { /* Restore interrupts. */ RAW_CRITICAL_EXIT(); /* Not enough memory, return a NULL pointer. */ return 0; } /* Walk through the memory pool in search for a large enough block. */ current_ptr pool_ptr -raw_byte_pool_search; examine_blocks pool_ptr -raw_byte_pool_fragments 1; available_bytes 0; do { /* Check to see if this block is free. */ if (*((RAW_U32 *) (current_ptr sizeof(RAW_U8 *))) RAW_BYTE_BLOCK_FREE) { /* Block is free, see if it is large enough. */ /* Pickup the next blocks pointer. */ next_ptr *((RAW_U8 * *) current_ptr); /* Calculate the number of byte available in this block. */ available_bytes next_ptr - current_ptr - sizeof(RAW_U8 *) - sizeof(RAW_U32); /* If this is large enough, we are done because our first-fit algorithm has been satisfied! */ if (available_bytes memory_size) { /* Find the suitable position */ break; } else { /* Clear the available bytes variable. */ available_bytes 0; /* Not enough memory, check to see if the neighbor is free and can be merged. */ if (*((RAW_U32 *) (next_ptr sizeof(RAW_U8 *))) RAW_BYTE_BLOCK_FREE) { /* Yes, neighbor block can be merged! This is quickly accomplished by updating the current block with the next blocks pointer. */ *((RAW_U8 * *) current_ptr) *((RAW_U8 * *) next_ptr); /* Reduce the fragment number, and does not need to increase available bytes since they are already there*/ pool_ptr -raw_byte_pool_fragments--; /* Update the search pointer, if it is involved */ if (pool_ptr -raw_byte_pool_search next_ptr) { pool_ptr -raw_byte_pool_search current_ptr; } } else { /* Neighbor is not free so get to the next search point*/ current_ptr *((RAW_U8 * *) next_ptr); /* Reduse the examined block since we have skiped one search */ if (examine_blocks) { examine_blocks--; } } } } else { /* Block is not free, move to next block. */ current_ptr *((RAW_U8 * *) current_ptr); } /* finish one block search*/ if (examine_blocks) { examine_blocks--; } /* Restore interrupts temporarily. */ RAW_CRITICAL_EXIT(); /* Disable interrupts. */ RAW_CRITICAL_ENTER(); /* Determine if anything has changed in terms of pool ownership. */ if (pool_ptr -raw_byte_pool_owner ! raw_task_active) { /* Pool changed ownership during interrupts.so we reset the search*/ current_ptr pool_ptr -raw_byte_pool_search; examine_blocks pool_ptr -raw_byte_pool_fragments 1; /* Setup our ownership again. */ pool_ptr -raw_byte_pool_owner raw_task_active; } } while (examine_blocks); /* Determine if a block was found. If so, determine if it needs to be split. */ if (available_bytes) { /* Do we need to split this block if this is big enough.*/ if ((available_bytes - memory_size) ((RAW_U32) RAW_BYTE_BLOCK_MIN)) { /* Split the block. */ next_ptr current_ptr memory_size sizeof(RAW_U8 *) sizeof(RAW_U32); /* Setup the new free block. */ *((RAW_U8 * *) next_ptr) *((RAW_U8 * *) current_ptr); *((RAW_U32 *) (next_ptr sizeof(RAW_U8 *))) RAW_BYTE_BLOCK_FREE; /* Increase the total fragment counter. */ pool_ptr -raw_byte_pool_fragments; /* Update the current pointer to point at the newly created block. */ *((RAW_U8 * *) current_ptr) next_ptr; /* Set available equal to memory size for subsequent calculation. */ available_bytes memory_size; } /* In any case, mark the current block as allocated. */ *((RAW_U32 *) (current_ptr sizeof(RAW_U8 *))) RAW_BYTE_BLOCK_ALLOC; /* Reduce the number of available bytes in the pool. */ pool_ptr -raw_byte_pool_available pool_ptr -raw_byte_pool_available - available_bytes - sizeof(RAW_U8 *) - sizeof(RAW_U32); /* Adjust the pointer for the application. */ current_ptr current_ptr sizeof(RAW_U8 *) sizeof(RAW_U32); } else { /* Set current pointer to NULL to indicate nothing was found. */ current_ptr 0; } /* Restore interrupts temporarily. */ RAW_CRITICAL_EXIT(); /* Return the searched result*/ return current_ptr; } 这个内存查找函数是这四个函数中最难的那个函数。看上去内容很多其实我们可以分成两个部分分析其中dowhile的这个部分就是负责查找合适的内存块后面的ifelse部分是对分配的节点进行后续处理下面我们可以详细来看看 1验证剩余空间是否满足条件不满足立即返回 2遍历所有的block节点查找合适的节点 a、如果寻找到合适的节点那么立即跳出循环 b、如果没有寻找到合适的节点可以做一些额外的工作比如说对相连的free节点进行合并 c、循环的结束条件是examine_blocks全部遍历结束当然如果发现raw_byte_pool_owner不为当前线程那么一切重来 3对于获得的block节点同样有两种方法处理 a、如果除去分配的空间剩下的节点空间仍然很多那么可以把当前节点拆分成两个节点进行处理 b、如果剩下的空间本身就很有限那么全部分配给当前线程算了。 4函数返回current_ptr包含了那个分配好的地址。 [cpp] view plaincopy RAW_U16 raw_byte_allocate(RAW_BYTE_POOL_STRUCT *pool_ptr, RAW_VOID **memory_ptr, RAW_U32 memory_size, RAW_U32 wait_option) { RAW_U16 status; /* Return status */ RAW_U8 *work_ptr; /* Working byte pointer */ RAW_TASK_OBJ *current_work_task; RAW_SR_ALLOC(); #if (RAW_BYTE_FUNCTION_CHECK 0) if (pool_ptr 0) { return RAW_NULL_POINTER; } if (memory_ptr 0) { return RAW_NULL_POINTER; } if (raw_int_nesting) { if (wait_option ! RAW_NO_WAIT) return RAW_NOT_CALLED_BY_ISR; } #endif /* align the memory size to 4 byte*/ memory_size ((memory_size (~3u)) 4u); current_work_task raw_task_active; /* Disable interrupts. */ RAW_CRITICAL_ENTER(); /* Loop to handle cases where the owner of the pool changed. */ do { /* Indicate that this thread is the current owner. */ pool_ptr -raw_byte_pool_owner current_work_task; /* Restore interrupts. */ RAW_CRITICAL_EXIT(); /*Search for free memory*/ work_ptr raw_byte_pool_search(pool_ptr, memory_size); /* Disable interrupts. */ RAW_CRITICAL_ENTER(); /*if raw_byte_pool_owner changed and we have not got memory yet, continue tom do search*/ } while ((!work_ptr) (pool_ptr -raw_byte_pool_owner ! current_work_task)); /* Determine if memory was found. */ if (work_ptr) { /* Copy the pointer into the return destination. */ *memory_ptr (RAW_U8 *) work_ptr; RAW_CRITICAL_EXIT(); /* Set the status to success. */ return RAW_SUCCESS; } else { if (wait_option) { /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { *memory_ptr 0; RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } raw_task_active-msg (RAW_VOID *)memory_size; raw_pend_object(pool_ptr-common_block_obj, raw_task_active, wait_option); } else { *memory_ptr 0; RAW_CRITICAL_EXIT(); /* Immediate return, return error completion. */ return RAW_NO_MEMORY; } } /* Restore interrupts. */ RAW_CRITICAL_EXIT(); raw_sched(); RAW_CRITICAL_ENTER(); *memory_ptr 0; status block_state_post_process(raw_task_active, memory_ptr); RAW_CRITICAL_EXIT(); return status; } 和内存查找函数相比内存分配函数就简单多了 1判断参数的合法性 2循环查找链表节点寻找合适的内存块注意循环的跳出条件即work_ptrNULL且没有其他线程的干扰 3如果寻找到了合适的节点那么没有问题反之需要根据wait_option判断是否需要把自己挂起在等待队列上 4线程再次得到运行的机会memory_ptr中包含了内存地址函数返回。 [cpp] view plaincopy RAW_U16 raw_byte_release(RAW_BYTE_POOL_STRUCT *pool_ptr, void *memory_ptr) { RAW_U8 *work_ptr; /* Working block pointer */ LIST *iter; LIST *iter_temp; LIST *byte_head_ptr; RAW_TASK_OBJ *task_ptr; RAW_U8 need_sche 0; RAW_SR_ALLOC(); #if (RAW_BYTE_FUNCTION_CHECK 0) if (pool_ptr 0) { return RAW_NULL_POINTER; } if (memory_ptr 0) { return RAW_NULL_POINTER; } #endif byte_head_ptr pool_ptr-common_block_obj.block_list; /* Back off the memory pointer to pickup its header. */ work_ptr (RAW_U8 *) memory_ptr - sizeof(RAW_U8 *) - sizeof(RAW_U32); /* Disable interrupts. */ RAW_CRITICAL_ENTER(); /* Indicate that this thread is the current owner. */ pool_ptr -raw_byte_pool_owner raw_task_active; /* Release the memory.*/ *((RAW_U32 *) (work_ptr sizeof(RAW_U8 *))) RAW_BYTE_BLOCK_FREE; /* Update the number of available bytes in the pool. */ pool_ptr -raw_byte_pool_available pool_ptr -raw_byte_pool_available (*((RAW_U8 * *) (work_ptr)) - work_ptr); /* Set the pool search value appropriately. */ pool_ptr -raw_byte_pool_search work_ptr; iter byte_head_ptr-next; while (iter ! byte_head_ptr) { iter_temp iter-next; task_ptr list_entry(iter, RAW_TASK_OBJ, task_list); RAW_CRITICAL_EXIT(); /* See if the request can be satisfied. */ work_ptr raw_byte_pool_search(pool_ptr, (RAW_U32)task_ptr-msg); RAW_CRITICAL_ENTER(); /* If there is not enough memory, break this loop! */ if (!work_ptr) { break; } wake_send_msg(task_ptr, (RAW_VOID *)work_ptr); need_sche 1; iter iter_temp; } RAW_CRITICAL_EXIT(); if (need_sche) { raw_sched(); } return RAW_SUCCESS; } 内存释放后本来我们只需要把内存节点的标志设置为RAW_BYTE_BLOCK_FREE即可。可是我们还需要判断当前是否存在等待唤醒的线程是否可以为等待线程寻找到合适的内存节点整个函数基本流程如下所示 1判断参数合法性 2将当前block节点设置为可用调整pool中的参数数据 3遍历等待线程并为之查找到合适的节点寻找到节点跳出遍历完所有等待节点也跳出 4如果有线程被唤醒那么需要重新调度否则函数返回。嵌入式操作系统内核原理和开发优先级的修改 之前在和rawos作者的闲聊中rawos作者认为实时操作系统中最大的特色就是互斥量的问题。一开始我对这个看法其实是有保留意见的直到我看到了修改优先级的相关代码才开始对作者的看法有了很大的认同感。实话说在嵌入式实时系统中修改优先级是一个很复杂的事情为什么呢因为这其中涉及到了互斥量的问题。我们大家都知道在嵌入式系统中优先级反转是一个绕不去的砍。低优先级的任务因为获得了互斥量因而比高优先级的任务获得了更多的运行机会这从根本上违背了实时系统设计的初衷。所以人们为了解决除了这一问题提出了优先级互斥量和优先级继承两种方法。 优先级互斥量的办法比较简单ucos也是这么做的。我们在分配一个互斥量的时候也给这个互斥量分配一定的优先级。任何获得该互斥量的任务都会把自己的优先级修改为互斥量的优先级这样保证了获得该优先级的任务可以在最短的时间内完成尽快释放资源。但是这也会导致一个问题那就是该互斥量需要占用一个优先级而且比此优先级高的任务也没办法获得该互斥量。另外一种方法就是优先级继承的方法简单一点说就是任何对阻塞线程优先级的修改都会导致拥有此互斥量的任务进行优先级的修改。闲话不多说我们看看rawos上面的代码是怎么描述的 [cpp] view plaincopy RAW_U16 raw_task_priority_change (RAW_TASK_OBJ *task_ptr, RAW_U8 new_priority, RAW_U8 *old_priority) { RAW_U8 ret_pri; RAW_U16 error; RAW_SR_ALLOC(); #if (RAW_TASK_FUNCTION_CHECK 0) if (task_ptr 0) { return RAW_NULL_OBJECT; } if (old_priority 0) { return RAW_NULL_OBJECT; } #endif /*Idle task is not allowed to change priority*/ if (task_ptr-priority IDLE_PRIORITY) { return RAW_CHANGE_PRIORITY_NOT_ALLOWED; } /*Not allowed change to idle priority*/ if (new_priority IDLE_PRIORITY) { return RAW_CHANGE_PRIORITY_NOT_ALLOWED; } RAW_CRITICAL_ENTER(); #if (CONFIG_RAW_MUTEX 0) ret_pri chg_pri_mutex(task_ptr, new_priority, error); if (error ! RAW_SUCCESS) { goto error_exit; } task_ptr-bpriority new_priority; new_priority ret_pri; #else task_ptr-bpriority new_priority; #endif *old_priority task_ptr-priority; error change_interal_task_priority(task_ptr, new_priority); if (error ! RAW_SUCCESS) { goto error_exit; } RAW_CRITICAL_EXIT(); do_possible_sche(); return RAW_SUCCESS; error_exit: RAW_CRITICAL_EXIT(); return error; } 这个函数是系统本身提供的一个函数内容不算少但是重点可以放在两个子函数上面。一个部分是函数chg_pri_mutex另外一个函数是change_interal_task_priority。前者判断当前优先级是否可以修改后者判断如何对当前的任务进行修改后面我们会看到会对这个问题有一个详细的说明。 [cpp] view plaincopy RAW_U8 chg_pri_mutex(RAW_TASK_OBJ *tcb, RAW_U8 priority, RAW_U16 *error) { RAW_MUTEX *mtxcb; RAW_U8 hi_pri, low_pri, pri; RAW_TASK_OBJ *first_block_task; LIST *block_list_head; hi_pri priority; low_pri 0; mtxcb (RAW_MUTEX *)(tcb-block_obj); if (mtxcb) { /*if it is blocked on mutex*/ if (mtxcb-common_block_obj.object_type RAW_MUTEX_OBJ_TYPE) { if (mtxcb-policy RAW_MUTEX_CEILING_POLICY) { pri mtxcb-ceiling_prio; if (pri low_pri) { low_pri pri; } } } } /* Locked Mutex */ pri hi_pri; for (mtxcb tcb-mtxlist; mtxcb ! 0; mtxcb mtxcb-mtxlist) { switch (mtxcb-policy) { case RAW_MUTEX_CEILING_POLICY: pri mtxcb-ceiling_prio; if ( pri low_pri ) { low_pri pri; } break; case RAW_MUTEX_INHERIT_POLICY: block_list_head mtxcb-common_block_obj.block_list; if (!is_list_empty(block_list_head)) { first_block_task list_entry(block_list_head-next, RAW_TASK_OBJ, task_list); pri first_block_task-priority; } break; default: /* nothing to do */ break; } if ( pri hi_pri ) { hi_pri pri; } } if (priority low_pri) { *error RAW_EXCEED_CEILING_PRIORITY; return RAW_EXCEED_CEILING_PRIORITY; } *error RAW_SUCCESS; return hi_pri; } 上面的代码还是比较复杂的我们看到其实任务的优先级不是可以随便修改的因为当前任务可能已经占有了许多互斥量资源而这些互斥量资源其实是有约束条件的。如果占有的互斥量类型是那种带优先级的互斥量那么必须找出的那个最低优先级的互斥量至少修改的任务优先级不能比它高。剩下的工作就是在继承优先级的体制下寻找到最高的优先级仅此而已。 [cpp] view plaincopy RAW_U16 change_interal_task_priority(RAW_TASK_OBJ *task_ptr, RAW_U8 new_priority) { RAW_U8 old_pri; switch (task_ptr-task_state) { case RAW_RDY: remove_ready_list(raw_ready_queue, task_ptr); task_ptr-priority new_priority; if (task_ptr raw_task_active) { add_ready_list_head(raw_ready_queue, task_ptr); } else { add_ready_list_end(raw_ready_queue, task_ptr); } break; case RAW_DLY: /* Nothing to do except change the priority in the OS_TCB */ case RAW_SUSPENDED: case RAW_DLY_SUSPENDED: task_ptr-priority new_priority; /* Set new task priority*/ break; case RAW_PEND: case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: old_pri task_ptr-priority; task_ptr-priority new_priority; change_pend_list_priority(task_ptr); #if (CONFIG_RAW_MUTEX 0) mtx_chg_pri(task_ptr, old_pri); #endif break; default: #if (CONFIG_RAW_ASSERT 0) RAW_ASSERT(0); #endif return RAW_STATE_UNKNOWN; } return RAW_SUCCESS; } 前面我们说到了优先级的修改函数而change_interal_task_priority就是完成这一功能的函数。当然我们需要针对目前任务的状态对任务的优先级进行修改如果任务此时正在运行或者延迟运行那么还好办关键是如果此时任务已经阻塞了那么考虑的情况就多了。 [cpp] view plaincopy RAW_VOID mtx_chg_pri(RAW_TASK_OBJ *tcb, RAW_U8 oldpri) { RAW_MUTEX *mtxcb; RAW_TASK_OBJ *mtxtsk; mtxcb (RAW_MUTEX *)(tcb-block_obj); if (mtxcb-common_block_obj.object_type RAW_MUTEX_OBJ_TYPE) { if (mtxcb-policy RAW_MUTEX_INHERIT_POLICY) { mtxtsk mtxcb-mtxtsk; if (mtxtsk-priority tcb-priority) { /* Since the highest priority of the lock wait task became higher, raise the lock get task priority higher */ change_interal_task_priority(mtxtsk, tcb-priority); } /*the highest priority task blocked on this mutex may decrease priority so reset the mutex task priority*/ else if(mtxtsk-priority oldpri) { release_mutex(mtxtsk, 0); } } } } mtx_chg_pri函数此时考虑的不光是它自己优先级的问题它还需要考虑此时占有互斥量的这个任务优先级该怎么修改。我们进一步看看release_mutex下面做了哪些工作。 [cpp] view plaincopy static RAW_VOID release_mutex(RAW_TASK_OBJ *tcb, RAW_MUTEX *relmtxcb) { RAW_MUTEX *mtxcb, **prev; RAW_U8 newpri, pri; RAW_TASK_OBJ *first_block_task; LIST *block_list_head; /* (B) The base priority of task */ newpri tcb-bpriority; /* (A) The highest priority in mutex which is locked */ pri newpri; prev tcb-mtxlist; while ((mtxcb *prev) ! 0) { if (mtxcb relmtxcb) { /* Delete from list */ *prev mtxcb-mtxlist; continue; } switch (mtxcb-policy) { case RAW_MUTEX_CEILING_POLICY: pri mtxcb-ceiling_prio; break; case RAW_MUTEX_INHERIT_POLICY: block_list_head mtxcb-common_block_obj.block_list; if (!is_list_empty(block_list_head)) { first_block_task list_entry(block_list_head-next, RAW_TASK_OBJ, task_list); pri first_block_task-priority; } break; default: break; } if (newpri pri) { newpri pri; } prev mtxcb-mtxlist; } if ( newpri ! tcb-priority ) { /* Change priority of lock get task */ change_interal_task_priority(tcb, newpri); } } 这个函数的工作就是修改那个获得互斥量的任务的优先级的在寻找到最高优先级之后那么又需要调用change_internall_task_priority函数了。有过递归函数编写经验的朋友就知道了这其实就是一个典型的递归函数。change_internall_task_priority函数调用到release_mutex然而release_mutex又调用到change_internall_task_priority感觉上没完没了了其实不是这样。递归函数都是需要出口的这个函数的出口就是change_internall_task_priority一切等到获得的那个任务不再是阻塞任务为止整个修改的过程就结束了。当然任务优先级恢复的工作也是非常麻烦的不管是带优先级的互斥量还是优先级继承机制的互斥量额外的优先级修改和计算都是必须的不知道我讲清楚了没有。rawos在互斥量的最大进步就是进一步说明了拥有多互斥量的任务该如何修改优先级当然了函数迭代的过程多了堆栈使用的空间也多了有没有溢出的危险这是我们需要考虑的另外一个重要因素。 嵌入式操作系统内核原理和开发总结篇 很多朋友都喜欢嵌入式操作系统的内容但是如何实现和仿真这样一个系统一直是困扰我们的难题。现在郑重推荐一下raw-os系统在我们的博客当中也多次提到了这个代码希望大家可以多多阅读不断加深对os的认识。如果有可能大家可以到
http://ishare.iask.sina.com.cn/f/33440944.html这里下载raw-os的vc6.0版本单步调试每一行代码肯定会有所收获。 01 嵌入式操作系统内核原理和开发优先级的修改
02嵌入式操作系统内核原理和开发改进的链表内存分配算法
03嵌入式操作系统内核原理和开发等值block内存池设计
04嵌入式操作系统内核原理和开发线程状态
05嵌入式操作系统内核原理和开发实时系统中的定时器
06嵌入式操作系统内核原理和开发延时操作
07嵌入式操作系统内核原理和开发实时调度
08嵌入式操作系统内核原理和开发消息队列
09嵌入式操作系统内核原理和开发事件
10嵌入式操作系统内核原理和开发互斥量
11嵌入式操作系统内核原理和开发信号量
12嵌入式操作系统内核原理和开发最快、最优、最差内存分配算法
13嵌入式操作系统内核原理和开发基于链表节点的内存分配算法
14嵌入式操作系统内核原理和开发固定内存分配算法
15嵌入式操作系统内核原理和开发内存分配算法
16嵌入式操作系统内核原理和开发头文件调整
17嵌入式操作系统内核原理和开发改进型优先级调度
18嵌入式操作系统内核原理和开发通用优先级调度
19嵌入式操作系统内核原理和开发多线程轮转
20嵌入式操作系统内核原理和开发任务创建和堆栈溢出检查
21嵌入式操作系统内核原理和开发线程切换
22嵌入式操作系统内核原理和开发系统中断仿真
23嵌入式操作系统内核原理和开发基础
24嵌入式操作系统内核原理和开发地址空间
25嵌入式操作系统内核原理和开发中断
26嵌入式操作系统内核原理和开发cpu的那些事
27嵌入式操作系统内核原理和开发开篇