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

一、内联汇编

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);
相关推荐
羚羊角uou1 天前
【Linux】system V共享内存
linux·运维·服务器
林克爱塞尔达1 天前
Linux入门(二)
linux·运维·chrome
破烂儿1 天前
Ubuntu Server 安装图形界面和通过Window远程桌面连接服务器(Xrdp)
linux·服务器·ubuntu
Hello.Reader1 天前
Kafka 运维实战基本操作含命令与最佳实践
运维·kafka·linq
存储服务专家StorageExpert1 天前
手搓一个 DELL EMC Unity存储系统健康检查清单
linux·运维·服务器·存储维护·emc存储
笑口常开xpr1 天前
Linux 库开发入门:静态库与动态库的 2 种构建方式 + 5 个编译差异 + 3 个加载技巧,新手速看
linux·c语言·动态库·静态库
SonOfWind03111 天前
CentOS搭建本地源
linux·运维·centos
IT成长日记1 天前
【Nginx开荒攻略】Nginx主配置文件结构与核心模块详解:从0到1掌握nginx.conf:
linux·运维·nginx·配置文件
Nimsolax1 天前
Linux线程控制
linux
代码的余温1 天前
Web服务器VS应用服务器:核心差异解析
运维·服务器·前端