MR的环形缓冲区(底层)

MapReduce的大致流程:

1、HDFS读取数据;

2、按照规则进行分片,形成若干个spilt;

3、进行Map

4、打上分区标签(patition)

5、数据入环形缓冲区(KVbuffer)

6、原地排序,并溢写(sort+spill)

7、combiner+merge,落地到磁盘

8、shuffle到reduce缓存

9、继续归并排序(mergesotr)

10、reduce

11、输出到HDFS

解剖环形缓冲区:

环形缓冲区解释:环形缓冲区分为三块,空闲区、数据区、索引区。初始位置取名叫做“赤道”,就是圆环上的白线那个位置。初始状态的时候,数据和索引都为0,所有空间都是空闲状态。

tips:这里有一个调优参数,可以设置环形缓冲区的大小:

mapreduce.task.io.sort.mb,默认100M,可以稍微设置大一些,但不要太大,因为每个spilt就128M。

个人理解:因为环形缓冲区底层是一个数组

1、首先发生溢出时,Map溢出的数据会转成byte字节,从两侧开始向中间写入,一侧写入数据,一侧写入记录的索引信息。

2、当溢出的数据量达到mapreduce.map.sort.spill.percent参数设置的比例时(默认80%,这个是调优的参数)之后,会从剩余的20%空间中选出一个新的“赤道”,后续Map的溢出数据,将从新的赤道向两侧写入,一侧写入数据,一侧写入记录的索引信息。

3、第二次写入剩余的20%同时,会将第一次写入的数据进行刷写到磁盘中,由此过程循环往复,同时完成溢入-写出的过程,且在主线程mr执行过程中,会产生多次溢写操作,每次溢写都会产生溢写文件,整个操作都在内存,形状像一个环,所以才叫环形缓冲区。

4、完整实现图

为什么要有环形缓冲区?

我们读取到文件,直接排序,然后写到HDFS里不就好了吗?为啥还要整一个环形缓冲区呢?

那从架构的角度看环形缓冲区,他这么设计有什么用呢?解决什么问题呢?

思路广的朋友应该已经反应过来了。环形缓冲区不需要重新申请新的内存,始终用的都是这个内存空间。大家知道MR是用java写的,而Java有一个最讨厌的机制就是Full GC。Full GC总是会出来捣乱,这个bug也非常隐蔽,发现了也不好处理。环形缓冲区从头到尾都在用那一个内存,不断重复利用,因此完美的规避了Full GC导致的各种问题,同时也规避了频繁申请内存引发的其他问题。

另外呢,环形缓冲区同时做了两件事情:1、排序;2、索引。在这里一次排序,将无序的数据变为有序,写磁盘的时候顺序写,读数据的时候顺序读,效率高非常多!

在这里设置索引区也是为了能够持续的处理任务。每读取一段数据,就往索引文件里也写一段,这样在排序的时候能加快速度。

更多的详细信息你可以参考《Hadoop技术内幕:深入解析MapReduce架构设计与实现原理 》

