嵌入式Linux驱动开发5---并发控制

Linux源码阅读网站:https://elixir.bootlin.com/linux/v5.15/source/include/linux/atomic/atomic-instrumented.h#L179

现代操作系统三大特性:中断处理,多任务处理,多处理器

多个线程,进程,或者多个CPU访问同一个资源时可以能会发生错误
内核需要提供一些控制机制,防止错误的发生,保护公用资源
以下实例来自正点原子
在这里插入图片描述

控制机制有:
原子变量操作
自旋锁
信号量
完成量

原子变量操作

1.原子变量操作,有原子的特性,以前人们觉得不可再分,最小。原子变量操作就是不会被中断的操作。
2.原子变量操作是需要硬件支持的,与架构相关。
3.使用汇编实现,C语言无法实现,定义在内核源码树/include/asm/drivers/atomic.h中
4.优点是编写简单,不被中断。缺点是只能实现简单的功能,如计数操作,保护的东西十分有限

如何使用

内核为原子类型特定地设计了一种结构,内核开发程序员可以使用此结构实例化出原子变量
为原子变量身上的原子操作也有一套特有的函数,用于对其进行设置,自增,自减等操作
内核中对原子结构的定义:/include/linux/types.h

//原子结构
typedef struct {
	int counter;
} atomic_t;
//INIT宏 用于定义一个原子变量
//定义和初始化一个结构体
//使用方法:atomic_t count = ATOMIC_INIT(0);
//展开:atomic_t count = { (0) };
#define ATOMIC_INIT(i) { (i) }

#ifdef CONFIG_64BIT
typedef struct {
	s64 counter;
} atomic64_t;
#endif

内核中对原子结构的使用案例:
对原子变量的操作函数接口都放在了/include/linux/atomic/atomic-instrumented.h

//定义变量
static atomic_t waiting_for_crash_ipi;
//.....

//变量使用
void machine_crash_nonpanic_core(void *unused)
{
	struct pt_regs regs;

	crash_setup_regs(&regs, get_irq_regs());
	printk(KERN_DEBUG "CPU %u will stop doing anything useful since another CPU has crashed\n",
	       smp_processor_id());
	crash_save_cpu(&regs, smp_processor_id());
	flush_cache_all();

	set_cpu_online(smp_processor_id(), false);
	atomic_dec(&waiting_for_crash_ipi);      	//在此处使用了
 
	while (1) {
		cpu_relax();
		wfe();
	}
}

原子位操作与原子操作差不多,只不过这个操作粒度更细一些;
原子操作只能对整形变量或者位进行保护,能保护的东西太有限了,如果涉及到其他类型如结构体,就需要使用到锁机制。

自旋锁

自旋锁,应该说是让别人自旋的锁,QAQ
当你拿到这个锁,那么你的资源别人就无法访问了,只能在旁边眼睁睁地看着,打转儿!这就是自旋的意思
看得出来,如果你长期持有这个锁,等待这个锁释放的所有线程都会搁那儿打转,浪费时间,降低了系统性能
所以它不适合在长时间持有锁的场合

定义所在文件:/include/linux/spinlock_types.h

typedef struct spinlock {
	struct rt_mutex_base	lock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
} spinlock_t;

使用案例:

static DEFINE_SPINLOCK(i8259_irq_lock);

inline void
i8259a_enable_irq(struct irq_data *d)
{
	spin_lock(&i8259_irq_lock);
	i8259_update_irq_hw(d->irq, cached_irq_mask &= ~(1 << d->irq));
	spin_unlock(&i8259_irq_lock);
}

自旋锁API

在这里插入图片描述

死锁原因

1.在自旋锁保护的临界区不能使用任何能够引起睡眠和阻塞的API函数

自旋锁会自动禁止抢占,也就说当线程 A得到锁以后会暂时禁止内核抢占。如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且内核抢占还被禁止了!线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,好了,死锁发生了!

2.使用自旋锁的线程,被中断打断访问共享资源

线程 A 先运行,并且获取到了 lock 这个锁,当线程 A 运行 functionA 函数的时候中断发生了,中断抢走了 CPU 使用权。右边的中断服务函数也要获取 lock 这个锁,但是这个锁被线程 A 占有着,中断就会一直自旋,等待锁有效。但是在中断服务函数执行完之前,线程 A 是不可能执行的,线程 A 说“你先放手”,中断说“你先放手”,场面就这么僵持着,
死锁发生!
在这里插入图片描述

带中断管理的API

为了能够附加上管理本地中断的功能,可以使用如下的配套API
在这里插入图片描述

使用案例

/* 线程 A */
void functionA ()
{
    unsigned long flags; /* 中断状态 */
    spin_lock_irqsave(&lock, flags) /* 获取锁 */
    /* 临界区 */
    spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}

 /* 中断服务函数 */
void irq() 
{
    spin_lock(&lock) /* 获取锁 */
    /* 临界区 */
    spin_unlock(&lock) /* 释放锁 */
}

读写自旋锁

写的时候不能读,读的时候也不能写
读的时候可以读

顺序锁

写的时候可以读,如果读的时候发生了写操作,最好重新读取,以保证数据完整性
不允许同时进行并发的写操作

信号量

信号量介绍

信号量是同步的一种方式
用于控制对共享资源的访问
正点原子的绝妙比喻:

某一天早上 A 去上厕所了,过了一会 B 也想用厕所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。 B
要么就一直在厕所门口等着,等 A 出来,这个时候就相当于自旋锁。 B 也可以告诉 A,让 A 出来以后通知他一下,然后 B
继续回房间睡觉,这个时候相当于信号量。

由此看来:
信号量可以提高处理器的效率,线程不必像自旋锁那样傻等了–> 适合长时间占有资源的场所(对比自旋锁的特点)
信号量也带来了更大的开销,信号量让线程进入休眠状态以后会切换线程,这里会产生开销
–>无法用在中断里(因为中断是无法被休眠的)
–> 如果 共享资源的持有时间比较短 切换频繁 就不要用信号量 开销会很大

信号量数据结构

/* Please don't access any members of this structure directly */
struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};

信号量使用示例

struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */

互斥锁

在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

互斥锁使用示例

1 struct mutex lock; /* 定义一个互斥体 */
2 mutex_init(&lock); /* 初始化互斥体 */
3 4
mutex_lock(&lock); /* 上锁 */
5 /* 临界区 */
6 mutex_unlock(&lock); /* 解锁 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值