中断触发的环形缓冲区入队 / 出队函数
下面是环形缓冲区的交互式原理示意图可以把代码放到.html文件里双击然后点击按钮直观感受入队 / 出队和指针环绕的过程html stylemargin:0;padding:0; div stylebackground-color:transparent;box-sizing:border-box;font-family:PingFang SC,Segoe UI,Arial,sans-serif;padding:16px 0; div styletext-align:center;margin-bottom:14px; div stylefont-size:17px;font-weight:600;color:#1A1B1C;环形缓冲区Ring Buffer工作原理/div div stylefont-size:12px;color:#6B7280;margin-top:4px;点击按钮体验入队/出队 · 观察读写指针如何环绕/div /div div styledisplay:flex;gap:16px;flex-wrap:wrap;box-sizing:border-box;align-items:flex-start;justify-content:center; !-- 左侧环形可视化 -- div styleflex:0 0 auto;box-sizing:border-box; svg idring-svg width260 height260 viewBox0 0 260 260 styledisplay:block; !-- 背景圆 -- circle cx130 cy130 r100 fillnone stroke#E4E3DD stroke-width28 stroke-linecapround stroke-dasharraynone/ !-- 数据填充弧动态更新 -- circle idring-fill cx130 cy130 r100 fillnone stroke#5B8FF9 stroke-width28 stroke-linecapround stroke-dasharray0 628 transformrotate(-90 130 130) opacity0.7/ !-- 槽位标记点8个 -- g idslots/g !-- 写指针 -- g idwrite-pointer circle cx130 cy30 r12 fill#52C41A stroke#fff stroke-width2/ text x130 y34 text-anchormiddle fill#fff font-size11 font-weight600W/text /g !-- 读指针 -- g idread-pointer circle cx130 cy30 r12 fill#FAAD14 stroke#fff stroke-width2/ text x130 y34 text-anchormiddle fill#fff font-size11 font-weight600R/text /g !-- 中心文字 -- text x130 y125 text-anchormiddle fill#1A1B1C font-size22 font-weight700 idcount-text0/text text x130 y148 text-anchormiddle fill#6B7280 font-size11/ 8 字节/text text x130 y168 text-anchormiddle fill#999 font-size10 idstatus-text队列为空/text /svg !-- 图例 -- div styledisplay:flex;gap:14px;justify-content:center;margin-top:8px; div styledisplay:flex;align-items:center;gap:5px; div stylewidth:12px;height:12px;border-radius:50%;background:#52C41A;/div span stylefont-size:11px;color:#6B7280;写指针 W入队/span /div div styledisplay:flex;align-items:center;gap:5px; div stylewidth:12px;height:12px;border-radius:50%;background:#FAAD14;/div span stylefont-size:11px;color:#6B7280;读指针 R出队/span /div /div /div !-- 右侧控制面板 -- div styleflex:1 1 200px;min-width:180px;max-width:260px;box-sizing:border-box; div stylebackground:#fff;border-radius:12px;padding:14px;box-sizing:border-box;border:0.5px solid rgba(0,0,0,0.08); div stylefont-size:13px;font-weight:600;color:#1A1B1C;margin-bottom:12px;操作面板/div !-- 入队按钮 -- button idbtn-enqueue stylewidth:100%;padding:12px;margin-bottom:8px;background:linear-gradient(135deg,#52C41A,#73D13D);color:#fff;border:none;border-radius:10px;font-size:13px;font-weight:600;cursor:pointer;box-sizing:border-box;min-height:44px; ⬇ 入队 Enqueue /button !-- 出队按钮 -- button idbtn-dequeue stylewidth:100%;padding:12px;margin-bottom:12px;background:linear-gradient(135deg,#FAAD14,#FFC53D);color:#fff;border:none;border-radius:10px;font-size:13px;font-weight:600;cursor:pointer;box-sizing:border-box;min-height:44px; ⬆ 出队 Dequeue /button !-- 重置按钮 -- button idbtn-reset stylewidth:100%;padding:10px;background:#f5f5f5;color:#666;border:none;border-radius:10px;font-size:12px;cursor:pointer;box-sizing:border-box;min-height:40px; ↺ 重置 /button !-- 分隔线 -- div styleheight:1px;background:rgba(0,0,0,0.06);margin:14px 0;/div !-- 状态信息 -- div stylefont-size:12px;color:#6B7280;line-height:1.8; div styledisplay:flex;justify-content:space-between; span缓冲区大小/spanspan stylecolor:#1A1B1C;font-weight:500;8 字节/span /div div styledisplay:flex;justify-content:space-between; span写指针 head/spanspan idinfo-head stylecolor:#52C41A;font-weight:500;font-family:monospace;0/span /div div styledisplay:flex;justify-content:space-between; span读指针 tail/spanspan idinfo-tail stylecolor:#FAAD14;font-weight:500;font-family:monospace;0/span /div div styledisplay:flex;justify-content:space-between; span已用空间/spanspan idinfo-count stylecolor:#5B8FF9;font-weight:500;0 / 8/span /div /div /div !-- 操作日志 -- div stylemargin-top:10px;background:#fff;border-radius:12px;padding:12px;box-sizing:border-box;border:0.5px solid rgba(0,0,0,0.08); div stylefont-size:12px;font-weight:600;color:#1A1B1C;margin-bottom:8px;操作日志/div div idlog-area stylefont-size:11px;color:#6B7280;font-family:monospace;line-height:1.7;max-height:100px;overflow-y:auto; div stylecolor:#999;[就绪] 等待操作.../div /div /div /div /div !-- 底部结构说明 -- div stylemargin-top:16px;padding:12px 14px;background:linear-gradient(135deg,rgba(91,143,249,0.06),rgba(82,196,26,0.06));border-radius:12px;box-sizing:border-box; div stylefont-size:12px;font-weight:600;color:#1A1B1C;margin-bottom:8px; 核心结构记忆/div div styledisplay:flex;gap:12px;flex-wrap:wrap;box-sizing:border-box;font-size:11px;color:#555;line-height:1.6; div styleflex:1 1 140px;min-width:0; b stylecolor:#5B8FF9;固定数组/b b stylecolor:#52C41A;写指针/b b stylecolor:#FAAD14;读指针/b /div div styleflex:1 1 140px;min-width:0; b入队/b只动写指针(head1) (size-1) 环绕 /div div styleflex:1 1 140px;min-width:0; b出队/b只动读指针(tail1) (size-1) 环绕 /div div styleflex:1 1 140px;min-width:0; b判空/bhead tailbr b判满/b(head1) mask tail浪费1格 /div /div /div script (function() { try { var SIZE 8; var head 0, tail 0; var data new Array(SIZE).fill(0); var logLines []; var svg document.getElementById(ring-svg); if (!svg) return; var writeG document.getElementById(write-pointer); var readG document.getElementById(read-pointer); var fillCircle document.getElementById(ring-fill); var countText document.getElementById(count-text); var statusText document.getElementById(status-text); var infoHead document.getElementById(info-head); var infoTail document.getElementById(info-tail); var infoCount document.getElementById(info-count); var logArea document.getElementById(log-area); var slotsG document.getElementById(slots); // 画8个槽位 var cx 130, cy 130, r 100; for (var i 0; i SIZE; i) { var angle (i / SIZE) * 2 * Math.PI - Math.PI / 2; var x cx r * Math.cos(angle); var y cy r * Math.sin(angle); var dot document.createElementNS(http://www.w3.org/2000/svg, circle); dot.setAttribute(cx, x); dot.setAttribute(cy, y); dot.setAttribute(r, 5); dot.setAttribute(fill, #E4E3DD); dot.setAttribute(class, slot-dot); dot.setAttribute(data-idx, i); slotsG.appendChild(dot); } function count() { return (head - tail SIZE) % SIZE; } function isFull() { return ((head 1) % SIZE) tail; } function isEmpty() { return head tail; } function movePointer(g, idx) { var angle (idx / SIZE) * 360 - 90; g.setAttribute(transform, rotate( angle cx cy )); } function updateFill() { var c count(); var circumference 2 * Math.PI * r; // ~628 var dashLen (c / SIZE) * circumference; fillCircle.setAttribute(stroke-dasharray, dashLen circumference); // 从tail开始填充 var startAngle (tail / SIZE) * 360 - 90; fillCircle.setAttribute(transform, rotate( startAngle cx cy )); // 更新槽位颜色 var dots slotsG.querySelectorAll(.slot-dot); for (var i 0; i SIZE; i) { var filled false; if (c 0) { if (head tail) { filled (i tail i head); } else { filled (i tail || i head); } } dots[i].setAttribute(fill, filled ? #5B8FF9 : #E4E3DD); } } function updateInfo() { var c count(); countText.textContent c; infoHead.textContent head; infoTail.textContent tail; infoCount.textContent c / SIZE; if (isEmpty()) { statusText.textContent 队列为空; statusText.setAttribute(fill, #999); } else if (isFull()) { statusText.textContent 队列已满!; statusText.setAttribute(fill, #EA6668); } else { statusText.textContent c 字节待处理; statusText.setAttribute(fill, #5B8FF9); } } function addLog(msg, type) { var color type err ? #EA6668 : type ok ? #52C41A : #6B7280; var line document.createElement(div); line.style.color color; line.textContent msg; logLines.push(line); if (logLines.length 20) { logArea.removeChild(logLines[0]); logLines.shift(); } logArea.appendChild(line); logArea.scrollTop logArea.scrollHeight; } function enqueue() { if (isFull()) { addLog([FAIL] 入队失败缓冲区满 (overflow), err); return false; } data[head] Math.floor(Math.random() * 256); var oldHead head; head (head 1) % SIZE; movePointer(writeG, head); updateFill(); updateInfo(); addLog([ENQ] buffer[ oldHead ] 0x data[oldHead].toString(16).toUpperCase().padStart(2,0) → head head, ok); return true; } function dequeue() { if (isEmpty()) { addLog([FAIL] 出队失败缓冲区空, err); return false; } var val data[tail]; var oldTail tail; tail (tail 1) % SIZE; movePointer(readG, tail); updateFill(); updateInfo(); addLog([DEQ] buffer[ oldTail ] 0x val.toString(16).toUpperCase().padStart(2,0) → tail tail, ok); return true; } function reset() { head 0; tail 0; data new Array(SIZE).fill(0); movePointer(writeG, 0); movePointer(readG, 0); updateFill(); updateInfo(); logArea.innerHTML div stylecolor:#999;[就绪] 等待操作.../div; logLines []; } document.getElementById(btn-enqueue).addEventListener(click, enqueue); document.getElementById(btn-dequeue).addEventListener(click, dequeue); document.getElementById(btn-reset).addEventListener(click, reset); // 初始化 movePointer(writeG, 0); movePointer(readG, 0); updateFill(); updateInfo(); } catch (e) { console.error(e); } })(); /script /div /html一、 它到底是个什么结构它本质上是一个固定大小的数组配上两个指针写指针write_ptr和读指针read_ptr。入队Enqueue只动写指针把数据塞进数组。出队Dequeue只动读指针从数组中把数据取出来。环形当指针指到数组末尾时自动回绕到数组开头像转圈圈一样循环利用固定大小内存。二、 为什么要用它最经典的场景在嵌入式开发中**中断服务函数ISR具有“必须立刻执行完毕不能卡顿”**的硬性要求。错误做法如果在串口中断里直接解析协议、或者用printf打印日志、或者做耗时运算不仅会导致系统卡死甚至可能破坏中断优先级导致硬故障。正确做法用环形缓冲区中断生产者串口每收到一个字节ISR 极速执行入队函数把字节丢进环形缓冲区然后立刻退出中断。耗时几微秒。主循环/任务消费者RTOS 任务或while(1)主循环里定期调用出队函数去读取缓冲区里的数据在非中断上下文中慢慢处理协议包哪怕耗时几毫秒也没关系。三、 函数C 语言实现核心代码逻辑大概是这样的typedef struct { uint8_t *buffer; // 数据缓冲区 uint32_t head; // 写指针入队 uint32_t tail; // 读指针出队 uint32_t max_len; // 缓冲区总大小 } ring_buffer_t; // 1. 入队函数通常在中断里调用 bool enqueue(ring_buffer_t *rb, uint8_t data) { uint32_t next_head (rb-head 1) (rb-max_len - 1); // 位运算取模 // 如果下一个写位置等于读位置说明队列满了不能覆盖未读数据 if (next_head rb-tail) { return false; // 溢出丢包 } rb-buffer[rb-head] data; rb-head next_head; return true; } // 2. 出队函数通常在主循环或RTOS任务里调用 bool dequeue(ring_buffer_t *rb, uint8_t *data) { if (rb-head rb-tail) { return false; // 队列是空的 } *data rb-buffer[rb-tail]; rb-tail (rb-tail 1) (rb-max_len - 1); return true; } 3 个问题中断入队时如果同时主循环也在出队会不会有竞争风险你如何保护临界区回答使用 FreeRTOS 提供的taskENTER_CRITICAL()和taskEXIT_CRITICAL()或者直接利用屏障指令保证单生产者/单消费者模式下的无锁访问。满和空怎么判断如果这个队列是用于 DMA 连续数据搬运怎么改回答当前代码在head1 tail时视为“满”会浪费一个字节空间。以及 DMA 场景下往往是**“大小循环 位置索引”**要求会用关 DMA 中断的方式来保护数据。如果在串口中断里处理enqueue发现return false缓冲区满了你会怎么处理回答不会死等而是直接把最老的数据覆盖掉丢旧包保新包或者在 ISR 里直接丢弃该字节同时设置一个全局标志位overflow_flag告知主循环刚才发生了丢包。

相关新闻

最新新闻

日新闻

周新闻

月新闻