Linux的内核同步

临界区

多个线程操作的共享数据的代码区域。

造成代码并发执行的原因

中断
软中断和tasklet
内核抢占
睡眠
多个处理器并发执行

要给共享数据加锁,而不是给代码

原子操作:开销比锁小很多

Linux内核提供了两组原子操作接口:一组针对整数操作,另一组针对位操作。几乎所有的架构都实现了这两组接口。

原子整数操作:atomic_t是32位,atomic64_t是64位

typedef struct {
	volatile int counter;
}atomic_t;

原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的,这些函数,通常被定义成一个宏。
常用函数:

ATOMIC_INIT(int i)  //声明一个atomic_t变量,将它初始化为i
int atomic_read(atomic_t* v); //原子读,返回int
void atomic_set(atomic* v,int i);
void atomic_add(int i, atomic_t*v);
void atomic_sub(int i,atomic_t*v);
...

原子位操作

位操作是对普通的内存地址操作的,将指定位置的位修改,不限制指定位置的数据类型。
它的参数是一个指针和一个位号。

void set_bit(int nr,void* addr);//原子地设置addr对象的第nr位
void clear_bit(int nr,void *addr);
void change_bit(int nr,void *addr);//原子地翻转
int test_and_set_bit(int nr,void *addr);//设置并且返回原先的值
int test_bit(int nr,void *addr);//原子地返回add的第nr位

当涉及一个或多个数据结构,无法使用原子操作时,就只能使用锁了

锁的是数据,而不是代码

自旋锁

不需要上下文切换,一个while持续申请锁。如果获得锁很快,那么自旋锁很合适。
自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为他们会导致睡眠)。

DEFINE_SPINLOCK(mr_lock);
spin_lock(&mr_lock);
//临界区
spin_unlock(&mr_lock);

读写自旋锁

DEFINE_RWLOCK(mr_rwlock);
读区:
read_lock(&mr_rwlock);
//临界区(只读)
read_unlock(&mr_rwlock);

写区:
write_lock(&mr_rwlock);
//临界区(读写)
write_unlock(&mr_rwlock);

信号量

信号量是一种睡眠锁。
如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进到一个等待队列,然后让其睡眠。当其信号量可用时,处于等待队列中的那个任务将被唤醒,并且获得信号量。

计数信号量和二进制信号量
读写信号量

互斥体mutex

其行为和二进制信号量相似,相当于一个简化的信号量。

锁的选择

尽量使用mutex,而不是信号量。低开销就使用自旋,短期使用自旋。

禁止抢占

内核是抢占的,内核中的进程在任何时刻都可能停下来以便另一个具有更高优先级的进程运行。
禁止内核抢占,可以在一段代码中禁止内核其他进程抢占,从而保证其原子性。

preempt_disable();
/* 抢占被禁止,执行原子的代码段 */
preempt_enable();

顺序和屏障

如果一段代码没有顺序依赖(由编译器和处理器判断),则编译器和处理器会对这段代码动态重新排序,从而获得其认为的最大性能。
如果有依赖顺序,则不会重新排序。

屏障

rmb()或者wmb()函数相当于指令,告诉处理器在继续执行前提交所有尚未处理的载入或者存储指令。
相当于一个分界屏障,屏障之前是已经提交的。

你可能感兴趣的:(Linux,linux,运维,服务器)