scull的缺陷
scull是个设备,如果两个进程同时访问下面代码,都分配内存给指针dptr->data,后访问的会覆盖前者,前者内存无法释放造成内存泄漏。 只要代码将一个指针传递给了内核的其他部分,一个新的共享就可能建立。
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(void *), GFP_KERNEL);
if (!dptr->data)
goto nomem;
memset(dptr->data, 0, qset * sizeof(char *));
}
Linux信号量
#include <asm/semaphore.h>
//直接创建信号量
void sema_init(struct semaphore *sem, int val);
//使用宏创建互斥信号量
DECLARE_MUTEX(name);//创建信号量,val=1
DECLATE_MUTEX_LOCKED(name);//创建信号量,val=0,即上锁
//前面为全局定义的信号量,运行时初始化使用下面函数
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
//上锁,解锁
void down(struct semaphore *sem);//得不到信号量则休眠
int down_interruptible(struct semaphore *sem);//得不到信号量则休眠,可被中断,被中断返回非零数,一般使用这个函数
int down_trylock(struct semaphore *sem);得不到信号量直接返回非零
void up(struct semaphore *sem);信号量加1,即释放信号量
scull中使用
int scullc_init(void)
{
...
for (i = 0; i < scullc_devs; i++) {
scullc_devices[i].quantum = scullc_quantum;
scullc_devices[i].qset = scullc_qset;
sema_init (&scullc_devices[i].sem, 1);
scullc_setup_cdev(scullc_devices + i, i);
}
...
{
ssize_t scullc_write (struct file *filp, const char __user *buf, size_t count,loff_t *f_pos) {
//操作设备内部数据时先上锁, 返回ERESTARTSYS后内核高层次代码会重新启动该调用
if (down_interruptible (&dev->sem))
return -ERESTARTSYS;
...
up (&dev->sem);
return count;
}
rwsem信号量,多并行读,少并行写时使用
completion是一种轻量型机制,它允许一个线程告诉另一个线程某个工作已经完成。
#include <linux/completion.h>
struct completion my_completion;
DECLARE_COMPLETION(struct completion );
//动态创建并初始化
init_completetion(struct completion *);
//等待完成
void wait_for_completion(struct completion *);
//通知完成
void complete(struct completion *);
自旋锁
spinlock自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。
如果驱动程序获得了自旋锁,然后被高优先级进程抢占,此时其他某个线程试图获得锁则可能进入死锁。拥有锁时应避免休眠;很多内核函数都可能导致休眠,如:用户空间和内核空间的数据复制,在复制继续前,必须的用户空间页也许需要从磁盘上换入,这个操作明显需要休眠。kmalloc在等待可用内存时,也会放弃处理器进入休眠。还有一种情况,驱动程序获得一个锁,然后被中断中断程序也需要获得这个锁,处理器将永远自选下去。
所以任何时候只要内核代码拥有自旋锁,相关处理器上的抢占就会被禁止。
#include <linux/spinlock.h>
//静态初始化
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);
//获取自旋锁之前禁止中断,中断状态保存在flag
void spin_lock_irqsave(spinlock_t *lock, unsigned long flag);
void spin_lock_irqsave(spinlock_t *lock);
//如果明确释放锁后在中断中则用如下API
void spin_lock_irq(spinlock_t *lock);
void spin_lock_irq(spinlock_t *lock);
//只禁止软中断
void spin_lock_bh(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock);
//非阻塞自旋锁
void spin_trylock(spinlock_t *lock);
void spin_trylock_bh(spinlock_t *lock);
rwlock_t读/写自旋锁, 这种锁允许任意数量的读取者同时进入临界区,但写入者必须互斥访问。
锁陷阱:如果某个获得锁的函数要调用其他同样试图获取该锁的函数就会发生死锁。代码通常会一次拥有多个锁,如果A,B都需要获取锁lock1和lock2,而A获取了lock1,B获取了lock2就会死锁。
免锁的方法:1,算法如环型缓冲区; 2, 原子变量;3,seqlock