一、原子操作
一)、相关介绍
1、原子操作可以保证指令以原子的方式执行——执行过程中不被打断。
2、内核提供了两组原子操作接口——针对整数进行操作,另一组针对单独的位进行操作。
二)、原子整数操作
1、针对整数的原子操作只能对atomic_t类型的数据进行处理。atomix_t类型定义在文件<linux/types.h>中:
typedef struct {
volatile int counter;
}atomic_t;
1)、尽管Linux支持的所有机器上的整数数据都是32位的,但是使用atomic_t的代码只能将该类型的数据当作24位来用。
2、使用原子整数操作需要的声明都在<asm/atomic.h>文件中。
3、原子整数操作列表(P143 表10.1)
1)、原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的。如果某个函数本来就是原子操作,那么它往往会被定义成一个宏。
三)、63位原子操作
1、对64位原子操作只能通过atomic64.t类型操作。
typedef struct {
volatile long counter;
}atomic64_t;
2、64位原子整型操作(P145 表10.2)
四)、原子位操作
1、位操作汗是是对普通的内存地址进行操作,它的参数是一个指针和一个位号,第0位是给定地址的最低有效位。
2、由于原子位操作是对普通的指针进行的操作,所以不像原子整数操作有对应atomic_t,其没有特殊的类型。相反,只要指针指向了任何你希望的数据,就可以对它进行操
作。
3、原子位操作(P146 表10-3)
4、内核提供了两个例程用来从指定的地址开始搜索第一个被设置的位:
int find_first_bit(unsigned *addr, unsigned int size);
int find_fiest_zero_bit(unsigned long *addr, unsigned int size);
二、自旋锁
一)、相关介绍
1、Linux内核中最常见的锁是自旋锁。自旋锁,最多只能被一个可执行的线程持有。在任意期间,自旋锁都可以防止多于一个的执行线程同时进入临界区。同一个锁可以用在
多个位置。
2、一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),这种行为是自旋锁的要点。所以自旋锁不应被长时间持有。
二)、自旋锁方法
1、自旋锁的实现和体系结构有关,代码往往通过汇编实现。这些与体系结构相关的代码定义在文件<asm/spinlock.h>中,实际需要用到的接口定义在文件<linux/spinlock.h>
中。自旋锁基本使用形式如下:
DEFONE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
/*临界区. . . */
spin_unlock(&mr_lock);
2、内核提供的禁止中断同时请求锁的接口:
DEFONE_SPINLOCK(mr_lock);
unsigned long flags;
spin_lock_irqsave(&mr_lock);
/*临界区. . . */
spin_unlock_irqrestore(&mr_lock);
1)、函数spin_lock_irqsave()保存中断的当前状态,并禁止本地中断,然后再去获取指定的锁。
2)、反过来spin_unlock_irqrestore();对指定的锁解锁,然后让中断恢复到加锁前的状态。
3、如果能确定中断在加锁前是激活的,就不需要解锁后恢复中断以前的状态。可以无条件地在解锁时激活中断。这时,使用spin_lock_irq()和spin_unlock_irq更好一些:
DEFONE_SPINLOCK(mr_lock);
spin_lock_irq(&mr_lock);
/*临界区. . . */
spin_unlock_irq(&mr_lock);
三)、其他针对自旋锁的操作
1、可以使用spin_lock_init()方法来初始化动态创建的自旋锁。
2、spin_try_lock()试图获得某个特定的自旋锁,如果该锁已经被争用,那么该方法立即会返回一个非0值,而不会自旋锁等待被释放;如果成功获得了这个锁,该函数返回0。
3、spin_is_locked()方法用于检查特定的锁当前是否已经被占用,如果已被占用,返回非0值,否则返回0.该方法制作判断,并不实际占用。
4、自旋锁方法列表(P150 表10-4)
四)、自旋锁和下半部
三、读-写锁
1、Linux内核提供了专门的读-写自旋锁,这种自旋锁为读和写分别提供了不同的锁。一个或多个读任务可以并发地持有读者锁;相反,用于写的锁最多只能被一个写任务持
有,而且此时不能有并发的读操作。
2、有时把读/写锁叫做共享/排斥锁,或者并发/排斥锁,因为这种锁以共享和排斥的形式获得使用。
3、读/写锁操作
1)、读/写自旋锁的初始化:
DEFINE_RWLOCK(mr_lock);
2)、然后,在读者的代码分支中使用如下函数:
read_lock(&mr_lock);
/*临界区(只读)...*/
read_unlock(&mr_lock);
3)、最后,在写者的代码分支中使用如下函数:
/*临界区(读写)...*/
write_unlock(&mr_lock);
4、读-写自旋锁方法列表(P152 表10-5)
五、信号量
1、信号量和自旋锁的差异。
2、如果需要在自旋锁和信号量中做出选择,应该根据锁被持有的时间长短作判断。另外,信号量不同于自旋锁,它不会禁止内核抢占,所以持有信号量的代码可以被抢占。
二)、计数信号量和二值信号量
1、信号量同时允许的持有者数量可以在声明信号量时指定。这个值称为使用者数量或简单地叫数量。
1)、通常情况下,信号量和自旋锁一样,在一个时刻仅允许有一个锁持有者。这时技术等于1,这样的信号量叫作二值信号量。或者称为互斥信号量。
2)另一方面ian,初始化时也可以把数量设置为大于1的非0值。这种请情况,信号量被称为计数信号量,它允许在一个时刻至多有count个锁持有者。
2、信号量支持两个原子操作P()和V()。前者叫做测试操作,后者叫做增加操作。后来的系统把两种操作分别叫做down()和up(),Linux也遵从这种叫法。
三)、创建和初始化信号量
1、信号量的实现与体系结构有关,具体实现方式在文件<asm/semaphore.h>中。struct semaphore类型用来表示信号量。
2、静态声明信号量
I、struct semaphore name;
sema_init(&name, count);
II、创建更普通的互斥信号量可以使用以下快捷方式:
static DECLARE_MUTEX(name);
3、动态声明信号量
I、sema_init(sem, count);
II、初始化一个东动态创建的互斥量:
init_MUTEX(sem);
四)、使用信号量
1、获取信号量
1)、函数down_interruptible()试图获取指定的信号量,如果信号量不可用,它将把调度进程置成TASK_INTERRUPTIBLE——进入睡眠。
2)、另一个函数down()会让进程在TASK_UNINTERRUPTIBLE状态下睡眠。
3)、使用down_trylock()函数,可以尝试以堵塞方式来获取指定的信号量。在信号量已被占据时,它立即返回非0值;否则。它返回0,而且让你成功持有信号量锁。
2、要释放指定的信号量,需要调用up()函数。
3、信号量方法列表(P155 表10-6)
五、读-写信号量
1、读-写信号量在内核中是由rw_semaphore结构表示,定义在文件<linux/rwsem.h>中。
2、通过以下语句可以创建静态声明的读-写信号量:
static DECLARE_RWSEM(name);
3、动态创建的读-写信号量可以通过以上函数初始化:
init_rwsem(struct rw_semaphore *sem);
4、读-写信号量也提供了down_read_trylock()和down_write_trylock()方法。
5、读-写信号量相比读-写自旋锁多一种特有的操作:downgrade_write()。这个函数可以动态地将获取的写锁转换为读锁。
六、互斥体
一)、相关简介
1、mutex在内核中对应数据结构mutex,其行为和使用计数为1的信号量类似,但操作接口更简单,实现也更高效,而且使用限制更强。
2、静态地定义mutex:
DEFINE_MUTEX(name);
3、动态初始化mutex:
mutex_init(&mutex);
4、对互斥体锁定和解锁:
mutex_lock(&mutex);
/* 临界区 */
mutex_unlock(&mutex);
5、Mutex方法(P157 表10-7)
6、mutex使用场景
二)、信号量和互斥体
三)、自旋锁和互斥体
1、自旋锁和互斥体的比较。
七、完成变量
1、如果在内核中一个任务需要发出通知另一个任务发生了某件事情,利用完成变量是使两个任务得以同步的简单方法。
2、完成变量由结构completion表示,定义在<linux/completion.h>中。
3、通过以下宏创建完成变量:
1)、静态:DECLARE_COMPLETION(mr_comp);
2)、动态:init_completion()
4、在一个特定的完成变量上,需要等待的任务调用wait_for_completion()来等待特定事件。当特定事件发生后,产生事件的任务调用completion()来发送信号唤醒正在等待的
任务。
5、完成变量方法(P159 表10-9)
八、BLK:大内核锁
1、BKL函数列表(P160 表10-10)
九、顺序锁
1、顺序锁,简称seq锁。这种锁提供了一种很简单的机制,用于读写共享数据。
2、顺序锁操作
1)、定义一个seq锁
seqlock_t mr_seq_lock = DEFINE_SEQLOCK(mr_seq_lock);
2)、写锁
write_seqlock(&mr_seq_lock);
/*写锁被获取 . . . */
write_sequnlock(&mr_seq_lock);
3、使用seq锁的选择。
十、禁止抢占
1、内核抢占的相关函数(P162 表10-11)
十一、顺序和屏障
1、内核和编译器屏障方法(P165 表10-12)