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位的整数。
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位的整数。