ringbuf{
buf[m axlen];
rptr ;
wplr;
环形缓冲区该写操作的分析与实现
环 形 缓 冲区是嵌人式系统中一种重要的常用数据结构.在多任务环境下实时,
如果有多个读写任务.一般需要用信号量来保护多个任务共享的环形缓冲区。但
是如果只存在1个读任务和1个写任务,采取适当的措施可以避免使用信号量,从
而提高程序的执行效率,并且避免任务间竟争所造成的不一致性。
1 单线程下的实现
先 定 义一 个简单的环形缓冲区数据结构;基于这样
一个条件,当环形缓冲区满时,不能再往里写数据了。
struct
uint8
uint8
uint8
}
数据元素是无符号8位整数,maxlen代表环形缓冲
区的最大长度,rptr为读指针,wptr为写指针。读写指
针初始化为rptr=wptr=0·
1.1 读操作的实现
BOOL readbuf(struct ringbuf *mybuf, uint8' readdata)I
/队 列 为 空
if(mybuf一>rptr == mybuf-> wptr)
return FALSE;
else{
把队列里有数据,读出来
'readdata = rrty buf->buf[mybuf->印tr];
mybuf-> rptr + +;
//调 整 读 指 针 ,以防止读指针越界
mybuf-> rptr- mybuf->rptr/ maxlen;
return TRUE ;
}
}
该函数在缓冲区为空时返回FALSE。在缓冲区有数据时,1次读1个敌据元素,存
放在readdata所指向的变量里,并返回TRUE.
2 写操作的实现
BOOL writebuf(struct ringbuf" mybuf ,uint8 writedata)]
/队 列 为 满
if(mybuf->rptr--=((mybuf->wptr+l)%maxlen))
return FALSE;
else{
/队 列 有 空 位 置,写数据
my bu f-> bu f[m ybuf->wptr]=writedata;
my bu f-> w ptr + +;
/调 整 写 指 针 ,以防止写指针越界
my bu f-> wp tr- m ybuf->wptr%maxlen;
ret ur nT R U E;
}
}
在 判 断 缓冲区是否满的条件中.之所以要摸maxlen, 是因为假设读写指针为
图1所示,
wptr所指的位置为空。为了区分缓冲区满和空两种情况的判定条件,此种情况缓
冲区被认为是已满。如果不采取模运算,wptr+l!=rptr,将认为可以继续写数据,
从而当写操作结束时,读写指针相等,会误认为队列空。该函 数 在 缓冲区为
满时返回FALSE;在队列有空位
置时写人一个数据元素.并返回TRUE.
2 并发条件下如何控制竞争
2.1 防止竞争的必要性
假设 写 任 务进行写操作时在语句mybuf->wptr++执行完时被打断,如图2所示,
此时写指针wptr所处的位置是非法的。当系统被切换到读任务时,如果读任务有
读多个数据的企图,那么不但应该读出的数据被读出来了,而且当读指针被调整
为0时、会将以前已读出的数据重复读出来。这种出错行为是由于写指针的不正
确位置所导致的。假设 读 任 务进行读操作在语句mybuf->rptr+十执行完时被
打断,如图3所示,此时读指针rptr所处的位置是非法的。当系统被切换到写任
务时,如果写任务有写多个数据的企图那么当写指针指向缓冲区的末尾时,本来
缓冲区应处于满状态,不应再写了。但由于读指针所处的非法位置。在读任务获
得控制权之前,写任务认为缓冲区总是有空位置,将会租盖填写还没有读出去的
数据。
2.2 使用信号量
通过 上 述 分析可知,虽然读指针只由读任务来改写,写指针只由写任务来改
写;但是读任务要用到写指针,写任务要用到读指针,读写指针处于一个非法位
置时会给对方的判断造成错误。如果引人信号且机制,有效地保护临界区代码,
问题自然会得以避免。
2.3 改变读写指针的赎值方式
既 然 出 错都是由读写指针所处的非法位置引起的,那么修改读写指针时应注
意,不要赋非法位置。读写指针的值要么是还没有修改的要么是修改以后的正确
值,避免错误的值出现在读写指针中。另外还必须结合所使用的处理器,保证对
读写指针所定义的数据类型的赋值操作是原子性的。例如:如果所用处理器对16
位变量的赋值不是用一条指令实现的,需要分别对低字节和高字节进行操作那么
读写指针的数据类型就不能定义成16位的,或者说读写指针的使用范围应限制在
256以内。按照此种方法,假设处理器对8位变且的赋值是原子性的,读写操作可
以被改写为:
BOOL readbuf(structri ngbuf* mybufu int8*re addata)I
uin t8 t emp;
/队 列为空
if(m y bu f一>rptr= =m ybuf一>wprt)
ret unr F AL SE ;
els e(
/队 列 里 有 数 据,读出来
*er add at a=m y buf->buljmybuf->rprt);
/j调 整 读 指 针 ,以防止读指针越界
tem p= m y bu f->rprt;
tem p= ( tem p +l)0f omaxlen;
my bu f-> 印 tr= temp;
ret unr TR U E ;
}
}
BOOL writebuf(structri ngbuf* mybufu int8w ritedata){
uin t8 t emp;
/1队 列为满
if(m ybuf->rptr--((mybuf->wptr+l)%mmlen))
ret ur nF A LS E ;
els e谭
/队 列 有 空 位 置,写数据
my bu f-> bu fjm ybuf->wprtl=writedata;
/调 整 写 指 针 ,以防止写指针越界
tem p= m yb uf ->wprt;
tem p= (t e m p+ 1)%maxlen;
my bu f-> wp tr -temp;
ert unr T R UE ;
}
}
引入 临 时 变量temp是为了进一步防范有些编译器在编译像
temp=( temp+1)%maxlen这种语句时,在最终赋值变且前有可能修改变里的值。
这样,无论读写操作在何处被打断,都不会引起出错。班
参考 文 献
I 昊 群 .实时任务处理程序设计中“易变的’。变量.单片
机与嵌入式系统应用,2003 (4):77-78
2 La bro sseJe anJ. uc/OS-II一源码公开的实时嵌入式操
作系统.邵贝贝译.北京:中国电力出版社,2001
3 赵 克 佳,沈志宇等.UNIX程序设计教程.北京:清
华大学出版社,2001
实现代码:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
package thread;
public class CircularBuf {
int NMAX=3;
int iput = 0; /* 环形缓冲区的当前放人位置 */
int iget = 0; /* 缓冲区的当前取出位置 */
int n = 0; /* 环形缓冲区中的元素总数量 */
double buffer[]=new double[3];
/* 环形缓冲区的地址编号计算函数,,如果到达唤醒缓冲区的尾部,将绕回到头部。
环形缓冲区的有效地址编号为:0 到(NMAX-1)
*/
public int addring (int i){
return (i+1) == NMAX ? 0 : i+1;
}
/* 从环形缓冲区中取一个元素 */
public double get() {
int pos;
if (n>0){
pos = iget;
iget = addring(iget);
n--;
System.out.println("get-->"+buffer[pos]);
return buffer[pos];
}else {
System.out.println("Buffer is Empty");
return 0.0;
}
}
/* 向环形缓冲区中放人一个元素*/
public void put(double z){
if (n