Atomic并发API简述

一.Atomic包,原子性。事实上,此包中的类可将volatile值、字段和数组元素的概念扩展到那些提供原子性操作的类上,主要方式为 compareAndSet(expectedValue,updateValue),

即CAS思想来确保内存一致性操作。expectedValue即当前值,updateValue为需要被更新的值,如果内存中的值为expectedVaue则更新,否则返回false。这些方法的实现基于处理器提供的高效机器级别原子性指令;由于平台的不同,cas的系统级别的底层实现,可能会基于锁,可能会阻塞线程。

 

除了包含表示单个值的类之外,此包还包含 Updater 类,该类可用于获取任意选定类的任意选定 volatile 字段上的 compareAndSet 操作。

 

AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater 和 AtomicLongFieldUpdater 是基于反射的实用工具,可以提供对关联字段类型的访问。它们主要用于原子数据结构中,该结构中同一节点(例如,树节点的链接)的几个 volatile 字段都独立受原子更新控制。这些类在如何以及何时使用原子更新方面具有更大的灵活性,但相应的弊端是基于映射的设置较为拙笨、使用不太方便,而且在保证方面也较差。

 

原子类也支持 weakCompareAndSet 方法,该方法具有受限制的适用性。在某些平台上,弱版本在正常情况下可能比 compareAndSet 更有效,但不同的是 weakCompareAndSet 方法的任何给定调用可能意外 返回 false(即没有明确的原因)。返回 false 仅意味着可以在需要时重新尝试操作,具体取决于重复执行调用的保证,当该变量保持 expectedValue 并且没有其他线程也在尝试设置该变量时,最终将获得成功。(例如,这样的虚假失败可能是由于内存争用的结果,该争用与期望值和当前值是否相等无关)。 此外,weakCompareAndSet 不提供通常需要同步控制的排序保证。但是,在这样的更新与程序的其他 happen-before 排序不相关时,该方法可用于更新计数器和统计数据。当一个线程看到对 weakCompareAndSet 导致的原子变量的更新时,它不一定能看到在 weakCompareAndSet 之前发生的对任何其他 变量的更新。例如,在更新性能统计数据时,这也许可以接受,但其他情况几乎不可以。

 

AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。

这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。

 

Atomic的各种类,并没有统一的接口,他们却都具有几乎相同的操作方法。

AtomicBoolean,AtomicInteger,AtomicLong内部实现基本一致。以AtomicInteger为例,其底层为一个volatile int作为数据存储,并通过unsafe API的compareAndSwap方式尝试更新数据。

 

  • void set(newValue)/void lazySet(newValue):设置新值,采用非原子性操作,直接设置新值。lazySet主要是更新操作,并非立即执行,而是根据CPU调度(操作有序化),会在适当的时候被执行,比如在多核cpu中,此值不具有竞争性修改时,执行lazySet。。一种简单的non-blocking操作。
  • void getAndSet(newValue):通过cas方式设置新值,并返回修改之前的值。///

 

for (;;) {
	int current = get();
	if (compareAndSet(current, newValue))
		return current;
	}
}
 

 

  • final boolean compareAndSet(int expect, int update):CAS方式设置新值,设置成功返回true。底层使用unsafe.compareAndSwapInt(..)
  • final boolean weakCompareAndSet(int expect, int update):对于AtomicInteger/AtomicLong,AtomicBoolean/AtomicReference,此方法的实现和compareAndSet一模一样。所以此方法,可能再其他地方有不同的实现,再次不再赘言。

 

 

二.AtomicReference:原子性的更新对象引用。

 AtomicMarkableReference:此API设计的作用非常简单,原子性的更新对象引用以及其标记位(boolean属性),将一个对象引用和一个boolean属性捆绑,内部通过AtomicReference来实现。

 比如:AtomicMarkableReference(T reference,boolean initialMark),此构造函数则构建一个reference并使其具有initialMark的状态。

  • public V getReference():返回绑定的reference。
  • public boolean isMarked():返回标记为状态
  • public boolean compareAndSet(V expectedReference,V newReference,boolean expectedMark,boolean newMark):CAS方式更新reference和mark位,只有当reference和mark为都为期望值时,才更新为新值。
  • public void set(V newReference, boolean newMark) :强制设置新的reference和mark位。

 同样,AtomicStampedReference类和AtomicMarkableReference功能几乎完全一样,只不过AtomicStampedReference的“标志位”是一个int型,其API方法基本一样。AtomicStampedReference(V initialRef, int initialStamp):创建对象,使其初始引用和标记位为initialStamp。

 

