1、原子操作可以保证指令以原子的方式执行——执行过程不被打断。内核提供了两组原子操作接口,一组针对整数进行操作,一组针对单独的位进行操作。
 
    2、针对整数的原子操作只能对atomic_t类型的数据进行处理。引入这个特殊数据类型主要是出于三个原因:首先,让原子函数只接受atomic_t类型的操作数可以确保原子操作只与这种特殊类型的数据一起使用。同时这也保证了该类型的数据不会被传递给其他任何非原子函数。其次,使用atomic_t类型确保编译器不对相应的值进行访问优化——这点使得原子操作最终接收到正确的内存地址,而不只是一个别名。最后,在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽期间的差异。
    3、尽管Linux支持的所有机器上的整型数据都是32位的,但是使用atomic_t的代码只能将该类型的数据当作24位来用。这是因为在SPARC体系结构上对原子操作缺乏指令级的支持,所以32位int类型的低8位被嵌入一个锁中,利用该锁来避免对原子类型数据的并发访问。
   
    4、原子整数操作最常见的应用是实现计数器,一般使用atomic_inc()和atomic_dec()这两个函数。所有的标准原子整数操作见下表:
 

原子整数操作

描述

ATOMIC_INIT(int i)

在声明一个atomic_t变量时,将它初始化为i

int atomic_read(atomic_t *v)

原子地读取整数变量v

void atomic_set(atomic_t *v, int i)

原子地设置v值为i

void atomic_add(int i, atomic_t *v)

原子地给v加i

void atomic_sub(int i, atomic_t *v)

原子地从v减i

void atomic_inc(atomic_t *v)

原子地给v加1

void atomic_dec(atomic_t *v)

原子地给v减1

int atomic_sub_and_test(int i, atomic_t *v)

原子地从v减i,若结果等于0返回真,否则返回假

int atomic_add_negative(int i, atomic_t *v)

原子地从v加i,若结果是负数返回真,否则返回假

int atomic_dec_and_test(atomic_t *v)

原子地从v减1,若结果等于0返回真,否则返回假

int atomic_inc_and_test(atomic_t *v)

原子地从v加1,若结果等于0返回真,否则返回假

 
    5、原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的。在编写代码时,能使用原子操作的时候,就尽量不要使用复杂的加锁机制。对多数体系结构来讲,原子操作与更复杂的同步方法相比较,给系统带来的开销小,对高速缓存行的影响也小。
 
    6、内核提供了针对位这一级数据进行操作的函数,他们定义在中。位操作函数是对普通的内存地址进行操作的,它的参数是一个指针和一个位号。标准原子位操作见下表:
 

原子位操作

描述

void set_bit(int nr, void *addr)

原子地设置addr所指对象的第nr位

void clear_bit(int nr, void *addr)

原子地清空addr所指对象的第nr位

void change_bit(int nr, void *addr)

原子地翻转addr所指对象的第nr位

int test_and_set_bit(int nr, void *addr)

原子地设置addr所指对象的第nr位,并返回原先的值

int test_and_clear_bit(int nr, void *addr)

原子地清空addr所指对象的第nr位,并返回原先的值

int test_and_change_bit(int nr, void *addr)

原子地翻转addr所指对象的第nr位,并返回原先的值

int test_bit(int nr, void *addr)

原子地返回addr所指对象的第nr位

 
内核还提供了一组与上述操作对应的非原子位函数,其名字前缀多两个下划线。内核还提供了两个例程用来从指定的地址开始搜索第一个被设置(或未被设置)的位:
int find_first_bit(unsigned long *addr,unsigned int size)
int find_first_zero_bit(unsigned long *addr,unsigned int size)
 
7、自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获得一个被争用(已经被持有)的自旋锁,那么该线程就会一直进行忙循环——旋转——等待锁重新可用。一个被争用的自旋锁使得请求它的线程在等待重新可用时自旋(特别浪费处理器时间),所以自旋锁不应该被长时间持有。自旋锁的初衷是在短期内进行轻量级加锁。另外,自旋锁是不可递归的。
 
8、自旋锁可以使用在中断处理程序中。在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断,否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图争用这个已经被持有的自旋锁。顺便提一下,选项CONFIG_DEBUG_SPINLOCK可用来调试自旋锁。针对自旋锁的操作见下表:
 

方法

描述

spin_lock( )

获取指定的自旋锁

spin_lock_irq( )

禁止本地中断并获取指定的锁

spin_lock_irqsave( )

保存本地中断的当前状态,禁止本地中断,并获取指定的锁

spin_unlock( )

释放指定的锁

spin_unlock_irq( )

释放指定的锁,并激活本地中断

spin_unlock_irqrestore( )

释放指定的锁,并让本地中断恢复到以前的状态

spin_lock_init( )

初始化指定的spinlock_t

spin_trylock( )

试图获取指定的锁,如果未获取则返回非0

spin_is_locked( )

如果指定的锁当前正在被获取则返回非0,否则返回0

spin_lock_bh( )

禁止所有下半部的执行,并获取指定的锁

spin_unlock_bh( )

释放指定的锁,允许下半部的执行

 
9、当下半部和进程上下文共享数据时,需要加锁的同时还要禁止下半部执行;当中断处理程序和下半部共享数据时,需要加锁的同时还要禁止中断;当数据被两个不同种类的tasklet共享或软中断共享时,没有必要禁止下半部。
 
10、当对某个数据结构的操作可以被划分为读/写两种类别时,可以使用Linux专门提供的读——写自旋锁。这种自旋锁为读和写分别提供了不同的锁。一个或多个读任务可以并发的持有读者锁;相反,用于写的锁最多只能被一个写任务持有,而且此时不能有并发的读操作。
 
11、通常情况下,读锁和写锁会位于完全分割开的代码分支中,下面的代码将会带来死锁:
     read_lock(&mr_rwlock);
     write_lock(&mr_rwlock);
因为写锁会不断自旋,等待所有的读锁释放,其中也包括它自己。当确实需要写操作时,要在一开始就请求写锁。如果写和读不能清晰分开的话,那么就使用一般的自旋锁。多个读者可以安全地获得同一个读锁,即使一个线程递归地获得一个读锁也是安全的。这个特性使读——写自旋锁成为一种有用并且常用的优化手段。读——写锁这种机制照顾读要比照顾写多一点。读锁被持有时,写锁只能等待,但读者却可以继续成功地占用锁,大量的读者就会使挂起的写者处于饥饿状态。读——写锁的操作见下表:
 

方法

描述

read_lock( )

获取指定的读锁

read_lock_irq( )

禁止本地中断并获取指定的读锁

read_lock_irqsave( )

保存本地中断的当前状态,禁止本地中断并获取指定的读锁

read_unlock( )

释放指定的读锁

read_unlock_irq( )

释放指定的读锁,并激活本地中断

read_unlock_irqrestore( )

释放指定的读锁,并让本地中断恢复到以前的状态

write_lock( )

获取指定的写锁

write_lock_irq( )

禁止本地中断并获取指定的写锁

write_lock_irqsave( )

保存本地中断的当前状态,禁止本地中断并获取指定的写锁

write_unlock( )

释放指定的写锁

write_unlock_irq( )

释放指定的写锁,并激活本地中断

write_unlock_irqrestore( )

释放指定的写锁,并让本地中断恢复到以前的状态

write_trylock( )

试图获得指定的写锁;如果写锁不可用,返回非0值

rw_lock_init( )

初始化指定的rwlock_t

rw_is_locked( )

如果指定的锁当前已被持有,该函数返回非0值,否则返回0

 
12、如果加锁时间不长且代码不会睡眠(如中断处理程序),利用自旋锁是最佳选择;如果加锁时间可能很长或者在持有锁时有可能睡眠,那么最好使用信号量来完成加锁功能。Linux中的信号量是一种睡眠锁。如果一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器能重获自由,从而去执行其他代码。信号量不会禁止内核抢占,这意味着,信号量不会对调度的等待时间带来负面影响。
 
13、信号量允许任意数量的锁的持有者,而自旋锁在一个时刻最多允许一个任务持有它。信号量同时允许的持有者数量可以在声明信号量时指定,这个值称为使用者数量。该数值为1的信号量成为互斥信号量,大于1的称为计数信号量。信号量支持两个原子操作P()和V(),这两个名字来自荷兰语Proberen(测试操作)和Vershogen(增加操作)。后来的系统包括Linux把这两种操作分别叫做down()和up()。信号量的操作见下表:
 

方法

描述

sema_init(struct semaphore *, int)

以指定的计数值初始化动态创建的信号量

init_MUTEX(struct semaphore *)

以计数值1初始化动态创建的信号量

init_MUTEX_LOCKED(struct semaphore *)

以计数值0初始化动态创建的信号量(初始化为加锁状态)

down_interruptible(struct semaphore *)

试图获得指定的信号量,如果信号已被争用,则进入可中断睡眠状态

down(struct semaphore *)

试图获得指定的信号量,如果信号已被争用,则进入不可中断睡眠状态

down_trylock(struct semaphore *)

试图获得指定的信号量,如果信号已被争用,则立刻返回非0值

up(struct semaphore *)

释放指定的信号量,如果睡眠队列不空,则唤醒其中的一个任务

 
14、与自旋锁一样,信号量也有区分读——写访问。读——写信号量要比普通信号量更具优势。所有的读——写信号量都是互斥信号量。只要没有写者,并发持有读锁的读者数不限。相反,只有唯一的写者可以获得写锁。所有读——写锁的睡眠都不会被信号打断。读——写信号量相比读——写自旋锁多了一种特有的操作:downgrade_writer()。这个函数可以动态地将获取的写锁转换成读锁。
 
15、如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,利用完成变量是使两个任务得以同步的简单方法。完成变量提供了代替信号量的一个简单解决办法。如果一个任务要执行一些工作时,另一个任务就会在完成变量上等待,当这个任务完成后,会使用完成变量去唤醒在等待的任务。完成变量通常的用法是,将完成变量作为数据结构中的一项动态创建,而完成数据结构初始化工作的内核代码将调用wait_for_completion()进行等待。初始化完成后,初始化函数调用completion()唤醒在等待的内核任务。完成变量的操作见下表:
 

方法

描述

init_completion(struct completion *)

初始化指定的动态创建的完成变量

wait_for_completion(struct completion *)

等待指定的完成变量接受信号

completion(struct completion *)

发信号唤醒任何等待任务

 
16、Seq锁是在2.6内核版本中才引入的一种新型锁。这种锁提供了一种很简单的机制,用于读写共享数据。实现这种锁主要依靠一个序列计数器。当有疑义的数据被写入时,会得到一个锁,并且序列值会增加。在读取数据之前和之后,序列号都被读取。如果读取的序列号相同,说明在读操作进行的过程中没有被写操作打断过。如果读的值是偶数,那么就表明写操作没有发生。Seq锁对写者更有利,只要没有其它写者,写锁总是能被成功获得。另外,挂起的写者会不断地使得读操作循环,直到不再有任何写者持有锁为止。
 
17、如果数据对每个处理器是唯一的,这样的数据可能就不需要使用锁来保护,但如果内核是抢占式的,为了防止数据被多个进程以伪并发的方式访问,需要禁止内核抢占,禁止抢占的相关操作如下表:
 

方法

描述

Preempt_disable( )

禁止内核抢占

Preempt_enable( )

激活内核抢占并检查和执行被挂起的需要调度的任务

Preempt_enable_no_resched( )

激活内核抢占但不再进行调度

Preempt_count( )

返回抢占计数

 
18、当处理多处理器之间或硬件设备之间的同步问题时,有时需要在程序代码中以指定的顺序发出读内存和写内存指令。在和硬件交互时,时常需要确保一个给定的读操作发生在其它读或写操作之前。另外,在多处理器上,可能需要按写数据的顺序读数据。但编译器和处理器为了提高效率,可能对读和写重新排序。Linux提供了确保顺序的指令称做屏障。其操作见下表:
 

方法

描述

rmb( )

阻止跨跃屏障的载入动作发生重排序

read_barrier_depends( )

阻止跨跃屏障的具有数据依赖关系的载入动作重排序

wmb( )

阻止跨跃屏障的存储动作发生重排序

mb( )

阻止跨跃屏障的载入和存储动作重新排序

smp_rmb( )

在SMP上提供rmb( )功能,在UP上提供barrier( )功能

smp_read_barrier_depends( )

在SMP上提供read_barrier_depends( )功能,在UP上提供barrier( )功能

smp_wmb( )

在SMP上提供wmb( )功能,在UP上提供barrier( )功能

smp_mb( )

在SMP上提供mb( )功能,在UP上提供barrier( )功能

barrier( )

组织编译器跨屏障对载入或存储操作进行优化

