您的位置:首页 > 其它

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

2009-10-23 11:11 423 查看
浅析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

...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: