Chinaunix首页 | 论坛 | 博客
  • 博客访问: 407768
  • 博文数量: 62
  • 博客积分: 1483
  • 博客等级: 上尉
  • 技术积分: 779
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-24 12:25
文章分类

全部博文(62)

文章存档

2012年(2)

2011年(6)

2010年(6)

2009年(48)

我的朋友

分类: LINUX

2009-09-12 16:54:07

浅析arm(kernel-2.6.13)自旋锁与信号量


在单处理器上,自旋锁仅仅当作一个设置内核抢占的开关。如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核
我的是s3c2440 armv4的单核。

这个内核被配置为可抢占的。
# Kernel Features
#
CONFIG_PREEMPT=y


自旋锁的实现:
spin_lock的定义如下:
#define spin_lock(lock)  _spin_lock(lock)

而_spin_lock():

#define _spin_lock(lock) \
do { \
 preempt_disable(); \
 _raw_spin_lock(lock); \
 __acquire(lock); \
} while(0)

#define _raw_spin_lock(lock) do { (void)(lock); } while(0)

可见他只是个抢占的开关
而在armv6或更高的版本多smp中

spinlock_t结构如下:
typedef struct {
 volatile unsigned int lock;
#ifdef CONFIG_PREEMPT
 unsigned int break_lock;
#endif
} spinlock_t;

下面分析原始加锁函数_raw_spin_lock():

static inline void _raw_spin_lock(spinlock_t *lock)
{
 unsigned long tmp;
 __asm__ __volatile__(
num1:"1: ldrex %0, [%1]\n"  //取lock->lock放在 tmp里,并且设置&lock->lock这个内存地址为独占访问。
num2:" teq %0, #0\n"  //测试lock_lock是0吗?影响标志位z
num3:" strexeq %0, %2, [%1]\n"  //如果lock_lock是0,并且是独占访问这个内存,就向lock->lock里写入1,并向tmp返回0,同时清除独占标记
num4:" teqeq %0, #0\n"  //如果lock_lock是0,并且strexeq返回了0,表示加锁成功,返回。
num5:" bne 1b"  //如果上面的条件(1:lock->lock里不为0,2:)有一个不符合,就在原地打转
num6: : "=&r" (tmp) //%0:输出放在tmp里,可以是任意寄存器
 : "r" (&lock->lock), "r" (1)   //%1:取&lock->lock放在任意寄存器,%2:任意寄存器放入1
 : "cc");
 smp_mb();
}


可以发现,第一个完成ldrex-strexeq指令对的进程(就是第一个完成strexeq指令的进程)可以得到自旋锁,其他cpu核将原地转。


信号量的实现:

struct semaphore {
 atomic_t count;
 int sleepers;
 wait_queue_head_t wait;
};
typedef struct { volatile int counter; } atomic_t;


/*
 * This is ugly, but we want the default case to fall through.
 * "__down" is the actual routine that waits...
 */
static inline void down(struct semaphore * sem)
{
 might_sleep();
 __down_op(sem, __down_failed);
}

#define __down_op(ptr,fail)  
 ({    
 __asm__ __volatile__(  
 "@ down_op\n"   
" mrs ip, cpsr\n" //首先禁止中断 
" orr lr, ip, #128\n"  
" msr cpsr_c, lr\n"  
" ldr lr, [%0]\n" //获取信号量 
" subs lr, lr, %1\n" //自减
" str lr, [%0]\n" //存起来
" msr cpsr_c, ip\n" //开中断
" movmi ip, %0\n" //看看是变成负数了没?是负数表示现在没有信号量。
" blmi " #fail  
 :   //无输出 
 : "r" (ptr), "I" (1) //ptr放在任意REG,%1:是个立即数1 
 : "ip", "lr", "cc"); //这些REG可能遭破坏
 smp_mb();   
 })


下面是v6或以上的版本
#if __LINUX_ARM_ARCH__ >= 6
#define __down_op(ptr,fail) \
 ({   \
 __asm__ __volatile__( \
 "@ down_op\n"  \
"1: ldrex lr, [%0]\n" \ //获取sem的第一个成员count,放在lr
" sub lr, lr, %1\n" \ //自减1
" strex ip, lr, [%0]\n" \ //放回去,ip存放操作结果,是成功还是失败
" teq ip, #0\n" \ //如果成功
" bne 1b\n"  \ //成功就继续,不成功就是说ldrex-strex没有组成原子操作,count可能被其他进程篡改(tampered)了。所以在来一边。
" teq lr, #0\n" \ //为下面的测试做准备
" movmi ip, %0\n" \ //内部过程调用暂时寄存器,取得count的地址,如果count是负数的话。
" blmi " #fail  \ //跳过去?(我不清楚他是怎么跳过去的,希望得到指点:-),如果不小于0,就是说还可以用,就不用sleep了。
 :   \ //无输出
 : "r" (ptr), "I" (1) \ //%0:sem指针ptr,放在任意寄存器里,大概是r0里吧?,%1:是个立即数
 : "ip", "lr", "cc"); \ //可能会改变的寄存器
 smp_mb();  \
 })
不管怎么样,他应该跳到了下面的那个标号处。
asm(" .section .sched.text,\"ax\"  \n\
 .align 5    \n\
 .globl __down_failed   \n\
__down_failed:     \n\
 stmfd sp!, {r0 - r3, lr}  \n\
 mov r0, ip    \n\
 bl __down    \n\ //这儿之后,当前进程就睡下了。
 ldmfd sp!, {r0 - r3, pc}  \n\
      \n\
 .align 5    \n\
 .globl __down_interruptible_failed \n\
__down_interruptible_failed:   \n\
 stmfd sp!, {r0 - r3, lr}  \n\
 mov r0, ip    \n\
 bl __down_interruptible  \n\
 mov ip, r0    \n\
 ldmfd sp!, {r0 - r3, pc}  \n\
      \n\
 .align 5    \n\
 .globl __down_trylock_failed  \n\
__down_trylock_failed:    \n\
 stmfd sp!, {r0 - r3, lr}  \n\
 mov r0, ip  
 ...
 ...
void __sched __down(struct semaphore * sem)
{
 struct task_struct *tsk = current;
 DECLARE_WAITQUEUE(wait, tsk);
 tsk->state = TASK_UNINTERRUPTIBLE;
 add_wait_queue_exclusive(&sem->wait, &wait);
 spin_lock_irq(&semaphore_lock);
 sem->sleepers++;
 for (;;) {
  int sleepers = sem->sleepers;
  /*
   * Add "everybody else" into it. They aren't
   * playing, because we own the spinlock.
   */
  if (!atomic_add_negative(sleepers - 1, &sem->count)) { //看看可以有信号量可用了吗?唯一的出口。
   sem->sleepers = 0;
   break;
  }
  sem->sleepers = 1; /* us - see -1 above */
  spin_unlock_irq(&semaphore_lock);
  schedule();
  tsk->state = TASK_UNINTERRUPTIBLE;
  spin_lock_irq(&semaphore_lock);
 }
 spin_unlock_irq(&semaphore_lock);
 remove_wait_queue(&sem->wait, &wait);
 tsk->state = TASK_RUNNING;
 wake_up(&sem->wait);
}

小结:自旋锁会导致死循环,锁定期间不允许阻塞(即临界区里没有导致阻塞的函数),要求锁定的临界区要小,信号量允许临界区阻塞,可以适用于临界区大的情况。

- 寄存器 r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。

 

from:http://blog.mcuol.com/User/emblinux/Article/13762_1.htm
3.3.8  LDREX和STREX指令
n 独占加载和存储寄存器
指令语法格式如下所示:
LDREX{cond} Rt, [Rn {, #offset}]
STREX{cond} Rd, Rt, [Rn {, #offset}]
LDREXB{cond} Rt, [Rn]
STREXB{cond} Rd, Rt, [Rn]
LDREXH{cond} Rt, [Rn]
STREXH{cond} Rd, Rt, [Rn]
LDREXD{cond} Rt, Rt2, [Rn]
STREXD{cond} Rd, Rt, Rt2, [Rn]
说明:
con            可选的条件代码。
Rd              存放返回状态的目标寄存器。
Rt               要加载或存储的寄存器。
Rt2             进行双字加载或存储时要用到的第二个寄存器。
Rn              内存地址所基于的寄存器。
offset        要应用到Rn中的值的可选偏移量。offset只可用于Thumb?2指令中。如果省略offset,则认为偏移量为0。
n  LDREX(独占装载指令)
LDREX可从内存中加载数据。
如果物理地址有共享TLB属性,那么LDREX会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的独占访问标签。否则,会标记为“执行处理器已经标记了一个物理地址,但访问尚未完毕”。
n  STREX(独占存储指令)
STREX可在一定条件下向内存中存储数据。条件具体如下:
·      如果物理地址没有共享TLB属性,执行处理器有一个已标记物理地址,但尚未访问完毕,那么将会进行存储,清除该标记,并向Rd中返回值0;
·      如果物理地址没有共享TLB属性,且执行处理器也没有已标记但尚未访问完毕的物理地址,那么将不会进行存储,而会向Rd返回值1;
·      如果物理地址有共享TLB属性,且已被标记为由执行处理器独占访问,则将进行存储,清除标签,并向Rd返回值0;
·      如果物理地址有共享TLB属性,但却没有标记为由执行处理器独占访问,则不会进行存储,而会向Rd中返回值1。
n 限制
offset不可用于ARM指令中;offset的值可为0~1020范围内的任意一个4的倍数。
r15不可用于Rd、Rt、Rt2或Rn。
对于STREX,Rd一定不能与Rt、Rt2或Rn为同一寄存器。
对于LDREX,Rt和Rt2不可为同一寄存器。
在ARM指令中,Rt必须是一个编号为偶数的寄存器,且不能为r14;同时Rt2必须为R(d+1)。
n 用法
利用LDREX和STREX可在多处理器和共享内存系统间实现进程间通信。
出于性能方面的考虑,请将相应LDREX指令和STREX指令间的指令数控制到最少。
n 注意
STREX指令中所用的地址必须要与近期执行次数最多的LDREX指令所用的地址相同。如果使用不同的地址,则STREX指令的执行结果将不可预知。
n 适用体系结构
ARM LDREX和STREX可用于ARMv6及更高版本中。
ARM LDREXB、LDREXH、LDREXD、STREXB、STREXD和STREXH可用于ARMv6K及更高版本中。
所有这些32位Thumb指令均可用于ARMv6T2和ARMv7,但LDREXD和STREXD不可用于ARMv7-M架构。
这些指令均无16位版本。
n 示例
MOV r1, #0x1
try
LDREX r0, [LockAddr]
CMP r0, #0
STREXEQ r0, r1, [LockAddr]
CMPEQ r0, #0
BNE try
...

阅读(3530) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

chinaunix网友2009-11-06 10:49:13

首先感谢作者 然后问个问题 如果一个cpu0 LDREX一个地址后,另一个cpu1也LDREX同一个地址,这个时候会发生什么事情?