Linux内核设计与实现读书笔记(8)-内核同步方法【转】的更多相关文章

  1. Linux内核设计与实现 读书笔记 转

    Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...

  2. Linux内核设计与实现——读书笔记2:进程管理

    1.进程: (1)处于执行期的程序,但不止是代码,还包括各种程序运行时所需的资源,实际上进程是正在执行的 程序的实时结果. (2)程序的本身并不是进程,进程是处于执行期的程序及其相关资源的总称. (3 ...

  3. 《Linux内核设计与实现》笔记-1-linux内核简单介绍

    一.Linux内核相对于传统的UNIX内核的比較: (1):Linux支持动态内核模块. 虽然Linux内核也是总体式结构,但是同意在须要的时候动态哦卸除(rmmod xxx)和载入内核模块(insm ...

  4. Linux内核设计与实现 读书笔记

    第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的ta ...

  5. Linux内核设计与实现——读书笔记1:内核简介

    内核:有的时候被称管理者或者操作系统核心,通常内核负责响应中断的中断服务程序, 负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间德内存管理程序 和网络,进程间通信等系统服务程序共同组 ...

  6. Linux Shell脚本攻略 读书笔记

    Linux Shell脚本攻略 读书笔记 这是一本小书,总共253页,但内容却很丰富,书中的示例小巧而实用,对我这样总是在shell门前徘徊的人来说真是如获至宝:最有价值的当属文本处理,对这块我单独整 ...

  7. 【2018.08.13 C与C++基础】C++语言的设计与演化读书笔记

    先占坑 老实说看这本书的时候,有很多地方都很迷糊,但却说不清楚问题到底在哪里,只能和Effective C++联系起来,更深层次的东西就想不到了. 链接: https://blog.csdn.net/ ...

  8. 《Linux内核分析与设计实现》读书笔记一

    第一章 Linux内核简介 1.1 Unix的历史 Unix的特点: Unix很简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的: 在Unix中,所有的东西都被当做文件对待. Unix的内核和 ...

  9. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单.2. 高效 ...

随机推荐

  1. 创建vpc网络

    vpc相关功能点: 模块 功能点 描述 备注 VPC 创建vpc网络 创建vpc网络,指定vpc网络名称   修改vpc网络 修改vpc网络名称   删除vpc网络 删除vpc网络   vpc相关命令 ...

  2. Visual Studio 2003安装包

    点击下载

  3. sql between and 边界问题

    1.不同的数据库对 BETWEEN...AND 操作符的处理方式是有差异的.需要自己测试 2.一般情况下.SQL Server中 between and是包括边界值的,not between不包括边界 ...

  4. 详解python 局部变量与全局变量

    本文将详细分析python的全局变量与局部变量,学过php的人都知道,php里面的全局变量是无法在function里面去使用的,只有超全局变量才可以,那么python会怎么处理全局变量与局部变量呢?下 ...

  5. Mybatis学习系列(三)动态SQL

    在mapper配置文件中,有时需要根据查询条件选择不同的SQL语句,或者将一些使用频率高的SQL语句单独配置,在需要使用的地方引用.Mybatis的一个特性:动态SQL,来解决这个问题. mybati ...

  6. Spring MVC温故而知新 – 参数绑定、转发与重定向、异常处理、拦截器

    请求参数绑定 当用户发送请求时,根据Spring MVC的请求处理流程,前端控制器会请求处理器映射器返回一个处理器,然后请求处理器适配器之心相应的处理器,此时处理器映射器会调用Spring Mvc 提 ...

  7. IIS注册asp.net4.0

    1. 运行->cmd 2. cd  C:\Windows\Microsoft.NET\Framework64\v4.0.30319 3. aspnet_regiis.exe -i

  8. springboot之mybatis别名的设置

    mybatis别名设置 在具体的mapper.xml文件中,定义很多的statement,statement需要parameterType指定输入参数的类型.需要resultType指定输出结果的映射 ...

  9. Hbase写入量大导致region过大无法split问题

    最近在线上往hbase导数据,因为hbase写入能力比较强,没有太在意写的问题.让业务方进行历史数据的导入操作,中间发现一个问题,写入速度太快,并且业务数据集中到其中一个region,这个region ...

  10. centos7 mysql cluster集群搭建基于docker

    1.准备 mn:集群管理服务器用于管理集群的其他节点.我们可以从管理节点创建和配置集群上的新节点.重新启动.删除或备份节点. db2/db3:这是节点间同步和数据复制的过程发生的层. db4/db5: ...