一、内联汇编
1.1、语法
内联汇编实现加法
1.2、同步互斥失败的例子
进程A在读出valid时发现它是1,减1后为0,这时if不成立;但是修改后的值尚未写回内存;假设这时被程序B抢占,程序B读出valid仍为1,减1后为0,这时if不成立,最后成功返回;轮到A继续执行,它把0值写到valid变量,最后也成功返回。这样程序A、B都成功打开了驱动程序。
1.3、原子操作的原理与使用
所谓"原子操作"就是1.2的操作不会被打断。
原子变量类型如下,实际上就是一个结构体(内核文件include/linux/types.h):
c
typedef struct{
int counter;
}atomic_t;
操作函数如下(下表中v都是atomic_t指针):
1.4、原子变量的内核实现
atomic_read,atomic_set这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。问题在于atomic_inc这类操作,要读出、修改、写回。
以atomic_inc为例,在atomic.h文件中,如下定义:
c
#define atomic_inc(v) atomic_add(1, v)
atomic_add又是怎样实现的呢?用下面这个宏:
c
ATOMIC_OPS(add, +=, add)
把这个宏展开:
c
#define ATOMIC_OPS(op, c_op, asm_op) \
ATOMIC_OP(op, c_op, asm_op) \
ATOMIC_OP_RETURN(op, c_op, asm_op) \
ATOMIC_FETCH_OP(op, c_op, asm_op)
以ATOMIC_OP为例在UP系统中的实现
对于ARMv6以下的CPU系统,不支持SMP(单核CPU) 。原子变量的操作简单粗暴:关中断。代码如下(arch\arm\include\asm\atomic.h):
对于ARMv6及以上的CPU(多核) ,有一些特殊的汇编指令来实现原子操作,不再需要关中断,代码如下(arch\arm\include\asm\atomic.h):
在ARMv6及以上的架构中,有ldrex、strex指令,ex表示exclude,意为独占地。这2条指令要配合使用,举例如下:
① 读出:ldrex r0, [r1]
读取r1所指内存的数据,存入r0;并且标记r1所指内存为"独占访问"。
如果有其他程序再次执行"ldrex r0, [r1]",一样会成功,一样会标记r1所指内存为"独占访问"。
② 修改r0的值
③ 写入:strex r2, r0, [r1]:
如果r1的"独占访问"标记还存在,则把r0的新值写入r1所指内存,并且清除"独占访问"的标记,把r2设为0表示成功。
如果r1的"独占访问"标记不存在了,就不会更新内存,并且把r2设为1表示失败。
1.4.1、原子变量使用案例
c
static atomic_t valid = ATOMIC_INIT(1);
static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)
{
if (atomic_dec_and_test(&valid))
{
return 0;
}
atomic_inc(&valid);
return -EBUSY;
}
static int gpio_key_drv_close (struct inode *node, struct file *file)
{
atomic_inc(&valid);
return 0;
}
1.5、原子位介绍
1.5.1、原子位的内核实现
在ARMv6以下的架构里,不支持SMP系统,关中断。
在ARMv6及以上的架构中,不需要关中断,有ldrex、strex等指令。
二、Linux锁的介绍与使用
Linux内核提供了很多类型的锁,它们可以分为两类:
① 自旋锁(spinning lock);
② 睡眠锁(sleeping lock)。
2.1、自旋锁
2.2、睡眠锁
2.3、锁的内核函数
2.3.1、自旋锁
spinlock函数在内核文件include\linux\spinlock.h中声明,如下表:
自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:
2.3.2、信号量semaphore
semaphore函数在内核文件include\linux\semaphore.h中声明,如下表:
2.3.3、互斥量mutex
mutex函数在内核文件include\linux\mutex.h中声明,如下表:
2.3.4、自旋锁使用条件(自旋锁可以用在中断上下文,但是睡眠锁不可以用在中断上下文)
举例简单介绍一下,上表中第一行"IRQ Handler A"和第一列"Softirq A"的交叉点是"spin_lock_irq()",意思就是说如果"IRQ Handler A"和"Softirq A"要竞争临界资源,那么需要使用"spin_lock_irq()"函数。为什么不能用spin_lock而要用spin_lock_irq?也就是为什么要把中断给关掉?假设在Softirq A中获得了临界资源,这时发生了IRQ A中断,IRQ Handler A去尝试获得自旋锁,这就会导致死锁:所以需要关中断。
2.3.4.1、只在用户上下文加锁
假设只有程序A、程序B会抢占资源,这2个程序都是可以休眠的,所以可以使用信号量,代码如下:
c
static DEFINE_SPINLOCK(clock_lock); // 或 struct semaphore sem; sema_init(&sem, 1);
if (down_interruptible(&sem)) // if (down_trylock(&sem))
{
/* 获得了信号量 */
}
/* 释放信号量 */
up(&sem);
对于down_interruptible函数,如果信号量暂时无法获得,此函数会令程序进入休眠;别的程序调用up()函数释放信号量时会唤醒它。
在down_interruptible函数休眠过程中,如果进程收到了信号,则会从down_interruptible中返回;对应的有另一个函数down,在它休眠过程中会忽略任何信号。
也可以使用mutex,代码如下:
c
static DEFINE_MUTEX(mutex); //或 static struct mutex mutex; mutex_init(&mutex);
mutex_lock(&mutex);
/* 临界区 */
mutex_unlock(&mutex);
注意:一般来说在同一个函数里调用mutex_lock或mutex_unlock,不会长期持有它。这只是惯例,如果你使用mutex来实现驱动程序只能由一个进程打开,在drv_open中调用mutex_lock,在drv_close中调用mutex_unlock,这也完全没问题。
2.3.4.2、在用户上下文与Softirqs、Tasklet、Timer之间加锁
c
static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock_bh(&lock); //禁止软中断
/* 临界区 */
spin_unlock_bh(&lock);
2.3.4.3、在Softirq之间加锁
在Softirq之间(含timer、tasklet、相同的Softirq、不同的Softirq),都可以使用spin_lock()、spin_unlock()来访问临界区。
2.3.4.4、硬中断上下文
示例代码如下:
c
static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock(&lock);
/* 临界区 */
spin_unlock(&lock);
示例代码如下:
c
unsigned long flags;
static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock_irqsave(&lock, flags);
/* 临界区 */
spin_unlock_irqrestore(&lock, flags);