Linux驱动开发笔记:设备驱动中的并发控制

本文详细介绍了Linux驱动开发中的并发控制技术,包括原子变量操作(原子整型和位操作)、自旋锁、信号量和完成量的使用。重点讨论了各种机制的工作原理和应用场景,如自旋锁的忙等待特性、信号量的进程睡眠与唤醒以及完成量的线程通知机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 并发与竞争

2 原子变量操作

2.1 原子整型操作

2.1 原子位操作

3 自旋锁

4 信号量

4.1 信号量的实现

4.2 信号量的使用

5 完成量

5.1 完成量的实现

5.2 完成量的使用


1 并发与竞争

并发:在操作系统中,一个时间段中有几个程序同时处于就绪状态,等待调度到CPU中运行

竞争:两个或两个以上的进程同时访问一个资源

并发机制:原子变量操作、自旋锁、信号量、完成量

2 原子变量操作

绝对不会在执行结束前被任何其他任务或事件打断(不可被打断操作)

优点:编写简单;

缺点:功能太简单,只能做计数操作,保护的东西太少。

typedef struct {
    volatile int counter;
} atomic_t;

linux中定义了两种操作方法:(1)原子整型操作;(2)原子位操作

2.1 原子整型操作

atomic_t count;

定义了一个atomic_t类型的变量

atomic_t类型变量只能通过Linux内核中定义的专用函数来操作

(1)定义atomic_t变量

ATOMIC_INIT的功能:定义一个atomic_t类型的变量。宏定义如下:

#define ATOMIC_INIT(i) { (i) }

定义一个名为count的atomic_t类型变量的方法:

atomic_t count = ATOMIC_INIT(0);

(2)设置atomic_t变量的值

//设置变量v的值为i
#define atomic_set(v, i) (((v)->counter) = i)  

(3)读atomic_t变量的值

//返回原子类型变量v的值
#define atomic_read(v) ((v)->counter)   

(4)原子变量的加减法

atomic_add()函数用来将第一个参数i的值加到第二个参数v中:

static inline void atomic_add(int i, volatile atomic_t *v)

atomic_sub()函数从原子变量v中减去i的值:

static inline void atomic_sub(int i, volatile atomic_t *v)

(5)原子变量的加减法

atomic_inc()函数用来将v指向的变量加1:

static inline void atomic_inc(volatile atomic_t *v)

atomic_dec()函数用来将v指向的变量减1:

static inline void atomic_dec(volatile atomic_t *v)

(6)加减测试

atomic_inc_and_test()函数用来将v指向的变量加1;若结果为0,返回真,若结果为非0,返回假:

static inline int atomic_inc_and_test(volatile atomic_t *v)

atomic_dec_and_test()函数用来将v指向的变量减1;若结果为0,返回真,若结果为非0,返回假:

static inline int atomic_dec_and_test(volatile atomic_t *v)

2.1 原子位操作

原子位操作不需要专门定义一个类似atomic_t类型的变量,只需要一个普通的指针就行:

// 将addr变量的第nr位置1
static inline void set_bit(int nr, volatile unsigned long *addr);

// 将addr变量的第nr位置0
static inline void clear_bit(int nr, volatile unsigned long *addr);

// 将addr变量的第nr位置为相反的数
static inline void change_bit(int nr, volatile unsigned long *addr);

//将addr变量的第nr位置1,并返回没修改的值
static inline int test_and_set_bit(int nr, volatile unsigned long *addr);

//将addr变量的第nr位置0,并返回没修改的值
static inline int test_and_clear_bit(int nr, volatile unsigned long *addr);

//将addr变量的第nr位置为相反的数,并返回没修改的值
static inline int test_and_change_bit(int nr, volatile unsigned long *addr);

3 自旋锁

Linux中提供了一些锁来避免竞争条件

自旋锁类型为:struct spinlock_t;

(1)定义和初始化

一个自旋锁必须初始化才能使用,对自旋锁的初始化可以在编译阶段通过宏来实现:

spinlock_t lock;

初始化自旋锁:

spinlock_t lock=SPIN_LOCK_UNLOCKED;

运行阶段,使用spin_lock_init()函数动态地初始化一个自旋锁:

void spin_lock_init(spinlock_t lock);

