单片机学习小组
直播中

张玉珍

7年用户 1041经验值
私信 关注

怎么在atmega128中实现自旋锁?

什么是自旋锁?有哪些缺陷?
怎么在atmega128中实现自旋锁?

回帖(1)

王波

2022-1-24 10:39:47
1.自旋锁

        自旋锁可以看做一个资源二值标识量,当在使用资源前,需要检测锁是否锁上,如果锁已经被锁上,那么就在当前位置不停地检测锁的状态,直到锁被解锁为止。如果锁没有锁没有锁上,那么就将锁锁上,然后再进入临界资源,当临界资源访问结束后,再将其解锁。其示意代码如下:

/* 初始化*Spin为真 */
SPIN_TYPE  MySpin = true;

void SpinLock(char *Spin)
{
   while(*Spin==false)    /* 检测操作为原子操作,且跳转操作不受其他线程影响 */
       ;
   *Spin = 0;             /* 上锁操作为原子操作 */
}

void SpinUnLock(char *Spin)
{
   *Spin = 1;             /* 解锁操作为原子操作 */
}

      考虑到atmega128中没有对操作系统支持的指令,为了在atmega128中实现以上的操作条件,这里只能使用开关中断来实现原子操作,但是开关中断的设置需要一定的技巧,尽量减少关中断的时间。参考atmega128指令集,这里给出WinAVR下自旋锁的实现代码:

char MySpin = 1;                 /* 初始化自旋锁     */

void SpinLock(char *Spin)
{
    asm volatile("cli ");      /* 关中断          */  
    loop:
    if((*Spin)==0)
    {
       asm volatile("sei");    /* 开中断          */
       goto loop;              /* 跳转达到循环检测 */
    }
    (*Spin) = 0;               /* 上锁操作        */
    asm volatile("sei");
}

void SpinUnLock(char *Spin)
{
    asm volatile("cli ");      /* 关中断   */
    (*Spin) = 1;               /* 解锁操作 */
    asm volatile("sei");       /* 开中断   */
}
  上面的上锁操作为了达到原子性,使用了goto语句,注意,在循环检测前必须打开中断,否则调度器会被锁住,导致无法进行任务调度。这里可能会有读者问,为什么在goto loop前可以开中断,那样不就破坏了原子性吗?其实不然,我们可以假设当在goto loop前调度器恰好切换到了其他线程,那么此时由于下一条指令是jump 指令,由于在atmega128中,跳转指令只与状态寄存器中的标志位和跳转地址有关,所以在线程切换回来时,由于线程的现场环境得到恢复,其他线程不会对跳转产生影响,所以该方法可以实现上锁操作。
      以上实现较为麻烦的原因是atmega128没有提供对操作系统支持的指令,只有LOAD和STR指令,从RAM中读取检测并写回的操作实际上会有4条左右的指令,显然在访问公共变量Spin时,会出现错乱。如果atmega128有像TAS,SWAP等这类指令,那么就可以很容易的实现自旋锁、信号量等线程同步组件。其实相对atmega128而言,51单片机更容易实现自旋锁和信号量,这是因为在51单片机中存在RAM与RAM之间直接操作的指令,一条指令即可实现RAM与RAM之间的数据交换,而且跳转指令中提供了直接根据RAM中数据条件来进行跳转的指令,这是非常有利于实现自旋锁和信号量。

2.自旋锁的缺陷

      自旋锁虽然简单,但是自旋锁有一个很致命的缺陷,就是当一个线程访问临界资源太久时,另一个线程只能干等,无限的忙等待,这显然会占用CPU的资源,而且做了很长时间的“无用功”。为了解决这个问题,最简单的方法是在发现锁已经被锁住时,直接将自己挂起,然后当另一个线程使用完临界资源后,再将挂起的线程唤醒。这样就节省了等待时间。同时极大减小了CPU的线程切换开销。
     自旋锁一般用在临界资源访问时间很短的情况下,在这种情况下,自旋锁比二值信号量的响应比更小。所以在具体的实际应用中,应该根据具体要求合理选择,没有哪个方法是通用的。
3.atmega128自旋锁实验

    以下是关键代码:

void task1(void)
{
  while(1)
  {
     SpinLock(&MySpin);
     USART_SendString("Task1 is running!");
     SpinUnLock(&MySpin);
     delay(3);
  }
}


void task2(void) //任务函数2
{

  while(1)
  {
     SpinLock(&MySpin);
     USART_SendString("Task2 is running!");
     SpinUnLock(&MySpin);
     delay(3);
  }

}


void task3(void) //任务函数2
{

  while(1)
  {
     SpinLock(&MySpin);
     USART_SendString("Task3 is running!");
     SpinUnLock(&MySpin);
     delay(3);
  }

}
以下是运行效果:
当我们不使用自旋锁时,结果如下

从控制台上可以看出,串口打印出了太多的错误,这显然是临界资源访问冲突导致的,即以串口为临界资源的访问冲突。
使用自旋锁的结果:

从上面可以看出,自旋锁有效地解决了临界资源的访问冲突问题。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分