三.AtomicReferenceFieldUpdater<T,V>

此类为抽象类,其提供了内置的实现方式。此类基于反射可以对指定类的指定 volatile 字段进行原子更新。

该类用于原子数据结构,该结构中同一节点的几个引用字段都独立受原子更新控制。我们之前看到的Atomic的类型API,只能对一种类型数据进行原子性操作。此类提供了可以针对一个class中多个volatile字段,

进行原子性更行的可能。不过AtomicReferenceFieldUpdater只能适用于类的一个属性。

 

class Node {
   private volatile Node left, right;
   private static final AtomicReferenceFieldUpdater leftUpdater =
   AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
   private static AtomicReferenceFieldUpdater rightUpdater =
   AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");
   Node getLeft() { return left;  }
   boolean compareAndSetLeft(Node expect, Node update) {
     return leftUpdater.compareAndSet(this, expect, update);
   }
   // ... and so on
 }

 

  • public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,Class<W> vclass,String fieldName):使用给定的字段为对象创建和返回一个更新器。需要 Class 参数检查反射类型和一般类型是否匹配。如果指定字段非volatile,将抛出异常。
  • boolean compareAndSet(T obj,V expect,V update):原子性的更新对象的属性值。
<think>好的,我现在需要帮用户解答关于Linux内核动态映射区的问题。用户之前已经询问过kva=page_offset+pg_pa的关系,现在想了解动态映射区。首先,我得回忆一下动态映射区的相关知识。 动态映射区,通常指的是内核中用于临时映射物理内存的区域,特别是高端内存。因为之前的线性映射区只能处理低端内存,当物理内存超过一定大小后,无法全部线性映射,这时候就需要动态映射。比如在x86架构下,32位系统可能只有4GB的地址空间,内核需要一部分来映射所有物理内存,当物理内存超过这个限制时,就需要动态映射。 用户可能是在学习内核内存管理,或者遇到相关开发问题。他们可能想知道动态映射区的作用、使用场景以及如何操作。我需要确保解释清楚动态映射区静态线性映射区的区别,为什么需要动态映射,以及相关的API如kmap、kmap_atomic等。 需要分步骤来组织回答:定义作用、使用场景、关键技术、生命周期管理、优缺点,以及应用示例。这样结构清晰,便于用户理解。同时,要注意用户可能对高端内存和低端内存的概念不太熟悉,需要简要说明。 还要注意用户之前的问题是关于线性映射的,所以可能需要对比两者的不同,强调动态映射的临时性和灵活性。另外,可能需要提到动态映射区的实现机制,比如使用固定的地址窗口,按需映射,以及原子性映射的特点。 最后,确保回答准确,避免技术错误,比如高端内存的存在条件,不同架构下的差异,以及正确使用API的方法。如果有代码示例的话,可以帮助用户更好地理解,但需要确保示例正确无误,比如kmap和kunmap的配对使用。</think>### **内核动态映射区详解** --- #### **1. 定义核心作用** **动态映射区**(Dynamic Mapping Region)是 Linux 内核中用于 **临时映射物理内存到虚拟地址空间的特殊区域**,主要解决以下问题: - **高端内存(HighMem)访问**:32 位系统或物理内存较大的 64 位系统中,**线性映射区(Direct Mapping)无法覆盖全部物理内存**,需动态分配虚拟地址窗口。 - **非连续物理内存操作**:如通过 `vmalloc()` 分配的物理离散内存,需动态建立页表映射。 - **设备内存(MMIO)映射**:外设寄存器的物理地址需按需映射到内核虚拟地址空间。 --- #### **2. 使用场景** | **场景** | **说明** | |----------------------|-------------------------------------------------------------------------| | **高端内存访问** | 物理内存超过线性映射区容量时(如 32 位系统物理内存 > 896MB),需动态映射。 | | **临时内核操作** | 进程上下文切换、中断处理等场景中,需短暂访问特定物理页。 | | **设备寄存器访问** | 将外设的物理地址(如 PCI BAR)映射到内核虚拟地址,进行寄存器读写。 | | **非连续内存分配** | 通过 `vmalloc()` 分配的物理离散内存,需动态建立虚拟连续映射。 | --- #### **3. 关键技术** ##### **(1) 动态映射机制** - **地址窗口复用**:内核预留少量虚拟地址空间(如 32 位系统的 `PKMAP_BASE` 区域),**按需映射不同物理页**,避免占用过多虚拟地址资源。 - **原子性映射**:通过 `kmap_atomic()` 实现无锁快速映射,适用于中断上下文等不可睡眠场景。 - **页表动态更新**:映射时修改内核页表,建立物理地址到虚拟地址的临时关联。 ##### **(2) 核心 API** | **函数** | **作用** | **使用限制** | |------------------------|-------------------------------------------------------------------------|---------------------------| | `kmap(page)` | 为物理页(`struct page`)建立长期映射,返回内核虚拟地址。 | 可能阻塞,不可用于中断上下文。 | | `kunmap(page)` | 解除 `kmap()` 建立的映射。 | 需 `kmap()` 配对使用。 | | `kmap_atomic(page)` | 为物理页建立原子性映射(无阻塞),返回内核虚拟地址。 | 映射在 CPU 退出原子上下文前有效。 | | `kunmap_atomic(kva)` | 解除 `kmap_atomic()` 建立的映射。 | 必须 `kmap_atomic()` 配对。 | | `ioremap(pa, size)` | 将设备物理地址映射到内核虚拟地址空间,支持 MMIO 操作。 | 需使用 `iounmap()` 释放。 | --- #### **4. 动态映射区生命周期** 1. **映射建立**:调用 `kmap()` 或 `kmap_atomic()`,内核分配虚拟地址窗口并更新页表。 2. **内存访问**:通过返回的虚拟地址直接读写内存(需注意并发安全)。 3. **映射释放**:调用 `kunmap()` 或 `kunmap_atomic()`,释放虚拟地址窗口供其他物理页重用。 --- #### **5. 动态映射 vs 线性映射** | **特性** | **动态映射** | **线性映射(Direct Mapping)** | |------------------|--------------------------------------|---------------------------------------| | **映射方式** | 按需分配虚拟地址,临时建立映射。 | 物理地址虚拟地址固定偏移(`PAGE_OFFSET`)。 | | **性能** | 较高开销(需修改页表)。 | 零开销(直接计算偏移)。 | | **适用范围** | 高端内存、设备内存、非连续内存。 | 低端内存(LowMem)。 | | **虚拟地址占用** | 少量窗口复用,节省地址空间。 | 占用大块连续虚拟地址空间。 | | **并发安全性** | `kmap_atomic()` 支持原子操作。 | 无需额外同步。 | --- #### **6. 代码示例** ##### **(1) 访问高端内存页** ```c struct page *highmem_page = alloc_pages(GFP_HIGHUSER, 0); // 分配高端内存页 void *kva = kmap(highmem_page); // 建立动态映射 memset(kva, 0, PAGE_SIZE); // 操作虚拟地址 kunmap(highmem_page); // 解除映射 ``` ##### **(2) 原子性映射(中断上下文)** ```c void interrupt_handler(void) { struct page *page = get_current_page(); char *kva = kmap_atomic(page); // 原子映射,无阻塞 memcpy(kva, src_data, PAGE_SIZE); kunmap_atomic(kva); // 必须显式解除映射 } ``` ##### **(3) 映射设备寄存器** ```c void __iomem *regs = ioremap(DEVICE_PA, REG_SIZE); // 映射设备物理地址 u32 value = readl(regs + REG_OFFSET); // 读取寄存器值 writel(0x1234, regs + REG_OFFSET); // 写入寄存器 iounmap(regs); // 解除映射 ``` --- #### **7. 动态映射区的优缺点** | **优点** | **缺点** | |----------------------------------|---------------------------------------| | 支持超大物理内存访问(突破线性映射限制)。 | 映射/解除映射需额外开销。 | | 灵活管理虚拟地址资源(窗口复用)。 | 需开发者手动管理映射生命周期。 | | 适应复杂场景(设备内存、非连续内存)。 | 原子映射(`kmap_atomic()`)有使用限制。| --- #### **8. 内核实现细节** - **高端内存管理**:通过 `struct page` 的 `_mapcount` 字段跟踪映射状态。 - **地址窗口分配**:32 位系统通常使用 `PKMAP_BASE` 到 `FIXADDR_START` 之间的固定区域。 - **并发控制**:`kmap()` 使用锁保护映射表,`kmap_atomic()` 利用每 CPU 上下文避免锁竞争。 --- ### **总结** 内核动态映射区是 **突破物理内存线性映射限制的核心机制**,通过虚拟地址窗口复用和页表动态更新,实现了对高端内存、设备内存和非连续内存的安全访问。其灵活性原子性映射特性使其在驱动开发、内存管理等场景中不可或缺,但需开发者谨慎管理映射生命周期以避免资源泄漏或性能问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值