一、软件法
1.轮转法
p0 进程:
while(turn != 0); //进入区
critical section ; //临界区
turn = 1; //退出区
remainder section; //剩余区
p1进程:
while(turn != 1); //进入区
critical section; //临界区
turn = 0; //退出区
remainder section; //剩余区
---------------------
作者:李永贵
来源:CSDN
原文:https://blog.csdn.net/lierming__/article/details/78974244
版权声明:本文为博主原创文章,转载请附上博文链接!
2.标志法
3.Perterson算法
Pi 进程:
flag[i] = TRUE; turn = j; //进入区
while(flag[j] && turn == j); //进入区
critical section; //临界区
flag[i] = FALSE ; //退出区
remainder section; //剩余区
Pj 进程:
flag[j] = TRUE; turn = i; //进入区
while(flag[i] && turn == i); //进入区
critical section; //临界区
flag[j] = FALSE; //退出区
remainder section; //剩余区
---------------------
作者:李永贵
来源:CSDN
原文:https://blog.csdn.net/lierming__/article/details/78974244
版权声明:本文为博主原创文章,转载请附上博文链接!
软件方法看似从逻辑上实现了互斥,但隐含条件是对共享标志变量的写和读需要在一条指令内完成,否则对标志的访问本身就会造成错误。例如,在Perterson算法中,若CPU为8位,turn为16位变量,则对标志变量的存取至少需要2条指令,如果一个任务先对turn写了低8位,然后切换到另一个任务完整的写了turn,再切换回去写了高8位,那么turn的值是无法预测的。
所以所谓软件方法要完全实现保护,也是需要和硬件特性相关的,实际上用汇编实现才能绝对正确。个人觉得完全不如下面的硬件法。
二、硬件法
1.利用硬件原子指令
2.关闭中断
3.关闭任务调度
4.利用信号量
(3和4实际上是有操作系统时,操作系统用其他方法实现的封装)
三、方法总结
1.单片机裸机编程
在单片机裸机编程时,出现的情况常常是在中断中获取数据,然后在后台循环中进行数据处理,我思考得到的方法伪代码如下:
//主函数
void main()
{
while(1)
{
...
intClose(); //关中断
if(flag==true) //标志为真
{
... //处理数据(操作尽量少,一般只是把数据移到局部变量中)
flag=false;
}
intOpen(); //开中断
...
}
}
//中断处理函数
void interrupt()
{
//加上判断时若数据未处理则不再接收,可不加判断使数据未处理时接收新数据覆盖
if(flag==false)
{
... //接收数据
flag=true;
}
}
(仅为示例,实际当中可使用环形缓冲区)
2.操作系统编程
在操作系统上编程主要使用信号量,我想到的信号量PV操作可以用如下伪代码实现
void P(sem)
{
int temp;
intClose(); //关中断
sem--; //信号量减一
temp = sem; //用局部变量保存信号量
intOpen(); //开中断
if(temp < 0)
{
sleep(); //阻塞自身进程
}
}
void V(sem)
{
int temp;
intClose(); //关中断
sem++; //信号量加一
temp = sem; //用局部变量保存信号量
intOpen(); //开中断
if(temp <= 0)
{
wake(); //从阻塞序列中唤醒进程
}
}
信号量可以实现共享资源的保护(线程锁)和任务同步,使用时应该遵循一定规则避免程序长时间非正常阻塞,通常是锁操作在同步操作的内部,伪代码如下:
datatype data; //共享变量
void taskSend() //发送任务
{
datatype buff;
while(1)
{
....... //操作
pthread_mutex_lock();
data = buff;
pthread_mutex_unlock();
sem_post();
}
}
void taskReceive() //接收任务
{
datatype buff;
while(1)
{
sem_wait();
pthread_mutex_lock();
buff = data;
pthread_mutex_unlock();
....... //操作
}
}
其中,如果data缓存大小比每次需要传输的大,就可以不用加锁。
笔记:操作缓冲区是否关中断的问题:
通信速率低时:例如比特率9600时,每次关中断的时间一般不会超过1ms,因此不会漏掉数据,这时关中断最严谨
通信速率高时:例如比特率115200时,每个字节发送时间大约100us,关中断时间大于这个时间就容易丢数据,因此不关中断,但不确定会不会产生错误
rs485半双工发送数据时必须等待发送完成(无论是上位机还是下位机)
记录:自己总结基于裸机编程的串口驱动通过环形队列实现,中断和普通程序中都得共享队列,如果直接关中断因为操作队列函数不短可能造成接收数据漏掉。想法是通过关中断实现简单互斥量(关中断时间很短),普通程序里先锁互斥量,再操作队列;中断程序里先判断是否锁,如果锁了将数据接收到中断独占的缓冲区然后返回,否则先将中断独占缓冲区里留存的数据(如果有)全部移到环形队列里再加上本次接收的数据完成接收。(中断发送时同理,只是中断中改成如果锁就不取发送队列里的数据发送)
这样干可能最后收到的数据在独占缓冲里,还是只有间歇开关中断或只在关键处关
最后决定,中断接收里用的环形队列还是自己简单实现吧,可以严格控中断开关
第一种,根据读写位置求得已存数据数量,开始要取另一方位置时有隐患,但一般取位置只需一条汇编指令,所以可以认为安全,最后修改自己位置时最后放回是一般也只要一条指令,所以安全。
第二种,根据count知道已存数据数量,开始判断时其中取count一般一条指令,安全。最后放回count一般也一条指令,安全。
以上实现对关键量的操作顺序不能变
环形队列的线程安全性:写程序使用时在单消费者单生产者是可以认为安全。自己写中断接收发送驱动时最好在关键位置关中断。
在不溢出覆盖的环形队列里,为保证在一个生产者一个消费者的情况下(包括其中一个是中断),只需在开始关中断将count放到局部变量中为后面利用,最后关中断改变count的值即可(顺序不能变)。多个生产消费者情况下有多个的一方(必须是在用户程序中)要加互斥量(在抢占调度的操作系统中需要,时间触发轮转里不需要)。中断里的一方必须是唯一的
(以上内容均为自己思考所得,如有错误和疏漏,感谢大家指正)