<think>好的,用户想了解DMA环形缓冲区的工作原理和实现方式。首先,我需要回忆一下相关的知识,结合提供的引用内容来整理答案。 首先,DMA(直接内存访问)的作用是减少CPU负担,让外设和内存之间直接传输数据。环形缓冲区,也就是循环缓冲区,是一种高效的数据结构,用于处理数据流,特别是在资源有限的嵌入式系统中。结合这两者,DMA环形缓冲区应该是利用DMA传输数据到环形缓冲区,然后由程序处理缓冲区的数据。 根据引用[2],循环缓冲区通过首尾相连的方式管理固定大小的内存,避免频繁的内存操作。引用[3]和[4]提到了在MCU中使用环形缓冲区的实例,特别是串口接收中使用DMA和空闲中断结合环形缓冲区。这可能涉及到DMA的连续传输,将接收到的数据自动存入环形缓冲区,当数据传输完成或达到一定条件时触发中断,通知CPU处理数据。 实现原理方面,DMA可能会被配置成循环模式,当缓冲区写满后自动回到起始位置覆盖旧数据。同时,结合空闲中断(如UART的空闲中断),当总线上没有数据传输时触发中断,此时处理缓冲区中的数据。需要维护读写指针来跟踪数据的位置,确保读写指针不会冲突,并且处理数据的速度足够快,避免溢出。 示例部分可能需要展示如何初始化环形缓冲区,配置DMA,以及中断处理函数。例如,在STM32中,配置UART的DMA接收为循环模式,指向环形缓冲区,当接收到数据时,DMA自动更新指针,而空闲中断发生时,程序读取缓冲区中的数据,并更新读指针。 还需要考虑线程安全,如果在多任务或中断上下文中访问缓冲区,可能需要禁用中断或使用锁来保护读写指针。此外,缓冲区的大小需要根据数据流量来设计,避免溢出或数据丢失。 用户可能的问题可能涉及如何避免数据覆盖、如何处理溢出、DMA的不同模式对环形缓冲区的影响,或者具体的代码实现示例。需要确保回答覆盖这些方面,同时引用提供的参考文献来支持解释。</think>### DMA环形缓冲区实现原理及示例 #### 一、工作原理 DMA环形缓冲区结合了**直接内存访问(DMA)**和**环形缓冲区(Ring Buffer)**的优势,主要用于高效处理连续数据流(如串口通信、音频采集等)。其核心原理如下: 1. **DMA自动搬运数据** DMA控制器将外设(如UART、ADC)的数据**直接写入环形缓冲区**,无需CPU干预,减少资源占用[^4]。 2. **环形缓冲区的循环存取** 缓冲区首尾相连,通过**读写指针**管理数据位置: - **写指针**:由DMA自动更新,指向下一个待写入位置 - **读指针**:由CPU控制,指向待读取数据的起始位置 当指针到达缓冲区末尾时,自动绕回起始地址[^2]。 3. **中断触发处理** 通常配合**空闲中断**或**半传输/完成中断**,通知CPU处理已接收的数据[^4]。 #### 二、实现步骤(以STM32串口为例) 1. **初始化环形缓冲区** ```c #define BUFFER_SIZE 128 uint8_t ring_buffer[BUFFER_SIZE]; volatile uint32_t read_ptr = 0, write_ptr = 0; // 需声明为volatile防止优化 ``` 2. **配置DMA循环模式** ```c // 配置UART接收DMA为循环模式 hdma_usart_rx.Init.Mode = DMA_CIRCULAR; HAL_DMA_Start(&hdma_usart_rx, (uint32_t)&huart1.Instance->DR, (uint32_t)ring_buffer, BUFFER_SIZE); ``` 3. **空闲中断处理** ```c void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); uint32_t received_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart_rx); write_ptr = (write_ptr + received_len) % BUFFER_SIZE; // 更新写指针 process_data(); // 触发数据处理 } } ``` 4. **数据读取逻辑** ```c uint32_t available = (write_ptr - read_ptr + BUFFER_SIZE) % BUFFER_SIZE; while (available > 0) { uint8_t data = ring_buffer[read_ptr]; read_ptr = (read_ptr + 1) % BUFFER_SIZE; // 处理数据... } ``` #### 三、关键设计要点 1. **避免数据覆盖** - 通过比较读写指针判断缓冲区剩余空间 - 公式:剩余空间 = `(read_ptr - write_ptr - 1 + BUFFER_SIZE) % BUFFER_SIZE` 2. **内存屏障与volatile** 多线程/中断环境下需使用`volatile`关键字修饰指针变量,防止编译器优化导致数据不一致[^3]。 3. **性能优化** - DMA循环模式+双缓冲:在DMA填充一半/全部缓冲区时触发中断,实现零拷贝处理[^4] - 内存对齐:缓冲区地址对齐到DMA总线宽度(如4字节对齐) #### 四、应用场景 1. 高速串口通信(Modbus、GPS模块) 2. 音频采集与实时处理 3. 传感器数据流接收(如IMU连续输出)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值