《Chapter 9~10 内核同步》
临界区(CS):访问共享资源的代码段。
造成并发的原因:
锁的使用本身并不是难点,真正的挑战在于辨认出需要共享的数据和相应的临界区。
在最开始设计代码的时候就要想到要使用锁,而不是在完成代码后再去加锁。
interrupt-safe / SMP-safe / preempt-safe
预防死锁的一个比较方便的方法(不能彻底解决):按顺序枷锁。
死锁的四个条件:
互斥(mutual exclusion),
请求与保持(hold and wait),
非剥夺(non-preempt),
循环等待(circular wait)。
Linux锁粒度越来越细,可扩展性也很好。
原子变量,原子位操作。
自旋锁:
自旋锁可以使用在中断处理函数中(这里不能使用信号量,因为信号量会导致睡眠)。
申请自旋锁前,先关本地中断(有接口实现关中断和申请自旋锁)。
针对数据结构加锁。
spin_try_lock()试图去获得某个自旋锁,如果该锁已被争用,立刻返回非零值,不会等待锁被释放。
读写自旋锁:共享读,互斥写。
错误使用读写自旋锁,导致死锁:
read_lock(&mr_rwlock);
write_lock(&mr_rwlock);//等待所有读者退出(包括自己这个读者)
信号量:
一种睡眠锁。1986年由Dijkstra提出,接口P(),V()(来自荷兰语Proberen和Vershogen,分别是探查和增加的意思,Linux中称为down(),up())。
读写信号量。
互斥体:
可以睡眠的强制互斥锁。
完成变量:
一个任务完成后需要通知另一个任务,使用完成变量(completion variable)。
三个API:
init_completion(struct completion *); wait_for_completion(struct completion *); complete(struct completion *);
BKL大内核锁:
Linux支持单CPU到支持SMP过渡期的锁。
顺序锁:
u64 get_jiffies_64(){ unsigned long seq; u64 ret; do{ seq = read_seqbegin(&xtime_lock); ret = jiffies64; }while(read_seqretry(&xtime_lock,seq)); return ret; }
//写jiffies write_seqlock(&xtime_lock); jiffies_64 += 1; write_sequnlock(&xtime_lock);
顺序和屏障:
rmb()保证其后的读内存操作在其前的读内存操作之后执行(现代处理器中为优化流水线可能会有乱序执行的情况)。
wmb()与之类似,mb()是rmb(),wmb()两种屏障。
例如:a,b原来初始值为1,2;
线程1 | 线程2 |
④a=3; | --- |
mb(); | --- |
① b=4; | ② c=b; |
--- | rmb(); |
--- | ③ d=a; |
如果没有mb(),rmb(),则可能发生c=4,d=1的情况(执行序列如图中序号,我们并不希望是这样的)。加上mb(),rmb()之后就避免了这种情况。
barrier()阻止编译器跨屏障对载入或存储操作优化。
《Chapter 11 定时器和时间管理》
#define time_before(unkown,know) ((long)(known)-(long)(unknow)<0)
x86 PIT(可编程中断时钟)
定时器:由这个结构体实现。
struct timer_list {
struct list_head entry; //定时器链表的入口
unsigned long expires; //以jiffies为单位的时值
void (*function)(unsigned long) //定时器处理函数
unsigned long data; //定时器处理函数的参数
struct tvec_t_base_s *base; //定时器内部值
}
延迟执行:
1、忙等:
忙等: while( time_before(jiffies, timeout) ); 忙等变体: while( time_before(jiffies, timeout) ) cond_resched();
2、内核接口:
void udelay(…);
void ndelay(…);
void mdelay(…);
3、schedule_timeout();