/****************************************************
Title: 嵌入式系统多任务抢占机制
Framework:MyOS V 1.0 Bate
Date:2014-9-18 21:31:54
Author:小X
Remark:ARM实现系统任务的调度
*****************************************************/
今天我给大家带来的是如何理解嵌入式系统多任务机制.
我们先来写一个多任务调节主架构。
1 /********************************************************** 2 * 多语言嵌套写嵌入式系统框架 3 * C/C++/C# 4 * 嵌入式系统,单片机的系统原理其实和PC类似,但是PC系统比单片机复杂上千上万倍。 5 * 本来是写C的,后来使用C++和C#大量语法,搞的代码有点不伦不类望见谅。 6 * ********************************************************/ 7 /// <summary>程序入口</summary> 8 int Main(int argc, char argv[]) 9 { 10 try 11 { 12 App.Init(); //初始化APP 13 MyOS.Init(); //操作系统初始化 14 MyOS.ShowInFo(); //操作系统信息 15 Debug.Print(DateTime.Now.ToString()); 16 Debug.Print("All OK!"); 17 MyOS.TaskCreate(Task0,&StackTask0[StackSizeTask0-1],PrioTask0); // 创建一个任务 18 Debug.Printf("Ready to start MyOS!\r\n\r\n"); 19 MyOS.Start(); //开始运行操作系统 20 return 0; 21 } 22 catch (Exception ex) 23 { 24 Debug.Print(ex.Message); 25 } 26 while (true) { Thread.Sleep(1000); } 27 } 28 void Task0(void) 29 { 30 App.Start(); //设置中断向量,启动操作系统的硬件定时器中断 31 Debug.Printf("Start MyOS!\r\n"); 32 // 创建其他任务 33 MyOS.TaskCreate(Task1,&StackTask1[StackSizeTask1 - 1],PrioTask1); 34 MyOS.TaskCreate(Task2,&StackTask2[StackSizeTask2 - 1],PrioTask2); 35 MyOS.TaskCreate(Task3,&StackTask3[StackSizeTask2 - 1],PrioTask3); 36 while(1) 37 { 38 Debug.Printf("Task0\r\n"); 39 MyOS.TimeDly(2000); // 2 秒运行一次 40 } 41 } 42 void Task1(void) 43 { 44 while(1) 45 { 46 Debug.Printf("Task1\r\n"); 47 MyOS.TimeDly(4000); // 4 秒运行一次 48 } 49 } 50 51 void Task2(void) 52 { 53 while(1) 54 { 55 Debug.Printf("Task2\r\n"); 56 MyOS.TimeDly(1000); // 1 秒运行一次 57 Debug.Printf("Suspend Task2!\r\n"); 58 MyOS.TaskSuspend(PrioTask2); // 进入挂起状态 59 } 60 } 61 void Task3(void) 62 { 63 while(1) 64 { 65 Debug.Printf("Resume Task2!\r\n"); 66 MyOS.TaskResume(PrioTask2); // 恢复任务 2 67 MyOS.TimeDly(10000); 68 } 69 }
整体语法可多错误,我们不必在意这些细节。
系统过程分析:
我们整个系统创建了四个任务处理,任务 0 每 2 秒运行一次,
任务 1 每 4 秒运行一次,
任务 2 每 1 秒运行一次,
然后即把自己挂起,
任务 3 每 10 秒运行一次,
并把任务 2 恢复。
/***********************先预测一下结果************************/
1 /******************终端显示效果************************* 2 * Erase Done...... 3 * Erase OK. 4 * Dowload...... 5 * Dowload OK. 6 * Verify...... 7 * Verify OK. 8 * Application running ...... 9 * ...................................................... 10 * Info: Hardware XXXXX 11 * OS:MyOS V1.0 Bete 12 * TinyBooter:V1.0 Bate 13 * App:V1.0 Bate 14 * Date:18th Sep,2004 15 * Author:BigBear 16 * Time:2014-9-18 17:12:05 17 * ALL OK! 18 * Ready to start MyOS! 19 * Start MyOS! 20 * 21 * Tesk0; 22 * Tesk1; 23 * Tesk2; 24 * Suspend Task2! 25 * Resume Task2! 26 * Tesk2; 27 * Tesk0; 28 * Tesk0; 29 * Tesk1; 30 * Tesk0; 31 * Tesk0; 32 * Tesk1; 33 * Tesk0; 34 * Tesk2; 35 * Suspend Task2! 36 * Resume Task2! 37 * Tesk2; 38 * Tesk0; 39 * Tesk0; 40 * Tesk1; 41 * Tesk0; 42 * Tesk0; 43 * Tesk1; 44 * * 45 * * 46 * * 47 * * 48 * (循环中。。。。。。) 49 * * 50 * * 51 ***************************************************/
什么协同多任务系统?
在我们电脑中,腾讯让我们让成了开机登QQ的习惯,QQ登陆的过程中我们打开工作邮箱,这是QQ登录相当于任务A初始化,FoxMail相当于B初始化,我们习惯打开一下浏览器看看救赎论坛,这属于任务C。
其实单核CPU里面,这些任务是单一工作的。不存在真正的多任务机制,每个任务同一时间都使用同一个CPU是不可能的。所有单一CPU是单一任务的。但是我们操作系统是通过任务调度来实现多个任务执行。通过CPU快速切换,达到让人感觉像是多个任务同时运行一样。这个就是操作系统的多任务调度机制。
现在的计算机硬件系统中,CPU通常是两个或多核CPU。可以真正意义上同时处理多个任务。这些是X86等复杂CPU以及操作系统来协同调节任务。
多任务的好处就是可以充分利用我们的硬件资源。在单片机中,硬件资源非常简单。多任务的机制显得更为重要。在之前的裸机程序中,我们的都是处理一件事情的时候就让他Delay一段时间。CPI在那里空转(等待),这样超级浪费CPU资源。但是我们加入任务调节机制之后,我们会让程序自己等待着,CPU去执行下一个任务,等条件满足后(比喻延时时间到了),我们在将这个任务挂载寄来去处理前面的事情。处理了之后再回来继续开始手上的事情。这样充分利用CPU。大大提高了应用的执行效率。
抢占式多任务处理调节机制。
什么是任务的抢占式调节呢?
当系统在按任务执行时。多个任务同时请求。我们的系统到底应该执行哪一个任务呢?当然我们一般处理方式是当同时有多个任务响操作系统发出请求时,谁的任务优先级最高我们就去执行谁的程序。
我们先会定义一大堆的任务优先级别:
1 public enum MultiTask:uint 2 { 3 PTesk0=0; 4 PTesk1=1; 5 PTesk2=2; 6 PTesk3=3; 7 PTesk4=4; 8 PTesk5=5; 9 PTesk6=6; 10 PTesk7=7; 11 PTesk8=8; 12 PTesk9=9; 13 } ;
我们把每一个任务分别分配唯一的一个优先级。
1 void TSys::Start() 2 { 3 debug_printf("系统准备就绪,开始循环处理%d个任务!\r\n", _TaskCount); 4 5 _Running = true; 6 while(_Running) 7 { 8 ulong now = Time.Current(); // 当前时间 9 int k = 0; 10 for(int i=0; i < ArrayLength(_Tasks) && k < _TaskCount; i++) 11 { 12 Task* task = _Tasks[i]; 13 if(task) 14 { 15 if(task->NextTime <= now) 16 { 17 // 先计算下一次时间 18 //task->NextTime += task->Period; 19 // 不能通过累加的方式计算下一次时间,因为可能系统时间被调整 20 task->NextTime = now + task->Period; 21 task->Callback(task->Param); 22 23 // 如果只是一次性任务,在这里清理 24 if(task->Period < 0) 25 { 26 _Tasks[i] = NULL; 27 delete task; 28 _TaskCount--; 29 } 30 } 31 32 k++; 33 } 34 } 35 } 36 debug_printf("系统停止调度,共有%d个任务!\r\n", _TaskCount); 37 }
这个调节机制做的比较好。非严格情况下可以说是实时系统。实时操作系统的做法都是先预测任务的延时时间。
通常在ms级别之类对任务请求作出反应。但是任务的改变会带来延时的复杂度。
我们的任务在还没有运行或者被Delete时,它的数据或者寄存器都会先保存在私有栈中。在任务切换时,都是按顺序入栈出栈。
任务切换原理:我们先把当前的任务现场状况保存在自己的私有栈中,再把将要进行的数据从内存的栈中转移到CPU上面去。
主要是改变下面的三个值来改变。
堆栈指针r13(SP) 程序把寄存器数据压入堆栈,返回时再出栈,保证了程序的完整性。
连接寄存器r14(LR) 保存子程序返回地址。当程序发生异常异常模式LR用来保存异常返回地址,处理栈中断。
程序计数器r15(PC) 计数功能
这三个参数传给CPU,CPU通过他们进行新旧任务切换。
过程如下:
旧任务挂载
①:处理器PC指针压栈到任务堆栈
②:SP指针保存到任务控制块
新任务启动:
①:任务控制块载入到SP指针。
②:新任务的私人堆栈中PC指针出栈。
新旧任务切换恰好是两个相反的过程。
我们再来写写任务调换实现函数代码:
1 void MyOSChange (void) 2 { 3 MyOS.Open(); 4 MyOS.High.Find(); // 找出就绪表中优先级最高的任务 5 if(MyOS.Tesk.High != MyOS.Ontime.High) // 如果不是当前运行的任务,进行任务调度 6 { 7 p_spAdd = &sp[MyOS.Ontime.High]; // 取得栈顶指针 8 p_spHigt = &sp[MyOS.Tesk.High]; 9 MyOS.Ontime.High= MyOS.Tesk.High; // 更新 10 MyOS.Tesk (); // 调度任务 11 } 12 MyOS.Exit(); 13 }
入栈出栈函数都在汇编里面。我们不做详细讲解
1 uint* p = (uint*)__get_MSP(); 2 3 // 直接使用RAM最后,需要减去一点,因为TSys构造函数有压栈,待会需要把压栈数据也拷贝过来 4 uint top = SRAM_BASE + (ramSize << 10); 5 __set_MSP(top - 0x40); // 左移10位,就是乘以1024 6 // 拷贝一部分栈内容到新栈 7 memcpy((void*)(top - 0x40), (void*)p, 0x40);
抢占式任务调节我们最高优先级的任务需要准备的时候。立即抢占正在运行任务的资源。
抢占优先级通俗的理解就是动态调节任务优先级别。
高优先级的任务有时候不需要CPU资源了。他就会主动请求自己挂起,然后CPu打开时钟滴答,调节任务优先级状态,执行当前状态下最高优先级的任务。
当时机滴答计数完还没有完。如果发现还有更高的优先级任务。CPU会切换更高优先级任务。这是嵌套的任务切换调节。
先写到这里。
End!
欢迎大家一起交流 ,分享程序员励志故事。 幸福的程序员 QQ群: