人们总是期望内核开发者确定和解决由内核控制路径的交错执行所引起的同步问题。但是,避免竞争条件是一项艰巨的任务,因为这需要对内核的各个成分如何相互作用有一个清楚的理解。为了直观地认识内核内部到底是什么样子,需要提及前面博文中所定义同步技术的几种典型应用场景。

1 引用计数器

引用计数器广泛地用在内核中以避免由于资源的并发分配和释放而产生的竞争条件。引用计数器(reference counter)只不过是一个atomic_t计数器,与特定的资源,如内存页、模块或文件相关。当内核控制路径开始使用资源时就原子地增加计数器的值,当内核控制路径使用完资源时就原子地减少计数器。当引用计数器变为0时,说明该资源未被使用,如果必要,就释放该资源。

2 大内核锁

在早期的Linux内核版本中,大内核锁(big kernel block,也叫全局内核锁或BKL)被广泛使用。在2.0版本中,这个锁是相对粗粒度的自旋锁,确保每次只有一个进程能运行在内核态。2.2和2.4内核具有极大的灵活性,不再依赖一个单独的自旋锁,而是由许多不同的自旋锁保护大量的内核数据结构。在Linux 2.6版本的内核中,用大内核锁来裸护旧的代码(绝大多数主要是与VFS和几个文件系统相关的函数)。

从内核版本2.6.11开始,用一个叫做kernel_sem的信号量来实现大内核锁(在较早的2.6版本中,大内核锁是通过自旋锁来实现的)。但是,大内核锁比简单的信号量要复杂一些。

每个进程描述符task_struct都含有lock_depth字段,其就是一个整形变量,这个字段允许同一个进程几次获取大内核锁。因此,对大内核锁两次连续的请求不挂起处理器(相对于普通自旋锁)。如果进程未获的过锁,则这个字段的值为-1;否则,这个字段的值加1,表示已经请求了多少次锁。lock_depth字段对中断处理程序、异常处理程序及可延迟函数获取大内核锁都是至关重要的。如果没有这个字段,那么,在当前进程已经拥有大内核锁的情况下,任何试图获得这个锁的异步函数都可能产生死锁。

lock_kernel ()和unlock_kernel()内核函数用来获得和释放大内核锁。前一个函数等价于:
    depth = current->lock_depth + 1;
    if (depth == 0)
        down(&kernel_sem);
    current->lock_depth = depth;

而后者等价于
    if (--current->lock_depth < 0)
        up(&kernel_sem);

注意,lock_kernel()和unlock_kernel()函数的if语句不需要原子地执行,因为lock_depth不是全局变量——这是每个CPU在自己当前进程描述符中访问的一个字段。在if语句确本地中断也不会引起竞争条件。即使新内核控制路径调用了lock_kernel(),它在终止前也必须释放大内核锁。

足以令人吃惊的是,允许一个持有大内核锁的进程调用schedule(),从而放弃CPU!不过,schedule()函数检查被替换进程的lock_depth字段,如果它的值是0或者正数,就自动释放kernel_sem信号量(参见“schedule()函数”博文)。因此,不会有显式调用schedule()的进程在进程切换前后都保持大内核锁。但是,当schedule()函数再次选择这个进程来执行的时候,将为该进程重新获得大内核锁。

然而,如果一个持有大内核锁的进程被另一个进程抢占,情况就有所不同了。一直到内核版本2.6.10还没有出现这种情况,因为获取自旋锁时会自动禁用内核抢占。但是,现在大内核锁的实现是基于信号量的,而且不会由于获得它而自动禁用内核抢占。实际上,在被大内核锁保护的临界区内允许内核抢占是改变大内核锁实现的主要原因。其次,这对于系统的响应时间会产生有益的影响。

当一个持有大内核锁的进程被抢占时,schedule()一定不能释放信号量,因为在临界区内执行代码的进程没有主动触发进程切换。所以,如果释放大内核锁,那么另外一个进程就可能获得它,并破坏由被抢占的进程所访问的数据结构。

为了避免被抢占的进程失去大内核锁,preempt_schedule_irq()临时把进程的lock_depth字段设置为-1。观察这个字段的值,schedule()假定被替换的进程不拥有kernel_sem信号量,也就不释放它。结果,被抢占的进程就一直拥有kernel_seen信号量。一旦这个进程再次被调度程序选中,preempt_schedule_irq()函数就恢复lock_depth字段原来的值,并让进程在被大内核锁保护的临界区中继续执行。

3 内存描述符读/写信号量

mm_struct类型的每个内存描述符在mmap_sem字段中都包含了自己的信号量。由于几个轻量级进程之间可以共享一个内存描述符,因此,信号量保护这个描述符以避免可能产生的竞争条件。

例如,让我们假设内核必须为某个进程创建或扩展一个内存区。为了做到这一点,内核调用do_mmap()函数分配一个新的vm_area_struct数据结构。在分配的过程中,如果没有可用的空闲内存,而共享同一内存描述符的另外一个进程可能在运行,那么当前进程可能被挂起。如果没有信号量,那么需要访问内存描述符的第二个进程的任何操作(例如,由于写时复制而产生的缺页)都可能会导致严重的数据崩溃。

这种信号量是作为读/写信号量来实现的,因为一些内核函数,如缺页异常处理程序只需要扫描内存描述符。

4 slab高速缓存链表的信号量

slab高速缓存描述符链表是通过cache_chain_sem信号量保护的,这个信号量允许互斥地访问和修改该链表。

当kmem_cache_create()在链表中增加一个新元素,而kmem_cache_shrink()和kmem_cache_reap()顺序地扫描整个链表时,可能产生竞争条件。然而,在处理中断时,这些函数从不被调用,在访问链表时它们也从不阻塞。由于内核是支持抢占的,因此这种信号量在多处理器系统和单处理器系统中都会起作用。

5 索引节点的信号量

我们以后将会提到,Linux把磁盘文件的信息存放在一种叫做索引节点(inode)的内存对象中。相应的数据结构也包括有自己的信号量,存放在i_sem字段中。

在文件系统的处理过程中会出现很多竞争条件。实际上,磁盘上的每个文件都是所有用户共有的一种资源,因为所有进程都(可能)会存取文件的内容、修改文件名或文件位置、删除或复制文件等等。例如,让我们假设一个进程在显示某个目录所包含的文件。由于每个磁盘操作都可能会阻塞,因此即使在单处理器系统中,当第一个进程正在执行显示操作的过程中,其他进程也可能存取同一目录并修改它的内容。或者,两个不同的进程可能同时修改同一目录。所有这些竞争条件都可以通过用索引节点信号量保护目录文件来避免。

只要一个程序使用了两个或多个信号量,就存在死锁的可能,因为两个不同的控制路径可能互相死等着释放信号量。一般来说,Linux在信号量请求上很少会发生死锁问题,因为每个内核控制路径通常一次只需要获得一个信号量。然而,在有些情况下,内核必须获得两个或更多的信号量锁。索引节点信号量倾向于这种情况,例如,在rename()系统调用的服务例程中就会发生这种情况。在这种情况下,操作涉及两个不同的索引节点,因此,必须采用两个信号量。为了避免这样的死锁,信号量的请求按预先确定的地址顺序进行。

一些避免竞争条件的实例相关推荐

  1. java lock condition_Java 通过 Lock 和 竞争条件 Condition 实现生产者消费者模式

    更多 Java 并发编程方面的文章,请参见文集<Java 并发编程> 竞争条件 多个线程共享对某些变量的访问,其最后结果取决于哪个线程偶然在竞争中获胜. condition.await() ...

  2. 查询表授权给谁了_SQL Server 全局临时表竞争条件漏洞利用

    在网络和应用程序渗透测试期间,SQL Server 全局临时表通常不是关注的焦点.然而,它们被开发人员周期性地不安全地用来存储敏感数据和代码块,这些数据和代码块可以被非特权用户访问.在本博客中,我将介 ...

  3. linux内核远程漏洞,CVE-2019-11815:Linux内核竞争条件漏洞导致远程代码执行

    *本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担. 运行了Linux发行版的计算机设备,如果内核版本小于5.0.8的话,将有可能受到一 ...

  4. linux内核竞争条件漏洞,Linux内核竞争条件漏洞-导致远程代码执行

    原标题: Linux内核竞争条件漏洞-导致远程代码执行 导读*本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担. 运行了Linux发行版 ...

  5. java 锁竞争_Java多线程中的竞争条件、锁以及同步的概念

    竞争条件 1.竞争条件: 在java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生"竞争条件"的现象.这种现象产生的根本原因是因为多个线程在对同一个数据进行操作 ...

  6. python里countsget_在Python中测试访问同一数据的竞争条件的方法

    当你有多个进程或线程访问相同的数据时,竞争条件是一个威胁.本文探讨了在发现竞争条件后如何测试它们. Incrmnt 你在一个名为"Incrmnt"的火热新创公司工作,该公司只做一件 ...

  7. Linux Kernel ‘install_user_keyrings()’竞争条件漏洞

    漏洞名称: Linux Kernel 'install_user_keyrings()'竞争条件漏洞 CNNVD编号: CNNVD-201303-141 发布时间: 2013-03-11 更新时间: ...

  8. java线程同步——竞争条件的荔枝+锁对象

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--竞争条件的荔枝+锁对象 的相关知识: 0.2) for full sou ...

  9. java 并发的原子性_Java并发教程–原子性和竞争条件

    java 并发的原子性 原子性是多线程程序中的关键概念之一. 我们说一组动作是原子的,如果它们都以不可分割的方式作为单个操作执行. 认为多线程程序中的一组操作将被串行执行是理所当然的,可能会导致错误的 ...

  10. Java并发教程–原子性和竞争条件

    原子性是多线程程序中的关键概念之一. 我们说一组动作是原子的,如果它们都以不可分割的方式作为一个单一的操作执行. 认为多线程程序中的一组操作将被串行执行是理所当然的,可能会导致错误的结果. 原因是由于 ...

最新文章

  1. Zookeeper分布式一致性原理(九):Zookeeper分布式应用
  2. easyPR源码解析之chars_identify.h
  3. 用VS调试的时候查看指定内存地址
  4. sql/c#十六进制与十进制的转换
  5. 阿里云下mysql远程访问被拒绝_记一次MySQL数据库拒绝访问的解决过程
  6. 负载均衡和故障转移的使用案例
  7. string 操作 java_Java中String类的一些常见问题
  8. plsql 64连接32oracle,32位plsql developer连接64位oracle的方法
  9. 弱电从业人员必须学习的一些CAD绘图技巧
  10. 关于神经网络中的shape问题
  11. postgresql中查询COMMENT注释的语句
  12. My 2007 Fash game: Elite Shooter
  13. 工作流学习2(书本)
  14. java BPM平台1:设计思路
  15. 【面试】小米公司嵌入式职位面试经验
  16. 简报 | 微软与戴尔之后,财富500强企业安富利成为第三大接受BTC支付的科技公司
  17. Commit: Not all refs have been pushed.
  18. 电影《龙马精神》观后感
  19. TIA博途下载PLC程序时提示“具有激活的TIS功能防止下载到设备”错误-处理办法
  20. mysql索引的使用

热门文章

  1. java中进行socket编程实现tcp、udp协议总结
  2. Asp.Net MVC Web应用程序中的安全向量
  3. 为ashx文件启用session管理
  4. Spark DataFrames DataSet
  5. CentOS6.5 firefox安装flash插件
  6. [转载] 中华典故故事(孙刚)——33 人上一百形形色色
  7. Way back into love
  8. kafka中zookeeper的作用
  9. intellij idea 中右键项目没有git
  10. python之Linux基础(三)