内核中的锁机制

atomic(原子操作):
原型:atomic_t数据类型,atomic_inc(atomic_t *v)将v加1
1,原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。
2, 在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。(这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。)
在多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。

在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。



3,在使用C代码编写程序时,并不能保证编译器会为a=a+1或者a++这样的代码的操作限定为一个原子指令,所以Linux内核提供专门atomic_t类型和一些专门的函数和宏,这些函数和宏作用于atomic_t类型的变量,并当作单独的,原子的汇编语言指令来使用。相关函数如下:

函数说明
atomic_read(v)返回*v
atomic_set(v, i)把*v置为i
atomic_add(i, v)给*v增加i
atomic_sub(i, v)从*v减去i
atomic_sub_and_test(i, v)从*v减去i,如果结果为0则返回1,否则返回0
atomic_inc(v)把1加到*v
atomic_dec(v)从*v减去1
atomic_dec_and_test(v)从*v减去1,如果结果为0则返回1,否则返回0
atomic_inc_and_test(v)把1加到*v,如果结果为0则返回1,否则返回0
atomic_add_negative(i, v)把i加到*v,如果结果为负,则返回1,否则返回0
atomic_inc_return(v)把1加到*v,返回*v的值
atomic_dec_return(v)从*v总减去1,并返回*v的值
atomic_add_return(i, v)把i加到*v并返回
atomic_sub_return(i, v)从*v减去i并返回

还有一些操作掩码的函数:

函数说明
test_bit(nr, addr)返回*addr的nr位的值
set_bit(nr, addr)设置*addr的nr位
clear_bit(nr, addr)清空*addr的nr位
change_bit(nr, addr)转换*addr的nr位
test_and_set_bit(nr, addr)设置*addr的nr位并返回原值
test_and_clear_bit(nr, addr)清*addr的nr位并返回原值
test_and_change_bit(nr, addr)转换*addr的nr位并返回原值
atomic_clear_mask(mask, addr)清mask指定的*addr的所有位
atomic_set_mask(mask, addr)设置mask指定的*addr的所有位

http://guojing.me/linux-kernel-architecture/posts/atomic-operations/

http://www.wowotech.net/kernel_synchronization/atomic.html




spin_lock(自旋锁):
原型:spinlock_t数据类型,spin_lock(&lock)和spin_unlock(&lock)是加锁和解锁
1,busy_waiting 忙等,等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态。
2,常用于短期保护某段代码,同时,持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁
如果是单核处理器,则自旋锁定义为空操作,因为简单的关闭中断即可实现互斥
3,spinlock的意思是,关调度,别人别和我抢这个临界区,同时也不让其它核抢。
4,自旋锁spin lock是busy-waiting。就是说当没有可用的锁时,就一直忙等待并不停的进行锁请求,直到得到这个锁为止。这个过程中cpu始终处于忙状态,不能做别的任务。
5,自旋锁会关调度。
6,等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态(忙等待),所以常用于短期保护某段代码
同时,持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁
如果是单核处理器,则自旋锁定义为空操作,因为简单的关闭中断即可实现互斥


mutex(互斥锁):
1,互斥锁也是由原子操作和自旋锁实现的。
2,mutex中表示是否上锁的count计数成员(如果为1,就是未上锁,为0,就是已经上锁)就是就是一个原子操作。
3,信号量mutex是sleep-waiting。 就是说当没有获得mutex时,会有上下文切换,将自己、加到忙等待队列中,直到另外一个线程释放mutex并唤醒它,而这时CPU是空闲的,可以调度别的任务处理。




(3)信号量与互斥量
struct semaphore数据类型,down(struct semaphore * sem)和up(struct semaphore * sem)是占用和释放
struct mutex数据类型,mutex_lock(struct mutex *lock)和mutex_unlock(struct mutex *lock)是加锁和解锁
竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区
互斥量与信号量的区别?(转载但找不到原文出处)
(1)互斥量用于线程的互斥,信号线用于线程的同步
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
(2)互斥量值只能为0/1,信号量值可以为非负整数
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到




总结
(1)Mutex适合对锁操作非常频繁的场景,并且具有更好的适应性。尽管相比spin lock它会花费更多的开销(主要是上下文切换),但是它能适合实际开发中复杂的应用场景,在保证一定性能的前提下提供更大的灵活度。


(2)spin lock的lock/unlock性能更好(花费更少的cpu指令),但是它只适应用于临界区运行时间很短的场景。而在实际软件开发中,除非程序员对自己的程序的锁操作行为非常的了解,否则使用spin lock不是一个好主意(通常一个多线程程序中对锁的操作有数以万次,如果失败的锁操作(contended lock requests)过多的话就会浪费很多的时间进行空等待)。


(3)更保险的方法或许是先(保守的)使用 Mutex,然后如果对性能还有进一步的需求,可以尝试使用spin lock进行调优。毕竟我们的程序不像Linux kernel那样对性能需求那么高(Linux Kernel最常用的锁操作是spin lock和rw lock)。


 
[转]mutex和spin lock的区别
mutex和spin lock的区别和应用(sleep-waiting和busy-waiting的区别)2011-10-19 11:43
信号量mutex是sleep-waiting。 就是说当没有获得mutex时,会有上下文切换,将自己、加到忙等待队列中,直到另外一个线程释放mutex并唤醒它,而这时CPU是空闲的,可以调度别的任务处理。


而自旋锁spin lock是busy-waiting。就是说当没有可用的锁时,就一直忙等待并不停的进行锁请求,直到得到这个锁为止。这个过程中cpu始终处于忙状态,不能做别的任务。


例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0 和Core1上。 用spin-lock,coer0上的线程就会始终占用CPU。
另外一个值得注意的细节是spin lock耗费了更多的user time。这就是因为两个线程分别运行在两个核上,大部分时间只有一个线程能拿到锁,所以另一个线程就一直在它运行的core上进行忙等待,CPU占用率一直是100%;而mutex则不同,当对锁的请求失败后上下文切换就会发生,这样就能空出一个核来进行别的运算任务了。(其实这种上下文切换对已经拿着锁的那个线程性能也是有影响的,因为当该线程释放该锁时它需要通知操作系统去唤醒那些被阻塞的线程,这也是额外的开销)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Linux内核,有多种机制可用于实现同步和互斥操作。以下是一些常见的机制: 1. 自旋(Spinlock):自旋是一种基于忙等待的机制。当一个进程或线程尝试获取自旋时,如果已被占用,它会一直自旋等待,直到被释放。 2. 互斥(Mutex):互斥是一种基于阻塞的机制。当一个进程或线程尝试获取互斥时,如果已被占用,它会被阻塞,直到被释放。 3. 读写(ReadWrite Lock):读写允许多个读操作同时进行,但只有一个写操作可以进行。读操作之间不会互斥,而写操作会独占资源。 4. 原子操作(Atomic Operations):原子操作是一种不可断的操作,可以确保在多线程环境下对共享变量的原子性访问。原子操作可以用于实现简单的同步和互斥。 5. 信号量(Semaphore):信号量是一种计数器,用于控制对共享资源的访问。它可以用于限制同时访问资源的进程或线程数目。 6. 屏障(Barrier):屏障是一种同步机制,它可以使一组进程或线程在某个点上等待,直到所有进程或线程都到达该点,然后再继续执行。 除了上述常见的机制,Linux内核还提供了其他更高级的机制,如读写自旋(Read-Write Spinlock)、顺序(Seqlock)等,用于满足不同场景下的同步需求。 这些机制在Linux内核被广泛应用于实现同步和互斥操作,确保共享资源的正确访问和保护。选择适当的机制取决于具体的需求和性能要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值