Linux Kernel - Synchronization

几个案例


case 1 : Single Variable

i++;   
汇编会进行如下操作
1. 获取变量 i 的值,并写入寄存器
2. 寄存器的值加 1
3. 寄存器的值写回变量 i 所在的内存空间

若两个线程同时进行对变量 i 进行 i++ 操作,则可能出现下面两种结果,因此,在共享内存的应用中,需要保证并发访问时共享资源是受保护的。

 解决方式:原子操作,指令不被分解和打断。

                                         

 

case 2 : Queue

function A( ) 请求对 head 操作, function B( ) 请求对 tail 操作,kernel 的不同部分调用 A( ) 和 B( )都对该链表进程操作时就很有可能导致并发的问题。

                                             

解决方式: 锁。任何代码要访问队列首先获取锁,锁在某一时刻只能由一个线程持有。

                                 

死锁问题,典型代表 ABBA 锁,2 个线程 2 把锁的情况。

                                   

解决方式:注意锁的顺序,嵌套锁一定是要以相同的顺序去获取。

 

同步方法


原子操作

1. 整形原子操作

#include <linux/types.h>
#include <asm/atomic.h>

atomic_t u = ATMOIC_INIT(0);  /* define u and init it to 0 */
atomic_set(&u, 4);            /* u = 4 */
atomic_add(2, &u);            /* u = u + 2 = 6 */
atomic_inc(&u);               /* u = u + 1 = 7 */
printk("%d\n", atomic_read(&u));  /* will print 7 */

2. 位原子操作

#include <asm/bitops.h>

void set_bit(nr, void *addr); /* 设置addr地址的第 nr 位 */
void clear_bit(nr, void *addr);
void change_bit(nr, void *addr);  /* 对 addr 的 nr 位反置 */
int  test_bit(nr, void *addr);     /* 返回 addr 地址的第 nr 位 */

自旋锁

自旋锁主要针对 SMP 或单 CPU 但内核可抢占的情况,对于单 CPU 和内核不支持抢占的系统,自旋锁退化为空操作。在单 CPU 和内核可抢占的系统中,自旋锁持有期间内核的抢占将被禁止。
                                      
i. SMP 体系结构,多个CPU使用共同的系统总线,可访问共同的外设和储存器
ii.Linux 2.6 内核支持抢占调度,一个进程在内核执行的时候可能被另一高优先级进程打断,进程与抢占它的进程访问共享资源的情况类似于 SMP 的多 个 CPU。

#include <asm/spinlock.h>
#include <linux/spinlock.h>

spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; //获取自旋锁,保护临界区
/* 临界区 */
spin_unlock (&lock) ; //解锁

自旋锁不是递归的,即持有锁时再去申请这一把锁将导致死锁。自旋锁可用于中断处理,考虑中断事件打断内核代码情况,需要失能当前CPU中断。

                                    

spinlock_t mr_lock;
unsigned long flags;
spin_lock_irqsave(&mr_lock, flags);
/* critical region ... */
spin_unlock_irqrestore(&mr_lock, flags);

spin_lock_irqsave() 保存当前中断状态并失能当前CPU中断,获取指定锁。
如果清楚的知道中断初始化时是使能的,就没必要存当前中断状态,使用 spin_lock_irq() 即可。(不推荐使用)
自旋锁是忙等锁,当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统的性能。

信号量

信号量在 linux 中属于休眠锁,当一个任务尝试获取的一个信号量不可用时,该信号量将任务放置到一个等待队列并休眠(常用于用户空间)。

二进制信号量和计数信号量

#include <asm/semaphore.h>

struct semaphore name;
int count;
sema_init(&name, count);   //count > 1 计数信号量 同一时刻允许 count 个持有者,不常用
init_MUTEX(&name);         //== sema_init (&name, 1) 二进制信号量
init_MUTEX_LOCKED (&name); //== sema_init (&name, 0)  initially locked

down(&name);  //获取信号量,进入睡眠状态的进程不能被信号打断,因此不能在中断上下文使用
/* critical region ... */
up(&name);    //释放信号量

if (down_interruptible(&name)) /* 进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非 0 */
{
    return - ERESTARTSYS;
}
/* critical region ... */
up(&name);    //释放信号量

down_trylock(&name); /* 若获得该信号量返回 0,否则,返回非 0 值。它不会导致调用者睡眠,可以在中断上下文使用*/
/* critical region ... */
up(&name);    //释放信号量

 

互斥体

类似二进制信号量,但是接口更简单,性能更出色。mutex 的使用方法和信号量用于互斥的场合完全一样。

struct mutex my_mutex;
mutex_init(&my_mutex);
mutex_lock(&my_mutex);
/* critical region ... */
mutex_unlock(&my_mutex);

  • 信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。
  • 信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过 down_trylock()方式进行,不能获取就立即返回以避免阻塞。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值