版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/83028651

现代操作系统有三大特性:中断处理、多任务处理和多核处理器(SMP)。这些特性导致当多个进程、线程或者CPU同时访问一个资源时,可能会发生错误,这些错误是操作系统运行所不允许的,这就是并发。

并发是指在操作系统中,一个时间段中有几个程序正在运行,且这几个程序都是在同一个处理机上运行,但任一时刻点上只有一个程序在处理机上运行。

并发容易导致竞争问题。竞争就是两个或者两个以上的新城同时访问同一个资源,从而引起资源的错误。

为了避免并发对系统资源的影响,Linux中提出了一下并发控制机制。这些控制机制有原子变量操作、锁机制。

一、原则变量操作

所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它是最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。

原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。

原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。

1.1. 原子类型定义

Typedef struct {volatile int counter; }     atomic_t;

volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。精确地说就是,优化器在用到这个变量时必须每次都小心地从内存重新读取这个变量的值,而不是使用保存在寄存器里的备份。
关于volatile可以看volatile关键字的解析

在Linux中,定义了两种原子变量操作,一种是原子整型操作,另一种是原子位操作。

1.2. 原子整型操作

有时候需要共享的资源可能只是一个简单的整型数值。这时候就可以使用原子整型操作。

1.申明定义atomic_t
ATOMIC_INIT宏的功能是定义一个atomic_t类型的变量,红参数是需要给该变量初始化的值。该宏定义如下:

#define ATOMIC_INIT(i)       { (i) }

我们知道atomic_t类型的变量是一个结构图类型,所以对其进行定义和初始化应该用结构体的方法来定义和初始化。
例如我们要声明定义一个名为section的atomic_t类型的变量:

atomic_t section = ATOMIC_INIT(0);

这句代码展开后,就是

atomic_t section = { (0) };

1.3. 原子操作API

  1. atomic_set(atomic_t* v, int i);
    该函数设置原子类型的变量v的值为i。

  2. atomic_read(atomic_t* v);
    该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。

  3. atomic_add(int i, atomic_t *v);
    该函数给原子类型的变量v增加值i。

  4. atomic_sub(inti, atomic_t *v);
    该函数从原子类型的变量v中减去i。

  5. atomic_inc(atomic_t*v);
    该函数对原子类型变量v原子地增加1。

  6. Int atomic_sub_and_test(inti, atomic_t *v);
    该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。

  7. Void atomic_dec(atomic_t*v);
    该函数对原子类型的变量v原子地减1。

  8. Int atomic_dec_and_test(atomic_t*v);
    该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。

  9. Int atomic_inc_and_test(atomic_t*v);
    该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。

  10. Int atomic_add_negative(inti, atomic_t*v);
    该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。

  11. Int atomic_add_return(inti, atomic_t *v);
    该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。

  12. Int atomic_sub_return(inti, atomic_t *v);
    该函数从原子类型的变量v中减去i,并且返回指向v的指针。

  13. Int atomic_inc_return(atomic_t* v);
    该函数对原子类型的变量v原子地增加1并且返回指向v的指针。

  14. Int atomic_dec_return(atomic_t* v);
    该函数对原子类型的变量v原子地减1并且返回指向v的指针。

原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1,当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放Ip碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。

1.4 原子位操作

原子位操作是根据数据的每一位单独进行操作。根据体系结构的不同,原子位操作函数的实现也不同。原子位操作和原子整数操作是不同的。原子位操作不需要专门定义一个类似atomic_t类型的变量,只需要一个普通的变量指针就可以了。
原子位操作API有

  1. static inline void set_bit(unsigned nr, volatile unsigned long *addr)
    将addr变量的第nr位设置为1

  2. static inline void clear_bit(unsigned nr, volatile unsigned long *addr)
    将addr变量的第nr位设置为0

  3. static inline void change_bit(unsigned nr, volatile unsigned long *addr)
    将addr变量的第nr位设置为相反的数

  4. static inline int test_and_set_bit(unsigned nr, volatile unsigned long *addr)
    将addr变量的第nr位设置为1,并返回没有修改之前的值

  5. static inline int test_and_clear_bit(unsigned nr, volatile unsigned long *addr)
    将addr变量的第nr位设置为0,并返回没有修改之前的值

  6. static inline int test_and_change_bit(unsigned nr,volatile unsigned long *addr)
    将addr变量的第nr位设置为相反的数,并返回没有修改之前的值

