LKD:内核同步

  1. 临界区:访问和操作共享数据的代码段。
  2. 竞争条件:如果两个执行线程有可能处于同一个临界区中同时执行,那么这就是程序的一个bug。
  3. 同步:避免并发和防止竞争条件。
  4. 各种锁机制之间的区别主要在于:当锁已经被其他线程持有,因而不可用时的行为表现——一些锁被争用时会简单地执行忙等待,而另外一些锁会使当前任务睡眠直到锁可用为止。
  5. 内核中可能造成并发执行的原因:

中断、软中断和tasklet、内核抢占、睡眠及用户空间的同步、对称多处理

  1. 找出哪些数据需要保护是关键所在:由于任何可能被并发访问的代码几乎无例外地需要保护,所以寻找哪些代码不需要保护反而相对更容易些。比如,局部自动变量(还有动态分配的数据结构,其地址仅存放在堆栈中)不需要任何锁,因为它们独立存在于执行线程的栈中。
  2. 要给数据而不是代码加锁。
  3. 死锁:一个或多个执行线程,每个线程都在等待其中一个资源,但所有资源都已经被占用。所有的线程都在相互等待,但它们永远都不会释放已经占有的资源。自死锁、ABBA锁。
  4. 避免死锁的简单规则:

<a>按顺序加锁。

<b>防止发生饥饿。这个代码的执行是否一定会结束?如果“张”不发生?“王”一定要等待下去吗?

<c>不要请求同一个锁。

 

 

内核同步方法

 

  1. 原子整数操作:

typedef struct{

           volatile int counter;

}atomic_t;

atomic_t v;

atomic_t u = ATOMIC_INIT(0);

atomic_set(&v, 4);

atomic_add(2, &v);

atomic_inc(&v);

atomic_read(&v);

int atomic_dec_and_test(atomic_t *v)

 

  1. 原子位操作:

unsigned long word = 0;

set_bit(0, &word);

set_bit(1, &word);

printk(“%ul\n”, word);

clear_bit(1, &word);

change_bit(0, &word);

 

if(test_and_set_bit(0, &word)){

}

 

  1. 自旋锁(spin lock)

如果一个执行线程试图获得一个已经被持有的自旋锁,那么该线程就会一直进行忙循环——旋转——等待锁重新可用(特别浪费处理器时间)所以自旋锁不应该被长时间持有。

DEFINE_SPINLOCK(mr_lock);

spin_lock(&mr_lock);

/*临界区…*/

spin_unlock(&mr_lock);

 

注意:自旋锁是不可递归的。

      自旋锁可以使用在中断处理程序中,但一定要在获取锁之前,首先禁用本地中断。

 

内核提供的禁止中断同时请求锁的接口,

DEFINE_SPINLOCK(mr_lock);

unsigned long flags;

 

spin_lock_irqsave(&mr_lock,flags);

spin_unlock_irqrestore(&mr_lock, flags);

 

自旋锁和下半部:

spin_lock_bh()  spin_unlock_bh()用于获取指定锁,同时禁止所有下半部的执行。

由于下半部可以抢占进程上下文的代码,所有当下半部和进程上下文共享数据时,必须对进程上下文中的共享数据进行保护,所有需要加锁的同时还要禁止下半部执行。同样,中断处理程序同理。

 

自旋锁提供了一种快速简单地锁实现方法。如果加锁时间不长并且代码不会睡眠(比如中断处理程序),自旋锁是最好的选择。如果加锁时间很长或者代码在持有锁时有可能睡眠,那么最好使用信号量。

 

  1. 信号量(semaphore)

<a>信号量是一种睡眠锁。

<b>由于争用信号量的进程在等待锁重新可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。

<c>由于执行线程在锁被争用时会睡眠,所有只能在进程上下文中才能获取信号量锁,因为在中断上下文不能进行调度。

<d>可以在持有信号量时去睡眠。

<e>在你占有信号量的同时不能占用自旋锁。

 

创建和初始化信号量

struct semaphore name;

sema_init(&name, count);

互斥信号量:

static DECLARE_MUTEX(name);

init_MUTEX(sem);

 

down_interruptible():试图获取指定的信号量,如果信号量不可用,它将把调用进程置为TASK_INTERRUPTIBLE状态——进入睡眠。

up():释放指定的信号量。

 

static DECLARE_MUTEX(mr_sem);

/*试图获取信号量*/

if (down_interruptible(&mr_sem)){

}

/*临界区…*/

up(&mr_sem);

 

  1. 互斥体(mutex)

静态定义:DEFINE_MUTEX(name);

动态初始化:mutex_init(&mutex);

mutex_lock(&mutex);

/*临界区…*/

mutex_unlock(&mutex);

 

mutex使用场景:

<a>任何时刻只有一个任务可以持有mutex。

<b>在同一上下文中上锁和解锁。

<b>不能再中断或者下半部中使用。

 

  1. 完成变量(completion)

静态创建:DECLARE_COMPLETION(mr_comp)

动态创建:init_completion()

等待:wait_for_completion(struct completion)

发信号唤醒等待任务:complete(struct completion)

 

  1. 禁止抢占

preempt_disable()

preempt_enable()

preempt_count()

 

1.为什么中断处理程序不能睡眠?

因为中断睡眠后无法被唤醒。中断处理程序位于中断上下文,内核调度的单位是进程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值