第五章:并发与竞态

竞态通常作为对资源的共享访问结果而产生。

linux信号量的实现

内核代码首先需要包括<asm/semaphore.h>头文件。相关的类型是struct semaphore;

直接创建并初始化信号的函数:

void sema_init(struct semaphore *sem, int val);

val是赋予信号量的初始值。

其他的宏有:

DECLARE_MUTEX(name); //一个成为name的信号量变量被初始化为1

DECLARE_MUTEX_LOCKED(name); //被初始化为0。这种情况,在允许任何线程访问之前,必须显式地解锁该互斥体

动态分配互斥体的情况下,信号量需要在运行时被初始化:

void init_MUTEX(struct semaphore *sem);

void init_MUTEX_LOCKED(struct semaphore *sem);

P函数成为down,有三个版本:

void down(struct semaphore *sem); //阻塞版本

int down_interruptible(struct semaphore *sem); //可中断版本

int down_trylock(struct semaphore *sem); //不会休眠版本

当上述函数调用成功以后,该线程就获得了该信号量,拥有处理共享资源的权利。

对应的up是:

void up(struct semaphore *sem);

读取这/写入者信号量

头文件:<linux/rwsem.h>

相关函数:

void init_rwsem(struct rw_semaphore *sem);

void down_read(struct rw_semaphore *sem);

int  down_read_trylock(struct rw_semaphore *sem);

void up_read(struct rw_semaphore *sem);

void down_write(struct rw_semaphore *sem);

int down_write_trylock(struct rw_semaphore *sem);

void up_write(struct rw_semaphore *sem);

void downgrade_write(struct rw_semaphore *sem);

completion机制

内核编程中有如下一种模式:在当前线程之外初始化某个活动,然后等待该活动的结束。此时,可以使用competition机制,头文件:<linux/completion.h>.

DECLARE_COMPLETION(my_completion); //创建completion

动态的创建并初始化completion

struct completion my_completion;

init_completion(&my_completion);

等待completion完成:

void wait_for_completion(struct completion *c);

非中断的等待,若调用了该函数,但没有进程理会该任务,则产生一个不可杀的进程。任务的完成调用下函数:

void complete(struct completion *c);

void complete_all(struct completion *c);

区别:若有多个等待线程,complete只会唤醒一个等待线程(具体哪一个不可知),complete_all会唤醒所有的等待线程。

completion一般是一个单次设备,即使用一次会被丢弃。若要重复使用建议重新初始化,尤其在使用complete_all的情况下:

INIT_COMPLETION(struct completion *c);

completion机制的典型应用:模块退出时的内核线程终止。某些驱动程序内部由一个while(1)循环完成,当内核准备清楚模块时,exit函数会告诉该线程退出,并等待completion(wait_for_completion)。被通知函数执行下函数:

void complete_and_exit(struct completion *c, long retval);

自旋锁

自旋锁的核心思想:进入某段代码前要检查锁,若锁可用,则代码继续,若不可用,则代码进入忙循环并重复检查这个锁。这个循环就是自旋锁的“自旋”部分。自旋期间,执行忙循环的处理器做不了任何有用的工作。

自旋锁API

自旋锁头文件:<linux/spinlock.h>。锁类型:spinlock_t

静态创建锁:

spinlock_t my_lock = SPIN_LOCK_UNLOCKED;

动态创建锁:

void spin_lock_init(spinlock_t *lock);

代码进入临界区之前,获得锁的函数:

void spin_lock(spinlock_t *lock);

释放锁:

void spin_unlock(spinlock_t *lock);

使用自旋锁应注意的问题:

若某段代码拥有了锁,此刻被中断,或者CPU被抢占,而中断代码或者新拥有CPU的代码中又有对该锁的请求,则在可以预见的时间里,锁将会一直处于自旋状态,因此,使用自旋锁的的核心规则:任何自旋锁的代码必须是原子的,不能休眠,不能放弃处理器,中断服务除外(某些情况下,此时也不能放弃处理器)。

² 内核抢占的情况自旋锁代码自身处理,即只要内核拥有了自旋锁,对应处理器上的抢占会被禁止。

² 自旋锁代码中不可以休眠,在调用内核函数时,需要多注意函数是否会休眠。

² 需要时,自旋锁需要禁止中断(仅在本地CPU上)。

² 最后一个重要原则是:自旋锁必须在尽可能短的时间里拥有。

自旋锁函数

void spin_lock(spinlock_t *lock);

void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

void spin_lock_irq(spinlock_t *lock);

void spin_lock_bh(spinlock_t *lock);

spin_lock_irqsave会在获得自旋锁之前禁止本地CPU上的中断,先前的中断状态保存在flags中。如果我们能够确保没有任何其他代码禁止本地处理器的中断(即,我们能够确保在释放自旋锁时应该启用中断),则可以使用spin_lock_irqspin_lock_bh在获得锁之前禁止软件中断,但是会让硬件中断保持打开。

释放自旋锁的方法:

void spin_unlock(spinlock_t *lock);

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);

void spin_unlock_irq(spinlock_t *lock);

void spin_unlock_bh(spinlock_t *lock);

spin_lock_irqsavespin_lock_irq和对应的解锁函数必须在同一个函数中调用,否则代码可能在某些架构上出现问题。

非阻塞的自旋锁操作:

int spin_trylock(spinlock_t *lock);

int spin_trylock_bh(spinlock_t *lock);

成功返回非零值,不成功返回零值。

读取者/写入者自旋锁

锁类型:rwlock_t类型。其他初始化以及应用API见头文件:<linux/spinlock.h>

锁陷阱

使用锁需要注意可能导致错误的东西:

² 如果某个获得锁的代码又调用其他同样试图获取这个锁的函数,代码就会死锁。因此,决不允许锁拥有者第二次或者这个锁。

² 尽量避免某个操作需要多个锁的情况,若不可避免,则规定获得锁的顺序。有益的规则:1.若有多个锁,先获得局部锁,再获得全局锁。2.若同时要获得信号量和自旋锁,须先获得信号量。因为down锁可导致休眠,而休眠在自旋锁中是不允许存在的。

² 使用lockmeter可以度量花在锁上的时间。

除了锁之外的方法

1. 循环缓冲区方法。适合一个生产者,多个消费者的情况。内核有一个通用的循环缓冲区的实现,头文件是<linux/kfifo.h>

2. 原子变量。头文件<asm/atomic.h>。类型为:atomic_t

3. 位操作。以原子形式来操作单个位,头文件<asm/bitops.h>

4. seqlock机制。适用于被保护的资源很小,会被频繁的访问而且写入访问很少发生且快速时的情况。seqlock通常不能用于保护包含数据指针的数据结构。头文件<linux/seqlock.h>。使用方法:读取访问获得一个(无符号)整数值而进入临界区,退出时,检查该顺序值与当前值,若不相等,则说明此值已被修改,则必须重试读取访问。写访问需获得一个互斥锁,具体函数见头文件,该函数使用自旋锁实现,因此自旋锁的常见限制也适用于此。

读取-复制-更新(RCU)。工作方法:需要修改某个共享的数据结构时,写入线程首先复制,然后修改副本,之后用新的版本替代相关指针。详见头文件<linux/rcupdate.h>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值