(2)锁定自旋锁

进入临界区前,需要用spin_lock宏来获得自旋锁:

// 如果能立即获得自旋锁,则立即返回,否则一直自旋在那里,直到被其他线程释放
#define spin_lock(lock)  _spin_lock(lock)

(3)释放自旋锁

// 当调用宏后,锁立即被释放
#define spin_unlock(lock)  _spin_unlock(lock)

(4)自旋锁使用

spinlock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
// 临界资源
spin_unlock(&lock);

注意事项:

  • 自旋锁是一种忙等待,所有不应该长时间持有
  • 自旋锁不能递归调用

4 信号量

与自旋锁相比,信号量只有当得到信号量的进程处于执行状态时才能够进入临界区

信号量与自旋锁的不同点:当一个进程试图去获取一个已经锁定的信号量时,进程不会像自旋锁那样在远处忙等待

当获取信号量没有释放时,进程会将自身加入一个等待队列中去睡眠,直到拥有信号量的进程释放后,睡眠才被唤醒

4.1 信号量的实现

信号量定义:

struct semaphore{
    spinlock_t lock;
    unsigned int count;
    struct list_head wait_list;
};

(1)lock自旋锁

当count要变化时,内部锁定lock锁;当修改完成后,会释放lock锁。

(2)count变量

count==0:表示信号量被其他进程使用,现在不可用,但wait_list队列中没有进程在等待信号量

count<0:表示自身有一个进程在wait_list队列中等待信号量被释放

count>0:表示这个信号量是空闲的,可以使用

信号量根据count的值设定有多少个进程持有这个信号量

(3)等待队列

存放正在睡眠的进程链表

4.2 信号量的使用

(1)定义和初始化

定义:

struct semaphore sema;

初始化:

// 设置sem中的count值为val
static inline void sema_init(struct semaphore *sem, int val);

**初始化一个信号量的值为1 (互斥体):

#define init_MUTEX(sem)  sema_init(sem, 1)

**初始化一个信号量的值为0:

#define init_MUTEX_LOCKED(sem)  sema_init(sem, 0)

(2)锁定信号量

进入临界区前,使用down()获得信号量:(不能用在上下文中断)

void down(struct semaphore *sem);

down()进入睡眠之后就不能被信号唤醒

down_interruptible()进入睡眠后可以被信号唤醒

// 被信号唤醒,返回非0
int down_interruptible(struct semaphore *sem);

(3)释放信号量

void up(struct semaphore *sem);

(4)同步操作

同步:一个线程的执行需要依赖另一个线程的执行

**自旋锁与信号量的对比:

  • 被包含的代码能够在短时间内执行完成,使用自旋锁;自旋锁只是忙等待,不会进入睡眠
  • 信号量用来在多个进程之间互斥;在一个进程对被保护的资源占用时间比进程切换时间短时,使用信号量

5 完成量

一个线程发送一个信号通知另一个线程开始执行某个任务

5.1 完成量的实现

完成量定义:

struct completion{
    unsigned int done;
    wait_queue_head_t wait;
};

(1)done

用于维护一个计数。

初始化时,done=1;

done==0,拥有完成量的线程置于等待状态。done>0,等待完成量的函数立即执行。

(2)wait

所有等待该完成量的任务组成一个链表结构

5.2 完成量的使用

(1)定义和初始化

定义:

struct completion com;

初始化函数定义:

static inline void init_completion(struct completion *x)
{
    x->done=0;
    init_waitqueue_head(&x->wait);
}

**还可以使用宏DECLARE_COMPLETION定义和初始化完成量:

#define DECLARE_COMPLETION(work) \
    struct completion = COMPLETION_INITIALIZER(work)

#define COMPLETION_INITIALIZER(work) \
    { 0, _WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }

(2)等待完成量

// 执行一个不被信号中断的等待
void __sched wait_for_completion(struct completion *X);

调用这个函数之后,若没有线程完成这个信号量,执行这个函数wait_for_completion的线程将一直等待下去

(3)释放完成量

// 唤醒一个等待进程
void complete(struct completion *x);

// 唤醒所有等待进程
void complete_all(struct completion *x);

唤醒之后,wait_for_completion函数之后的代码才可以继续执行

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值