关键字volatile,公共堆栈,私有堆栈,非原子特性

本文探讨了Java中volatile关键字的作用机制,解释了它如何确保多线程环境下的数据可见性,同时指出volatile并不能保证操作的原子性,通过示例说明了在进行i++等操作时可能遇到的非线程安全问题。
一、公共堆栈与线程的私有堆栈

在启动线程时,变量的值是存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server 模式时为了线程运行的效率,线程一直在私有堆栈中取变量的值,即使有其他线程将变量的值进行了修改,更新的却是公共堆栈中的变量值,私有堆栈中的值不会变,这就导致线程运行存在问题。内存关系如图:

····························内存关系如图

volatile作用

使用volatile 关键字,可以强制的从公共内存中读取值。使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字的缺点是不支持原子性。

volatile与synchronized 比较

1)volatile 是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,但是volatile只能修饰变量,而synchronized可以修饰方法,代码块。
2)多线程访问volatile 不会发生阻塞,而synchronized 会发生阻塞。
3)volatile 能保证数据的可见性,但不能保证原子性,而 synchronized可以保证原子性,也可以保证可见性,因为它会将私有内存和公共内存中的数据做同步。

volatile的非原子特性
  1. 程序的原子性概念:一个操作的过程是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。即使在多个线程一起执行的时候,一旦一个原子性的操作开始后,就不会受其他线程的干扰。

  2. 验证

public class MyThread extends Thread{
	// static修饰的变量只被初始化一次,下一次依据上一次结果值
	volatile public static int count;
	private static void addCount(){
		for (int i = 0; i < 100; i++){
			count++;
		}
		System.out.println("count="+count);
	}
	@Override
	public void run(){
		addCount();
	}
}
public class VolatileRun {

	public static void main(String[] args) {
		MyThread[] mythreadArr = new MyThread[100];
		// 创建 100个实例
		for(int i = 0; i < 100; i++){
			mythreadArr[i] = new MyThread();
		}
		for(int i = 0; i < 100; i++){
			mythreadArr[i].start();
		}
	}
}

运行结果:不是10000
··························运行结果

        关键字volatile 提示线程每次从公共堆栈读取值,而不是从私有内存中读取,这样就保证了数据的同步数据可见性。但是:如果修改实例变量中的数据,比如 i++,也就是i = i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全的。表达式i++ 的操作步骤分解如下:
        1)从内存中取出 i 的值;
        2)计算 i 的值;
        3)将 i 的值写到内存中

        假如在第2步计算值的时候,另一个线程也修改了 i 的值,那么这时候就会出现脏数据。解决办法就是使用synchronized 关键字。所以说 volatile 本身并不处理数据的原子性,只是强制数据的读写及时影响到主内存中。

  1. 用图来演示下volatile出现非线程安全的原因。变量在内存中工作的过程。
            1)read 和 load 阶段:读取和加载
            2)use 和 assign 阶段: 操作和赋值
            3)store 和 write 阶段:储存和 写
    变量在内存中的工作过程
            在多线程环境,use和assign 是多次出现的,但这一操作不是原子性的,在read和load之后,如果主内存的count变量的值修改之后,线程工作内存中的值 由于已经加载,不会产生对应变化,也就是私有内存和公共内存中的变量不同步了,所以计算出的结果和预期结果不一样,也就出现了非线程安全的问题。