1.5 非原子位操作

在Linux中,还定义了一组与原子位操作功能相同但非原子位的操作。这些函数的命名是在原子位操作的函数前加两个下划线。例如与原子位操作set_bit()函数想对应的是__set_bit(),这个函数不会保证是一个原子操作。与此类似的函数原型如下:

static inline void __set_bit(int nr, volatile unsigned long *addr)
static inline void __clear_bit(int nr, volatile unsigned long *addr)
static inline void __change_bit(int nr, volatile unsigned long *addr)
static inline int __test_and_set_bit(int nr, volatile unsigned long *addr)
static inline int __test_and_clear_bit(int nr, volatile unsigned long *addr)
static inline int __test_and_change_bit(int nr,volatile unsigned long *addr)

二、锁机制

Linux中提供了一些锁机制来避免竞争条件,引入锁机制,是因为单独的原子操作不能满足复杂的内核设计需要。例如,当一个临界区域要在多个函数之间来回运行时,原子操作就显得无能为力了。
一般可以认为有两种锁,一种是自旋锁,另一种是信号量。当然还有其他的一下锁。

锁机制的实现理念是:
去获得锁(自旋锁/信号量),如果获取失败,则进程会等待或者休眠,这样获取锁这条代码后面的代码将暂时不会执行,只有获取成功返回之后才会开始执行下面的代码,我们就把临界资源放到获取锁这条代码后面,这样就达到了锁定临界资源的目的了

自旋锁总结:

Spanlock(自旋锁-调用进程不会睡眠)

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用),而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共巷资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。

互斥锁是计数器值为1的信号量

跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。

论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。

(3)在单处理机器上,自旋锁是无意义的。因为在编译时不会加入自旋锁,仅仅被当作一个设置内核抢占机制是否被启用的开关。如果禁止内核抢占,那么在编译时自旋锁会被完全剔除出内核。

(4)Linux内核中,自旋锁是不可递归的。如果试图得到一个你正在持有的锁,你必须去自旋,等待你自己释放这个锁。但这时你处于自旋忙等待中,所以永远不会释放锁,就会造成死锁现象。

(5)在中断处理程序中,获取锁之前一定要先禁止本地中断(当前处理器的中断),否则,中断程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。这样就会造成双重请求死锁(中断处理程序会自旋,等待该锁重新可用,但锁的持有者在这个处理程序执行完之前是不可能运行的)

(6)锁真正保护的是数据(共享数据),而不是代码。对于BLK(大内核锁)保护的是代码。

设备驱动中的并发控制相关推荐

  1. linux 两个驱动 竞态,第7章 Linux设备驱动中的并发控制之一(并发与竞态)

    本章导读 Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态(竞争状态). Linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景. 7.1讲解了并 ...

  2. Linux设备驱动中的并发控制总结

    并发(concurrency)指的是多个执行单元同时.并行被执行.而并发的执行单元对共享资源(硬件资源和软件上的全局.静态变量)的访问则容易导致竞态(race conditions).   SMP是一 ...

  3. Linux设备驱动开发详解:第7章 Linux设备驱动中的并发控制

    7.1并发与竞态 (1).竞态的发生场景:CPU0的进程与CPU1的进程之间.CPU0的中断与CPU1的进程之间.CPU0的中断与CPU1的中断之间: (2).解决竞态问题的途径是保证对共享资源的互斥 ...

  4. 蜕变成蝶~Linux设备驱动中的并发控制

    并发和竞争发生在两类体系中: 对称多处理器(SMP)的多个CPU 内核可抢占的单CPU系统 访问共享资源的代码区域称为临界区(critical sections),临界区需要以某种互斥机制加以保护.在 ...

  5. Linux 设备驱动中的并发控制 小感

    为什么要控制并发, 多核心cpu 可以实现真正的并发,如果是单核心的cpu,或者双核心的cpu都是无法真正的实现,并发, 换句话说,就是串行执行的,那么控制并发的意义就在于,1.协调cpu的任务执行, ...

  6. 设备驱动中的并发控制-自旋锁

    在linux中提供了一些锁机制来避免竞争,引入锁的机制是因为单独的原子操作不能满足复杂的内核设计需求.Linux中一般可以认为有两种锁,一种是自旋锁,另一种是信号量.这两种锁是为了解决内核中遇到的不同 ...

  7. linux设备驱动中的并发控制

    并发控制的概念 ----并发指的是多个执行单元并行执行,而并发的执行单元对共享资源(硬件资源和 ----软件上的全局变量.静态变量等)的访问则很容易导致竞态. 竞态发生的情况 ----对称多处理器(S ...

  8. Linux设备驱动中的阻塞与非阻塞I/O

    阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式 本例子讲述了这两者的区别 并实现I/O的等待队列机制, 并进行了用户空间的验证 基本概念: 1> ...

  9. Linux 设备驱动开发 —— 设备树在platform设备驱动中的使用

    关与设备树的概念,我们在Exynos4412 内核移植(六)-- 设备树解析 里面已经学习过,下面看一下设备树在设备驱动开发中起到的作用 Device Tree是一种描述硬件的数据结构,设备树源(De ...

最新文章

  1. solr-cloud 集群动态增加、删除节点
  2. 深入理解 Ribbon-Hystrix-Feign 三者之间的关系(一)
  3. MutationObserver详解
  4. 算法---会议最大安排问题
  5. 排名前100的PHP函数及分析
  6. 机器学习算法_机器学习算法之PCA算法
  7. python字符串驻留机制_Python中的字符串驻留
  8. L1-035 情人节 (15 分)—团体程序设计天梯赛
  9. CAVLC基于上下文自适应的可变长编码
  10. python中换行的转义字符_Python语言中表示换行的转义字符是____________。(2.5分)_学小易找答案...
  11. Androidstudio控制台分层输出接口日志.类似BeJSON,HiJson格式化JSON
  12. 2022年信息安全工程师考试知识点:信息系统安全产品的配置与使用
  13. hihocoder1498 Diligent Robots
  14. The JSP specification requires that an attribute name is preceded by whitesp
  15. 算法设计——基姆拉尔森计算公式:计算几月几号是星期几
  16. MTK优美代码赏析2:MenuItemMask_flag
  17. 计算机毕业设计django基于python的汽车租赁系统
  18. 广东教国笔怎样才能提高自制力!
  19. 学计算机必备软件,电脑必备6个黑科技软件,每个都是顶尖,学习、工作必不可少!...
  20. h5压缩图片并上传到oss

热门文章

  1. Java版本和JDK版本
  2. 跳跃游戏 (贪心/动态规划/dfs)
  3. 迭代阈值图像分割matlab,Matlab 图像分割 (阈值处理)
  4. 程序设计方法与技术——C语言 程序设计概述
  5. 基于Master-DistributedMaster-Slave架构的replication
  6. 手机号批量查询归属地方法及其简介批量查询号码归属地方法
  7. HTML5期末大作业:旅游酒店网站设计——旅游酒店服务预订(1页) web网页设计—— 出游
  8. 浅谈神经网络之链式法则与反向传播算法
  9. php无版权图库api,哪里有无版权php源码
  10. CPython是什么?PyPy是什么?Python和这两个东西有什么关系