嵌入式Linux驱动开发(同步与互斥专题)(一)

一、内联汇编

1.1、语法

嵌入式Linux驱动开发(同步与互斥专题)(一)_第1张图片
内联汇编实现加法
嵌入式Linux驱动开发(同步与互斥专题)(一)_第2张图片

1.2、同步互斥失败的例子

嵌入式Linux驱动开发(同步与互斥专题)(一)_第3张图片
进程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):

typedef struct{
	int counter;
}atomic_t;

操作函数如下(下表中v都是atomic_t指针):
嵌入式Linux驱动开发(同步与互斥专题)(一)_第4张图片

1.4、原子变量的内核实现

atomic_read,atomic_set这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。问题在于atomic_inc这类操作,要读出、修改、写回。
以atomic_inc为例,在atomic.h文件中,如下定义:

#define atomic_inc(v)		atomic_add(1, v)

atomic_add又是怎样实现的呢?用下面这个宏:

ATOMIC_OPS(add, +=, add)

把这个宏展开:

#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):
嵌入式Linux驱动开发(同步与互斥专题)(一)_第5张图片
对于ARMv6及以上的CPU(多核),有一些特殊的汇编指令来实现原子操作,不再需要关中断,代码如下(arch\arm\include\asm\atomic.h):
嵌入式Linux驱动开发(同步与互斥专题)(一)_第6张图片
在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、原子变量使用案例

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;
}

嵌入式Linux驱动开发(同步与互斥专题)(一)_第7张图片

1.5、原子位介绍

嵌入式Linux驱动开发(同步与互斥专题)(一)_第8张图片

1.5.1、原子位的内核实现

在ARMv6以下的架构里,不支持SMP系统,关中断。
嵌入式Linux驱动开发(同步与互斥专题)(一)_第9张图片
在ARMv6及以上的架构中,不需要关中断,有ldrex、strex等指令。
嵌入式Linux驱动开发(同步与互斥专题)(一)_第10张图片

二、Linux锁的介绍与使用

Linux内核提供了很多类型的锁,它们可以分为两类:
① 自旋锁(spinning lock);
② 睡眠锁(sleeping lock)。

2.1、自旋锁

在这里插入图片描述

2.2、睡眠锁

嵌入式Linux驱动开发(同步与互斥专题)(一)_第11张图片

2.3、锁的内核函数

2.3.1、自旋锁

spinlock函数在内核文件include\linux\spinlock.h中声明,如下表:
嵌入式Linux驱动开发(同步与互斥专题)(一)_第12张图片
自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:
嵌入式Linux驱动开发(同步与互斥专题)(一)_第13张图片

2.3.2、信号量semaphore

semaphore函数在内核文件include\linux\semaphore.h中声明,如下表:
嵌入式Linux驱动开发(同步与互斥专题)(一)_第14张图片嵌入式Linux驱动开发(同步与互斥专题)(一)_第15张图片

2.3.3、互斥量mutex

mutex函数在内核文件include\linux\mutex.h中声明,如下表:
嵌入式Linux驱动开发(同步与互斥专题)(一)_第16张图片嵌入式Linux驱动开发(同步与互斥专题)(一)_第17张图片

2.3.4、自旋锁使用条件(自旋锁可以用在中断上下文,但是睡眠锁不可以用在中断上下文)

嵌入式Linux驱动开发(同步与互斥专题)(一)_第18张图片举例简单介绍一下,上表中第一行“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个程序都是可以休眠的,所以可以使用信号量,代码如下:

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,代码如下:

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之间加锁

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、硬中断上下文

示例代码如下:

static DEFINE_SPINLOCK(lock); // static spinlock_t lock; spin_lock_init(&lock);
spin_lock(&lock);
/* 临界区 */
spin_unlock(&lock);

示例代码如下:

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);

你可能感兴趣的:(Linux,驱动以及裸机,linux,驱动开发,运维)