1. 之前的问题
-
if (!dptr->data[s_pos]) {
-
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
-
if (!dptr->data[s_pos])
-
goto out;
-
}
这里如果两个进程同时走到kmalloc则会有问题。kmalloc之前进程切换,则会导致某个进程调用时内核内存泄漏。所以要并发控制
2. 信号量、互斥锁
即理论上的P V操作,内核封装了一层作为
互斥锁(概念上默认是睡眠的)。以下三个版本区别如下:
void down(struct semaphore *sem); 这个一直等待。不可中断,是建立“非可杀进程”的手段
int down_interruptible(struct semaphore *sem); 也一直等待,但是可中断。
int down_trylock(struct semaphore *sem); 这个则不睡眠
另一种是读写类型的信号量,实现成读写类的互斥锁。这种互斥锁的问题是写者多时,读的执行线程会饥饿。
这里的执行线程不是线程,而是指可能执行到它的代码流。既包括用户空间调进来的,也包含正常的中断等。
一个执行线程等待另一个执行线程完成一些工作时,一般不用互斥锁,而是可用completion接口。
3. 自旋锁
即忙等,它最初是用于多处理器上的。而在单处理下,内核抢占的行为就类似于SMP。
优点:1.它广泛用于不可休眠的代码中,如中断处理中;2.正确使用时,性能比信号量更好。
非内核抢占单处理器上的内核代码进入自旋状态,则会永远自旋。
自旋锁使用原则:拥有锁的代码必须是原子的,原子的意思是不可以睡眠,不可以中断:
睡眠会如何:如果拥有自旋锁的代码睡眠,则会造成其他
执行线程取锁忙等,性能差;解决方法是一般拥有自旋锁则禁止内核抢占;
中断会如何:如果取到自旋锁的代码被中断,假如中断处理试图再取此锁,则死锁。解决方法是在自旋锁获取的同时禁止当前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)
4. 锁的注意
死锁防止:
1. 单锁:单锁死锁最可能的是获取锁的代码去调用某函数,而这函数中又试图取锁。无论互斥锁还是自旋锁,都死锁;
2. 多锁:理论上的方法是取锁顺序一致,但这方法不可操作;
实践性强的如下:先取局部锁,后取大锁; 有互斥锁(信号量)与自旋锁同时存在时,一定先取互斥锁,否则自旋锁睡眠。
最好防止需要多锁的情况。
粒度:
2.0最初是大内核锁,慢慢优化到如今。
设计锁的规则:最初使用大粒度锁,除非真正发现锁竞争会导致问题再尝试细化。因为真正的性能瓶颈往往出现在非预期的情况。
5. 非锁处理方法
循环缓冲区:生产消费者模型;原子变量、内核位操作:cpu硬件级锁;
seqlock、rcu等高级货。
阅读(500) | 评论(0) | 转发(0) |