<think>好的,我现在需要帮助用户解决关于Zynq 7020 AMP架构下CPU1的私有定时器中断裸机开发的问题。首先,我需要回忆一下Zynq 7020的双核结构。Zynq-7000系列有两个Cortex-A9核,AMP模式下两个核可以独立运行不同的操作系统或裸机程序。用户的问题集中在CPU1的私有定时器设置,我需要确保自己了解私有定时器的相关寄存器配置。 首先,私有定时器(Private Timer)是每个CPU核心独有的,地址应该从0xF8F00600开始。需要配置加载值、控制寄存器,并启用中断。中断号方面,私有定时器的中断属于PPI(私有外设中断),CPU0的PPI中断号是29,而CPU1的应该是对应的。不过需要确认一下,可能用户在使用CPU1时,中断号是否相同或者不同,这里可能需要查文档。 接下来,中断控制器的配置。Zynq使用的是GIC(通用中断控制器),在裸机开发中需要配置GIC的Distributor和CPU Interface。对于CPU1来说,可能需要确保中断分配到对应的CPU,并且优先级和使能位设置正确。同时,中断服务函数的注册需要将中断处理函数地址写入向量表,或者通过设置VBR(向量基址寄存器)来指向自定义的向量表。 然后,代码结构方面,用户可能需要初始化定时器,配置中断控制器,编写中断服务函数,并启用全局中断。需要注意的是,在AMP模式下,两个核之间的资源访问需要协调,比如共享内存或者使用核间通信机制,但用户的问题主要聚焦在CPU1的私有定时器,可能暂时不需要处理核间通信部分,但可能需要确保内存分配正确,避免冲突。 此外,启动流程方面,在AMP环境中,CPU1通常由CPU0启动,通过写入CPU1的启动地址并释放复位。因此,用户可能需要将CPU1的代码编译成不同的可执行文件,并确保其加载到正确的内存地址。同时,在代码中需要设置好CPU1的堆栈和异常向量表。 可能还需要考虑缓存和MMU的配置,确保定时器相关寄存器的访问是有效的。在裸机开发中,通常需要禁用MMU,但缓存可能需要根据情况进行配置,比如是否启用数据缓存来加速访问,但需要确保寄存器映射的内存区域配置正确,避免缓存导致的问题。 最后,测试和调试部分,用户可能需要使用JTAG调试器分别连接到两个核,或者在代码中加入日志输出到共享内存,通过串口或其他方式查看CPU1的中断触发情况。同时,需要确保中断处理函数正确清除中断标志,避免重复进入中断。 可能遇到的问题包括中断未触发,原因可能是中断号配置错误、GIC未正确使能、定时器控制寄存器的配置有误,或者中断处理函数没有正确清除中断标志。需要逐步排查这些方面,并参考Xilinx的官方文档和例程,确保寄存器的配置值正确。</think>### Zynq 7020 AMP 裸机开发中 CPU1 私有定时器中断配置步骤 #### 1. **硬件基础** - Zynq 7020 包含双 Cortex-A9 核(CPU0 和 CPU1),私有定时器(Private Timer)是每个核的独立外设,地址为 $0xF8F00600$(CPU0)和 $0xF8F01600$(CPU1)[^1]。 - 私有定时器中断属于 PPI(私有外设中断),CPU1 的私有定时器中断号为 **30**。 #### 2. **关键寄存器配置** ##### a. **定时器初始化** ```c // 设置定时器加载值(32 位) *(volatile uint32_t*)(0xF8F01600 + 0x00) = LOAD_VALUE; // Timer Load Register // 控制寄存器配置 *(volatile uint32_t*)(0xF8F01600 + 0x08) = 0x07; // Timer Control Register: // 0x01: 使能定时器 // 0x02: 自动重载模式 // 0x04: 中断使能 ``` ##### b. **中断控制器(GIC)配置** ```c // GIC Distributor 配置(使能中断分配) *(volatile uint32_t*)(0xF8F01000 + 0x100) |= (1 << 30); // GICD_ISENABLERn(使能 PPI 30) // GIC CPU Interface 配置(CPU1 侧) *(volatile uint32_t*)(0xF8F00100 + 0x0C) = 0x1F0; // ICCPMR(优先级掩码) *(volatile uint32_t*)(0xF8F00100 + 0x00) = 0x01; // ICCICR(使能 CPU Interface) ``` #### 3. **中断服务函数(ISR)** ```c void __attribute__((interrupt)) timer_isr(void) { // 清除定时器中断标志 *(volatile uint32_t*)(0xF8F01600 + 0x0C) = 0x01; // Timer Interrupt Status Register // 用户自定义操作(如翻转 GPIO) } ``` #### 4. **向量表与中断绑定** - 在启动代码中修改向量表,将 IRQ 入口指向通用中断处理函数: ```assembly .section .vectors ldr pc, [pc, #24] // IRQ 跳转指令 .word irq_handler // IRQ 处理函数地址 ``` - 在 C 代码中绑定具体中断: ```c void irq_handler(void) { uint32_t int_id = *(volatile uint32_t*)(0xF8F00100 + 0x0C); // ICCIAR if (int_id == 30) timer_isr(); *(volatile uint32_t*)(0xF8F00100 + 0x10) = int_id; // ICCEOIR } ``` #### 5. **启动流程(AMP 模式)** - CPU0 需通过写 **SLCR.ACP_CTRL** 寄存器释放 CPU1 复位,并设置其启动地址: ```c *(volatile uint32_t*)(0xF8000000 + 0x244) = CPU1_ENTRY_ADDR; // 设置 CPU1 启动地址 *(volatile uint32_t*)(0xF8000000 + 0x8) |= 0x1; // 释放 CPU1 复位 ``` #### 6. **注意事项** - **内存隔离**:在 AMP 模式下需通过 MPU 或 MMU 配置隔离 CPU0 和 CPU1 的内存访问。 - **共享资源同步**:若操作共享外设(如 GPIO),需使用原子操作或软件锁。 - **调试方法**:可通过 UART 打印日志或 LED 指示灯验证中断触发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值