目录
前言
如何写出高质量的代码,是每一个软件工程师的毕生追求。何为高质量代码?可读性好、健壮性好、可移植性好、执行效率高等等。今天就执行效率方面分享一下自己在软件编程上的一些处理。
时间片
时间片原理
时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。
为什么要使用时间片调度?
记得刚工作那会,经常会写类似下边格式的代码,然后就有老员工跟我讲,"你这个代码一看就很“学院派”,刚毕业没多久吧?",因为这件事还纳闷过一阵子,在我看来,我的代码浅显易懂,他们的代码晦涩难懂(甚至觉得这些高工有点“装”,故意把代码写的让人看不懂,嗯。。你们懂的),再后来我就开始慢慢的研究他们的写法,并尝试理解其底层的设计思想,最后发现原来自己才是小丑(我为自己的不自量力向大佬们致歉)!
/******************by 小丑****************/
void task(void)
{
while(1)
{
if(flg == 1)
{
handle1();
}
else if(flg == 2)
{
handle2();
}
...
}
}
/**********************by 大佬*******************/
void task1(void)
{
if(isovertime2(&deely1,20)) //每20ms执行一次
{
if(flg == 1)
{
handle1();
}
}
}
void task2(void)
{
if(isovertime2(&deely1,10)) //每10ms执行一次
{
if(flg == 2)
{
handle2();
}
}
}
void task(void)
{
while(1)
{
task1();
task2();
}
}
以上代码逻辑上没有太多的区别,后者只是在前者的基础上添加了"时间片"处理机制,怎么就执行效率更高了呢?想要弄清楚这两者的区别,我们还需要理解两个概念:实时和分时。
实时
实时操作系统是指具有实时性,能支持实时控制系统工作的操作系统。实时操作系统的首要任务是调度一切可利用的资源完成实时控制任务;其次才着眼于提高计算机系统的使用效率,其重要特点是通过任务调度来满足对于重要事件在规定的时间内做出正确的响应。
分时
分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即"时间片",通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做。此时CPU又分配给另一个作业去使用。由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所"停顿";但用户察觉不出来,好像整个系统全由它"独占"似的。
实时操作系统与分时操作系统有着明显的区别。具体地说,对于分时操作系统,软件的执行在时间上的要求并不严格,时间上的延误或者时序上的错误,一般不会造成灾难性的后果。而对于实时操作系统,主要任务是对事件进行实时的处理,虽然事件可能在无法预知的时刻到达,但是软件必须在事件随机发生时,在严格的时限内做出响应(系统的响应时间)。
为了提高程序执行效率,大家在很多应用中都采用了多线程模式,这样可以将原来的序列化执行变为并行执行,任务的分解以及并行执行能够极大地提高程序的运行效率。但这都是代码级别的表现,而硬件是如何支持的呢?那就要靠CPU的时间片模式来说明这一切。程序的任何指令的执行往往都会要竞争CPU这个最宝贵的资源,不论你的程序分成了多少个线程去执行不同的任务,他们都必须排队等待获取这个资源来计算和处理命令。
在了解了上述概念后,再去看上边的代码,前者虽然也能实现功能,但浪费了大量的CPU资源,因为大部分时间CPU都在空跑,程序命中率极低(事实上,判断事件对实时性要求并不算高);而下边的那种写法利用了"时间片"的处理思想,在特定的时间段处理特定的事件,实现了多任务的"并行"执行,并且这种写法还可以根据不同任务的不同实时性要求而做出相应的调整,从而实现CPU资源的充分利用,相对于前者那种"一锅炖"的写法,显然后者在程序执行效率上更胜一筹,尤其在任务越多的时候后者的优势体现的越明显(多任务需要考虑优先级和实时性的问题,关于时间片如何优化可以参考这篇文章)。
方案实现
实现"时间片"首先需要提供一个"时基",类似于"心跳",后来使用的"时间片"周期都以该"时基"为单位,"时基"可以通过系统滴答更新,也可以通过基本定时器更新(一般对时间刻度要求比较高的时候使用基本定时器提供"时基")。
//非阻塞延时函数 需设置系统定时器1ms gSysTickCount++;
/****************************************************************
*函数名:CalcOverTime
*功能: 计算超时
*输入参数:uint32_t *Param 定时器寄存器
****************************************************************/
extern __inline uint32_t CalcOverTime(uint32_t *Param)
{
return gSysTickCount - *Param;
}
/****************************************************************
*函数名:ResetTimer
*功能: 重置定时寄存器
*输入参数:uint32_t *Param 定时器寄存器
****************************************************************/
extern __inline void ResetTimer(uint32_t *Param)
{
*Param = gSysTickCount;
}
extern __inline uint32_t isOverTime(uint32_t *Param,uint32_t DelayTime)
{
uint32_t rs;
rs = gSysTickCount - *Param;
if(rs == 0xffffffff)
return 0;
return (rs >= DelayTime);
}
/****************************************************************
*函数名:isOverTime2
*功能: 定时器周期性循环,当定时器超时时,重新重置定时器
*输入参数:uint32_t *Param 定时器寄存器
uint32_t DelayTime 定时时间
*返回 1: 定时器超时
0: 定时器非超时;
****************************************************************/
extern __inline uint32_t isOverTime2(uint32_t *Param,uint32_t DelayTime)
{
if(isOverTime(Param,DelayTime))
{
*Param = gSysTickCount;
return 1;
}
else
return 0;
}
/****************************************************************
*函数名:isOverTime3
*功能: 超时计算,防溢出和防止数据穿越
*输入参数:uint32_t *Param 定时器寄存器
uint32_t DelayTime 定时时间
*返回 1: 定时器超时
0: 定时器非超时;
****************************************************************/
extern __inline uint32_t isOverTime3(uint32_t *Param,uint32_t DelayTime)
{
if(isOverTime(Param,DelayTime))
{
if(isOverTime(Param,DelayTime+1))
{
*Param = gSysTickCount - DelayTime - 1;
}
return 1;
}
else
return 0;
}
使用例程
以上接口实现了超时检测、任务轮询(时间片)、超时计算等功能。
uint32_t delay_cycle = 0,delay_once = 0,time_run = 0;
void demo_task(void)
{
sys_init();
resetimer(&delay_once); //清空定时器
resetimer(&delay_cycle); //清空定时器
while(1)
{
if(isovertime2(&delay_cycle,1000)) //1s 执行一次
{
led_toggle();
}
if(isovertime3(&delay_once,5000)) //5s后执行一次
{
time_run = CalcOverTime(&delay_once);
printf("delay_once run time:%d\r\n",time_run);
open_uart();
resetimer(&delay_once);
}
}
}
软件定时器
软件定时器也利用了时间片管理的思想,在mcu资源有限的情况下,软件定时器的使用是非常有必要的,关于概念这里不再赘述,直接上代码。
方案实现
#include "my_swtime.h"
static uint32_t systick = 0;
static swtime_t swtime_instance[MAX_TIME_NUM] = {0}; //实例化软件定时器
softime_t softime_create(TIMEOUT_CB fun,uint32_t time)
{
uint8_t i = 0;
for(i=0;i<MAX_TIME_NUM;i++)
{
if(swtime_instance[i].reload == 0)
{
swtime_instance[i].cb = fun;
swtime_instance[i].reload = time;
return i;
}
}
return -1;
}
int8_t softime_delete(softime_t time_id)
{
if(time_id < MAX_TIME_NUM)
{
swtime_instance[time_id].reload = 0;
swtime_instance[time_id].startflg = 0;
return 0;
}
return -1;
}
int8_t softime_start(softime_t time_id,uint8_t type)
{
if(time_id < MAX_TIME_NUM)
{
swtime_instance[time_id].tick = systick;
swtime_instance[time_id].startflg = 1 + type;
return 0;
}
return -1;
}
int8_t softime_stop(softime_t time_id)
{
if(time_id < MAX_TIME_NUM)
{
swtime_instance[time_id].startflg = 0;
return 0;
}
return -1;
}
void softime_task(void)
{
uint8_t i = 0;
uint32_t tmp = 0;
for(i=0;i<MAX_TIME_NUM;i++)
{
switch(swtime_instance[i].startflg)
{
case 0:
{
//code
}break;
case 1: //循环调用
{
if(swtime_instance[i].reload)
{
tmp = systick - swtime_instance[i].tick;
if(tmp >= swtime_instance[i].reload)
{
swtime_instance[i].tick = systick;
swtime_instance[i].cb();
}
}
}break;
case 2: //只调用一次
{
if(swtime_instance[i].reload)
{
tmp = systick - swtime_instance[i].tick;
if(tmp >= swtime_instance[i].reload)
{
softime_stop(i);
swtime_instance[i].cb();
}
}
}break;
default:
{
//code
}break;
}
}
}
//1ms中断中调用
void systick_update(void)
{
systick ++;
}
#ifndef _MY_SWTIME_H_
#define _MY_SWTIME_H_
#include "project.h"
typedef int8_t softime_t;
#define MAX_TIME_NUM 10 //实例化最大数量
typedef void (*TIMEOUT_CB)(void);
//超时类型0:循环监测,1:只执行一次
typedef enum{
CIRCLE = 0,ONCE = 1
}time_type_e;
#define test(u16)
typedef struct{
uint8_t startflg;
uint32_t reload;
uint32_t tick;
TIMEOUT_CB cb;
}swtime_t;
#endif
使用例程
void led_toggle(void)
{
led0 ^= 1;
}
void demo_task(void)
{
softime_t timer_id = softime_create(led_toggle,1000);
if(timer_id != null)
softime_start(timer_id ,0);
while(1)
{
softime_task();
}
}