竞态和并发

A进程和B进程同时对scull调用write函数


write有如下操作:
if(!dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL);
if(!dptr->data[s_pos]) goto out;
}
如果A和B刚好到达这段代码,假设A执行,然后到B执行,则A申请的内存将会被B取代,这样就造成了内存泄露。




解决方法有如下几种:


1、添加信号量:


信号量的实现:
struct scull_dev {
struct scull_qset *data;  /* Pointer to first quantum set */
int quantum;              /* the current quantum size */
int qset;                 /* the current array size */
unsigned long size;       /* amount of data stored here */
unsigned int access_key;  /* used by sculluid and scullpriv */
struct semaphore sem;     /* mutual exclusion semaphore     */ //添加信号量
struct cdev cdev;  /* Char device structure */
};


初始化信号量:
sema_init(struct semaphore* sem, int val);
初始化为0:表示锁定。


P函数对应的down函数,减少信号量,如果信号量不可用,则进程就会休眠,等待信号量可用。
三个版本:
void down(struct semaphore* sem);减少信号量,并必要时一直等待。
void down_interruptible(struct semaphore* sem)
void down_trylock(struct semaphore* sem)
当一个线程成功调用down后,那么该线程就拥有了该信号量,这样该线程被赋予访问由该信号量保护的临界区的权利。
当互斥操作完成后,必须返回该信号量,linux等价于V操作的函数是up:
void up(struct semaphore* sem)


调用up后调用者不再拥有该信号量。


P()
{
临界资源
}
V()


这个种操作:如果P出错,那么会是线程返回,无法得到资源的利用,或者P会使当前线程休眠,等待资源可利用!


思考:如果是多个线程访问该资源呢?会不会都会休眠,那又该如何唤醒呢?我在稍后做出解答。


读取者/写入者信号量 rsem/wsem


比如:A,B 只读取临界资源的,而C,D只修改临界资源。
那么A和B就不存在互斥的情况,所以使用读取者信号量,就不会使A和B发生互斥。
读取者信号量允许多个读线程并发执行。但是不允许写线程访问该临界资源,直到该信号量被up。




写入者信号量好读取者信号一样。一般都不建议使用读写信号量。


还有一个读写信号量;rwsem可允许一个写入者或无限个写入者拥有该信号量,写入者具有更高的优先级,
当某个给定写入者试图进入临界区时,在所有写入者完成其工作之前,不允许读取者获取该访问。


如果有大量的写入者竞争该信号,则会导致读取者“饿死”。
为此,最好在很少需要写访问且写入者只会短期拥有信号量的时候使用rwsem。




completion允许一个线程告诉另外一个线程某个工作已经完成。
创建:
DECLARE_COMPLETION(my_completion)
等待:
wait_for_completion(my_completion)
Note:该函数执行一个非中断等待,如果代码调用了这个函数,而且没有人会完成任务,则将会产生一个不可杀死的进程。


在complete的例子中,如果在read线程休眠过程中,卸载模块提示如下:
root@ubuntu:/home/liqinghan/workspace/examples/misc-modules# rmmod complete
ERROR: Module complete is in use
root@ubuntu:/home/liqinghan/workspace/examples/misc-modules# rmmod complete -f
ERROR: Removing 'complete': Resource temporarily unavailable


completion机制的典型使用是模块退出时,内核线程终止,在这种原型中,某些驱动程序的内部工作由一个内核线程在while(1)循环中完成。
当内核准备清除该模块时,exit函数会告诉该线程退出并等待completion,为了实现这种目的,内核包含了可用于这种线程的一个特殊函数
void complete_and_exit(struct completion* c ,long retval);


2、自旋锁
信号量对互斥来讲是非常有用的工具,但它并不是内核提供唯一的这类工具。相反大多数锁定通过名为“自旋锁(spinlock)”的机制实现
和信号量不同,自旋锁可在不能休眠的代码中使用,比如中断处理例程。


spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;

spin_lock()
{
临界区
}
spin_unlock()


理解:if spin_lock() == useable ,则代码进入临界区,反之,则代码进入忙循环并重复检查这个锁,知道该锁可以使用为止。这个循环就是自旋锁的“自旋”部分。
自旋锁的本质是不可中断的,一旦调用了spin_lock,在获得前将一直处于自旋状态。


自旋锁和原子上下文


进程P1拥有自旋锁,进程P2也想访问相同的资源,在自旋,内核运行进程P3,发生抢占,把进程P1的执行停止,用来运行了P3,则P2和P1就会造成死锁,必须等到处理器
让P1运行,然后进程P1释放自旋锁,P2才可以运行。如果P1一直得不到CPU的时间,那么P2也跟着一直自旋下去。
为了避免的这种况,规定自旋锁的规则是:任何拥有自旋锁的代码都必须是原子,不能休眠,事实上,它不能因为任何原因放弃处理器,除了服务中断以外(某些情况下此时也不能放弃处理器)


情景1:
驱动程序正在执行,并且已经获得了一个锁,这个锁控制着对设备的访问,在拥有这个锁的时候,设备产生一个中断,它导致中断处理例程被调用。而中断处理程序例程在访问设备之前,也要获得这个锁,
在中断处理例程中拥有锁是合法的,这也是为什么自旋锁不能休眠的一个原因。但是,当中断例程在最初拥有锁的代码所在的处理器上运行时,会发生什么情况呢?在中断例程自旋时,非中断代码将没有任何机会
来释放这个锁。处理器将会永远自选下去。


解决方法:我们需要在拥有自旋锁时禁止中断(仅在本地CPU上)。




循环缓冲区:


已懂!


原子变量:
是一个int型的变量,但是又不能全是int型,在atomic_t变量不能记录大于24位的整数。















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

john_liqinghan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值