os/log/
buffer.rs

1//! 锁无关的日志存储环形缓冲区
2//!
3//! 该模块实现了高性能、多生产者单消费者 (MPSC) 环形缓冲区,
4//! 使用原子操作进行同步。
5
6use core::ops::{Deref, DerefMut};
7use core::sync::atomic::{AtomicUsize, Ordering};
8
9use super::config::GLOBAL_LOG_BUFFER_SIZE;
10use super::entry::LogEntry;
11
12/// 单个日志条目的大小(以字节为单位)
13const LOG_ENTRY_SIZE: usize = core::mem::size_of::<LogEntry>();
14
15/// 缓冲区中可存储的最大日志条目数
16pub(crate) const MAX_LOG_ENTRIES: usize = GLOBAL_LOG_BUFFER_SIZE / LOG_ENTRY_SIZE;
17
18/// 计算日志条目格式化后的精确字节长度
19///
20/// 格式: "{color_code}{level} [{timestamp:12}] [CPU{cpu_id}/T{task_id:3}] {message}{reset}\n"
21///
22/// **重要**: 此函数的计算逻辑必须与以下格式化函数保持一致:
23/// - `log_core::direct_print_entry` - 控制台输出格式
24/// - `log_core::format_log_entry` - syslog 系统调用格式
25///
26/// 如果修改了日志输出格式,需要同步更新三处:
27/// 1. `log_core::direct_print_entry` - 实际格式化输出
28/// 2. `log_core::format_log_entry` - 字符串格式化
29/// 3. `calculate_formatted_length` (此函数) - 字节长度计算
30///
31/// # 组成部分计算
32/// - ANSI 颜色代码: entry.level().color_code().len() (开始)
33/// - ANSI 重置代码: entry.level().reset_color_code().len() (结束)
34/// - 级别标签: entry.level().as_str().len() (例如 "\\[INFO\\]")
35/// - 时间戳: 14 字节 (" [" + 12位数字 + "]")
36/// - CPU ID: " [CPU" + digit_count(cpu_id)
37/// - 任务 ID: "/T" + digit_count_padded(task_id, 3) + "]"
38/// - 消息内容: entry.message().len()
39/// - 空格分隔: 3 字节 (level后1个, timestamp后1个, context后1个)
40/// - 换行: 1 字节
41fn calculate_formatted_length(entry: &LogEntry) -> usize {
42    // ANSI 颜色代码长度
43    let color_start_len = entry.level().color_code().len();
44    let color_reset_len = entry.level().reset_color_code().len();
45
46    // 级别标签长度
47    let level_len = entry.level().as_str().len();
48
49    // 时间戳: " [{:12}]" = 2 + 12 = 14 字节
50    let timestamp_len = 14;
51
52    // CPU ID 的数字位数
53    let cpu_id = entry.cpu_id();
54    let cpu_digits = if cpu_id == 0 {
55        1
56    } else {
57        // 计算十进制位数: floor(log10(n)) + 1
58        let mut n = cpu_id;
59        let mut digits = 0;
60        while n > 0 {
61            digits += 1;
62            n /= 10;
63        }
64        digits
65    };
66
67    // 任务 ID 的数字位数(至少3位,有padding)
68    let task_id = entry.task_id();
69    let task_digits = if task_id == 0 {
70        3 // "  0" 三位
71    } else {
72        let mut n = task_id;
73        let mut digits = 0;
74        while n > 0 {
75            digits += 1;
76            n /= 10;
77        }
78        if digits < 3 {
79            3 // padding 到至少3位
80        } else {
81            digits
82        }
83    };
84
85    // " [CPU<digits>/T<digits>]"
86    // " [CPU" = 5, "/T" = 2, "]" = 1
87    let context_len = 5 + cpu_digits + 2 + task_digits + 1;
88
89    // 消息内容长度
90    let message_len = entry.message().len();
91
92    // 分隔符和换行: level后1个空格 + timestamp后1个空格 + context后1个空格 = 3
93    let separators_len = 3;
94
95    color_start_len
96        + level_len
97        + timestamp_len
98        + context_len
99        + message_len
100        + color_reset_len
101        + separators_len
102}
103
104/// 缓存行填充封装器,用于防止伪共享
105///
106/// 将封装的类型填充到 64 字节(典型的缓存行大小),以确保
107/// 不同 CPU 核心使用的不同原子变量不会共享缓存行,
108/// 从而避免因缓存一致性流量导致的性能下降。
109#[repr(C, align(64))]
110struct CachePadded64<T> {
111    inner: T,
112}
113
114impl<T> Deref for CachePadded64<T> {
115    type Target = T;
116
117    #[inline]
118    fn deref(&self) -> &Self::Target {
119        &self.inner
120    }
121}
122
123impl<T> DerefMut for CachePadded64<T> {
124    #[inline]
125    fn deref_mut(&mut self) -> &mut Self::Target {
126        &mut self.inner
127    }
128}
129
130/// 全局日志缓冲区实例
131///
132/// 我们不需要锁 (如 Mutex) 或延迟初始化 (如 OnceCell),因为:
133///
134/// 1.  **不需要 `Mutex` (锁):** `GlobalLogBuffer` 本身是线程安全的。
135///     它使用 `AtomicUsize` 字段以**内部可变性**设计。
136///     由于其所有方法 (`write`, `read`) 都对共享引用 (`&self`) 操作,
137///     并且所有内部修改都通过原子操作安全处理,因此整个
138///     结构体是 `Sync`。我们不需要将一个已经线程安全的
139///     类型封装在**另一个**锁中。
140///
141/// 2.  **不需要 `Lazy` 或 `OnceCell`:** `GlobalLogBuffer::new()`
142///     函数是一个 `const fn`。这意味着它在**编译时**执行,
143///     而不是在运行时执行。整个、完全初始化的 `GlobalLogBuffer`
144///     实例在内核编译时被直接烘焙到内核的数据段 (`.data` 或 `.bss`) 中。
145///
146///     因此,没有运行时初始化步骤,也就不存在 `Lazy` 旨在解决的
147///     "首次初始化"竞态条件。缓冲区从第一条 CPU 指令开始,
148///     就已完全初始化并存在于内存中。
149///
150/// 这种模式产生了零开销、锁无关且无数据竞争的
151/// 全局静态实例。
152static GLOBAL_LOG_BUFFER: GlobalLogBuffer = GlobalLogBuffer::new();
153
154/// 存储日志条目的锁无关环形缓冲区
155///
156/// 采用多生产者单消费者 (MPSC) 设计,其中:
157/// - 多个 CPU 可以并发地写入日志而无需阻塞
158/// - 单个消费者线程按顺序读取日志
159#[repr(C)]
160pub(super) struct GlobalLogBuffer {
161    /// 写入侧数据(由生产者更新)
162    writer_data: CachePadded64<WriterData>,
163    /// 读取侧数据(由消费者更新)
164    reader_data: CachePadded64<ReaderData>,
165    /// 固定大小的日志条目数组
166    buffer: [LogEntry; MAX_LOG_ENTRIES],
167    /// 记录未读日志的总字节数
168    unread_bytes: AtomicUsize,
169}
170
171/// 写入侧同步数据
172#[repr(C)]
173struct WriterData {
174    /// 写入操作的单调递增序列号
175    write_seq: AtomicUsize,
176}
177
178/// 读取侧同步数据
179#[repr(C)]
180struct ReaderData {
181    /// 读取操作的单调递增序列号
182    read_seq: AtomicUsize,
183    /// 由于缓冲区溢出而丢弃的日志计数
184    dropped: AtomicUsize,
185}
186
187impl GlobalLogBuffer {
188    /// 在编译时创建一个新的全局日志缓冲区
189    pub(super) const fn new() -> Self {
190        const EMPTY: LogEntry = LogEntry::empty();
191        Self {
192            writer_data: CachePadded64 {
193                inner: WriterData {
194                    write_seq: AtomicUsize::new(1),
195                },
196            },
197            reader_data: CachePadded64 {
198                inner: ReaderData {
199                    read_seq: AtomicUsize::new(1),
200                    dropped: AtomicUsize::new(0),
201                },
202            },
203            buffer: [EMPTY; MAX_LOG_ENTRIES],
204            unread_bytes: AtomicUsize::new(0),
205        }
206    }
207
208    /// 将日志条目写入缓冲区
209    ///
210    /// 这是一个**锁无关**操作,执行以下步骤:
211    /// 1. 原子地获取一个唯一的序列号(票据)
212    /// 2. 使用模运算计算目标槽位索引
213    /// 3. 检查并处理潜在的缓冲区满(覆盖)逻辑
214    /// 4. 将日志数据复制到槽位(*不包括* seq 字段)
215    /// 5. 使用 **Release** 内存屏障原子地设置 seq 来发布条目
216    /// 6. 增加未读字节计数
217    pub(super) fn write(&self, entry: &LogEntry) {
218        // step1: 原子地获取一个唯一的序列号(票据)
219        let seq = self.writer_data.write_seq.fetch_add(1, Ordering::Relaxed);
220
221        // step2: 从序列号计算目标槽位索引
222        let slot = seq % MAX_LOG_ENTRIES;
223        let slot_ptr = unsafe { self.buffer.as_ptr().add(slot) as *mut LogEntry };
224
225        // step3: 检查并处理潜在的缓冲区满(覆盖)逻辑
226        self.handle_overwrite(seq);
227
228        // step4: 将所有日志数据(*不包括* seq 字段)复制到槽位
229        unsafe {
230            entry.copy_data_to(slot_ptr);
231        }
232
233        // step5: 通过原子地设置其 seq 来发布条目(Release 屏障)
234        unsafe {
235            entry.publish(slot_ptr, seq);
236        }
237
238        // step6: 增加未读字节计数
239        let formatted_len = calculate_formatted_length(entry);
240        self.unread_bytes
241            .fetch_add(formatted_len, Ordering::Release);
242    }
243
244    /// 处理缓冲区溢出,必要时推进读取指针
245    ///
246    /// 当缓冲区满且新的写入将覆盖未读条目时,此函数:
247    /// 1. 检测溢出条件
248    /// 2. 计算将被覆盖的条目数
249    /// 3. 更新丢弃计数
250    /// 4. 使用 CAS 循环原子地推进读取指针
251    fn handle_overwrite(&self, current_seq: usize) {
252        let read_seq = self.reader_data.read_seq.load(Ordering::Acquire);
253        if current_seq < read_seq + MAX_LOG_ENTRIES {
254            return;
255        }
256        let new_read_seq = current_seq - MAX_LOG_ENTRIES + 1;
257        let overwritten = new_read_seq.saturating_sub(read_seq);
258        self.reader_data
259            .dropped
260            .fetch_add(overwritten, Ordering::Relaxed);
261
262        // CAS 循环以推进 read_seq
263        let mut current_read_seq = read_seq;
264        while current_read_seq < new_read_seq {
265            match self.reader_data.read_seq.compare_exchange_weak(
266                current_read_seq,
267                new_read_seq,
268                Ordering::Release,
269                Ordering::Relaxed,
270            ) {
271                Ok(_) => break,
272                Err(seen_seq) => {
273                    if seen_seq >= new_read_seq {
274                        break;
275                    }
276                    current_read_seq = seen_seq;
277                }
278            }
279        }
280    }
281
282    /// 从缓冲区读取下一个日志条目
283    ///
284    /// 如果没有可用条目,则返回 `None`。这是一个**锁无关**的
285    /// 单消费者操作,使用 **Acquire** 内存顺序确保与生产者的正确同步。
286    /// 读取后会减少未读字节计数。
287    pub(super) fn read(&self) -> Option<LogEntry> {
288        let read_seq = self.reader_data.read_seq.load(Ordering::Acquire);
289
290        let slot = read_seq % MAX_LOG_ENTRIES;
291        let slot_ptr = unsafe { self.buffer.as_ptr().add(slot) as *const LogEntry };
292
293        const EMPTY: LogEntry = LogEntry::empty();
294        unsafe {
295            if !EMPTY.is_ready(slot_ptr, read_seq) {
296                return None;
297            }
298        }
299
300        let entry_data = unsafe { (*slot_ptr).clone() };
301
302        // 减少未读字节计数
303        let formatted_len = calculate_formatted_length(&entry_data);
304        self.unread_bytes
305            .fetch_sub(formatted_len, Ordering::Release);
306
307        self.reader_data
308            .read_seq
309            .store(read_seq + 1, Ordering::Release);
310
311        Some(entry_data)
312    }
313
314    /// 返回缓冲区中未读日志条目的数量
315    pub(super) fn len(&self) -> usize {
316        let write = self.writer_data.write_seq.load(Ordering::Relaxed);
317        let read = self.reader_data.read_seq.load(Ordering::Relaxed);
318        write.saturating_sub(read)
319    }
320
321    /// 返回未读日志的总字节数(格式化后)
322    pub(super) fn unread_bytes(&self) -> usize {
323        self.unread_bytes.load(Ordering::Acquire)
324    }
325
326    /// 返回由于缓冲区溢出而丢弃的日志总数
327    pub(super) fn dropped_count(&self) -> usize {
328        self.reader_data.dropped.load(Ordering::Relaxed)
329    }
330
331    /// 非破坏性读取:按索引 peek 日志条目,不移动读指针
332    ///
333    /// 此方法允许读取缓冲区中的日志而不删除它们,主要用于
334    /// `SyslogAction::ReadAll` 操作。
335    ///
336    /// # 参数
337    /// * `index` - 全局序列号(从 1 开始,与内部 seq 对应)
338    ///
339    /// # 返回值
340    /// * `Some(LogEntry)` - 如果条目存在且有效
341    /// * `None` - 如果索引超出范围或条目已被覆盖
342    ///
343    /// # 并发安全
344    /// 此方法是完全无锁的,可以与 write 和 read 并发调用。
345    pub(super) fn peek(&self, index: usize) -> Option<LogEntry> {
346        let current_write = self.writer_data.write_seq.load(Ordering::Acquire);
347        let current_read = self.reader_data.read_seq.load(Ordering::Acquire);
348
349        // 检查索引是否在有效范围内
350        // 有效范围: [current_read, current_write)
351        if index < current_read || index >= current_write {
352            return None;
353        }
354
355        // 检查是否已被覆盖(环形缓冲区溢出)
356        // 如果 writer 已经超过 reader + BUFFER_SIZE,则旧数据可能被覆盖
357        if current_write >= current_read + MAX_LOG_ENTRIES {
358            // 缓冲区已满,旧数据可能被覆盖
359            let oldest_valid = current_write.saturating_sub(MAX_LOG_ENTRIES);
360            if index < oldest_valid {
361                return None; // 数据已被覆盖
362            }
363        }
364
365        // 计算缓冲区索引
366        let slot = index % MAX_LOG_ENTRIES;
367        let slot_ptr = unsafe { self.buffer.as_ptr().add(slot) as *const LogEntry };
368
369        // 检查序列号是否匹配(确保数据有效)
370        const EMPTY: LogEntry = LogEntry::empty();
371        unsafe {
372            if !EMPTY.is_ready(slot_ptr, index) {
373                return None;
374            }
375        }
376
377        // 克隆并返回条目
378        Some(unsafe { (*slot_ptr).clone() })
379    }
380
381    /// 获取当前可读取的起始索引(读指针位置)
382    pub(super) fn reader_index(&self) -> usize {
383        self.reader_data.read_seq.load(Ordering::Acquire)
384    }
385
386    /// 获取当前写入位置(下一个要写入的索引)
387    pub(super) fn writer_index(&self) -> usize {
388        self.writer_data.write_seq.load(Ordering::Acquire)
389    }
390}
391
392/// 将日志条目写入全局缓冲区(内部使用)
393#[inline]
394pub(super) fn write_log(entry: &LogEntry) {
395    GLOBAL_LOG_BUFFER.write(entry);
396}
397
398/// 从全局缓冲区读取下一个日志条目
399///
400/// 如果没有可供读取的条目,则返回 `None`。
401#[inline]
402pub fn read_log() -> Option<LogEntry> {
403    GLOBAL_LOG_BUFFER.read()
404}
405
406/// 返回由于缓冲区溢出而丢弃的日志总数
407#[inline]
408pub fn log_dropped_count() -> usize {
409    GLOBAL_LOG_BUFFER.dropped_count()
410}
411
412/// 返回缓冲区中当前未读日志条目的数量
413#[inline]
414pub fn log_len() -> usize {
415    GLOBAL_LOG_BUFFER.len()
416}
417
418/// 返回未读日志的总字节数(格式化后)
419#[inline]
420pub fn log_unread_bytes() -> usize {
421    GLOBAL_LOG_BUFFER.unread_bytes()
422}
423
424/// 非破坏性读取:按索引 peek 日志条目
425///
426/// 不移动读指针,允许重复读取同一条目。
427#[inline]
428pub fn peek_log(index: usize) -> Option<LogEntry> {
429    GLOBAL_LOG_BUFFER.peek(index)
430}
431
432/// 获取当前可读取的起始索引
433#[inline]
434pub fn log_reader_index() -> usize {
435    GLOBAL_LOG_BUFFER.reader_index()
436}
437
438/// 获取当前写入位置
439#[inline]
440pub fn log_writer_index() -> usize {
441    GLOBAL_LOG_BUFFER.writer_index()
442}