嵌入式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);
相关推荐
€☞扫地僧☜€39 分钟前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
hjjdebug44 分钟前
linux 下 signal() 函数的用法,信号类型在哪里定义的?
linux·signal
其乐无涯1 小时前
服务器技术(一)--Linux基础入门
linux·运维·服务器
Diamond技术流1 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
写bug的小屁孩1 小时前
前后端交互接口(三)
运维·服务器·数据库·windows·用户界面·qt6.3
斑布斑布1 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习
紅色彼岸花1 小时前
第六章:DNS域名解析服务器
运维·服务器
Spring_java_gg1 小时前
如何抵御 Linux 服务器黑客威胁和攻击
linux·服务器·网络·安全·web安全
✿ ༺ ོIT技术༻1 小时前
Linux:认识文件系统
linux·运维·服务器
会掉头发1 小时前
Linux进程通信之共享内存
linux·运维·共享内存·进程通信