动手写简单的嵌入式操作系统二
接下来需要完成任务间的同步和通信。 任务间同步,为什么需要任务间同步,比如对公共资源的访问,如果不同步,一个任务正在访问资源,另一个任务不知道这个资源正在被访问,也去访问了,这就出现问题了。还有就是任务再等待某一事件的触发,触发后才能运行。实现的一种同步方法就是信号量。何为信号量?举个简单的例子来说,就像是资源的标识,如停车位,当还有停车位时,车才可以停进来,但没有停车位时,外面的车就必须等待,等到有停车位时再进来。下面是一个信号量的简单实现,原理就是用一个全局变量代表可用的资源。当有资源时,这个变量加一,当这个变量为0时代表没有资源了,任务开始挂起,同时开始切换到其它任务。
/*当前信号量列表*/
OS_SEM Sem[MAX_SEM_NUM];
/*
* 创建信号量
*/
OS_SEM* OSSemCreate(int32 conuter)
{
OS_CPU_SR cpu_sr = 0;
uint32 index;
if (conuter < 0)
{
return (OS_SEM*)NULL;
}
OS_ENTER_CRITICAL();
for(index=0;index<MAX_SEM_NUM;index++)
{
if(Sem[index]==-1)
{
Sem[index]=conuter;
OS_EXIT_CRITICAL();
return(Sem[index]);
}
}
OS_EXIT_CRITICAL();
return (OS_SEM*)NULL;
}
int8 OSSemDelete(OS_SEM* pSem)
{
OS_CPU_SR cpu_sr = 0;
OS_ENTER_CRITICAL();
/*当且仅当信号量计数为0的时候,才能释放该信号量*/
if ((*pSem) != 0)
{
OS_EXIT_CRITICAL();
return OS_Err;
}
else
{
(*pSem) = (OS_SEM)-1;
OS_EXIT_CRITICAL();
return OS_OK;
}
}
/*这个是一个不完全精确的实现*/
/*申请信号量*/
/*其超时时间不会非常精确*/
int8 OSSemPend(OS_SEM* pSem,uint32 timeout)
{
uint32 index;
OS_CPU_SR cpu_sr = 0;
for (index = 0;index < timeout;index++)
{
OS_ENTER_CRITICAL();
if ((*pSem) > 0)
{
(*pSem)--;
OS_EXIT_CRITICAL();
return OS_OK;/*获取到了信号量*/
}
else
{
/*等待一个时间片*/
OS_EXIT_CRITICAL();
OSTimeDly(1);
}
}
return OS_Err;
}
/*不等待,立即返回是否信号量能否获取*/
int8 OSSemGet(OS_SEM* pSem)
{
OS_CPU_SR cpu_sr = 0;
OS_ENTER_CRITICAL();
if ((*pSem) > 0)
{
(*pSem)--;
OS_EXIT_CRITICAL();
return OS_OK;/*获取到了信号量*/
}
OS_EXIT_CRITICAL();
return OS_Err;
}
/*释放(发送)一个信号量*/
int8 OSSemPost(OS_SEM* pSem)
{
OS_CPU_SR cpu_sr = 0;
OS_ENTER_CRITICAL();
(*pSem)++;
OS_EXIT_CRITICAL();
return OS_OK;
}
信号量如何使用?如何使用信号量来进行同步?下面是一个简单的应用例子。 我们知道printf函数不可重入,在调用这个函数时,必须保证不能被其他任务占用。所以不同任务需要保持同步,当一个任务释放了信号量后另一个任务方可使用。
OS_SEM* testSem;
void task6(void * arg)
{
testSem=OSSemCreate(1); //创建一个信号量
while(1 )
{
OSSemPend(testSem, 0);
printf("task 6 Running! 27rn");
OSSemPost(testSem);
OSTimeDly(100);/*100毫秒10个*/
}
}
任务间如何通讯呢?可以用消息队列来实现。为什么要用消息队列?
消息被发送到队列中。“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传它。 下面是一个简单的实现,很容易看懂
/*用于对于的标记消息队列是否使用*/
uint8 MsgQueueFlag[MAX_QUEUE_NUMBER];
/*实际的所有消息队列*/
OS_Q MsgQueue[MAX_QUEUE_NUMBER];
/*
* 创建消息队列
*/
OS_Q* OSQCreate()
{
OS_CPU_SR cpu_sr = 0;
uint32 index;
OS_ENTER_CRITICAL();
for(index=0;index<MAX_QUEUE_NUMBER;index++)
{
/*该消息队列未被使用*/
if (MsgQueueFlag[index]==0)
{
MsgQueueFlag[index]=1;
/*该队列首尾初始化*/
MsgQueue[index].front=NULL;
MsgQueue[index].rear=NULL;
OS_EXIT_CRITICAL();
return &(MsgQueue[index]);
}
}
OS_EXIT_CRITICAL();
return (OS_Q*)NULL;
}
/*
*删除消息队列
*/
int8 OSQDelate(OS_Q* q)
{
OS_CPU_SR cpu_sr = 0;
OS_ENTER_CRITICAL();
/*信号量不存在*/
if (q == NULL)
{
OS_EXIT_CRITICAL();
return OS_Err;
}
/*队列指针越界*/
if ((( q-MsgQueue ) < 0)||(( q-MsgQueue ) > (MAX_QUEUE_NUMBER-1)))
{
OS_EXIT_CRITICAL();
return OS_Err;
}
/*将标记位置0*/
MsgQueueFlag[q-MsgQueue] = (uint8)0;
OS_EXIT_CRITICAL();
return OS_OK;
}
/*
*发送一个消息
*该函数可用在中断函数中
*/
int8 OSQPost(OS_Q* q,OS_MSG msg)
{
OS_CPU_SR cpu_sr = 0;
OS_ENTER_CRITICAL();
if (q == NULL)
{
OS_EXIT_CRITICAL();
return OS_Err;
}
if ((( q-MsgQueue ) < 0)||(( q-MsgQueue ) > (MAX_QUEUE_NUMBER-1)))
{
OS_EXIT_CRITICAL();
return OS_Err;
}
if((q->rear+1)%MAX_MSG_NUMBER==q->front)
{
OS_EXIT_CRITICAL();
return OS_Err;
}
else
{
q->msgQueue[q->rear]=msg;
q->rear=(q->rear+1)%MAX_MSG_NUMBER;
OS_EXIT_CRITICAL();
return OS_OK;
}
}
/*
*在有限时间片内等待一个消息
*该函数不能用在中断函数中,也不能在关中断的地方运行
*/
OS_MSG OSQPend(OS_Q *q, uint32 timeout)
{
uint32 index;
uint32 cpu_sr = 0;
OS_MSG msg;
for (index = 0;index < timeout+1;index++)
{
OS_ENTER_CRITICAL();
if (q->front==q->rear)
{
OS_EXIT_CRITICAL();
OSTimeDly(1);
}
else
{
msg=q->msgQueue[q->front];
/*消息个数满时自动从0开始重新计数*/
q->front=(q->front+1)%MAX_MSG_NUMBER;
OS_EXIT_CRITICAL();
return msg;
}
}
OS_EXIT_CRITICAL();
return NULL ;
}
/*
*直接获取一个消息,可用在中断函数中
*/
OS_MSG OSQGet(OS_Q *q)
{
OS_MSG msg;
uint32 cpu_sr = 0;
OS_ENTER_CRITICAL();
if (q->front==q->rear)
{
OS_EXIT_CRITICAL();
return NULL;
}
else
{
msg=q->msgQueue[q->front];
q->front=(q->front+1)%MAX_MSG_NUMBER;
OS_EXIT_CRITICAL();
return msg;
}
}
消息队列使用的一个例子:
void task6(void * arg)
{
testQ=OSQCreate();//创建一个消息队列
testSem=OSSemCreate(1); //创建一个信号量
while(1 )
{
OSSemPend(testSem, 0);
printf("task 6 Running! 27rn");
OSSemPost(testSem);
OSTimeDly(AppTaskDelay);/*100毫秒10个*/
}
}
void task3(void * arg)
{
int i=0;
char buf[]={1,2,3,4,5};
while(1 )
{
OSSemPend(testSem, 0);
printf("task 3 Running! 24rn");
for(i=0;i<5;i++)
{
OSQPost(testQ,&buf[i]);//发送五个消息
printf("send MSG %drn",buf[i]);
}
OSSemPost(testSem);
OSTaskSuspend(OSCurTCB);//挂起任务
OSTimeDly(AppTaskDelay);/*100毫秒10个*/
}
}
void task4(void * arg)
{
char* s;
while(1 )
{
OSSemPend(testSem, 0);
printf("task 4 Running! 25rn");
s=(char*)OSQPend(testQ,0); //接收消息
printf("recv MSG is %drn",*s);
OSSemPost(testSem);
OSTimeDly(AppTaskDelay);/*100毫秒10个*/
}
}
实时性和相关的优先级反转问题, 在实时领域,是个很关键的问题
首先说多任务, 任务就是让一段“流程”,一般都是一遍又一遍的循环运行(死循环)。 一次“流程”运行一遍之后,常常会等待一段时间, 自己休息休息,也让其他任务也运行一下, 这就是多任务并行。
在多任务的系统之中,实时性,就是让当前最高优先级的任务优先运行; 若当前最高优先级的任务不是当前正在运行的任务,那么就要给一个时机(时钟中断), 让高优先级的任务运行,正在运行的(低优先级)任务等下再运行。 这就是实时系统中的抢占调度。
实时操作系统的本质就是, 让当前最高优先级的任务以最快的速度运行! (如果有同优先级的任务,则大家轮流运行)
由此看来,实时的多任务设计,难度在于: 要保证系统性能满足的需求, 在硬性保证高优先级任务在deadline之前运行完的同时 也要保证低优先级的任务顺利的完成自己的工作。
当然,这里就提出了优先级反转的问题了 典型情况如下: 高优先级的任务A要请求的资源被低优先级任务C所占用, 但是任务C的优先级比任务B的优先级低 于是任务B一直运行,比A低优先级的其他任务也一直能运行, 反而高优先级的任务A不能被运行了。
从实时性上讲,若高优先级在等待一个某个资源, 那么为了保证高优先级任务能顺利运行, 则必须要让当前占用该资源的任务赶紧运行下去,直到把资源释放。 再让高优先级的任务来占用这个资源。
优先级反转在RTOS中是一个很深刻的课题, 目前还没有非常好的解决方案。 在这个问题上,目前业界比较典型的做法是VxWorks的做法 原理如下: 当任务A请求的资源被任务C所占用的时候 则将C的优先级提升到任务A的级别,让占有资源的任务先运行, 这样能在一定程度上解决优先级反转的问题。 但是这样做,事实上破坏了实时系统里面运行优先级的意义...
其他,有些商业RTOS也提出了一些解决方案 比如常见的极限优先级方案: 将使用资源的任务优先级提升到系统最高级别 使得任何使用该资源的任务都能快速通过 但是,对优先级意义的破坏性,比优先级继承方案更大!
接下来又有好多事情可以做了。比如可以细读一些其他的开源系统如ucos,freeRTOS,smallRTOS,RAW OS,keil RTX,RTTherad,uclinux,minix,linux以及一些比较著名的开源代码,虽然代码量很大,但是可以慢慢来,先看比较关注的某个模块是如何实现的。 一次看懂少部分,慢慢的就很有提高了。兴趣是最好的老师,多实践,看的再多也不如经手一遍。
- 通过shell脚本监控sql执行频率(r3笔记第50天)
- 和Null有关的函数(r3笔记第48天)
- 关于查询转换的一些简单分析(二) (r3笔记第68天)
- 跨网络拷贝文件的简单实践(r3笔记第67天)
- 关于enq: TX - allocate ITL entry的问题分析(r3笔记第66天)
- Tensorflow学习:使用Tensorflow搭建深层网络分类器
- 关于interval partitioning(r3笔记65天)
- Spark Tips4: Kafka的Consumer Group及其在Spark Streaming中的“异动”(更新)
- 关于数据库中的一些name(r3笔记第64天)
- 码农的瑞士军刀-脚本语言
- shell基础学习总结(一) (r3笔记第63天)
- 关于sysdba,sysoper,dba的区别(r3笔记第62天)
- 使用句柄实现特定场景的无备份恢复 (r3笔记第61天)
- 关于dual表的破坏性测试(r3笔记第60天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一个超酷的开源uHand2.0机械手掌项目
- 神经网络低比特量化——TQT
- web前端面试题:您能读懂的Promise源码实现(手写代码)
- web前端面试题对答篇:HTTP fetch发送2次请求的原因?
- MySQL 8.0之hash join
- MySQL 8.0 之原子DDL
- 翻译|MySQL 基于ScaleFlux SSD性能测试
- 使用srsLTE搭建4G基站
- 构建高性能队列,你不得不知道的底层知识!
- 案例| +1s导致的故障
- 前端|如何制作音乐播放器
- 密码破解神器Hydra初识
- 基于R语言的lmer混合线性回归模型
- Open3d 学习计划—10(KDTree)
- Powershell语法入门