linux 中的原子操作和内存屏蔽

由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态。这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题。

因此,我们需要一些解决方法,在Linux内核中它提供了如下几种锁机制,供用户在针对不同情况分别或配合使用,包括:原子操作内存屏障自旋锁读写自旋锁顺序锁信号量读写信号量完成量RCU机制BKL(大内核锁)等等

一、原子操作

所谓原子操作,就是“不可中断的一个或一系列操作”。不同构架的cpu具体实现方式不同,对於单核cpu没有意义

Linux内核提供了两组原子操作接口:一组是针对整数进行操作;另一组是针对单独的位进行操作。

1、原子整数操作

atomic_read(v) 读取v指向的原子变量的值。

atomic_set(v,i) 设置v指向的原子变量的值为i。

void atomic_sub(int i,atomic_t *v) 从v指向的原子变量减去i。

void atomic_inc(atomic_t *v) 递增v指向的原子变量。

void atomic_dec(atomic_t *v) 递减v指向的原子变量。

int atomic_dec_and_test(atomic_t *v) 递减v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。

int atomic_inc_and_test(atomic_t *v) 递增v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。

int atomic_add_negative(int i,atomic_t *v) 将v指向的原子变量加上i,并测试结果是否为负。若为负,返回真。这个操作用于实现semaphore。

2、原子位操作

void set_bit(int nr, void *addr) 原子设置addr所指的第nr位

void clear_bit(int nr, void *addr) 原子的清空所指对象的第nr位

void change_bit(nr, void *addr) 原子的翻转addr所指的第nr位

int test_bit(nr, void *addr) 原子的返回addr位所指对象nr位

int test_and_set_bit(nr, void *addr) 原子设置addr所指对象的第nr位,并返回原先的值

int test_and_clear_bit(nr, void *addr) 原子清空addr所指对象的第nr位,并返回原先的值

int test_and_change_bit(nr, void *addr) 原子翻转addr所指对象的第nr位,并返回原先的值

二、内存屏蔽

值得一提的是,使用这些内存屏障,相当于停用了处理器或编译器提供的优化机制,显然这样的结果必然影响程序的性能。

内存屏障,从处理器角度来说,是用来串行化读写操作的,从软件角度来讲,就是用来解决顺序一致性问题的。编译器不是要打乱代码执行顺序吗,处理器不是要乱序执行吗,你插入一个内存屏障,就相当于告诉编译器,屏障前后的指令顺序不能颠倒,告诉处理器,只有等屏障前的指令执行完了,屏障后的指令才能开始执行。当然,内存屏障能阻挡编译器乱来,但处理器还是有办法。处理器中不是有多发射、乱序执行、顺序完成的概念吗,它在内存屏障时只要保证前面指令的读写操作,一定在后面指令的读写操作完成之前完成,就可以了。所以内存屏障才会对应有读屏障、写屏障和读写屏障三类。如x86之前保证写操作都是顺序完成的,所以不需要写屏障,但现在也有部分ia32处理器的写操作变成乱序完成,所以也需要写屏障。

其实,除了专门的读写屏障指令,还有很多指令的执行是带有读写屏障功能的,比如带lock前缀的指令。在专门的读写屏障指令出现之前,linux就是靠lock熬过来的。
至于在那里插入读写屏障,要视软件的需求而定。读写屏障无法完全实现顺序一致性,但多处理器上的线程也不会一直盯着你的执行顺序看,只要保证在它看过来的时候,认为你符合顺序一致性,执行不会出现你代码中没有预料到的情况。所谓预料外的情况,举例而言,你的线程是先给变量a赋值,再给变量b赋值,结果别的处理器上运行的线程看过来,发现b赋值了,a却没有赋值,(注意这种不一致不是由缓存不一致造成的,而是处理器写操作完成的顺序不一致造成的),这时就要在a赋值与b赋值之间,加一个写屏障。

mb() 适用于多处理器和单处理器的内存屏障。

rmb() 适用于多处理器和单处理器的读内存屏障。

wmb() 适用于多处理器和单处理器的写内存屏障。

smp_mb() 适用于多处理器的内存屏障。

smp_rmb() 适用于多处理器的读内存屏障。

smp_wmb() 适用于多处理器的写内存屏障。

三、顺序锁

顺序锁是对读写锁的一种优化。

1.读执行单元绝对不会被写执行单元阻塞。即读执行单元可以在写执行单元对被顺序锁保护的共享资源进行写操作的同时仍然可以继续读,而不必等待写执行单元完成之后再去读,同样,写执行单元也不必等待所有的读执行单元读完之后才去进行写操作

2.写执行单元与写执行单元之间仍然是互斥的。

3.如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新去读数据,以便确保读到的数据是完整的。

4.要求共享资源中不能含有指针。


seqlock_init(x);       //动态初始化

DEFINE_SEQLOCK(x);     //静态初始化

void write_seqlock(seqlock_t* sl); //写加锁

int write_tryseqlock(seqlock_t* sl); //尝试写加锁

write_seqlock_irqsave(lock, flags); //local_irq_save() + write_seqlock()

write_seqlock_irq(lock); //local_irq_disable() + write_seqlock()

write_seqlock_bh(lock); //local_bh_disable() + write_seqlock()

void write_sequnlock(seqlock_t* sl); //写解锁

write_sequnlock_irqrestore(lock, flags); //write_sequnlock() + local_irq_restore()

write_sequnlock_irq(lock); //write_sequnlock() + local_irq_enable()

write_sequnlock_bh(lock); //write_sequnlock() + local_bh_enable()


参考:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html

发布了34 篇原创文章 · 获赞 5 · 访问量 4万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章